hackmyagent 0.11.8 → 0.11.9
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 +8 -16
- package/dist/cli.js +229 -21
- package/dist/cli.js.map +1 -1
- package/dist/hardening/index.d.ts +1 -1
- package/dist/hardening/index.d.ts.map +1 -1
- package/dist/hardening/scanner.d.ts +75 -0
- package/dist/hardening/scanner.d.ts.map +1 -1
- package/dist/hardening/scanner.js +1202 -232
- package/dist/hardening/scanner.js.map +1 -1
- package/dist/hardening/security-check.d.ts +3 -1
- package/dist/hardening/security-check.d.ts.map +1 -1
- package/dist/hardening/taxonomy.d.ts.map +1 -1
- package/dist/hardening/taxonomy.js +16 -0
- package/dist/hardening/taxonomy.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/registry/client.d.ts +26 -0
- package/dist/registry/client.d.ts.map +1 -1
- package/dist/registry/client.js +63 -0
- package/dist/registry/client.js.map +1 -1
- package/dist/registry/index.d.ts +2 -2
- package/dist/registry/index.d.ts.map +1 -1
- package/dist/registry/index.js +2 -1
- package/dist/registry/index.js.map +1 -1
- package/dist/registry/publish.d.ts +11 -0
- package/dist/registry/publish.d.ts.map +1 -1
- package/dist/registry/publish.js +62 -0
- package/dist/registry/publish.js.map +1 -1
- package/dist/semantic/integration/finding-adapter.d.ts +1 -0
- package/dist/semantic/integration/finding-adapter.d.ts.map +1 -1
- package/dist/semantic/integration/finding-adapter.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
6
6
|
[](https://github.com/opena2a-org/hackmyagent)
|
|
7
7
|
|
|
8
|
-
**
|
|
8
|
+
**199 security checks for AI agents. Find what can go wrong before an attacker does.**
|
|
9
9
|
|
|
10
10
|
Security scanner and red-team toolkit for Claude Code, Cursor, VS Code, and any MCP server setup.
|
|
11
11
|
|
|
@@ -31,7 +31,7 @@ npx opena2a-cli review
|
|
|
31
31
|
|
|
32
32
|
**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).
|
|
33
33
|
|
|
34
|
-
**Static analysis** --
|
|
34
|
+
**Static analysis** -- 199 security checks across 60 categories covering credentials, MCP configs, OpenClaw/NemoClaw, Unicode steganography, CVE detection, governance, supply chain, memory poisoning, agent identity, and sandbox escape patterns.
|
|
35
35
|
|
|
36
36
|
<details>
|
|
37
37
|
<summary>Attack testing details (115 payloads)</summary>
|
|
@@ -49,7 +49,7 @@ npx opena2a-cli review
|
|
|
49
49
|
</details>
|
|
50
50
|
|
|
51
51
|
<details>
|
|
52
|
-
<summary>Static analysis details (
|
|
52
|
+
<summary>Static analysis details (199 checks)</summary>
|
|
53
53
|
|
|
54
54
|
- **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))
|
|
55
55
|
- **Hardcoded credentials** -- API keys, tokens, and passwords in source or config files
|
|
@@ -65,7 +65,7 @@ npx opena2a-cli review
|
|
|
65
65
|
|
|
66
66
|
</details>
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
199 checks across 60 categories. 115 attack payloads. No flags needed.
|
|
69
69
|
|
|
70
70
|
---
|
|
71
71
|
|
|
@@ -107,10 +107,9 @@ npm install --save-dev hackmyagent
|
|
|
107
107
|
|
|
108
108
|
Step-by-step guides for common workflows:
|
|
109
109
|
|
|
110
|
-
- **[Scan my agent](docs/use-cases/scan-my-agent.md)** -- Run all
|
|
110
|
+
- **[Scan my agent](docs/use-cases/scan-my-agent.md)** -- Run all 199 checks and auto-fix findings (5 min)
|
|
111
111
|
- **[Red-team MCP servers](docs/use-cases/red-team-mcp.md)** -- Test MCP servers with adversarial payloads (10 min)
|
|
112
|
-
- **[Secure OpenClaw](docs/use-cases/openclaw-security.md)** --
|
|
113
|
-
- **Secure NemoClaw** -- Scan NVIDIA NemoClaw sandbox installations for credential exposure, network misconfig, and sandbox escape vectors (5 min)
|
|
112
|
+
- **[Secure OpenClaw](docs/use-cases/openclaw-security.md)** -- Auto-detected when OpenClaw files are present. Includes CVE detection and ClawHavoc IOC scanning (10 min)
|
|
114
113
|
- **[CI/CD pipeline](docs/use-cases/ci-pipeline.md)** -- GitHub Actions with JSON/SARIF output (5 min)
|
|
115
114
|
|
|
116
115
|
---
|
|
@@ -280,16 +279,9 @@ hackmyagent harden-soul --dry-run # preview without writing
|
|
|
280
279
|
|
|
281
280
|
---
|
|
282
281
|
|
|
283
|
-
###
|
|
282
|
+
### OpenClaw and NemoClaw Detection
|
|
284
283
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
```bash
|
|
288
|
-
hackmyagent secure-nemoclaw # scan auto-detected directory
|
|
289
|
-
hackmyagent secure-nemoclaw ~/.nemoclaw # scan specific directory
|
|
290
|
-
hackmyagent secure-nemoclaw --json # JSON output for CI
|
|
291
|
-
hackmyagent secure-nemoclaw --verbose # show all checks including passed
|
|
292
|
-
```
|
|
284
|
+
`hackmyagent secure` auto-detects OpenClaw and NemoClaw installations by looking for `.openclaw/`, `.moltbot/`, `.nemoclaw/`, `openclaw.json`, and `openclaw.plugin.json`. When detected, it automatically runs platform-specific checks (28 NemoClaw checks, 34 OpenClaw checks) alongside the standard 199 security checks. No separate commands needed.
|
|
293
285
|
|
|
294
286
|
|
|
295
287
|
---
|
package/dist/cli.js
CHANGED
|
@@ -82,6 +82,19 @@ function resolveCliPrefix() {
|
|
|
82
82
|
return 'hackmyagent';
|
|
83
83
|
}
|
|
84
84
|
const CLI_PREFIX = resolveCliPrefix();
|
|
85
|
+
/**
|
|
86
|
+
* Validate that a registry URL uses HTTPS.
|
|
87
|
+
* Allows http://localhost for local development.
|
|
88
|
+
* Rejects all other non-HTTPS URLs to prevent credential leakage.
|
|
89
|
+
*/
|
|
90
|
+
function validateRegistryUrl(url) {
|
|
91
|
+
if (url && !url.startsWith('https://') && !url.startsWith('http://localhost')) {
|
|
92
|
+
console.error('Error: Registry URL must use HTTPS. Got: ' + url);
|
|
93
|
+
console.error('Only https:// URLs and http://localhost are allowed.');
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
return url;
|
|
97
|
+
}
|
|
85
98
|
// Check for NO_COLOR env or non-TTY to disable colors by default
|
|
86
99
|
const noColorEnv = process.env.NO_COLOR !== undefined || !process.stdout.isTTY;
|
|
87
100
|
// Color codes - will be cleared if --no-color is passed
|
|
@@ -105,7 +118,7 @@ program
|
|
|
105
118
|
.name('hackmyagent')
|
|
106
119
|
.description(`Find it. Break it. Fix it.
|
|
107
120
|
|
|
108
|
-
The hacker's toolkit for AI agents.
|
|
121
|
+
The hacker's toolkit for AI agents. 199 security checks, 115 attack
|
|
109
122
|
payloads, auto-fix with rollback, and OASB benchmark compliance.
|
|
110
123
|
|
|
111
124
|
Documentation: https://hackmyagent.com/docs
|
|
@@ -114,10 +127,10 @@ Updates (v${index_1.VERSION}):
|
|
|
114
127
|
- NemoClaw sandbox scanner (28 installation checks)
|
|
115
128
|
- 10 new static analysis patterns (NEMO series)
|
|
116
129
|
- Community trust contributions
|
|
117
|
-
-
|
|
130
|
+
- 199 checks across 60 categories
|
|
118
131
|
|
|
119
132
|
Examples:
|
|
120
|
-
$ hackmyagent secure Find vulnerabilities (
|
|
133
|
+
$ hackmyagent secure Find vulnerabilities (199 checks)
|
|
121
134
|
$ hackmyagent attack --local Break it with 115 attack payloads
|
|
122
135
|
$ hackmyagent secure --fix Fix issues automatically
|
|
123
136
|
$ hackmyagent fix-all Run all security plugins
|
|
@@ -126,7 +139,7 @@ Examples:
|
|
|
126
139
|
.option('--no-color', 'Disable colored output (also respects NO_COLOR env)');
|
|
127
140
|
program.addHelpText('beforeAll', `
|
|
128
141
|
Quick start:
|
|
129
|
-
$ hackmyagent secure Scan current directory (
|
|
142
|
+
$ hackmyagent secure Scan current directory (199 checks)
|
|
130
143
|
$ hackmyagent fix-all --with-aim Auto-fix + create agent identity
|
|
131
144
|
$ hackmyagent attack Red-team your agent
|
|
132
145
|
`);
|
|
@@ -1540,6 +1553,66 @@ function resolvePackageVersion(targetDir) {
|
|
|
1540
1553
|
catch { /* ignore */ }
|
|
1541
1554
|
return null;
|
|
1542
1555
|
}
|
|
1556
|
+
/**
|
|
1557
|
+
* Resolve package name from pyproject.toml (Python projects).
|
|
1558
|
+
*/
|
|
1559
|
+
function resolvePackageNamePyproject(targetDir) {
|
|
1560
|
+
try {
|
|
1561
|
+
const fs = require('fs');
|
|
1562
|
+
const path = require('path');
|
|
1563
|
+
const pyprojectPath = path.join(targetDir, 'pyproject.toml');
|
|
1564
|
+
if (fs.existsSync(pyprojectPath)) {
|
|
1565
|
+
const content = fs.readFileSync(pyprojectPath, 'utf-8');
|
|
1566
|
+
// Match [project] section's name field
|
|
1567
|
+
const nameMatch = content.match(/\[project\][\s\S]*?name\s*=\s*"([^"]+)"/);
|
|
1568
|
+
if (nameMatch)
|
|
1569
|
+
return nameMatch[1];
|
|
1570
|
+
// Also try [tool.poetry] section
|
|
1571
|
+
const poetryMatch = content.match(/\[tool\.poetry\][\s\S]*?name\s*=\s*"([^"]+)"/);
|
|
1572
|
+
if (poetryMatch)
|
|
1573
|
+
return poetryMatch[1];
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
catch { /* ignore */ }
|
|
1577
|
+
return null;
|
|
1578
|
+
}
|
|
1579
|
+
/**
|
|
1580
|
+
* Resolve package version from pyproject.toml (Python projects).
|
|
1581
|
+
*/
|
|
1582
|
+
function resolvePackageVersionPyproject(targetDir) {
|
|
1583
|
+
try {
|
|
1584
|
+
const fs = require('fs');
|
|
1585
|
+
const path = require('path');
|
|
1586
|
+
const pyprojectPath = path.join(targetDir, 'pyproject.toml');
|
|
1587
|
+
if (fs.existsSync(pyprojectPath)) {
|
|
1588
|
+
const content = fs.readFileSync(pyprojectPath, 'utf-8');
|
|
1589
|
+
const versionMatch = content.match(/\[project\][\s\S]*?version\s*=\s*"([^"]+)"/);
|
|
1590
|
+
if (versionMatch)
|
|
1591
|
+
return versionMatch[1];
|
|
1592
|
+
const poetryMatch = content.match(/\[tool\.poetry\][\s\S]*?version\s*=\s*"([^"]+)"/);
|
|
1593
|
+
if (poetryMatch)
|
|
1594
|
+
return poetryMatch[1];
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
catch { /* ignore */ }
|
|
1598
|
+
return null;
|
|
1599
|
+
}
|
|
1600
|
+
/**
|
|
1601
|
+
* Resolve the repository URL from the git remote 'origin'.
|
|
1602
|
+
*/
|
|
1603
|
+
function resolveRepoUrl(targetDir) {
|
|
1604
|
+
try {
|
|
1605
|
+
const { execSync } = require('child_process');
|
|
1606
|
+
const url = execSync('git remote get-url origin', {
|
|
1607
|
+
cwd: targetDir,
|
|
1608
|
+
encoding: 'utf-8',
|
|
1609
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1610
|
+
}).trim();
|
|
1611
|
+
return url || null;
|
|
1612
|
+
}
|
|
1613
|
+
catch { /* ignore */ }
|
|
1614
|
+
return null;
|
|
1615
|
+
}
|
|
1543
1616
|
/**
|
|
1544
1617
|
* Handle community contribution after a scan completes.
|
|
1545
1618
|
*
|
|
@@ -1622,13 +1695,13 @@ program
|
|
|
1622
1695
|
.command('secure')
|
|
1623
1696
|
.description(`Scan and harden your agent setup
|
|
1624
1697
|
|
|
1625
|
-
Performs
|
|
1698
|
+
Performs 199 security checks across 60 categories:
|
|
1626
1699
|
• Credentials: API key exposure, secrets in configs
|
|
1627
1700
|
• MCP: Server configs, tool permissions, secrets
|
|
1628
1701
|
• Network: TLS, interface bindings, CORS
|
|
1629
1702
|
• Prompt: Injection defenses, role protection
|
|
1630
1703
|
• Encryption: At-rest encryption, secure hashing
|
|
1631
|
-
• And
|
|
1704
|
+
• And 54 more categories...
|
|
1632
1705
|
|
|
1633
1706
|
Benchmark mode (--benchmark):
|
|
1634
1707
|
oasb-1 OASB-1 infrastructure compliance (L1/L2/L3 levels)
|
|
@@ -1669,11 +1742,13 @@ Examples:
|
|
|
1669
1742
|
.option('-l, --level <level>', 'Benchmark level: L1 (Essential), L2 (Standard), L3 (Hardened)', 'L1')
|
|
1670
1743
|
.option('-c, --category <name>', 'Filter to specific benchmark category')
|
|
1671
1744
|
.option('--deep', 'Enable LLM-powered semantic analysis (requires ANTHROPIC_API_KEY)')
|
|
1745
|
+
.option('--scan-depth <depth>', 'CAAT scan depth: quick (config+creds only), standard (default), deep (+ LLM analysis)', 'standard')
|
|
1746
|
+
.option('--ci-publish', 'Submit scan results to registry CI endpoint (requires CI_SCAN_HMAC_SECRET env)')
|
|
1672
1747
|
.option('--publish', 'Push scan results to the OpenA2A Registry')
|
|
1673
1748
|
.option('--registry-report', 'Post results to OpenA2A Registry')
|
|
1674
1749
|
.option('--no-registry', 'Skip auto-publishing results to OpenA2A Registry')
|
|
1675
1750
|
.option('--version-id <id>', 'Registry version ID to report against')
|
|
1676
|
-
.option('--registry-url <url>', 'Registry URL (default: REGISTRY_URL env)', process.env.REGISTRY_URL || 'https://api.oa2a.org')
|
|
1751
|
+
.option('--registry-url <url>', 'Registry URL (default: REGISTRY_URL env)', validateRegistryUrl(process.env.REGISTRY_URL || 'https://api.oa2a.org'))
|
|
1677
1752
|
.option('--registry-key <key>', 'Registry API key (default: REGISTRY_API_KEY env)')
|
|
1678
1753
|
.option('--contribute', 'Share anonymized scan findings with OpenA2A Registry (overrides config)')
|
|
1679
1754
|
.option('--no-contribute', 'Do not share findings for this scan (overrides config)')
|
|
@@ -1733,8 +1808,15 @@ Examples:
|
|
|
1733
1808
|
console.log(`\nScanning ${targetDir}...\n`);
|
|
1734
1809
|
}
|
|
1735
1810
|
}
|
|
1736
|
-
//
|
|
1737
|
-
const
|
|
1811
|
+
// Validate scan depth
|
|
1812
|
+
const validDepths = ['quick', 'standard', 'deep'];
|
|
1813
|
+
const scanDepth = (options.scanDepth || 'standard');
|
|
1814
|
+
if (!validDepths.includes(scanDepth)) {
|
|
1815
|
+
console.error(`Error: Invalid scan depth '${options.scanDepth}'. Use: ${validDepths.join(', ')}`);
|
|
1816
|
+
process.exit(1);
|
|
1817
|
+
}
|
|
1818
|
+
// Deep mode: --deep flag OR --scan-depth deep
|
|
1819
|
+
const isDeep = options.deep ?? (scanDepth === 'deep');
|
|
1738
1820
|
const onProgress = isDeep && format === 'text'
|
|
1739
1821
|
? (msg) => process.stdout.write(msg)
|
|
1740
1822
|
: undefined;
|
|
@@ -1745,6 +1827,9 @@ Examples:
|
|
|
1745
1827
|
console.log(` npx ${CLI_PREFIX} init-mcp\n`);
|
|
1746
1828
|
}
|
|
1747
1829
|
}
|
|
1830
|
+
if (scanDepth === 'quick' && format === 'text') {
|
|
1831
|
+
console.log(`Scan depth: quick (config checks + credential detection only)\n`);
|
|
1832
|
+
}
|
|
1748
1833
|
const scanner = new index_1.HardeningScanner();
|
|
1749
1834
|
const scanStartMs = Date.now();
|
|
1750
1835
|
const result = await scanner.scan({
|
|
@@ -1753,6 +1838,7 @@ Examples:
|
|
|
1753
1838
|
dryRun: options.dryRun ?? false,
|
|
1754
1839
|
ignore: ignoreList,
|
|
1755
1840
|
deep: isDeep,
|
|
1841
|
+
scanDepth,
|
|
1756
1842
|
cliName: CLI_PREFIX,
|
|
1757
1843
|
onProgress,
|
|
1758
1844
|
});
|
|
@@ -1862,7 +1948,7 @@ Examples:
|
|
|
1862
1948
|
if (options.publish && options.registry !== false) {
|
|
1863
1949
|
try {
|
|
1864
1950
|
const { publishScanResults } = await Promise.resolve().then(() => __importStar(require('./registry/publish')));
|
|
1865
|
-
const registryUrl = options.registryUrl || process.env.REGISTRY_URL || 'https://api.oa2a.org';
|
|
1951
|
+
const registryUrl = validateRegistryUrl(options.registryUrl || process.env.REGISTRY_URL || 'https://api.oa2a.org');
|
|
1866
1952
|
const packageName = resolvePackageName(targetDir);
|
|
1867
1953
|
if (packageName) {
|
|
1868
1954
|
const publishData = {
|
|
@@ -2060,7 +2146,7 @@ Examples:
|
|
|
2060
2146
|
if (options.versionId || options.registryReport) {
|
|
2061
2147
|
try {
|
|
2062
2148
|
const core = await Promise.resolve().then(() => __importStar(require('./index')));
|
|
2063
|
-
const registryUrl = options.registryUrl || process.env.REGISTRY_URL || 'https://api.oa2a.org';
|
|
2149
|
+
const registryUrl = validateRegistryUrl(options.registryUrl || process.env.REGISTRY_URL || 'https://api.oa2a.org');
|
|
2064
2150
|
if (options.versionId) {
|
|
2065
2151
|
// Authenticated path: existing behavior (version-id + API key)
|
|
2066
2152
|
const registryKey = options.registryKey || process.env.REGISTRY_API_KEY;
|
|
@@ -2107,7 +2193,7 @@ Examples:
|
|
|
2107
2193
|
else if (options.publish) {
|
|
2108
2194
|
try {
|
|
2109
2195
|
const { publishScanResults, formatPublishOutput } = await Promise.resolve().then(() => __importStar(require('./registry/publish')));
|
|
2110
|
-
const registryUrl = options.registryUrl || process.env.REGISTRY_URL || 'https://api.oa2a.org';
|
|
2196
|
+
const registryUrl = validateRegistryUrl(options.registryUrl || process.env.REGISTRY_URL || 'https://api.oa2a.org');
|
|
2111
2197
|
const packageName = resolvePackageName(targetDir);
|
|
2112
2198
|
if (!packageName) {
|
|
2113
2199
|
console.error('\nCould not determine package name. Publish requires a package.json with a name field.');
|
|
@@ -2139,6 +2225,97 @@ Examples:
|
|
|
2139
2225
|
console.error('Scan results are still available locally.');
|
|
2140
2226
|
}
|
|
2141
2227
|
}
|
|
2228
|
+
// CI publish: submit results to registry CAAT pipeline endpoint
|
|
2229
|
+
if (options.ciPublish) {
|
|
2230
|
+
const hmacSecret = process.env.CI_SCAN_HMAC_SECRET;
|
|
2231
|
+
if (!hmacSecret) {
|
|
2232
|
+
console.error('\nError: --ci-publish requires the CI_SCAN_HMAC_SECRET environment variable.');
|
|
2233
|
+
process.exit(1);
|
|
2234
|
+
}
|
|
2235
|
+
try {
|
|
2236
|
+
const { RegistryClient } = await Promise.resolve().then(() => __importStar(require('./registry/client')));
|
|
2237
|
+
const { computeTreeHash } = await Promise.resolve().then(() => __importStar(require('./registry/publish')));
|
|
2238
|
+
const registryUrl = validateRegistryUrl(options.registryUrl || process.env.REGISTRY_URL || 'https://api.oa2a.org');
|
|
2239
|
+
const packageName = resolvePackageName(targetDir) || resolvePackageNamePyproject(targetDir);
|
|
2240
|
+
const packageVersion = resolvePackageVersion(targetDir) || resolvePackageVersionPyproject(targetDir);
|
|
2241
|
+
const repoUrl = resolveRepoUrl(targetDir);
|
|
2242
|
+
if (!packageName) {
|
|
2243
|
+
console.error('\nCould not determine package name from package.json or pyproject.toml.');
|
|
2244
|
+
}
|
|
2245
|
+
else if (!repoUrl) {
|
|
2246
|
+
console.error('\nCould not determine repo URL from git remote. Ensure a git remote is configured.');
|
|
2247
|
+
}
|
|
2248
|
+
else {
|
|
2249
|
+
// Compute CAAT tree hash
|
|
2250
|
+
let contentHash = '';
|
|
2251
|
+
try {
|
|
2252
|
+
contentHash = computeTreeHash(targetDir);
|
|
2253
|
+
}
|
|
2254
|
+
catch {
|
|
2255
|
+
console.error('Warning: Could not compute tree hash. Using empty hash.');
|
|
2256
|
+
}
|
|
2257
|
+
// Count severity
|
|
2258
|
+
const failed = result.findings.filter((f) => !f.passed && !f.fixed);
|
|
2259
|
+
const counts = { critical: 0, high: 0, medium: 0, low: 0 };
|
|
2260
|
+
for (const f of failed) {
|
|
2261
|
+
if (f.severity === 'critical')
|
|
2262
|
+
counts.critical++;
|
|
2263
|
+
else if (f.severity === 'high')
|
|
2264
|
+
counts.high++;
|
|
2265
|
+
else if (f.severity === 'medium')
|
|
2266
|
+
counts.medium++;
|
|
2267
|
+
else if (f.severity === 'low')
|
|
2268
|
+
counts.low++;
|
|
2269
|
+
}
|
|
2270
|
+
const status = (counts.critical > 0 || counts.high > 0) ? 'failed'
|
|
2271
|
+
: (counts.medium > 0 || counts.low > 0) ? 'warnings' : 'passed';
|
|
2272
|
+
const client = new RegistryClient({ registryUrl, apiKey: '' });
|
|
2273
|
+
const scanId = `hma-ci-${Date.now()}`;
|
|
2274
|
+
// Get scanner version from package.json
|
|
2275
|
+
let scannerVersion = 'unknown';
|
|
2276
|
+
try {
|
|
2277
|
+
const hmaPackagePath = require('path').resolve(__dirname, '../package.json');
|
|
2278
|
+
scannerVersion = require(hmaPackagePath).version || 'unknown';
|
|
2279
|
+
}
|
|
2280
|
+
catch { /* ignore */ }
|
|
2281
|
+
const ciResult = await client.submitCIScanResult({
|
|
2282
|
+
packageName,
|
|
2283
|
+
packageType: undefined,
|
|
2284
|
+
version: packageVersion ?? undefined,
|
|
2285
|
+
repoUrl,
|
|
2286
|
+
scanId,
|
|
2287
|
+
status,
|
|
2288
|
+
criticalCount: counts.critical,
|
|
2289
|
+
highCount: counts.high,
|
|
2290
|
+
mediumCount: counts.medium,
|
|
2291
|
+
lowCount: counts.low,
|
|
2292
|
+
contentHash,
|
|
2293
|
+
scannerVersion,
|
|
2294
|
+
hmacSecret,
|
|
2295
|
+
rawReport: {
|
|
2296
|
+
generator: 'hackmyagent',
|
|
2297
|
+
totalFindings: result.findings.length,
|
|
2298
|
+
failedFindings: failed.length,
|
|
2299
|
+
scanDepth,
|
|
2300
|
+
},
|
|
2301
|
+
});
|
|
2302
|
+
if (format === 'text') {
|
|
2303
|
+
console.log(`\nCI scan result submitted to registry.`);
|
|
2304
|
+
console.log(` Scan ID: ${scanId}`);
|
|
2305
|
+
console.log(` Valid: ${ciResult.valid}`);
|
|
2306
|
+
console.log(` Trust impact: ${ciResult.trustImpact}\n`);
|
|
2307
|
+
}
|
|
2308
|
+
else if (format === 'json') {
|
|
2309
|
+
console.error(JSON.stringify({ ciPublish: { scanId, ...ciResult } }, null, 2));
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
catch (ciErr) {
|
|
2314
|
+
const msg = ciErr instanceof Error ? ciErr.message : 'unknown error';
|
|
2315
|
+
console.error(`\nFailed to submit CI scan result: ${msg}`);
|
|
2316
|
+
console.error('Scan results are still available locally.');
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2142
2319
|
// Community contribution: share anonymized findings with OpenA2A Registry
|
|
2143
2320
|
await handleContribution(options.contribute, targetDir, result.findings, scanDurationMs, options.registryUrl, format);
|
|
2144
2321
|
// Star prompt (interactive TTY only, text format only)
|
|
@@ -2738,7 +2915,7 @@ Examples:
|
|
|
2738
2915
|
.option('--registry-report', 'Post results to OpenA2A Registry')
|
|
2739
2916
|
.option('--no-registry', 'Skip auto-publishing results to OpenA2A Registry')
|
|
2740
2917
|
.option('--version-id <id>', 'Registry version ID to report against')
|
|
2741
|
-
.option('--registry-url <url>', 'Registry URL (default: REGISTRY_URL env)', process.env.REGISTRY_URL || 'https://api.oa2a.org')
|
|
2918
|
+
.option('--registry-url <url>', 'Registry URL (default: REGISTRY_URL env)', validateRegistryUrl(process.env.REGISTRY_URL || 'https://api.oa2a.org'))
|
|
2742
2919
|
.option('--registry-key <key>', 'Registry API key (default: REGISTRY_API_KEY env)')
|
|
2743
2920
|
.action(async (targetUrl, options) => {
|
|
2744
2921
|
try {
|
|
@@ -2894,7 +3071,7 @@ Examples:
|
|
|
2894
3071
|
if (shouldReport) {
|
|
2895
3072
|
try {
|
|
2896
3073
|
const core = await Promise.resolve().then(() => __importStar(require('./index')));
|
|
2897
|
-
const registryUrl = options.registryUrl || process.env.REGISTRY_URL || 'https://api.oa2a.org';
|
|
3074
|
+
const registryUrl = validateRegistryUrl(options.registryUrl || process.env.REGISTRY_URL || 'https://api.oa2a.org');
|
|
2898
3075
|
if (options.versionId) {
|
|
2899
3076
|
// Authenticated path: existing behavior (version-id + API key)
|
|
2900
3077
|
const registryKey = options.registryKey || process.env.REGISTRY_API_KEY;
|
|
@@ -2941,7 +3118,7 @@ Examples:
|
|
|
2941
3118
|
else if (options.publish && targetType !== 'local') {
|
|
2942
3119
|
try {
|
|
2943
3120
|
const { publishScanResults, formatPublishOutput } = await Promise.resolve().then(() => __importStar(require('./registry/publish')));
|
|
2944
|
-
const regUrl = options.registryUrl || process.env.REGISTRY_URL || 'https://api.oa2a.org';
|
|
3121
|
+
const regUrl = validateRegistryUrl(options.registryUrl || process.env.REGISTRY_URL || 'https://api.oa2a.org');
|
|
2945
3122
|
const packageName = target.url || targetUrl || 'unknown';
|
|
2946
3123
|
if (format === 'text') {
|
|
2947
3124
|
console.log('\nPublishing results to registry...\n');
|
|
@@ -4126,7 +4303,7 @@ Examples:
|
|
|
4126
4303
|
console.log(`\n Detected: ${result.tool}\n`);
|
|
4127
4304
|
console.log(` Added HackMyAgent MCP server to ${result.configPath}\n`);
|
|
4128
4305
|
console.log(` Available tools in ${result.tool}:`);
|
|
4129
|
-
console.log(` hackmyagent_scan —
|
|
4306
|
+
console.log(` hackmyagent_scan — 199 checks + structural analysis`);
|
|
4130
4307
|
console.log(` hackmyagent_deep_scan — Full analysis with LLM reasoning`);
|
|
4131
4308
|
console.log(` hackmyagent_analyze_file — Analyze a single file`);
|
|
4132
4309
|
console.log(` hackmyagent_benchmark — OASB-1 compliance assessment\n`);
|
|
@@ -4215,7 +4392,7 @@ Examples:
|
|
|
4215
4392
|
.option('--fail-below <score>', 'Exit 1 if score below threshold (0-100)')
|
|
4216
4393
|
.option('--deep', 'Enable LLM semantic analysis for ambiguous controls (requires claude CLI or ANTHROPIC_API_KEY)')
|
|
4217
4394
|
.option('--publish', 'Push scan results to the OpenA2A Registry')
|
|
4218
|
-
.option('--registry-url <url>', 'Registry URL (default: REGISTRY_URL env)', process.env.REGISTRY_URL || 'https://api.oa2a.org')
|
|
4395
|
+
.option('--registry-url <url>', 'Registry URL (default: REGISTRY_URL env)', validateRegistryUrl(process.env.REGISTRY_URL || 'https://api.oa2a.org'))
|
|
4219
4396
|
.option('--contribute', 'Share anonymized scan findings with OpenA2A Registry (overrides config)')
|
|
4220
4397
|
.option('--no-contribute', 'Do not share findings for this scan (overrides config)')
|
|
4221
4398
|
.option('--ci', 'CI mode: suppress interactive prompts, exit non-zero on findings')
|
|
@@ -4248,7 +4425,7 @@ Examples:
|
|
|
4248
4425
|
if (options.publish) {
|
|
4249
4426
|
try {
|
|
4250
4427
|
const { publishScanResults } = await Promise.resolve().then(() => __importStar(require('./registry/publish')));
|
|
4251
|
-
const registryUrl = options.registryUrl || process.env.REGISTRY_URL || 'https://api.oa2a.org';
|
|
4428
|
+
const registryUrl = validateRegistryUrl(options.registryUrl || process.env.REGISTRY_URL || 'https://api.oa2a.org');
|
|
4252
4429
|
const packageName = resolvePackageName(targetDir);
|
|
4253
4430
|
if (packageName) {
|
|
4254
4431
|
const publishData = {
|
|
@@ -4363,7 +4540,7 @@ Examples:
|
|
|
4363
4540
|
if (options.publish) {
|
|
4364
4541
|
try {
|
|
4365
4542
|
const { publishScanResults, formatPublishOutput } = await Promise.resolve().then(() => __importStar(require('./registry/publish')));
|
|
4366
|
-
const registryUrl = options.registryUrl || process.env.REGISTRY_URL || 'https://api.oa2a.org';
|
|
4543
|
+
const registryUrl = validateRegistryUrl(options.registryUrl || process.env.REGISTRY_URL || 'https://api.oa2a.org');
|
|
4367
4544
|
const packageName = resolvePackageName(targetDir);
|
|
4368
4545
|
if (!packageName) {
|
|
4369
4546
|
process.stderr.write('Could not determine package name. Publish requires a package.json with a name field.\n');
|
|
@@ -4739,10 +4916,10 @@ Examples:
|
|
|
4739
4916
|
.option('--audit <file>', 'Audit a dependency file (package.json or requirements.txt)')
|
|
4740
4917
|
.option('--batch <names...>', 'Batch trust lookup for multiple packages')
|
|
4741
4918
|
.option('--min-trust <level>', 'Minimum trust level threshold (0-4)', '3')
|
|
4742
|
-
.option('--registry-url <url>', 'Registry base URL', REGISTRY_DEFAULT_URL)
|
|
4919
|
+
.option('--registry-url <url>', 'Registry base URL', validateRegistryUrl(REGISTRY_DEFAULT_URL))
|
|
4743
4920
|
.option('--json', 'Output as JSON')
|
|
4744
4921
|
.action(async (packageName, opts) => {
|
|
4745
|
-
const registryUrl = opts.registryUrl.replace(/\/+$/, '');
|
|
4922
|
+
const registryUrl = validateRegistryUrl(opts.registryUrl).replace(/\/+$/, '');
|
|
4746
4923
|
const minTrust = parseInt(opts.minTrust, 10);
|
|
4747
4924
|
if (isNaN(minTrust) || minTrust < 0 || minTrust > 4) {
|
|
4748
4925
|
process.stderr.write('Error: --min-trust must be a number between 0 and 4\n');
|
|
@@ -4821,5 +4998,36 @@ Examples:
|
|
|
4821
4998
|
process.exit(1);
|
|
4822
4999
|
}
|
|
4823
5000
|
});
|
|
5001
|
+
program
|
|
5002
|
+
.command('check-metadata')
|
|
5003
|
+
.description('Export metadata for all security checks by scanning test fixtures (JSON)')
|
|
5004
|
+
.option('-d, --directory <dir>', 'Directory to scan for check metadata extraction')
|
|
5005
|
+
.action(async (options) => {
|
|
5006
|
+
const { getAttackClass } = require('./hardening/taxonomy');
|
|
5007
|
+
const targetDir = options.directory || process.cwd();
|
|
5008
|
+
// Run a real scan to collect all check metadata from findings
|
|
5009
|
+
const scanner = new index_1.HardeningScanner();
|
|
5010
|
+
const result = await scanner.scan({ targetDir, autoFix: false, scanDepth: 'deep' });
|
|
5011
|
+
const metadata = {};
|
|
5012
|
+
for (const finding of result.findings) {
|
|
5013
|
+
if (!metadata[finding.checkId]) {
|
|
5014
|
+
metadata[finding.checkId] = {
|
|
5015
|
+
checkId: finding.checkId,
|
|
5016
|
+
name: finding.name,
|
|
5017
|
+
category: finding.category,
|
|
5018
|
+
attackClass: getAttackClass(finding.checkId) || '',
|
|
5019
|
+
severity: finding.severity,
|
|
5020
|
+
fix: finding.fix || '',
|
|
5021
|
+
guidance: finding.guidance || '',
|
|
5022
|
+
};
|
|
5023
|
+
}
|
|
5024
|
+
}
|
|
5025
|
+
writeJsonStdout({ totalChecks: Object.keys(metadata).length, checks: metadata });
|
|
5026
|
+
});
|
|
5027
|
+
// Show help and exit 0 when no arguments provided
|
|
5028
|
+
if (process.argv.length <= 2) {
|
|
5029
|
+
program.outputHelp();
|
|
5030
|
+
process.exit(0);
|
|
5031
|
+
}
|
|
4824
5032
|
program.parse();
|
|
4825
5033
|
//# sourceMappingURL=cli.js.map
|