hackmyagent 0.12.9 → 0.13.1
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/dist/.integrity-manifest.json +1 -1
- package/dist/attack/custom-payloads.d.ts.map +1 -1
- package/dist/attack/custom-payloads.js +3 -0
- package/dist/attack/custom-payloads.js.map +1 -1
- package/dist/attack/payloads/fake-tool.d.ts +13 -0
- package/dist/attack/payloads/fake-tool.d.ts.map +1 -0
- package/dist/attack/payloads/fake-tool.js +216 -0
- package/dist/attack/payloads/fake-tool.js.map +1 -0
- package/dist/attack/payloads/index.d.ts +4 -1
- package/dist/attack/payloads/index.d.ts.map +1 -1
- package/dist/attack/payloads/index.js +13 -1
- package/dist/attack/payloads/index.js.map +1 -1
- package/dist/attack/payloads/parser-differential.d.ts +13 -0
- package/dist/attack/payloads/parser-differential.d.ts.map +1 -0
- package/dist/attack/payloads/parser-differential.js +216 -0
- package/dist/attack/payloads/parser-differential.js.map +1 -0
- package/dist/attack/payloads/persistent-agent.d.ts +13 -0
- package/dist/attack/payloads/persistent-agent.d.ts.map +1 -0
- package/dist/attack/payloads/persistent-agent.js +216 -0
- package/dist/attack/payloads/persistent-agent.js.map +1 -0
- package/dist/attack/scanner.d.ts.map +1 -1
- package/dist/attack/scanner.js +3 -0
- package/dist/attack/scanner.js.map +1 -1
- package/dist/attack/types.d.ts +1 -1
- package/dist/attack/types.d.ts.map +1 -1
- package/dist/attack/types.js +15 -0
- package/dist/attack/types.js.map +1 -1
- package/dist/checker/skill-identifier.d.ts.map +1 -1
- package/dist/checker/skill-identifier.js +9 -1
- package/dist/checker/skill-identifier.js.map +1 -1
- package/dist/cli.js +206 -29
- package/dist/cli.js.map +1 -1
- package/dist/nanomind-core/inference/tme-classifier.d.ts.map +1 -1
- package/dist/nanomind-core/inference/tme-classifier.js +4 -5
- package/dist/nanomind-core/inference/tme-classifier.js.map +1 -1
- package/dist/nanomind-core/telemetry/auto-update.d.ts +27 -0
- package/dist/nanomind-core/telemetry/auto-update.d.ts.map +1 -0
- package/dist/nanomind-core/telemetry/auto-update.js +129 -0
- package/dist/nanomind-core/telemetry/auto-update.js.map +1 -0
- package/dist/nanomind-core/telemetry/client.d.ts +66 -0
- package/dist/nanomind-core/telemetry/client.d.ts.map +1 -0
- package/dist/nanomind-core/telemetry/client.js +123 -0
- package/dist/nanomind-core/telemetry/client.js.map +1 -0
- package/dist/nanomind-core/telemetry/config.d.ts +33 -0
- package/dist/nanomind-core/telemetry/config.d.ts.map +1 -0
- package/dist/nanomind-core/telemetry/config.js +119 -0
- package/dist/nanomind-core/telemetry/config.js.map +1 -0
- package/dist/nanomind-core/telemetry/index.d.ts +15 -0
- package/dist/nanomind-core/telemetry/index.d.ts.map +1 -0
- package/dist/nanomind-core/telemetry/index.js +27 -0
- package/dist/nanomind-core/telemetry/index.js.map +1 -0
- package/dist/wild/scorer.js +2 -2
- package/dist/wild/scorer.js.map +1 -1
- package/dist/wild/types.d.ts +1 -1
- package/dist/wild/types.d.ts.map +1 -1
- package/package.json +5 -2
package/dist/cli.js
CHANGED
|
@@ -45,14 +45,14 @@ const wild_1 = require("./wild");
|
|
|
45
45
|
const nemoclaw_scanner_1 = require("./hardening/nemoclaw-scanner");
|
|
46
46
|
const program = new commander_1.Command();
|
|
47
47
|
program.showHelpAfterError('(run with --help for usage)');
|
|
48
|
-
// Write
|
|
48
|
+
// Write a string to stdout synchronously with retry for pipe backpressure.
|
|
49
49
|
// process.stdout.write() is async and gets truncated when process.exit()
|
|
50
50
|
// runs before the stream flushes. fs.writeFileSync(1, ...) can fail with
|
|
51
51
|
// EAGAIN on non-blocking pipes when the buffer (64KB on macOS) fills up.
|
|
52
52
|
// This function writes in chunks with retry to handle both cases.
|
|
53
|
-
function
|
|
53
|
+
function writeLargeStdout(text) {
|
|
54
54
|
const fs = require('fs');
|
|
55
|
-
const buf = Buffer.from(
|
|
55
|
+
const buf = Buffer.from(text);
|
|
56
56
|
let offset = 0;
|
|
57
57
|
while (offset < buf.length) {
|
|
58
58
|
try {
|
|
@@ -61,13 +61,16 @@ function writeJsonStdout(data) {
|
|
|
61
61
|
}
|
|
62
62
|
catch (e) {
|
|
63
63
|
if (e && typeof e === 'object' && 'code' in e && e.code === 'EAGAIN') {
|
|
64
|
-
// Pipe buffer full
|
|
64
|
+
// Pipe buffer full -- spin-wait briefly then retry
|
|
65
65
|
continue;
|
|
66
66
|
}
|
|
67
67
|
throw e;
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
|
+
function writeJsonStdout(data) {
|
|
72
|
+
writeLargeStdout(JSON.stringify(data, null, 2) + '\n');
|
|
73
|
+
}
|
|
71
74
|
// Resolve the CLI command name based on how we were invoked.
|
|
72
75
|
// When run via `opena2a scan secure`, use `opena2a scan` prefix.
|
|
73
76
|
// When run directly as `hackmyagent`, use that.
|
|
@@ -96,6 +99,10 @@ function validateRegistryUrl(url) {
|
|
|
96
99
|
}
|
|
97
100
|
return url;
|
|
98
101
|
}
|
|
102
|
+
// Global CI mode flag -- set before parse() by stripping --ci from argv.
|
|
103
|
+
// Commands that already define --ci (secure, scan-soul) use their own opts;
|
|
104
|
+
// all others can check this module-level flag.
|
|
105
|
+
let globalCiMode = false;
|
|
99
106
|
// Check for NO_COLOR env or non-TTY to disable colors by default
|
|
100
107
|
const noColorEnv = process.env.NO_COLOR !== undefined || !process.stdout.isTTY;
|
|
101
108
|
// Color codes - will be cleared if --no-color is passed
|
|
@@ -136,7 +143,7 @@ Examples:
|
|
|
136
143
|
$ hackmyagent secure --fix Fix issues automatically
|
|
137
144
|
$ hackmyagent fix-all Run all security plugins
|
|
138
145
|
$ hackmyagent scan example.com Scan external infrastructure`)
|
|
139
|
-
.version('hackmyagent ' + index_1.VERSION, '-
|
|
146
|
+
.version('hackmyagent ' + index_1.VERSION, '-v, --version', 'Output the version number')
|
|
140
147
|
.option('--no-color', 'Disable colored output (also respects NO_COLOR env)');
|
|
141
148
|
program.addHelpText('beforeAll', `
|
|
142
149
|
Quick start:
|
|
@@ -212,7 +219,7 @@ Examples:
|
|
|
212
219
|
const riskDisplay = RISK_DISPLAY[risk];
|
|
213
220
|
console.log(`\n${riskDisplay.color()}${riskDisplay.symbol} ${risk.toUpperCase()} RISK${RESET()}\n`);
|
|
214
221
|
console.log(`Path: ${resolved}`);
|
|
215
|
-
console.log(`
|
|
222
|
+
console.log(`Semantic analysis: ${nmResult.compiledArtifacts} file(s) analyzed\n`);
|
|
216
223
|
if (issues.length === 0) {
|
|
217
224
|
console.log(`${colors.green}No security issues detected.${RESET()}\n`);
|
|
218
225
|
}
|
|
@@ -232,10 +239,13 @@ Examples:
|
|
|
232
239
|
process.exit(1);
|
|
233
240
|
return;
|
|
234
241
|
}
|
|
235
|
-
// Registry lookup path (non-local identifier)
|
|
236
|
-
const
|
|
242
|
+
// Registry lookup path (non-local identifier) with 10s timeout
|
|
243
|
+
const checkPromise = (0, index_1.checkSkill)(skill, {
|
|
237
244
|
skipDnsVerification: options.offline,
|
|
238
245
|
});
|
|
246
|
+
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error(`Timed out verifying "${skill}" (10s). The publisher may not exist or DNS is unreachable.\n` +
|
|
247
|
+
`Try: ${CLI_PREFIX.replace(' scan', '')} check ${skill} --offline`)), 10000));
|
|
248
|
+
const result = await Promise.race([checkPromise, timeoutPromise]);
|
|
239
249
|
if (options.json) {
|
|
240
250
|
writeJsonStdout(result);
|
|
241
251
|
return;
|
|
@@ -1797,8 +1807,8 @@ Examples:
|
|
|
1797
1807
|
.option('-b, --benchmark <name>', 'Run benchmark compliance check (e.g., oasb-1)')
|
|
1798
1808
|
.option('-l, --level <level>', 'Benchmark level: L1 (Essential), L2 (Standard), L3 (Hardened)', 'L1')
|
|
1799
1809
|
.option('-c, --category <name>', 'Filter to specific benchmark category')
|
|
1800
|
-
.option('--deep', 'Maximum analysis: static +
|
|
1801
|
-
.option('--static-only', 'Disable
|
|
1810
|
+
.option('--deep', 'Maximum analysis: static + semantic + behavioral simulation + adaptive attacks (~30s per file)')
|
|
1811
|
+
.option('--static-only', 'Disable semantic analysis and simulation (static checks only, fast, deterministic)')
|
|
1802
1812
|
.option('--scan-depth <depth>', 'CAAT scan depth: quick (config+creds only), standard (default), deep (+ simulation)', 'standard')
|
|
1803
1813
|
.option('--ci-publish', 'Submit scan results to registry CI endpoint (requires CI_SCAN_HMAC_SECRET env)')
|
|
1804
1814
|
.option('--publish', 'Push scan results to the OpenA2A Registry')
|
|
@@ -1898,14 +1908,13 @@ Examples:
|
|
|
1898
1908
|
// Static only -- no extra output
|
|
1899
1909
|
}
|
|
1900
1910
|
else if (nanomindAvailable && isDeep) {
|
|
1901
|
-
console.log(`Analysis: static +
|
|
1911
|
+
console.log(`Analysis: static + semantic + behavioral simulation + adaptive attacks\n`);
|
|
1902
1912
|
}
|
|
1903
1913
|
else if (nanomindAvailable) {
|
|
1904
|
-
console.log(`Analysis: static +
|
|
1914
|
+
console.log(`Analysis: static + semantic (ML-enhanced accuracy)\n`);
|
|
1905
1915
|
}
|
|
1906
1916
|
else if (isDeep) {
|
|
1907
1917
|
console.log(`Analysis: static + behavioral simulation\n`);
|
|
1908
|
-
console.log(` Tip: Install NanoMind for even better results: nanomind-daemon start\n`);
|
|
1909
1918
|
}
|
|
1910
1919
|
// Default static-only: no message needed, it's the baseline
|
|
1911
1920
|
}
|
|
@@ -2068,14 +2077,14 @@ Examples:
|
|
|
2068
2077
|
printBenchmarkReport(benchmarkResult, options.verbose ?? false);
|
|
2069
2078
|
output = '';
|
|
2070
2079
|
}
|
|
2071
|
-
// Write output
|
|
2080
|
+
// Write output (use writeLargeStdout to avoid 64KB pipe truncation)
|
|
2072
2081
|
if (output) {
|
|
2073
2082
|
if (options.output) {
|
|
2074
2083
|
require('fs').writeFileSync(options.output, output);
|
|
2075
2084
|
console.error(`Report written to ${options.output}`);
|
|
2076
2085
|
}
|
|
2077
2086
|
else {
|
|
2078
|
-
|
|
2087
|
+
writeLargeStdout(output + '\n');
|
|
2079
2088
|
}
|
|
2080
2089
|
}
|
|
2081
2090
|
// Check fail threshold
|
|
@@ -2139,7 +2148,7 @@ Examples:
|
|
|
2139
2148
|
console.error(`Report written to ${options.output}`);
|
|
2140
2149
|
}
|
|
2141
2150
|
else {
|
|
2142
|
-
|
|
2151
|
+
writeLargeStdout(output + '\n');
|
|
2143
2152
|
}
|
|
2144
2153
|
const critHigh = result.findings.filter((f) => !f.passed && !f.fixed && (f.severity === 'critical' || f.severity === 'high'));
|
|
2145
2154
|
if (critHigh.length > 0)
|
|
@@ -2298,10 +2307,10 @@ Examples:
|
|
|
2298
2307
|
console.log(`\n ${colors.yellow}${unverifiedCount} fix${unverifiedCount === 1 ? '' : 'es'} could not be verified. Review these manually.${RESET()}`);
|
|
2299
2308
|
}
|
|
2300
2309
|
console.log();
|
|
2301
|
-
// Remaining
|
|
2302
|
-
const
|
|
2303
|
-
if (
|
|
2304
|
-
console.log(`${
|
|
2310
|
+
// Remaining issues with fix guidance (not yet auto-fixed)
|
|
2311
|
+
const remainingWithFix = issues.filter((f) => !f.fixed && (f.fix || f.fixable));
|
|
2312
|
+
if (remainingWithFix.length > 0) {
|
|
2313
|
+
console.log(`${remainingWithFix.length} remaining issue${remainingWithFix.length === 1 ? '' : 's'} ${remainingWithFix.length === 1 ? 'has' : 'have'} fix guidance. Run \`${CLI_PREFIX} fix-all\` to apply all available fixes.\n`);
|
|
2305
2314
|
}
|
|
2306
2315
|
if (result.backupPath) {
|
|
2307
2316
|
console.log(`${colors.yellow}Backup created:${RESET()} ${result.backupPath}`);
|
|
@@ -2947,6 +2956,18 @@ Examples:
|
|
|
2947
2956
|
.option('-v, --verbose', 'Show detailed finding information')
|
|
2948
2957
|
.action(async (target, options) => {
|
|
2949
2958
|
try {
|
|
2959
|
+
// Detect local path confusion: user probably wants 'secure' not 'scan'
|
|
2960
|
+
const fs = require('fs');
|
|
2961
|
+
if (fs.existsSync(target) && (target === '.' || target.startsWith('./') || target.startsWith('/') || target.startsWith('..'))) {
|
|
2962
|
+
const secureCmd = CLI_PREFIX.includes('scan')
|
|
2963
|
+
? CLI_PREFIX.replace('scan', 'secure')
|
|
2964
|
+
: `${CLI_PREFIX} secure`;
|
|
2965
|
+
console.error(`\n"scan" is for external targets (hostnames/IPs).` +
|
|
2966
|
+
`\nTo scan a local project, use:\n` +
|
|
2967
|
+
`\n ${secureCmd} ${target}` +
|
|
2968
|
+
`\n`);
|
|
2969
|
+
process.exit(1);
|
|
2970
|
+
}
|
|
2950
2971
|
const timeoutMs = parseInt(options.timeout ?? '5000', 10);
|
|
2951
2972
|
const customPorts = options.ports
|
|
2952
2973
|
? options.ports.split(',').map((p) => parseInt(p.trim(), 10))
|
|
@@ -3240,14 +3261,14 @@ Examples:
|
|
|
3240
3261
|
printAttackReport(report, options.verbose ?? false);
|
|
3241
3262
|
output = '';
|
|
3242
3263
|
}
|
|
3243
|
-
// Write output
|
|
3264
|
+
// Write output (use writeLargeStdout to avoid 64KB pipe truncation)
|
|
3244
3265
|
if (output) {
|
|
3245
3266
|
if (options.output) {
|
|
3246
3267
|
require('fs').writeFileSync(options.output, output);
|
|
3247
3268
|
console.error(`Report written to ${options.output}`);
|
|
3248
3269
|
}
|
|
3249
3270
|
else {
|
|
3250
|
-
|
|
3271
|
+
writeLargeStdout(output + '\n');
|
|
3251
3272
|
}
|
|
3252
3273
|
}
|
|
3253
3274
|
// Registry reporting: only when explicitly requested via --version-id (CI) or --registry-report
|
|
@@ -3502,6 +3523,9 @@ function generateAttackHtmlReport(report) {
|
|
|
3502
3523
|
'context-window': 'CTX',
|
|
3503
3524
|
'supply-chain': 'SUP',
|
|
3504
3525
|
'tool-shadow': 'SHADOW',
|
|
3526
|
+
'parser-differential': 'PARSE',
|
|
3527
|
+
'persistent-agent': 'PERSIST',
|
|
3528
|
+
'fake-tool': 'FAKETOOL',
|
|
3505
3529
|
};
|
|
3506
3530
|
// Donut chart for attack results
|
|
3507
3531
|
const donutRadius = 60;
|
|
@@ -4574,8 +4598,8 @@ Examples:
|
|
|
4574
4598
|
.option('--tier <tier>', 'Override agent tier detection (BASIC, TOOL-USING, AGENTIC, MULTI-AGENT)')
|
|
4575
4599
|
.option('--profile <profile>', 'Override agent profile (conversational, code-assistant, tool-agent, autonomous, orchestrator, custom)')
|
|
4576
4600
|
.option('--fail-below <score>', 'Exit 1 if score below threshold (0-100)')
|
|
4577
|
-
.option('--deep', 'Maximum analysis:
|
|
4578
|
-
.option('--static-only', 'Disable
|
|
4601
|
+
.option('--deep', 'Maximum analysis: semantic + SOUL governance simulation (~15s)')
|
|
4602
|
+
.option('--static-only', 'Disable semantic analysis (static governance checks only)')
|
|
4579
4603
|
.option('--publish', 'Push scan results to the OpenA2A Registry')
|
|
4580
4604
|
.option('--registry-url <url>', 'Registry URL (default: REGISTRY_URL env)', validateRegistryUrl(process.env.REGISTRY_URL || 'https://api.oa2a.org'))
|
|
4581
4605
|
.option('--contribute', 'Share anonymized scan findings with OpenA2A Registry (overrides config)')
|
|
@@ -4647,7 +4671,7 @@ Examples:
|
|
|
4647
4671
|
process.stdout.write('\nOASB v2 Behavioral Governance Scan\n');
|
|
4648
4672
|
process.stdout.write('----------------------------------------------------\n');
|
|
4649
4673
|
if (options.deep) {
|
|
4650
|
-
process.stdout.write(`Analysis: static +
|
|
4674
|
+
process.stdout.write(`Analysis: static + semantic (ML-enhanced deep scan)\n`);
|
|
4651
4675
|
}
|
|
4652
4676
|
process.stdout.write('\n');
|
|
4653
4677
|
if (result.file) {
|
|
@@ -4713,7 +4737,7 @@ Examples:
|
|
|
4713
4737
|
}
|
|
4714
4738
|
else if (result.deepAnalysisResults && result.deepAnalysisResults.length > 0) {
|
|
4715
4739
|
const llmUpgraded = result.deepAnalysisResults.filter((e) => e.llmPassed).length;
|
|
4716
|
-
process.stdout.write(`Deep Analysis: ${llmUpgraded} control${llmUpgraded === 1 ? '' : 's'} upgraded by
|
|
4740
|
+
process.stdout.write(`Deep Analysis: ${llmUpgraded} control${llmUpgraded === 1 ? '' : 's'} upgraded by ML semantic analysis\n`);
|
|
4717
4741
|
}
|
|
4718
4742
|
else {
|
|
4719
4743
|
process.stdout.write(`Deep Analysis: all controls passed, no further analysis needed\n`);
|
|
@@ -5310,7 +5334,7 @@ program
|
|
|
5310
5334
|
program
|
|
5311
5335
|
.command('explain')
|
|
5312
5336
|
.argument('<findingId>', 'Finding ID to explain (e.g., SKILL-SEMANTIC-007 or CRED-001)')
|
|
5313
|
-
.description('Explain a security finding in plain English
|
|
5337
|
+
.description('Explain a security finding in plain English')
|
|
5314
5338
|
.action(async (findingId) => {
|
|
5315
5339
|
console.log(`Explaining finding: ${findingId}\n`);
|
|
5316
5340
|
// Try NanoMind daemon first for dynamic explanation
|
|
@@ -5474,7 +5498,7 @@ Examples:
|
|
|
5474
5498
|
process.stderr.write(`Report written to ${options.output}\n`);
|
|
5475
5499
|
}
|
|
5476
5500
|
else {
|
|
5477
|
-
|
|
5501
|
+
writeLargeStdout(output + '\n');
|
|
5478
5502
|
}
|
|
5479
5503
|
}
|
|
5480
5504
|
else {
|
|
@@ -5486,7 +5510,7 @@ Examples:
|
|
|
5486
5510
|
}
|
|
5487
5511
|
}
|
|
5488
5512
|
// Exit with non-zero if resilience is poor
|
|
5489
|
-
if (report.resilienceRating === 'critical' || report.resilienceRating === '
|
|
5513
|
+
if (report.resilienceRating === 'critical' || report.resilienceRating === 'needs-attention') {
|
|
5490
5514
|
process.exit(1);
|
|
5491
5515
|
}
|
|
5492
5516
|
}
|
|
@@ -5539,6 +5563,153 @@ function printWildReport(report) {
|
|
|
5539
5563
|
console.log(`page content through an LLM. For static config scanning, use:${colors.reset}`);
|
|
5540
5564
|
console.log(` ${colors.cyan}npx hackmyagent secure${colors.reset}`);
|
|
5541
5565
|
}
|
|
5566
|
+
// pull-stubs: fetch pending HMA check stubs from the registry
|
|
5567
|
+
program
|
|
5568
|
+
.command('pull-stubs')
|
|
5569
|
+
.description(`Fetch pending HMA check stubs from the registry for review.
|
|
5570
|
+
|
|
5571
|
+
The ARIA pipeline discovers new attack patterns and creates stub definitions
|
|
5572
|
+
for checks that HMA doesn't yet implement. This command pulls those stubs
|
|
5573
|
+
so you can review, refine, and integrate them.
|
|
5574
|
+
|
|
5575
|
+
Requires INTERNAL_API_KEY environment variable for registry authentication.
|
|
5576
|
+
|
|
5577
|
+
Examples:
|
|
5578
|
+
$ ${CLI_PREFIX} pull-stubs
|
|
5579
|
+
$ ${CLI_PREFIX} pull-stubs --status review
|
|
5580
|
+
$ ${CLI_PREFIX} pull-stubs --json`)
|
|
5581
|
+
.option('--status <status>', 'Filter by stub status (draft, review, integrated, rejected)', 'draft')
|
|
5582
|
+
.option('--registry-url <url>', 'Registry base URL', validateRegistryUrl(process.env.REGISTRY_URL || 'https://api.oa2a.org'))
|
|
5583
|
+
.option('--json', 'Output raw JSON instead of formatted table')
|
|
5584
|
+
.action(async (opts) => {
|
|
5585
|
+
const validStatuses = ['draft', 'review', 'integrated', 'rejected'];
|
|
5586
|
+
if (!validStatuses.includes(opts.status)) {
|
|
5587
|
+
process.stderr.write(`Error: --status must be one of: ${validStatuses.join(', ')}\n`);
|
|
5588
|
+
process.stderr.write(` Got: ${opts.status}\n`);
|
|
5589
|
+
process.exit(1);
|
|
5590
|
+
}
|
|
5591
|
+
const apiKey = process.env.INTERNAL_API_KEY;
|
|
5592
|
+
if (!apiKey) {
|
|
5593
|
+
process.stderr.write('Error: INTERNAL_API_KEY environment variable is not set.\n');
|
|
5594
|
+
process.stderr.write('\nThis command requires registry authentication.\n');
|
|
5595
|
+
process.stderr.write('Set the variable and retry:\n');
|
|
5596
|
+
process.stderr.write(' export INTERNAL_API_KEY=<your-key>\n');
|
|
5597
|
+
process.stderr.write(` ${CLI_PREFIX} pull-stubs\n`);
|
|
5598
|
+
process.exit(1);
|
|
5599
|
+
}
|
|
5600
|
+
const registryUrl = validateRegistryUrl(opts.registryUrl).replace(/\/+$/, '');
|
|
5601
|
+
const endpoint = `${registryUrl}/internal/aria/hma-stubs`;
|
|
5602
|
+
let responseData;
|
|
5603
|
+
try {
|
|
5604
|
+
const controller = new AbortController();
|
|
5605
|
+
const timeout = setTimeout(() => controller.abort(), 15000);
|
|
5606
|
+
const res = await fetch(endpoint, {
|
|
5607
|
+
headers: {
|
|
5608
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
5609
|
+
'Accept': 'application/json',
|
|
5610
|
+
},
|
|
5611
|
+
signal: controller.signal,
|
|
5612
|
+
});
|
|
5613
|
+
clearTimeout(timeout);
|
|
5614
|
+
if (!res.ok) {
|
|
5615
|
+
const body = await res.text().catch(() => '');
|
|
5616
|
+
process.stderr.write(`Error: Registry returned ${res.status} ${res.statusText}\n`);
|
|
5617
|
+
if (res.status === 401 || res.status === 403) {
|
|
5618
|
+
process.stderr.write(' Your INTERNAL_API_KEY may be invalid or expired.\n');
|
|
5619
|
+
}
|
|
5620
|
+
if (body)
|
|
5621
|
+
process.stderr.write(` ${body.slice(0, 200)}\n`);
|
|
5622
|
+
process.exit(1);
|
|
5623
|
+
}
|
|
5624
|
+
responseData = await res.json();
|
|
5625
|
+
}
|
|
5626
|
+
catch (err) {
|
|
5627
|
+
if (err instanceof Error && err.name === 'AbortError') {
|
|
5628
|
+
process.stderr.write(`Error: Registry request timed out after 15s.\n`);
|
|
5629
|
+
process.stderr.write(` URL: ${endpoint}\n`);
|
|
5630
|
+
process.stderr.write(` Check your network connection and registry URL.\n`);
|
|
5631
|
+
}
|
|
5632
|
+
else {
|
|
5633
|
+
process.stderr.write(`Error: Could not reach the registry.\n`);
|
|
5634
|
+
process.stderr.write(` URL: ${endpoint}\n`);
|
|
5635
|
+
process.stderr.write(` ${err instanceof Error ? err.message : String(err)}\n`);
|
|
5636
|
+
}
|
|
5637
|
+
process.exit(1);
|
|
5638
|
+
}
|
|
5639
|
+
// Filter by status
|
|
5640
|
+
const stubs = responseData.stubs.filter(s => s.status === opts.status);
|
|
5641
|
+
if (stubs.length === 0) {
|
|
5642
|
+
if (opts.json) {
|
|
5643
|
+
writeJsonStdout({ stubs: [], total: responseData.total, filtered: 0, status: opts.status });
|
|
5644
|
+
}
|
|
5645
|
+
else {
|
|
5646
|
+
console.log(`No stubs with status "${opts.status}" found.`);
|
|
5647
|
+
if (responseData.total > 0) {
|
|
5648
|
+
console.log(` Registry has ${responseData.total} total stub(s). Try a different --status filter.`);
|
|
5649
|
+
}
|
|
5650
|
+
}
|
|
5651
|
+
return;
|
|
5652
|
+
}
|
|
5653
|
+
// JSON output mode
|
|
5654
|
+
if (opts.json) {
|
|
5655
|
+
writeJsonStdout({ stubs, total: responseData.total, filtered: stubs.length, status: opts.status });
|
|
5656
|
+
return;
|
|
5657
|
+
}
|
|
5658
|
+
// Formatted output
|
|
5659
|
+
const severityColor = {
|
|
5660
|
+
critical: colors.brightRed,
|
|
5661
|
+
high: colors.red,
|
|
5662
|
+
medium: colors.yellow,
|
|
5663
|
+
low: colors.cyan,
|
|
5664
|
+
info: colors.dim,
|
|
5665
|
+
};
|
|
5666
|
+
console.log(`\nHMA Check Stubs (status: ${opts.status})\n`);
|
|
5667
|
+
for (const stub of stubs) {
|
|
5668
|
+
const sc = severityColor[stub.severity?.toLowerCase()] || '';
|
|
5669
|
+
console.log(`${'='.repeat(60)}`);
|
|
5670
|
+
console.log(` Check ID: ${stub.checkId}`);
|
|
5671
|
+
console.log(` Series: ${stub.series}`);
|
|
5672
|
+
console.log(` Name: ${stub.name}`);
|
|
5673
|
+
console.log(` Severity: ${sc}${stub.severity}${colors.reset}`);
|
|
5674
|
+
console.log(` ARIA ID: ${stub.ariaFindingId}`);
|
|
5675
|
+
console.log(` Status: ${stub.status}`);
|
|
5676
|
+
if (stub.description) {
|
|
5677
|
+
console.log(` Description: ${stub.description}`);
|
|
5678
|
+
}
|
|
5679
|
+
if (stub.detectionLogic) {
|
|
5680
|
+
console.log(` Detection logic:`);
|
|
5681
|
+
for (const line of stub.detectionLogic.split('\n')) {
|
|
5682
|
+
console.log(` ${line}`);
|
|
5683
|
+
}
|
|
5684
|
+
}
|
|
5685
|
+
console.log('');
|
|
5686
|
+
}
|
|
5687
|
+
// Summary
|
|
5688
|
+
console.log('='.repeat(60));
|
|
5689
|
+
console.log(`\nSummary`);
|
|
5690
|
+
console.log(` Total in registry: ${responseData.total}`);
|
|
5691
|
+
console.log(` Matching "${opts.status}": ${stubs.length}`);
|
|
5692
|
+
// By series
|
|
5693
|
+
const bySeries = {};
|
|
5694
|
+
for (const s of stubs) {
|
|
5695
|
+
bySeries[s.series] = (bySeries[s.series] || 0) + 1;
|
|
5696
|
+
}
|
|
5697
|
+
console.log(`\n By series:`);
|
|
5698
|
+
for (const [series, count] of Object.entries(bySeries).sort((a, b) => b[1] - a[1])) {
|
|
5699
|
+
console.log(` ${series}: ${count}`);
|
|
5700
|
+
}
|
|
5701
|
+
// By severity
|
|
5702
|
+
const bySeverity = {};
|
|
5703
|
+
for (const s of stubs) {
|
|
5704
|
+
bySeverity[s.severity] = (bySeverity[s.severity] || 0) + 1;
|
|
5705
|
+
}
|
|
5706
|
+
console.log(`\n By severity:`);
|
|
5707
|
+
for (const [sev, count] of Object.entries(bySeverity).sort((a, b) => b[1] - a[1])) {
|
|
5708
|
+
const sc = severityColor[sev?.toLowerCase()] || '';
|
|
5709
|
+
console.log(` ${sc}${sev}${colors.reset}: ${count}`);
|
|
5710
|
+
}
|
|
5711
|
+
console.log('');
|
|
5712
|
+
});
|
|
5542
5713
|
// create-skill: generate best-practice, secured skills from plain English
|
|
5543
5714
|
program
|
|
5544
5715
|
.command('create-skill')
|
|
@@ -5585,6 +5756,12 @@ program
|
|
|
5585
5756
|
catch {
|
|
5586
5757
|
// Integrity check itself failed -- continue (don't block on missing manifest in dev)
|
|
5587
5758
|
}
|
|
5759
|
+
// Global --ci flag: strip from argv so individual commands don't reject it.
|
|
5760
|
+
// Any command can check globalCiMode to adjust behavior.
|
|
5761
|
+
if (process.argv.includes('--ci')) {
|
|
5762
|
+
globalCiMode = true;
|
|
5763
|
+
process.argv = process.argv.filter(a => a !== '--ci');
|
|
5764
|
+
}
|
|
5588
5765
|
if (process.argv.length <= 2) {
|
|
5589
5766
|
program.outputHelp();
|
|
5590
5767
|
process.exit(0);
|