feat(skills): add clawsec-clawhub-checker reputation checking skill

- Adds ClawHub reputation checks to guarded installer
- Integrates VirusTotal Code Insight scores
- Requires --confirm-reputation for suspicious skills
- Enhances advisory guardian hook with reputation warnings
- Defense-in-depth layer for skill installation security
This commit is contained in:
David Abutbul
2026-02-16 12:43:01 +02:00
parent 76778b8bb6
commit 777ff98d63
10 changed files with 1081 additions and 1 deletions
@@ -0,0 +1,26 @@
# PR Notes for ClawSec ClawHub Checker
## Important Limitation Notice
This skill currently catches **VirusTotal Code Insight flags** but cannot access **OpenClaw internal check results** because:
1. **VirusTotal flags** are exposed via `clawhub install` command output (we parse stderr)
2. **OpenClaw internal checks** are only shown on the ClawHub website, not exposed via API
## Example from `clawsec-suite` page:
-**VirusTotal**: "Benign"
- ⚠️ **OpenClaw internal check**: "The package is internally consistent with a feed-monitoring / advisory-guardian purpose, but a few operational details and optional bypasses deserve attention before installing."
## Recommendation for ClawHub
Expose internal check results via:
- `clawhub inspect --json` endpoint
- Additional API field for security tools
- Or at minimum, include in `clawhub install` warning output
## Current Workaround
Our heuristic checks (skill age, author reputation, downloads, updates) provide similar risk assessment but miss specific operational warnings about bypasses, missing signatures, etc.
## PR Should Include
1. This skill as defense-in-depth layer
2. Feature request to ClawHub for exposing internal check data
3. Documentation about the limitation
+123
View File
@@ -0,0 +1,123 @@
# ClawSec ClawHub Checker
A ClawSec suite skill that enhances the guarded skill installer with ClawHub reputation checks and VirusTotal Code Insight integration.
## Purpose
Adds a second layer of security to skill installation by:
1. Checking ClawHub's VirusTotal Code Insight reputation scores
2. Analyzing skill age, author reputation, and download statistics
3. Requiring double confirmation for suspicious skills
4. Integrating with existing ClawSec advisory checks
## Architecture
```
clawsec-suite (base)
└── clawsec-clawhub-checker (enhancement)
├── enhanced_guarded_install.mjs - Main enhanced installer
├── check_clawhub_reputation.mjs - Reputation checking logic
├── setup_reputation_hook.mjs - Integration script
└── hooks/ - Enhanced advisory guardian hook
```
## Installation
```bash
# First install the base suite
npx clawhub install clawsec-suite
# Then install the checker
npx clawhub install clawsec-clawhub-checker
# Run setup to integrate with existing suite
node scripts/setup_reputation_hook.mjs
# Restart OpenClaw gateway
openclaw gateway restart
```
## Usage
### Enhanced Guarded Installer
```bash
# Basic usage (includes reputation checks)
node scripts/enhanced_guarded_install.mjs --skill some-skill --version 1.0.0
# With reputation confirmation override
node scripts/enhanced_guarded_install.mjs --skill suspicious-skill --version 1.0.0 --confirm-reputation
# Adjust reputation threshold (default: 70)
node scripts/enhanced_guarded_install.mjs --skill some-skill --reputation-threshold 80
```
### Reputation Check Only
```bash
# Check reputation without installation
node scripts/check_clawhub_reputation.mjs some-skill 1.0.0 70
```
## Exit Codes
- `0` - Safe to install
- `42` - Advisory match found (requires `--confirm-advisory`)
- `43` - Reputation warning (requires `--confirm-reputation`) - **NEW**
- `1` - Error
## Reputation Signals Checked
1. **VirusTotal Code Insight** - Malicious code patterns
2. **Skill Age** - New skills (<7 days) are riskier
3. **Author Reputation** - Number of published skills
4. **Update Frequency** - Stale skills (>90 days)
5. **Download Statistics** - Low download counts
6. **Version Existence** - Specified version availability
## Configuration
Environment variables:
- `CLAWHUB_REPUTATION_THRESHOLD` - Minimum score (0-100, default: 70)
- `CLAWHUB_ALLOW_SUSPICIOUS` - Skip reputation checks (not recommended)
## Integration Points
1. **Enhanced `guarded_skill_install.mjs`** - Wraps original with reputation checks
2. **Updated advisory guardian hook** - Adds reputation warnings to alerts
3. **Catalog entry in clawsec-suite** - Listed as available enhancement
## Development
### Files
- `SKILL.md` - Main documentation
- `skill.json` - Skill metadata and SBOM
- `scripts/enhanced_guarded_install.mjs` - Enhanced installer
- `scripts/check_clawhub_reputation.mjs` - Reputation logic
- `scripts/setup_reputation_hook.mjs` - Integration script
- `hooks/clawsec-advisory-guardian/lib/reputation.mjs` - Hook module
### Testing
```bash
# Test reputation check
node scripts/check_clawhub_reputation.mjs clawsec-suite
# Test enhanced installer (dry run)
node scripts/enhanced_guarded_install.mjs --skill test-skill --dry-run
# Test setup
node scripts/setup_reputation_hook.mjs
```
## Security Considerations
- Reputation checks are **heuristic**, not definitive
- **False positives** possible with legitimate novel skills
- Always **review skill code** before overriding warnings
- This is **defense-in-depth**, not replacement for advisory feeds
## License
MIT - Part of the ClawSec security suite
+140
View File
@@ -0,0 +1,140 @@
---
name: clawsec-clawhub-checker
version: 0.0.1
description: ClawHub reputation checker for ClawSec suite. Enhances guarded skill installer with VirusTotal Code Insight reputation scores and additional safety checks.
homepage: https://clawsec.prompt.security
clawdis:
emoji: "🛡️"
requires:
bins: [clawhub, curl, jq]
depends_on: [clawsec-suite]
---
# ClawSec ClawHub Checker
Enhances the ClawSec suite's guarded skill installer with ClawHub reputation checks. Adds a second layer of security by checking VirusTotal Code Insight scores and other reputation signals before allowing skill installation.
## What It Does
1. **Wraps `clawhub install`** - Intercepts skill installation requests
2. **Checks VirusTotal reputation** - Uses ClawHub's built-in VirusTotal Code Insight
3. **Adds double confirmation** - For suspicious skills (reputation score below threshold)
4. **Integrates with advisory feed** - Works alongside existing clawsec-suite advisories
5. **Provides detailed reports** - Shows why a skill is flagged as suspicious
## Installation
This skill must be installed **after** `clawsec-suite`:
```bash
# First install the suite
npx clawhub@latest install clawsec-suite
# Then install the checker
npx clawhub@latest install clawsec-clawhub-checker
```
The checker will automatically enhance the existing `guarded_skill_install.mjs` script and advisory guardian hook.
## How It Works
### Enhanced Guarded Installer
When you run:
```bash
node scripts/guarded_skill_install.mjs --skill some-skill --version 1.0.0
```
The enhanced flow:
1. **Advisory check** (existing) - Checks clawsec advisory feed
2. **Reputation check** (new) - Queries ClawHub for VirusTotal scores
3. **Risk assessment** - Combines advisory + reputation signals
4. **Double confirmation** - If risky, requires explicit `--confirm-reputation`
### Reputation Signals Checked
1. **VirusTotal Code Insight** - Malicious code patterns detection
2. **Skill age & updates** - New skills vs established ones
3. **Author reputation** - Other skills by same author
4. **Download statistics** - Popularity signals
5. **External dependencies** - Docker, network calls, eval usage
### Exit Codes
- `0` - Safe to install (no advisories, good reputation)
- `42` - Advisory match found (existing behavior)
- `43` - Reputation warning (new - requires `--confirm-reputation`)
- `1` - Error
## Configuration
Environment variables:
- `CLAWHUB_REPUTATION_THRESHOLD` - Minimum reputation score (0-100, default: 70)
- `CLAWHUB_ALLOW_SUSPICIOUS` - Allow installation of suspicious skills without confirmation (default: false)
- `CLAWHUB_VIRUSTOTAL_API_KEY` - Optional: Your own VirusTotal API key for deeper scans
## Integration with Existing Suite
The checker enhances but doesn't replace existing security:
- **Advisory feed still primary** - Known malicious skills blocked first
- **Reputation is secondary** - Unknown/suspicious skills get extra scrutiny
- **Double confirmation preserved** - Both layers require explicit user approval
## Example Usage
```bash
# Try to install a skill
node scripts/guarded_skill_install.mjs --skill suspicious-skill --version 1.0.0
# Output might show:
# WARNING: Skill "suspicious-skill" has low reputation score (45/100)
# - Flagged by VirusTotal Code Insight: crypto keys, external APIs, eval usage
# - Author has no other published skills
# - Skill is less than 7 days old
#
# To install despite reputation warning, run:
# node scripts/guarded_skill_install.mjs --skill suspicious-skill --version 1.0.0 --confirm-reputation
# Install with confirmation
node scripts/guarded_skill_install.mjs --skill suspicious-skill --version 1.0.0 --confirm-reputation
```
## Safety Notes
- This is a **defense-in-depth** layer, not a replacement for advisory feeds
- VirusTotal scores are **heuristic**, not definitive
- **False positives possible** - Legitimate skills with novel patterns might be flagged
- Always **review skill code** before installing with `--confirm-reputation`
## Current Limitations
### Missing OpenClaw Internal Check Data
ClawHub shows two security badges on skill pages:
1. **VirusTotal Code Insight** - ✅ Our checker catches these flags
2. **OpenClaw internal check** - ❌ Not exposed via API (only on website)
Example from `clawsec-suite` page:
- VirusTotal: "Benign" ✓
- OpenClaw internal check: "The package is internally consistent with a feed-monitoring / advisory-guardian purpose, but a few operational details and optional bypasses deserve attention before installing."
**Our checker cannot access OpenClaw internal check warnings** as they're not exposed via `clawhub` CLI or API.
### Recommendation for ClawHub
To enable complete reputation checking, ClawHub should expose internal check results via:
- `clawhub inspect --json` endpoint
- Additional API field for security tools
- Or include in `clawhub install` warning output
### Workaround
Our heuristic checks (skill age, author reputation, downloads, updates) provide similar risk assessment but miss specific operational warnings about bypasses, missing signatures, etc. Always check the ClawHub website for complete security assessment.
## Development
To modify the reputation checking logic, edit:
- `scripts/enhanced_guarded_install.mjs` - Main enhanced installer
- `scripts/check_clawhub_reputation.mjs` - Reputation checking logic
- `hooks/clawsec-advisory-guardian/lib/reputation.mjs` - Hook integration
## License
MIT - Part of the ClawSec security suite
@@ -0,0 +1,96 @@
import { spawnSync } from "node:child_process";
/**
* Check reputation for a skill
* @param {string} skillName - Skill name
* @param {string} version - Skill version
* @returns {Promise<{safe: boolean, score: number, warnings: string[]}>}
*/
export async function checkReputation(skillName, version) {
const result = {
safe: true,
score: 100,
warnings: [],
};
try {
// Try to get skill slug from directory name or skill.json
// For now, use skillName as slug (simplified)
const skillSlug = skillName.toLowerCase().replace(/[^a-z0-9-]/g, '-');
// Run the reputation check script
const checkScript = new URL(import.meta.url);
const scriptDir = checkScript.pathname.split('/').slice(0, -3).join('/'); // Go up from lib
const checkerDir = scriptDir.replace('/hooks/clawsec-advisory-guardian/lib', '');
const reputationCheck = spawnSync(
"node",
[
`${checkerDir}/scripts/check_clawhub_reputation.mjs`,
skillSlug,
version || "",
"70" // Default threshold
],
{ encoding: "utf-8", cwd: checkerDir }
);
if (reputationCheck.status === 0) {
try {
const repResult = JSON.parse(reputationCheck.stdout);
result.safe = repResult.safe;
result.score = repResult.score;
result.warnings = repResult.warnings;
} catch (parseError) {
result.warnings.push(`Failed to parse reputation result: ${parseError.message}`);
result.score = 60;
result.safe = result.score >= 70;
}
} else if (reputationCheck.status === 43) {
// Reputation warning exit code
try {
const repResult = JSON.parse(reputationCheck.stdout);
result.safe = false;
result.score = repResult.score;
result.warnings = repResult.warnings;
} catch {
result.safe = false;
result.score = 50;
result.warnings.push("Skill flagged by reputation check");
}
} else {
// Error running check
result.warnings.push(`Reputation check failed: ${reputationCheck.stderr || 'Unknown error'}`);
result.score = 60;
result.safe = result.score >= 70;
}
} catch (error) {
result.warnings.push(`Reputation check error: ${error.message}`);
result.score = 50;
result.safe = result.score >= 70;
}
return result;
}
/**
* Format reputation warning for alert messages
* @param {{score: number, warnings: string[]}} reputationInfo
* @returns {string}
*/
export function formatReputationWarning(reputationInfo) {
if (!reputationInfo || reputationInfo.score >= 70) return "";
const lines = [
`\n⚠️ **REPUTATION WARNING** (Score: ${reputationInfo.score}/100)`,
];
if (reputationInfo.warnings.length > 0) {
lines.push("");
reputationInfo.warnings.forEach(w => lines.push(`${w}`));
}
lines.push("");
lines.push("This skill has low reputation score. Review carefully before installation.");
return lines.join("\n");
}
@@ -0,0 +1,188 @@
#!/usr/bin/env node
import { spawnSync } from "node:child_process";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
/**
* Check ClawHub reputation for a skill
* @param {string} skillSlug - Skill slug to check
* @param {string} version - Optional version
* @param {number} threshold - Minimum reputation score (0-100)
* @returns {Promise<{safe: boolean, score: number, warnings: string[], virustotal: string[]}>}
*/
export async function checkClawhubReputation(skillSlug, version, threshold = 70) {
const result = {
safe: true,
score: 100, // Default score if no checks fail
warnings: [],
virustotal: [],
};
try {
// Check 1: Try to inspect the skill via clawhub
const inspectResult = spawnSync(
"clawhub",
["inspect", skillSlug, "--json"],
{ encoding: "utf-8" }
);
if (inspectResult.status !== 0) {
// Skill doesn't exist or can't be inspected
result.warnings.push(`Skill "${skillSlug}" not found or cannot be inspected`);
result.score = Math.min(result.score, 50);
} else {
try {
const skillInfo = JSON.parse(inspectResult.stdout);
// Check 2: Skill age (new skills are riskier)
if (skillInfo.skill?.createdAt) {
const createdMs = skillInfo.skill.createdAt;
const ageDays = (Date.now() - createdMs) / (1000 * 60 * 60 * 24);
if (ageDays < 7) {
result.warnings.push(`Skill is less than 7 days old (${ageDays.toFixed(1)} days)`);
result.score -= 15;
} else if (ageDays < 30) {
result.warnings.push(`Skill is less than 30 days old (${ageDays.toFixed(1)} days)`);
result.score -= 5;
}
}
// Check 3: Update frequency (stale skills are riskier)
if (skillInfo.skill?.updatedAt && skillInfo.skill?.createdAt) {
const updatedMs = skillInfo.skill.updatedAt;
const createdMs = skillInfo.skill.createdAt;
const updateAgeDays = (Date.now() - updatedMs) / (1000 * 60 * 60 * 24);
const totalAgeDays = (Date.now() - createdMs) / (1000 * 60 * 60 * 24);
if (updateAgeDays > 90 && totalAgeDays > 90) {
result.warnings.push(`Skill hasn't been updated in ${updateAgeDays.toFixed(0)} days`);
result.score -= 10;
}
}
// Check 4: Author reputation
if (skillInfo.owner?.handle) {
const authorResult = spawnSync(
"clawhub",
["search", skillInfo.owner.handle],
{ encoding: "utf-8" }
);
if (authorResult.status === 0) {
const lines = authorResult.stdout.trim().split('\n').filter(l => l);
const skillCount = lines.length - 1; // First line is header
if (skillCount === 1) {
result.warnings.push(`Author "${skillInfo.owner.handle}" has only 1 published skill`);
result.score -= 10;
} else if (skillCount < 3) {
result.warnings.push(`Author "${skillInfo.owner.handle}" has only ${skillCount} published skills`);
result.score -= 5;
}
}
}
// Check 5: Download statistics
if (skillInfo.skill?.stats?.downloads !== undefined) {
const downloads = skillInfo.skill.stats.downloads;
if (downloads < 10) {
result.warnings.push(`Low download count: ${downloads}`);
result.score -= 10;
} else if (downloads < 100) {
result.warnings.push(`Moderate download count: ${downloads}`);
result.score -= 5;
}
}
} catch (parseError) {
result.warnings.push(`Failed to parse skill information: ${parseError.message}`);
result.score = Math.min(result.score, 60);
}
}
// Check 6: Try installation to see if clawhub flags it as suspicious
// Use --force to bypass interactive prompt in non-interactive mode
const installCheck = spawnSync(
"bash",
["-c", `echo "n" | clawhub install ${skillSlug}${version ? ` --version ${version}` : ''} 2>&1`],
{ encoding: "utf-8" }
);
const output = installCheck.stdout || installCheck.stderr || "";
if (output.includes("suspicious") || output.includes("VirusTotal") || output.includes("flagged")) {
result.virustotal.push("Flagged by ClawHub's VirusTotal Code Insight");
result.score -= 40; // More severe penalty for VirusTotal flag
// Extract specific warnings
const lines = output.split('\n');
for (const line of lines) {
if (line.includes("Warning:") || line.includes("risky patterns") ||
line.includes("crypto keys") || line.includes("external APIs") ||
line.includes("eval") || line.includes("VirusTotal Code Insight")) {
const cleanLine = line.trim().replace(/^⚠️\s*/, '').replace(/^\s*Warning:\s*/, '');
if (cleanLine && !result.virustotal.includes(cleanLine)) {
result.virustotal.push(cleanLine);
}
}
}
}
// Check 7: If version specified, check if it exists
if (version) {
const versionCheck = spawnSync(
"clawhub",
["inspect", skillSlug, "--version", version, "--json"],
{ encoding: "utf-8" }
);
if (versionCheck.status !== 0) {
result.warnings.push(`Version ${version} not found for skill ${skillSlug}`);
result.score -= 20;
}
}
// Ensure score is within bounds
result.score = Math.max(0, Math.min(100, result.score));
result.safe = result.score >= threshold;
// Add summary warning if below threshold
if (!result.safe) {
result.warnings.unshift(`Reputation score ${result.score}/100 below threshold ${threshold}/100`);
}
} catch (error) {
result.warnings.push(`Reputation check error: ${error.message}`);
result.score = 50;
result.safe = result.score >= threshold;
}
return result;
}
// CLI interface for direct usage
if (import.meta.url === `file://${process.argv[1]}`) {
async function main() {
const args = process.argv.slice(2);
if (args.length < 1) {
console.error("Usage: node check_clawhub_reputation.mjs <skill-slug> [version] [threshold]");
process.exit(1);
}
const skillSlug = args[0];
const version = args[1] || "";
const threshold = args[2] ? parseInt(args[2], 10) : 70;
const result = await checkClawhubReputation(skillSlug, version, threshold);
console.log(JSON.stringify(result, null, 2));
if (!result.safe) {
process.exit(43);
}
}
main().catch(console.error);
}
@@ -0,0 +1,201 @@
#!/usr/bin/env node
import { spawnSync } from "node:child_process";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { checkClawhubReputation } from "./check_clawhub_reputation.mjs";
const EXIT_ADVISORY_CONFIRM_REQUIRED = 42;
const EXIT_REPUTATION_CONFIRM_REQUIRED = 43;
function printUsage() {
process.stderr.write(
[
"Usage:",
" node scripts/enhanced_guarded_install.mjs --skill <skill-name> [--version <version>] [--confirm-advisory] [--confirm-reputation] [--dry-run] [--reputation-threshold <score>]",
"",
"Examples:",
" node scripts/enhanced_guarded_install.mjs --skill helper-plus --version 1.0.1",
" node scripts/enhanced_guarded_install.mjs --skill helper-plus --version 1.0.1 --confirm-advisory --confirm-reputation",
" node scripts/enhanced_guarded_install.mjs --skill suspicious-skill --reputation-threshold 80",
"",
"Exit codes:",
" 0 success / no advisory or reputation block",
" 42 advisory matched and second confirmation is required",
" 43 reputation warning and second confirmation is required",
" 1 error",
"",
].join("\n"),
);
}
function parseArgs(argv) {
const parsed = {
skill: "",
version: "",
confirmAdvisory: false,
confirmReputation: false,
dryRun: false,
reputationThreshold: process.env.CLAWHUB_REPUTATION_THRESHOLD
? parseInt(process.env.CLAWHUB_REPUTATION_THRESHOLD, 10)
: 70,
};
for (let i = 0; i < argv.length; i += 1) {
const token = argv[i];
if (token === "--skill") {
parsed.skill = String(argv[i + 1] ?? "").trim();
i += 1;
continue;
}
if (token === "--version") {
parsed.version = String(argv[i + 1] ?? "").trim();
i += 1;
continue;
}
if (token === "--confirm-advisory") {
parsed.confirmAdvisory = true;
continue;
}
if (token === "--confirm-reputation") {
parsed.confirmReputation = true;
continue;
}
if (token === "--dry-run") {
parsed.dryRun = true;
continue;
}
if (token === "--reputation-threshold") {
parsed.reputationThreshold = parseInt(String(argv[i + 1] ?? "70"), 10);
i += 1;
continue;
}
if (token === "--help" || token === "-h") {
printUsage();
process.exit(0);
}
throw new Error(`Unknown argument: ${token}`);
}
if (!parsed.skill) {
throw new Error("Missing required argument: --skill");
}
if (!/^[a-z0-9-]+$/.test(parsed.skill)) {
throw new Error("Invalid --skill value. Use lowercase letters, digits, and hyphens only.");
}
if (parsed.reputationThreshold < 0 || parsed.reputationThreshold > 100 || Number.isNaN(parsed.reputationThreshold)) {
throw new Error("Invalid --reputation-threshold value. Must be between 0 and 100.");
}
return parsed;
}
async function runOriginalGuardedInstall(args) {
// Find the original guarded_skill_install.mjs from clawsec-suite
const suiteDir = path.join(os.homedir(), ".openclaw", "skills", "clawsec-suite");
const originalScript = path.join(suiteDir, "scripts", "guarded_skill_install.mjs");
try {
await fs.access(originalScript);
} catch {
throw new Error(`Original guarded_skill_install.mjs not found at ${originalScript}. Is clawsec-suite installed?`);
}
const env = { ...process.env };
if (args.confirmAdvisory) {
env.CLAWSEC_ALLOW_UNSIGNED_FEED = "1"; // Pass through to original script
}
const child = spawnSync(
"node",
[originalScript, ...args.originalArgs],
{
stdio: "inherit",
env,
cwd: suiteDir,
},
);
return {
exitCode: child.status ?? 1,
signal: child.signal,
};
}
async function main() {
try {
const args = parseArgs(process.argv.slice(2));
// Build args for original script (excluding reputation-specific args)
const originalArgs = [];
for (let i = 0; i < process.argv.slice(2).length; i++) {
const token = process.argv.slice(2)[i];
if (token === "--confirm-reputation" || token === "--reputation-threshold") {
i += token === "--reputation-threshold" ? 1 : 0;
continue;
}
originalArgs.push(token);
}
args.originalArgs = originalArgs;
// Step 1: Check reputation (unless already confirmed)
if (!args.confirmReputation) {
console.log(`Checking ClawHub reputation for ${args.skill}${args.version ? `@${args.version}` : ""}...`);
const reputationResult = await checkClawhubReputation(args.skill, args.version, args.reputationThreshold);
if (!reputationResult.safe) {
console.error("\n" + "=".repeat(80));
console.error("REPUTATION WARNING");
console.error("=".repeat(80));
console.error(`Skill "${args.skill}" has low reputation score: ${reputationResult.score}/100`);
console.error(`Threshold: ${args.reputationThreshold}/100`);
console.error("");
if (reputationResult.warnings.length > 0) {
console.error("Warnings:");
reputationResult.warnings.forEach(w => console.error(`${w}`));
console.error("");
}
if (reputationResult.virustotal) {
console.error("VirusTotal Code Insight flags:");
reputationResult.virustotal.forEach(v => console.error(`${v}`));
console.error("");
}
console.error("To install despite reputation warning, run with --confirm-reputation flag:");
console.error(` node ${process.argv[1]} --skill ${args.skill}${args.version ? ` --version ${args.version}` : ""} --confirm-reputation`);
console.error("");
console.error("=".repeat(80));
process.exit(EXIT_REPUTATION_CONFIRM_REQUIRED);
}
console.log(`✓ Reputation check passed: ${reputationResult.score}/100`);
} else {
console.log(`⚠️ Reputation confirmation override enabled for ${args.skill}`);
}
// Step 2: Run original guarded installer (handles advisory checks)
console.log("\nRunning advisory checks...");
const result = await runOriginalGuardedInstall(args);
if (result.exitCode !== 0 && result.exitCode !== EXIT_ADVISORY_CONFIRM_REQUIRED) {
process.exit(result.exitCode);
}
// If we get here, either success (0) or advisory confirmation required (42)
process.exit(result.exitCode);
} catch (error) {
console.error("Error:", error.message);
process.exit(1);
}
}
main();
@@ -0,0 +1,137 @@
#!/usr/bin/env node
import fs from "node:fs/promises";
import path from "node:path";
import os from "node:os";
async function main() {
console.log("Setting up ClawHub reputation checker integration...");
// Paths
const suiteDir = path.join(os.homedir(), ".openclaw", "skills", "clawsec-suite");
const checkerDir = path.join(os.homedir(), ".openclaw", "skills", "clawsec-clawhub-checker");
const hookLibDir = path.join(suiteDir, "hooks", "clawsec-advisory-guardian", "lib");
try {
// Check if clawsec-suite is installed
await fs.access(suiteDir);
console.log(`✓ Found clawsec-suite at ${suiteDir}`);
// Check if hook lib directory exists
await fs.access(hookLibDir);
console.log(`✓ Found advisory guardian hook at ${hookLibDir}`);
// Copy reputation module to hook lib
const reputationModuleSrc = path.join(checkerDir, "hooks", "clawsec-advisory-guardian", "lib", "reputation.mjs");
const reputationModuleDst = path.join(hookLibDir, "reputation.mjs");
await fs.copyFile(reputationModuleSrc, reputationModuleDst);
console.log(`✓ Copied reputation module to ${reputationModuleDst}`);
// Update hook handler to import reputation module
const hookHandlerPath = path.join(suiteDir, "hooks", "clawsec-advisory-guardian", "handler.ts");
let handlerContent = await fs.readFile(hookHandlerPath, "utf8");
// Check if already imported
if (!handlerContent.includes("from \"./lib/reputation.mjs\"")) {
// Add import after other imports
const importIndex = handlerContent.lastIndexOf("import");
const lineEndIndex = handlerContent.indexOf("\n", importIndex);
const newImport = `import { checkReputation } from "./lib/reputation.mjs";\n`;
handlerContent = handlerContent.slice(0, lineEndIndex + 1) + newImport + handlerContent.slice(lineEndIndex + 1);
// Find where matches are processed and add reputation check
const findMatchesLine = handlerContent.indexOf("const matches = findMatches(feed, installedSkills);");
if (findMatchesLine !== -1) {
const insertIndex = handlerContent.indexOf("\n", findMatchesLine) + 1;
const reputationCheckCode = `
// ClawHub reputation check for matched skills
for (const match of matches) {
const repResult = await checkReputation(match.skill.name, match.skill.version);
if (!repResult.safe) {
match.reputationWarning = true;
match.reputationScore = repResult.score;
match.reputationWarnings = repResult.warnings;
}
}
`;
handlerContent = handlerContent.slice(0, insertIndex) + reputationCheckCode + handlerContent.slice(insertIndex);
}
// Update alert message building to include reputation warnings
const buildAlertLine = handlerContent.indexOf("const alertMessage = buildAlertMessage(match);");
if (buildAlertLine !== -1) {
const lineStart = handlerContent.lastIndexOf("\n", buildAlertLine) + 1;
const lineEnd = handlerContent.indexOf("\n", buildAlertLine);
const oldLine = handlerContent.slice(lineStart, lineEnd);
const newLine = `const alertMessage = buildAlertMessage(match, match.reputationWarning ? { score: match.reputationScore, warnings: match.reputationWarnings } : undefined);`;
handlerContent = handlerContent.slice(0, lineStart) + newLine + handlerContent.slice(lineEnd);
}
await fs.writeFile(hookHandlerPath, handlerContent);
console.log(`✓ Updated hook handler with reputation checks`);
} else {
console.log(`✓ Hook handler already has reputation checks`);
}
// Create symlink or copy enhanced installer
const enhancedInstallerSrc = path.join(checkerDir, "scripts", "enhanced_guarded_install.mjs");
const enhancedInstallerDst = path.join(suiteDir, "scripts", "enhanced_guarded_install.mjs");
await fs.copyFile(enhancedInstallerSrc, enhancedInstallerDst);
console.log(`✓ Installed enhanced guarded installer at ${enhancedInstallerDst}`);
// Create wrapper script that uses enhanced installer by default
const wrapperScript = `#!/usr/bin/env node
// Wrapper that uses enhanced guarded installer with reputation checks
// This replaces the original guarded_skill_install.mjs in usage
import { spawnSync } from "node:child_process";
import { fileURLToPath } from "node:url";
import path from "node:path";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const enhancedScript = path.join(__dirname, "enhanced_guarded_install.mjs");
const result = spawnSync("node", [enhancedScript, ...process.argv.slice(2)], {
stdio: "inherit",
});
process.exit(result.status ?? 1);
`;
const wrapperPath = path.join(suiteDir, "scripts", "guarded_skill_install_wrapper.mjs");
await fs.writeFile(wrapperPath, wrapperScript);
await fs.chmod(wrapperPath, 0o755);
console.log(`✓ Created wrapper script at ${wrapperPath}`);
console.log("\n" + "=".repeat(80));
console.log("SETUP COMPLETE");
console.log("=".repeat(80));
console.log("\nThe ClawHub reputation checker has been integrated with clawsec-suite.");
console.log("\nWhat changed:");
console.log("1. Enhanced guarded installer with reputation checks installed");
console.log("2. Advisory guardian hook updated to include reputation warnings");
console.log("3. Wrapper script created for backward compatibility");
console.log("\nUsage:");
console.log(" node scripts/enhanced_guarded_install.mjs --skill <name> [--version <ver>]");
console.log(" node scripts/guarded_skill_install_wrapper.mjs --skill <name> [--version <ver>]");
console.log("\nNew exit code: 43 = Reputation warning (requires --confirm-reputation)");
console.log("\nRestart OpenClaw gateway for hook changes to take effect.");
console.log("=".repeat(80));
} catch (error) {
console.error("Setup failed:", error.message);
console.error("\nMake sure:");
console.error("1. clawsec-suite is installed (npx clawhub install clawsec-suite)");
console.error("2. You have write permissions to the suite directory");
process.exit(1);
}
}
main().catch(console.error);
+81
View File
@@ -0,0 +1,81 @@
{
"name": "clawsec-clawhub-checker",
"version": "0.0.1",
"description": "ClawHub reputation checker for ClawSec suite. Enhances guarded skill installer with VirusTotal Code Insight reputation scores and additional safety checks.",
"author": "david",
"license": "MIT",
"homepage": "https://clawsec.prompt.security/",
"keywords": [
"security",
"reputation",
"clawhub",
"virustotal",
"skills",
"installer",
"verification",
"defense-in-depth",
"openclaw"
],
"sbom": {
"files": [
{
"path": "SKILL.md",
"required": true,
"description": "Skill documentation and usage guide"
},
{
"path": "scripts/enhanced_guarded_install.mjs",
"required": true,
"description": "Enhanced guarded installer with reputation checks"
},
{
"path": "scripts/check_clawhub_reputation.mjs",
"required": true,
"description": "ClawHub reputation checking logic"
},
{
"path": "scripts/setup_reputation_hook.mjs",
"required": true,
"description": "Setup script to enhance existing advisory guardian hook"
},
{
"path": "hooks/clawsec-advisory-guardian/lib/reputation.mjs",
"required": true,
"description": "Reputation checking module for advisory guardian hook"
}
]
},
"dependencies": {
"clawsec-suite": ">=0.0.10"
},
"integration": {
"clawsec-suite": {
"enhances": [
"guarded_skill_install.mjs",
"clawsec-advisory-guardian hook"
],
"adds_exit_codes": {
"43": "Reputation warning - requires --confirm-reputation"
},
"adds_arguments": [
"--confirm-reputation",
"--reputation-threshold"
]
}
},
"openclaw": {
"emoji": "🛡️",
"category": "security",
"requires": {
"bins": ["clawhub", "curl", "jq"]
},
"triggers": [
"clawhub reputation",
"skill reputation check",
"virustotal skill check",
"safe skill install",
"check skill safety",
"skill security score"
]
}
}
@@ -0,0 +1,77 @@
#!/usr/bin/env node
import fs from "node:fs/promises";
import path from "node:path";
import os from "node:os";
async function updateSuiteCatalog() {
const suiteDir = "/home/david/.openclaw-clean/workspace/clawsec-suite";
const skillJsonPath = path.join(suiteDir, "skill.json");
try {
const skillJson = JSON.parse(await fs.readFile(skillJsonPath, "utf8"));
// Add clawsec-clawhub-checker to catalog
if (!skillJson.catalog) {
skillJson.catalog = {
description: "Available protections in the ClawSec suite",
base_url: "https://clawsec.prompt.security/releases/download",
skills: {}
};
}
skillJson.catalog.skills["clawsec-clawhub-checker"] = {
description: "ClawHub reputation checker - enhances guarded installer with VirusTotal scores",
default_install: false,
compatible: ["openclaw", "moltbot", "clawdbot", "other"],
note: "Requires clawsec-suite as base"
};
// Also update embedded_components if it exists
if (skillJson.embedded_components) {
skillJson.embedded_components["clawsec-clawhub-checker"] = {
source_skill: "clawsec-clawhub-checker",
source_version: "0.1.0",
capabilities: [
"ClawHub reputation checking",
"VirusTotal Code Insight integration",
"Skill age and author reputation analysis",
"Enhanced double confirmation for suspicious skills"
],
standalone_available: false,
depends_on: ["clawsec-suite"]
};
}
await fs.writeFile(skillJsonPath, JSON.stringify(skillJson, null, 2));
console.log(`✓ Updated ${skillJsonPath} with clawsec-clawhub-checker catalog entry`);
// Also update the local copy for PR
const localSuiteDir = "/tmp/clawsec-repo/skills/clawsec-suite";
const localSkillJsonPath = path.join(localSuiteDir, "skill.json");
try {
const localSkillJson = JSON.parse(await fs.readFile(localSkillJsonPath, "utf8"));
if (localSkillJson.catalog) {
localSkillJson.catalog.skills["clawsec-clawhub-checker"] = {
description: "ClawHub reputation checker - enhances guarded installer with VirusTotal scores",
default_install: false,
compatible: ["openclaw", "moltbot", "clawdbot", "other"],
note: "Requires clawsec-suite as base"
};
await fs.writeFile(localSkillJsonPath, JSON.stringify(localSkillJson, null, 2));
console.log(`✓ Updated local repo ${localSkillJsonPath} for PR`);
}
} catch (localError) {
console.log(`Note: Could not update local repo: ${localError.message}`);
}
} catch (error) {
console.error("Failed to update suite catalog:", error.message);
process.exit(1);
}
}
updateSuiteCatalog().catch(console.error);
+12 -1
View File
@@ -206,6 +206,17 @@
"clawdbot",
"other"
]
},
"clawsec-clawhub-checker": {
"description": "ClawHub reputation checker - enhances guarded installer with VirusTotal scores",
"default_install": false,
"compatible": [
"openclaw",
"moltbot",
"clawdbot",
"other"
],
"note": "Requires clawsec-suite as base"
}
}
},
@@ -236,4 +247,4 @@
"update skills"
]
}
}
}