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 +59 -13
- package/dist/cli.js +212 -43
- package/dist/cli.js.map +1 -1
- package/dist/hardening/index.d.ts +1 -0
- package/dist/hardening/index.d.ts.map +1 -1
- package/dist/hardening/index.js +4 -1
- package/dist/hardening/index.js.map +1 -1
- package/dist/hardening/nemoclaw-scanner.d.ts +46 -0
- package/dist/hardening/nemoclaw-scanner.d.ts.map +1 -0
- package/dist/hardening/nemoclaw-scanner.js +1061 -0
- package/dist/hardening/nemoclaw-scanner.js.map +1 -0
- package/dist/hardening/scanner.d.ts +7 -0
- package/dist/hardening/scanner.d.ts.map +1 -1
- package/dist/hardening/scanner.js +598 -0
- package/dist/hardening/scanner.js.map +1 -1
- package/dist/hardening/taxonomy.d.ts.map +1 -1
- package/dist/hardening/taxonomy.js +40 -0
- package/dist/hardening/taxonomy.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -3
- package/dist/index.js.map +1 -1
- package/dist/telemetry/contribute.d.ts +58 -49
- package/dist/telemetry/contribute.d.ts.map +1 -1
- package/dist/telemetry/contribute.js +187 -127
- package/dist/telemetry/contribute.js.map +1 -1
- package/dist/telemetry/index.d.ts +2 -2
- package/dist/telemetry/index.d.ts.map +1 -1
- package/dist/telemetry/index.js +8 -2
- package/dist/telemetry/index.js.map +1 -1
- package/dist/telemetry/opt-in.d.ts +22 -13
- package/dist/telemetry/opt-in.d.ts.map +1 -1
- package/dist/telemetry/opt-in.js +93 -102
- package/dist/telemetry/opt-in.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/hackmyagent)
|
|
6
6
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
7
|
-
[](https://github.com/opena2a-org/hackmyagent)
|
|
8
8
|
|
|
9
|
-
**
|
|
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
|
-
|
|
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** --
|
|
44
|
-
- **OpenClaw security** -- 34 checks for
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
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 |
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
-
|
|
113
|
-
-
|
|
114
|
-
-
|
|
115
|
-
-
|
|
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 (
|
|
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 (
|
|
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,
|
|
1550
|
-
*
|
|
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,
|
|
1555
|
-
//
|
|
1556
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
1593
|
-
|
|
1594
|
-
if (
|
|
1595
|
-
|
|
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
|
|
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 —
|
|
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);
|