hackmyagent 0.11.4 → 0.11.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,9 +4,9 @@
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/hackmyagent.svg)](https://www.npmjs.com/package/hackmyagent)
6
6
  [![License: Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
7
- [![Tests](https://img.shields.io/badge/tests-765%20passing-brightgreen)](https://github.com/opena2a-org/hackmyagent)
7
+ [![Tests](https://img.shields.io/badge/tests-1051%20passing-brightgreen)](https://github.com/opena2a-org/hackmyagent)
8
8
 
9
- **163 security checks for AI agents. Find what can go wrong before an attacker does.**
9
+ **183 security checks for AI agents. Find what can go wrong before an attacker does.**
10
10
 
11
11
  Security scanner and red-team toolkit for Claude Code, Cursor, VS Code, and any MCP server setup.
12
12
 
@@ -30,23 +30,43 @@ npx opena2a-cli review
30
30
 
31
31
  ## What It Finds
32
32
 
33
- **Attack testing:**
33
+ **Attack testing** -- 115 adversarial payloads across 11 categories (prompt injection, data exfiltration, jailbreak, MCP exploitation, supply chain, memory weaponization, A2A protocol attacks, context window attacks).
34
+
35
+ **Static analysis** -- 183 security checks across 35 categories covering credentials, MCP configs, OpenClaw/NemoClaw, Unicode steganography, CVE detection, governance, supply chain, memory poisoning, agent identity, and sandbox escape patterns.
36
+
37
+ <details>
38
+ <summary>Attack testing details (115 payloads)</summary>
39
+
34
40
  - **Prompt injection** -- tests whether agents follow injected instructions from untrusted input
35
41
  - **Data exfiltration** -- checks if agents can be tricked into leaking sensitive data to external endpoints
36
42
  - **Jailbreak and context manipulation** -- probes agent guardrails with adversarial prompts
37
43
  - **MCP exploitation** -- tests MCP servers for tool misuse, capability abuse, and unauthorized access
38
44
  - **Capability abuse** -- verifies agents can't exceed their intended permissions
45
+ - **Supply chain attacks** -- dependency confusion, tool shadowing, package impersonation
46
+ - **Memory weaponization** -- persistent instruction injection via agent memory systems
47
+ - **A2A protocol attacks** -- identity spoofing, capability escalation in multi-agent communication
48
+ - **Context window attacks** -- token flooding, attention manipulation, context poisoning
39
49
 
40
- **Static analysis:**
50
+ </details>
51
+
52
+ <details>
53
+ <summary>Static analysis details (183 checks)</summary>
54
+
55
+ - **Unicode steganography** -- invisible codepoints, zero-width chars, bidi attacks, homoglyph confusables, GlassWorm decoders ([real-world: os-info-checker-es6 npm attack, May 2025](https://thehackernews.com/2025/05/malicious-npm-package-leverages-unicode.html))
41
56
  - **Hardcoded credentials** -- API keys, tokens, and passwords in source or config files
42
57
  - **MCP server misconfigurations** -- open ports, root filesystem access, missing auth
43
- - **AI agent CVE detection** -- scans for CVE-2026-25253 (OpenClaw WebSocket RCE), CVE-2026-25157, CVE-2026-24763, and ClawHavoc IOCs
44
- - **OpenClaw security** -- 34 checks for OpenClaw configurations, skills, gateway, and credential redaction ([6 PRs merged upstream](https://opena2a.org/blogs/securing-openclaw-6-prs-merged))
58
+ - **AI agent CVE detection** -- CVE-2026-25253 (OpenClaw RCE), CVE-2026-25157, CVE-2026-24763, ClawHavoc IOCs
59
+ - **OpenClaw security** -- 34 checks for configurations, skills, gateway, credential redaction ([6 PRs merged upstream](https://opena2a.org/blogs/securing-openclaw-6-prs-merged))
60
+ - **NemoClaw/sandbox patterns** -- curl-pipe without checksum, empty artifact digests, exec() injection, predictable /tmp paths, process.env leakage, TOCTOU races, unsafe deserialization, messaging API egress
45
61
  - **Governance gaps** -- missing SOUL.md, no capability policies, unsigned MCP servers
46
62
  - **Credential scope drift** -- Google Maps keys accessing Gemini, AWS S3 keys reaching Bedrock
47
63
  - **Supply chain risks** -- vulnerable dependencies, unsigned skills, tampered packages
64
+ - **Memory and RAG poisoning** -- persistent instruction injection, knowledge base contamination
65
+ - **Agent identity** -- missing cryptographic identity, capability claims without attestation
66
+
67
+ </details>
48
68
 
49
- 163 checks across 34 categories. 55+ attack payloads. No flags needed.
69
+ 183 checks across 35 categories. 115 attack payloads. No flags needed.
50
70
 
51
71
  ---
52
72
 
@@ -69,7 +89,7 @@ npm install --save-dev hackmyagent
69
89
  ```
70
90
 
71
91
  ┌──────────────────────────────────────────┐
72
- │ HackMyAgent v0.10.1 — Security Scanner │
92
+ │ HackMyAgent v0.11.5 — Security Scanner │
73
93
  │ Found: 3 critical · 5 high · 12 medium │
74
94
  │ │
75
95
  │ CRED-001 critical Hardcoded API key in .env │
@@ -88,9 +108,10 @@ npm install --save-dev hackmyagent
88
108
 
89
109
  Step-by-step guides for common workflows:
90
110
 
91
- - **[Scan my agent](docs/use-cases/scan-my-agent.md)** -- Run all 163 checks and auto-fix findings (5 min)
111
+ - **[Scan my agent](docs/use-cases/scan-my-agent.md)** -- Run all 183 checks and auto-fix findings (5 min)
92
112
  - **[Red-team MCP servers](docs/use-cases/red-team-mcp.md)** -- Test MCP servers with adversarial payloads (10 min)
93
113
  - **[Secure OpenClaw](docs/use-cases/openclaw-security.md)** -- OpenClaw-specific checks, CVE detection, ClawHavoc IOC scanning (10 min)
114
+ - **Secure NemoClaw** -- Scan NVIDIA NemoClaw sandbox installations for credential exposure, network misconfig, and sandbox escape vectors (5 min)
94
115
  - **[CI/CD pipeline](docs/use-cases/ci-pipeline.md)** -- GitHub Actions with JSON/SARIF output (5 min)
95
116
 
96
117
  ---
@@ -124,7 +145,7 @@ hackmyagent secure --publish # push results to OpenA2A Registry
124
145
 
125
146
 
126
147
  <details>
127
- <summary>All 30 security categories</summary>
148
+ <summary>All 35 security categories</summary>
128
149
 
129
150
  | Category | Checks | What it detects |
130
151
  |----------|--------|-----------------|
@@ -157,7 +178,12 @@ hackmyagent secure --publish # push results to OpenA2A Registry
157
178
  | CONFIG | 9 | Insecure default settings |
158
179
  | SUPPLY | 8 | Supply chain attack vectors |
159
180
  | SKILL | 12 | Malicious skill/tool detection |
160
- | HEARTBEAT | 6 | Heartbeat/cron abuse |
181
+ | HEARTBEAT | 7 | Heartbeat/cron abuse |
182
+ | UNICODE-STEGO | 5 | Invisible codepoints, zero-width chars, bidi attacks, homoglyphs, GlassWorm decoders |
183
+ | MEM | 5 | Memory poisoning, context injection |
184
+ | RAG | 4 | RAG/knowledge base poisoning |
185
+ | AIM | 3 | Agent identity verification |
186
+ | NEMO | 10 | NemoClaw/sandbox patterns: curl-pipe, digest bypass, exec injection, /tmp races, env leakage |
161
187
 
162
188
  </details>
163
189
 
@@ -185,7 +211,7 @@ Use `--dry-run` to preview changes. Backups are created in `.hackmyagent-backup/
185
211
 
186
212
  ### `hackmyagent attack` -- Red Team
187
213
 
188
- Test your AI agent with 55 adversarial payloads across 5 attack categories.
214
+ Test your AI agent with 115 adversarial payloads across 11 attack categories.
189
215
 
190
216
  ```bash
191
217
  hackmyagent attack --local # local simulation
@@ -205,6 +231,12 @@ hackmyagent attack https://api.example.com --fail-on-vulnerable medium # CI gat
205
231
  | `data-exfiltration` | 11 | Extract sensitive data, system prompts, credentials |
206
232
  | `capability-abuse` | 10 | Misuse agent tools for unintended actions |
207
233
  | `context-manipulation` | 10 | Poison agent context or memory |
234
+ | `supply-chain` | 10 | Dependency confusion, package impersonation |
235
+ | `tool-shadow` | 10 | Tool shadowing, capability escalation |
236
+ | `mcp-exploitation` | 10 | MCP protocol abuse, tool injection |
237
+ | `memory-weaponization` | 10 | Persistent memory poisoning attacks |
238
+ | `a2a-attacks` | 10 | Agent-to-agent identity spoofing |
239
+ | `context-window` | 10 | Token flooding, attention manipulation |
208
240
 
209
241
  > Only test systems you own or have written authorization to test.
210
242
 
@@ -247,11 +279,25 @@ hackmyagent harden-soul --dry-run # preview without writing
247
279
  ```
248
280
 
249
281
 
282
+ ---
283
+
284
+ ### `hackmyagent secure-nemoclaw` -- NemoClaw Sandbox Scanner
285
+
286
+ Scan NVIDIA NemoClaw installations for credential exposure, network misconfiguration, blueprint integrity issues, sandbox escape vectors, and inherited OpenClaw vulnerabilities. 28 checks across 6 categories.
287
+
288
+ ```bash
289
+ hackmyagent secure-nemoclaw # scan auto-detected directory
290
+ hackmyagent secure-nemoclaw ~/.nemoclaw # scan specific directory
291
+ hackmyagent secure-nemoclaw --json # JSON output for CI
292
+ hackmyagent secure-nemoclaw --verbose # show all checks including passed
293
+ ```
294
+
295
+
250
296
  ---
251
297
 
252
298
  ### `hackmyagent trust` -- Package Trust Verification
253
299
 
254
- Check trust levels for AI packages before installing them. Queries the [OpenA2A Registry](https://registry.opena2a.org) trust graph.
300
+ Check trust levels for AI packages before installing them. Queries the OpenA2A Registry trust graph (launching April 2026).
255
301
 
256
302
  ```bash
257
303
  hackmyagent trust server-filesystem # MCP shorthand
package/dist/cli.js CHANGED
@@ -41,6 +41,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
41
41
  const commander_1 = require("commander");
42
42
  const index_1 = require("./index");
43
43
  const resolve_mcp_1 = require("./resolve-mcp");
44
+ const nemoclaw_scanner_1 = require("./hardening/nemoclaw-scanner");
44
45
  const program = new commander_1.Command();
45
46
  // Write JSON to stdout synchronously with retry for pipe backpressure.
46
47
  // process.stdout.write() is async and gets truncated when process.exit()
@@ -103,19 +104,19 @@ program
103
104
  .name('hackmyagent')
104
105
  .description(`Find it. Break it. Fix it.
105
106
 
106
- The hacker's toolkit for AI agents. 147+ security checks, 115 attack
107
+ The hacker's toolkit for AI agents. 183 security checks, 115 attack
107
108
  payloads, auto-fix with rollback, and OASB benchmark compliance.
108
109
 
109
110
  Documentation: https://hackmyagent.com/docs
110
111
 
111
112
  Updates (v${index_1.VERSION}):
112
- - MCP JSON-RPC and A2A protocol attack modes
113
- - SARIF and HTML output for all scan modes
114
- - Semantic engine (structural + LLM analysis)
115
- - OpenA2A Registry integration for trust scoring
113
+ - NemoClaw sandbox scanner (28 installation checks)
114
+ - 10 new static analysis patterns (NEMO series)
115
+ - Community trust contributions
116
+ - 183 checks across 35 categories
116
117
 
117
118
  Examples:
118
- $ hackmyagent secure Find vulnerabilities (147+ checks)
119
+ $ hackmyagent secure Find vulnerabilities (183 checks)
119
120
  $ hackmyagent attack --local Break it with 115 attack payloads
120
121
  $ hackmyagent secure --fix Fix issues automatically
121
122
  $ hackmyagent fix-all Run all security plugins
@@ -124,7 +125,7 @@ Examples:
124
125
  .option('--no-color', 'Disable colored output (also respects NO_COLOR env)');
125
126
  program.addHelpText('beforeAll', `
126
127
  Quick start:
127
- $ hackmyagent secure Scan current directory (147+ checks)
128
+ $ hackmyagent secure Scan current directory (183 checks)
128
129
  $ hackmyagent fix-all --with-aim Auto-fix + create agent identity
129
130
  $ hackmyagent attack Red-team your agent
130
131
  `);
@@ -1544,16 +1545,21 @@ function resolvePackageVersion(targetDir) {
1544
1545
  * Determines whether to contribute based on:
1545
1546
  * 1. --contribute / --no-contribute CLI flags (highest priority)
1546
1547
  * 2. ~/.opena2a/config.json contribute.enabled setting
1547
- * 3. Interactive opt-in prompt (first scan or scan #10)
1548
1548
  *
1549
- * If contributing, builds an anonymized payload and submits it
1550
- * asynchronously (non-blocking). Failures are logged as warnings.
1549
+ * If contributing, queues an anonymized event to ~/.opena2a/contribute-queue.json
1550
+ * (compatible with @opena2a/contribute format) and flushes when threshold reached.
1551
+ *
1552
+ * Also records the scan and shows a delayed consent tip after the 3rd scan
1553
+ * if the user hasn't opted in or dismissed.
1551
1554
  */
1552
- async function handleContribution(contributeFlag, targetDir, findings, registryUrl, format) {
1555
+ async function handleContribution(contributeFlag, targetDir, findings, durationMs, registryUrl, format) {
1553
1556
  try {
1554
- const { isContributeEnabled, shouldPromptContribute, showContributePrompt, incrementScanCount, buildContributionPayloadFromDir, submitContribution, } = await Promise.resolve().then(() => __importStar(require('./telemetry')));
1555
- // Always increment scan count
1556
- incrementScanCount();
1557
+ const { isContributeEnabled, recordScanAndMaybeShowTip, buildScanEvent, queueAndMaybeFlush, } = await Promise.resolve().then(() => __importStar(require('./telemetry')));
1558
+ // Record scan count and maybe show the delayed consent tip
1559
+ const tip = recordScanAndMaybeShowTip();
1560
+ if (tip && format === 'text' && process.stdout.isTTY) {
1561
+ process.stdout.write(tip + '\n');
1562
+ }
1557
1563
  // Determine whether to contribute
1558
1564
  let shouldContribute;
1559
1565
  if (contributeFlag === true) {
@@ -1566,35 +1572,19 @@ async function handleContribution(contributeFlag, targetDir, findings, registryU
1566
1572
  }
1567
1573
  else {
1568
1574
  // Check config
1569
- const configSetting = isContributeEnabled();
1570
- if (configSetting === true) {
1571
- shouldContribute = true;
1572
- }
1573
- else if (configSetting === false) {
1574
- shouldContribute = false;
1575
- }
1576
- else {
1577
- // Not configured -- prompt after 3 scans (interactive TTY only)
1578
- if (format === 'text' && process.stdout.isTTY && shouldPromptContribute()) {
1579
- shouldContribute = await showContributePrompt();
1580
- }
1581
- else {
1582
- shouldContribute = false;
1583
- }
1584
- }
1575
+ shouldContribute = isContributeEnabled() === true;
1585
1576
  }
1586
1577
  if (!shouldContribute)
1587
1578
  return;
1588
- // Build and submit contribution (non-blocking)
1579
+ // Build and queue contribution event (non-blocking, flushes at threshold)
1589
1580
  const packageName = resolvePackageName(targetDir);
1590
1581
  if (!packageName)
1591
1582
  return;
1592
- const payload = buildContributionPayloadFromDir(packageName, targetDir, findings);
1593
- const result = await submitContribution(payload, registryUrl);
1594
- if (result.success && format === 'text') {
1595
- console.log('Contributed anonymized scan summary to OpenA2A Registry (--no-contribute to opt out)');
1583
+ const event = buildScanEvent(packageName, targetDir, findings, durationMs);
1584
+ await queueAndMaybeFlush(event, registryUrl, format === 'text');
1585
+ if (format === 'text') {
1586
+ process.stdout.write('Queued anonymized scan summary for OpenA2A Registry (--no-contribute to opt out)\n');
1596
1587
  }
1597
- // Failures are silently ignored -- contribution is best-effort
1598
1588
  }
1599
1589
  catch {
1600
1590
  // Non-fatal: contribution failure must never crash the scan
@@ -1606,7 +1596,7 @@ async function handleContribution(contributeFlag, targetDir, findings, registryU
1606
1596
  * Converts SoulScanResult controls into SecurityFinding-like objects
1607
1597
  * for the contribution module, then delegates to handleContribution.
1608
1598
  */
1609
- async function handleSoulContribution(contributeFlag, targetDir, result, registryUrl, format) {
1599
+ async function handleSoulContribution(contributeFlag, targetDir, result, durationMs, registryUrl, format) {
1610
1600
  // Convert soul controls into SecurityFinding-shaped objects
1611
1601
  const findings = [];
1612
1602
  for (const domain of result.domains) {
@@ -1625,13 +1615,13 @@ async function handleSoulContribution(contributeFlag, targetDir, result, registr
1625
1615
  });
1626
1616
  }
1627
1617
  }
1628
- await handleContribution(contributeFlag, targetDir, findings, registryUrl, format);
1618
+ await handleContribution(contributeFlag, targetDir, findings, durationMs, registryUrl, format);
1629
1619
  }
1630
1620
  program
1631
1621
  .command('secure')
1632
1622
  .description(`Scan and harden your agent setup
1633
1623
 
1634
- Performs 147 security checks across 30 categories:
1624
+ Performs 183 security checks across 35 categories:
1635
1625
  • Credentials: API key exposure, secrets in configs
1636
1626
  • MCP: Server configs, tool permissions, secrets
1637
1627
  • Network: TLS, interface bindings, CORS
@@ -1755,6 +1745,7 @@ Examples:
1755
1745
  }
1756
1746
  }
1757
1747
  const scanner = new index_1.HardeningScanner();
1748
+ const scanStartMs = Date.now();
1758
1749
  const result = await scanner.scan({
1759
1750
  targetDir,
1760
1751
  autoFix: options.fix ?? false,
@@ -1764,6 +1755,7 @@ Examples:
1764
1755
  cliName: CLI_PREFIX,
1765
1756
  onProgress,
1766
1757
  });
1758
+ const scanDurationMs = Date.now() - scanStartMs;
1767
1759
  // OASB-2 composite mode: infrastructure (50%) + governance (50%)
1768
1760
  if (isOasb2) {
1769
1761
  const infraResult = generateBenchmarkReport(result.allFindings || result.findings, level, options.category);
@@ -1899,7 +1891,7 @@ Examples:
1899
1891
  writeJsonStdout(jsonOutput);
1900
1892
  }
1901
1893
  // Community contribution (non-blocking, runs in JSON mode too)
1902
- await handleContribution(options.contribute, targetDir, result.findings, options.registryUrl, format);
1894
+ await handleContribution(options.contribute, targetDir, result.findings, scanDurationMs, options.registryUrl, format);
1903
1895
  const critHigh = result.findings.filter((f) => !f.passed && !f.fixed && (f.severity === 'critical' || f.severity === 'high'));
1904
1896
  if (critHigh.length > 0)
1905
1897
  process.exitCode = 1;
@@ -2128,7 +2120,7 @@ Examples:
2128
2120
  }
2129
2121
  }
2130
2122
  // Community contribution: share anonymized findings with OpenA2A Registry
2131
- await handleContribution(options.contribute, targetDir, result.findings, options.registryUrl, format);
2123
+ await handleContribution(options.contribute, targetDir, result.findings, scanDurationMs, options.registryUrl, format);
2132
2124
  // Star prompt (interactive TTY only, text format only)
2133
2125
  if (process.stdout.isTTY) {
2134
2126
  console.log(`${colors.cyan}Helpful?${RESET()} Star the project: https://github.com/opena2a-org/opena2a\n`);
@@ -2377,6 +2369,180 @@ Examples:
2377
2369
  process.exit(1);
2378
2370
  }
2379
2371
  });
2372
+ // NemoClaw-specific helpers
2373
+ const NEMOCLAW_CHECK_CATEGORIES = nemoclaw_scanner_1.NEMOCLAW_CATEGORIES;
2374
+ function detectNemoClawDirectory(providedDir) {
2375
+ const os = require('os');
2376
+ const fs = require('fs');
2377
+ const path = require('path');
2378
+ if (providedDir && providedDir !== '') {
2379
+ return providedDir.startsWith('/') ? providedDir : path.join(process.cwd(), providedDir);
2380
+ }
2381
+ const homeDir = os.homedir();
2382
+ const candidates = [
2383
+ path.join(homeDir, '.nemoclaw'),
2384
+ path.join(homeDir, '.openshell'),
2385
+ path.join(homeDir, '.openclaw'),
2386
+ ];
2387
+ for (const candidate of candidates) {
2388
+ if (fs.existsSync(candidate)) {
2389
+ return candidate;
2390
+ }
2391
+ }
2392
+ return process.cwd();
2393
+ }
2394
+ function filterNemoClawFindings(findings) {
2395
+ return findings.filter((f) => {
2396
+ const checkId = f.checkId.toUpperCase();
2397
+ return checkId.startsWith('HMA-NMC-');
2398
+ });
2399
+ }
2400
+ function assessNemoClawRiskLevel(findings) {
2401
+ const criticalCount = findings.filter((f) => f.severity === 'critical').length;
2402
+ const highCount = findings.filter((f) => f.severity === 'high').length;
2403
+ const mediumCount = findings.filter((f) => f.severity === 'medium').length;
2404
+ if (criticalCount > 0) {
2405
+ return {
2406
+ level: 'Critical',
2407
+ color: colors.brightRed,
2408
+ description: `${criticalCount} critical finding(s) with recommended fixes available.`,
2409
+ };
2410
+ }
2411
+ if (highCount > 0) {
2412
+ return {
2413
+ level: 'High',
2414
+ color: colors.red,
2415
+ description: `${highCount} high-severity finding(s) detected. Fixes available below.`,
2416
+ };
2417
+ }
2418
+ if (mediumCount > 0) {
2419
+ return {
2420
+ level: 'Moderate',
2421
+ color: colors.yellow,
2422
+ description: 'Some findings detected. Review the recommendations below.',
2423
+ };
2424
+ }
2425
+ if (findings.length === 0) {
2426
+ return {
2427
+ level: 'None',
2428
+ color: colors.dim,
2429
+ description: `No NemoClaw installation detected. Run \`${CLI_PREFIX} secure\` for a full scan.`,
2430
+ };
2431
+ }
2432
+ return {
2433
+ level: 'Low',
2434
+ color: colors.green,
2435
+ description: 'No critical or high findings detected.',
2436
+ };
2437
+ }
2438
+ program
2439
+ .command('secure-nemoclaw')
2440
+ .description(`Security scan for NVIDIA NemoClaw installations
2441
+
2442
+ Performs focused security checks for NemoClaw sandbox deployments:
2443
+ - Secrets: NVIDIA API key exposure in configs, logs, Docker, shell history
2444
+ - Network: Gateway/k3s/inference port binding, Docker socket, egress policies
2445
+ - Skills: Blueprint integrity, skill verification, directory permissions
2446
+ - Process: Sandbox privileges, seccomp/Landlock enforcement, root execution
2447
+ - OpenClaw layer: Inherited misconfigs that survive NemoClaw sandboxing
2448
+
2449
+ Auto-detects ~/.nemoclaw, ~/.openshell, or ~/.openclaw directories.
2450
+ Exit code 1 if critical/high issues found.
2451
+
2452
+ Examples:
2453
+ $ hackmyagent secure-nemoclaw Scan auto-detected directory
2454
+ $ hackmyagent secure-nemoclaw ~/.nemoclaw Scan specific directory
2455
+ $ hackmyagent secure-nemoclaw --json JSON output for CI`)
2456
+ .argument('[directory]', 'Directory to scan (default: ~/.nemoclaw or ~/.openshell)', '')
2457
+ .option('--json', 'Output as JSON (for scripting/CI)')
2458
+ .option('-v, --verbose', 'Show all checks including passed ones')
2459
+ .action(async (directory, options) => {
2460
+ try {
2461
+ const targetDir = detectNemoClawDirectory(directory);
2462
+ if (!options.json) {
2463
+ console.log(`\nNemoClaw Security Report`);
2464
+ console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`);
2465
+ console.log(`Scanning ${targetDir}...\n`);
2466
+ }
2467
+ const scanner = new nemoclaw_scanner_1.NemoClawScanner();
2468
+ const findings = await scanner.scan(targetDir, {});
2469
+ // Enrich with taxonomy
2470
+ const { enrichWithTaxonomy } = require('./hardening/taxonomy');
2471
+ enrichWithTaxonomy(findings);
2472
+ const issues = findings.filter((f) => !f.passed);
2473
+ const passedFindings = findings.filter((f) => f.passed);
2474
+ if (options.json) {
2475
+ const jsonOutput = {
2476
+ target: targetDir,
2477
+ riskLevel: assessNemoClawRiskLevel(issues).level,
2478
+ totalChecks: findings.length,
2479
+ issues: issues.length,
2480
+ passed: passedFindings.length,
2481
+ findings: findings,
2482
+ };
2483
+ writeJsonStdout(jsonOutput);
2484
+ return;
2485
+ }
2486
+ // Risk assessment
2487
+ const risk = assessNemoClawRiskLevel(issues);
2488
+ console.log(`Risk Level: ${risk.color}${risk.level}${RESET()}`);
2489
+ console.log(`${risk.description}\n`);
2490
+ // Summary stats
2491
+ console.log(`Checks: ${findings.length} total | ${issues.length} issues | ${passedFindings.length} passed\n`);
2492
+ // Show issues
2493
+ if (issues.length > 0) {
2494
+ console.log(`${colors.red}Findings:${RESET()}\n`);
2495
+ for (const finding of issues) {
2496
+ const display = SEVERITY_DISPLAY[finding.severity];
2497
+ const location = finding.file
2498
+ ? finding.line
2499
+ ? `${finding.file}:${finding.line}`
2500
+ : finding.file
2501
+ : '';
2502
+ const sevLabel = finding.severity.charAt(0).toUpperCase() + finding.severity.slice(1);
2503
+ console.log(`${display.color()}${display.symbol} [${finding.checkId}] ${sevLabel}${RESET()}`);
2504
+ console.log(` ${finding.description}`);
2505
+ if (location) {
2506
+ console.log(` File: ${location}`);
2507
+ }
2508
+ if (finding.fix) {
2509
+ console.log(` ${colors.cyan}Recommended fix:${RESET()} ${finding.fix}`);
2510
+ }
2511
+ console.log();
2512
+ }
2513
+ }
2514
+ else {
2515
+ console.log(`${colors.green}No NemoClaw-specific issues found.${RESET()}\n`);
2516
+ }
2517
+ // Show passed checks in verbose mode
2518
+ if (options.verbose && passedFindings.length > 0) {
2519
+ console.log(`${colors.green}Passed Checks:${RESET()}`);
2520
+ for (const finding of passedFindings) {
2521
+ console.log(` ${colors.green}[ok]${RESET()} [${finding.checkId}] ${finding.name}`);
2522
+ }
2523
+ console.log();
2524
+ }
2525
+ // Shodan self-check guidance
2526
+ if (issues.some((f) => f.category === 'network')) {
2527
+ console.log(`${colors.yellow}Internet Exposure Check:${RESET()}`);
2528
+ console.log(` Check if your instance is visible on Shodan:`);
2529
+ console.log(` https://www.shodan.io/host/<YOUR-IP>`);
2530
+ console.log(` Known NemoClaw dorks: port:18789, port:6443 ssl.cert.subject.cn:"k3s-serving"\n`);
2531
+ }
2532
+ console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
2533
+ console.log(`Run '${CLI_PREFIX} secure-openclaw' for OpenClaw-specific checks.`);
2534
+ console.log(`Run '${CLI_PREFIX} secure' for a full security scan.\n`);
2535
+ // Exit with non-zero if critical/high issues remain
2536
+ const criticalOrHigh = issues.filter((f) => f.severity === 'critical' || f.severity === 'high');
2537
+ if (criticalOrHigh.length > 0) {
2538
+ process.exit(1);
2539
+ }
2540
+ }
2541
+ catch (error) {
2542
+ console.error(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
2543
+ process.exit(1);
2544
+ }
2545
+ });
2380
2546
  program
2381
2547
  .command('scan')
2382
2548
  .description(`Scan external target for exposed MCP endpoints
@@ -3940,7 +4106,7 @@ Examples:
3940
4106
  console.log(`\n Detected: ${result.tool}\n`);
3941
4107
  console.log(` Added HackMyAgent MCP server to ${result.configPath}\n`);
3942
4108
  console.log(` Available tools in ${result.tool}:`);
3943
- console.log(` hackmyagent_scan — 147+ checks + structural analysis`);
4109
+ console.log(` hackmyagent_scan — 183 checks + structural analysis`);
3944
4110
  console.log(` hackmyagent_deep_scan — Full analysis with LLM reasoning`);
3945
4111
  console.log(` hackmyagent_analyze_file — Analyze a single file`);
3946
4112
  console.log(` hackmyagent_benchmark — OASB-1 compliance assessment\n`);
@@ -4047,12 +4213,14 @@ Examples:
4047
4213
  }
4048
4214
  const prefix = getCommandPrefix();
4049
4215
  const scanner = new index_1.SoulScanner();
4216
+ const soulScanStartMs = Date.now();
4050
4217
  const result = await scanner.scanSoul(targetDir, {
4051
4218
  verbose: options.verbose,
4052
4219
  tier: options.tier,
4053
4220
  profile: options.profile,
4054
4221
  deepAnalysis: options.deep,
4055
4222
  });
4223
+ const soulScanDurationMs = Date.now() - soulScanStartMs;
4056
4224
  // JSON output
4057
4225
  if (options.json) {
4058
4226
  // Run publish in JSON mode and include result in output
@@ -4090,6 +4258,7 @@ Examples:
4090
4258
  process.exit(1);
4091
4259
  }
4092
4260
  }
4261
+ await handleSoulContribution(options.contribute, targetDir, result, soulScanDurationMs, options.registryUrl, 'json');
4093
4262
  return;
4094
4263
  }
4095
4264
  // Text output
@@ -4203,7 +4372,7 @@ Examples:
4203
4372
  }
4204
4373
  // Community contribution: share anonymized findings with OpenA2A Registry
4205
4374
  const soulFormat = options.json ? 'json' : 'text';
4206
- await handleSoulContribution(options.contribute, targetDir, result, options.registryUrl, soulFormat);
4375
+ await handleSoulContribution(options.contribute, targetDir, result, soulScanDurationMs, options.registryUrl, soulFormat);
4207
4376
  // In CI mode, exit non-zero if any controls failed
4208
4377
  if (options.ci) {
4209
4378
  const failedControls = result.domains.flatMap(d => d.controls).filter(c => !c.passed);