hackmyagent 0.9.5 → 0.9.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/dist/attack/scanner.d.ts.map +1 -1
- package/dist/attack/scanner.js +6 -3
- package/dist/attack/scanner.js.map +1 -1
- package/dist/attack/types.d.ts +2 -0
- package/dist/attack/types.d.ts.map +1 -1
- package/dist/attack/types.js.map +1 -1
- package/dist/benchmarks/oasb-1.d.ts +1 -1
- package/dist/benchmarks/oasb-1.d.ts.map +1 -1
- package/dist/benchmarks/oasb-1.js +3 -3
- package/dist/benchmarks/oasb-1.js.map +1 -1
- package/dist/cli.js +221 -104
- package/dist/cli.js.map +1 -1
- package/dist/hardening/scanner.d.ts.map +1 -1
- package/dist/hardening/scanner.js +42 -15
- package/dist/hardening/scanner.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/registry/client.d.ts +1 -0
- package/dist/registry/client.d.ts.map +1 -1
- package/dist/registry/client.js +26 -3
- package/dist/registry/client.js.map +1 -1
- package/dist/scanner/external-scanner.js +1 -1
- package/dist/scanner/external-scanner.js.map +1 -1
- package/dist/semantic/deep-scan.js +1 -1
- package/dist/semantic/structural/credential-context.js +1 -1
- package/dist/semantic/structural/credential-context.js.map +1 -1
- package/dist/soul/scanner.d.ts +3 -1
- package/dist/soul/scanner.d.ts.map +1 -1
- package/dist/soul/scanner.js +48 -14
- package/dist/soul/scanner.js.map +1 -1
- package/dist/soul/templates.d.ts.map +1 -1
- package/dist/soul/templates.js +32 -0
- package/dist/soul/templates.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -41,6 +41,29 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
41
41
|
const commander_1 = require("commander");
|
|
42
42
|
const index_1 = require("./index");
|
|
43
43
|
const program = new commander_1.Command();
|
|
44
|
+
// Write JSON to stdout synchronously with retry for pipe backpressure.
|
|
45
|
+
// process.stdout.write() is async and gets truncated when process.exit()
|
|
46
|
+
// runs before the stream flushes. fs.writeFileSync(1, ...) can fail with
|
|
47
|
+
// EAGAIN on non-blocking pipes when the buffer (64KB on macOS) fills up.
|
|
48
|
+
// This function writes in chunks with retry to handle both cases.
|
|
49
|
+
function writeJsonStdout(data) {
|
|
50
|
+
const fs = require('fs');
|
|
51
|
+
const buf = Buffer.from(JSON.stringify(data, null, 2) + '\n');
|
|
52
|
+
let offset = 0;
|
|
53
|
+
while (offset < buf.length) {
|
|
54
|
+
try {
|
|
55
|
+
const written = fs.writeSync(1, buf, offset, buf.length - offset);
|
|
56
|
+
offset += written;
|
|
57
|
+
}
|
|
58
|
+
catch (e) {
|
|
59
|
+
if (e && typeof e === 'object' && 'code' in e && e.code === 'EAGAIN') {
|
|
60
|
+
// Pipe buffer full — spin-wait briefly then retry
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
throw e;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
44
67
|
// Check for NO_COLOR env or non-TTY to disable colors by default
|
|
45
68
|
const noColorEnv = process.env.NO_COLOR !== undefined || process.stdout.isTTY === false;
|
|
46
69
|
// Color codes - will be cleared if --no-color is passed
|
|
@@ -50,14 +73,15 @@ let colors = {
|
|
|
50
73
|
red: '\x1b[31m',
|
|
51
74
|
brightRed: '\x1b[91m',
|
|
52
75
|
cyan: '\x1b[36m',
|
|
76
|
+
dim: '\x1b[2m',
|
|
53
77
|
reset: '\x1b[0m',
|
|
54
78
|
};
|
|
55
79
|
if (noColorEnv) {
|
|
56
|
-
colors = { green: '', yellow: '', red: '', brightRed: '', cyan: '', reset: '' };
|
|
80
|
+
colors = { green: '', yellow: '', red: '', brightRed: '', cyan: '', dim: '', reset: '' };
|
|
57
81
|
}
|
|
58
82
|
// Deprecation warning for removed HMAC auth
|
|
59
83
|
if (process.env.HMA_COMMUNITY_SECRET) {
|
|
60
|
-
console.error('
|
|
84
|
+
console.error('Note: HMA_COMMUNITY_SECRET is deprecated and no longer used. Scan tokens are now issued automatically.');
|
|
61
85
|
}
|
|
62
86
|
program
|
|
63
87
|
.name('hackmyagent')
|
|
@@ -85,15 +109,15 @@ Examples:
|
|
|
85
109
|
.hook('preAction', (thisCommand) => {
|
|
86
110
|
const opts = thisCommand.opts();
|
|
87
111
|
if (opts.color === false) {
|
|
88
|
-
colors = { green: '', yellow: '', red: '', brightRed: '', cyan: '', reset: '' };
|
|
112
|
+
colors = { green: '', yellow: '', red: '', brightRed: '', cyan: '', dim: '', reset: '' };
|
|
89
113
|
}
|
|
90
114
|
});
|
|
91
115
|
// Risk level colors and symbols
|
|
92
116
|
const RISK_DISPLAY = {
|
|
93
|
-
low: { symbol: '
|
|
94
|
-
medium: { symbol: '
|
|
95
|
-
high: { symbol: '
|
|
96
|
-
critical: { symbol: '
|
|
117
|
+
low: { symbol: '[+]', color: () => colors.green },
|
|
118
|
+
medium: { symbol: '[~]', color: () => colors.yellow },
|
|
119
|
+
high: { symbol: '[!]', color: () => colors.red },
|
|
120
|
+
critical: { symbol: '[!!]', color: () => colors.brightRed },
|
|
97
121
|
};
|
|
98
122
|
const RESET = () => colors.reset;
|
|
99
123
|
program
|
|
@@ -122,7 +146,7 @@ Examples:
|
|
|
122
146
|
skipDnsVerification: options.offline,
|
|
123
147
|
});
|
|
124
148
|
if (options.json) {
|
|
125
|
-
|
|
149
|
+
writeJsonStdout(result);
|
|
126
150
|
return;
|
|
127
151
|
}
|
|
128
152
|
const risk = RISK_DISPLAY[result.risk];
|
|
@@ -130,19 +154,19 @@ Examples:
|
|
|
130
154
|
// Publisher info
|
|
131
155
|
console.log(`Publisher: @${result.publisher.name}`);
|
|
132
156
|
if (result.publisher.verified) {
|
|
133
|
-
console.log(`├─
|
|
157
|
+
console.log(`├─ [+] Verified via DNS`);
|
|
134
158
|
if (result.publisher.domain) {
|
|
135
|
-
console.log(`├─
|
|
159
|
+
console.log(`├─ Domain: ${result.publisher.domain}`);
|
|
136
160
|
}
|
|
137
161
|
if (result.publisher.verifiedAt && options.verbose) {
|
|
138
|
-
console.log(`└─
|
|
162
|
+
console.log(`└─ Verified at: ${result.publisher.verifiedAt.toISOString()}`);
|
|
139
163
|
}
|
|
140
164
|
else {
|
|
141
165
|
console.log(`└─ Method: DNS TXT record`);
|
|
142
166
|
}
|
|
143
167
|
}
|
|
144
168
|
else {
|
|
145
|
-
console.log(`├─
|
|
169
|
+
console.log(`├─ [-] Not verified`);
|
|
146
170
|
if (result.publisher.failureReason && options.verbose) {
|
|
147
171
|
console.log(`└─ Reason: ${result.publisher.failureReason}`);
|
|
148
172
|
}
|
|
@@ -161,13 +185,13 @@ Examples:
|
|
|
161
185
|
}
|
|
162
186
|
else {
|
|
163
187
|
for (const perm of result.permissions.safe) {
|
|
164
|
-
console.log(`├─
|
|
188
|
+
console.log(`├─ [+] ${perm}`);
|
|
165
189
|
}
|
|
166
190
|
for (const perm of result.permissions.reviewNeeded) {
|
|
167
|
-
console.log(`├─
|
|
191
|
+
console.log(`├─ [~] ${perm} (review needed)`);
|
|
168
192
|
}
|
|
169
193
|
for (const perm of result.permissions.dangerous) {
|
|
170
|
-
console.log(`├─
|
|
194
|
+
console.log(`├─ [!] ${perm} (elevated risk)`);
|
|
171
195
|
}
|
|
172
196
|
console.log(`└─ Risk score: ${result.permissions.riskScore}/100`);
|
|
173
197
|
}
|
|
@@ -175,10 +199,10 @@ Examples:
|
|
|
175
199
|
// Revocation
|
|
176
200
|
console.log('Revocation:');
|
|
177
201
|
if (result.revocation.revoked) {
|
|
178
|
-
console.log(`└─
|
|
202
|
+
console.log(`└─ [!!] Revoked: ${result.revocation.reason}`);
|
|
179
203
|
}
|
|
180
204
|
else {
|
|
181
|
-
console.log(`└─
|
|
205
|
+
console.log(`└─ [+] Not on blocklist`);
|
|
182
206
|
}
|
|
183
207
|
console.log();
|
|
184
208
|
// Verbose details
|
|
@@ -198,10 +222,10 @@ Examples:
|
|
|
198
222
|
});
|
|
199
223
|
// Severity colors and symbols for secure command
|
|
200
224
|
const SEVERITY_DISPLAY = {
|
|
201
|
-
critical: { symbol: '
|
|
202
|
-
high: { symbol: '
|
|
203
|
-
medium: { symbol: '
|
|
204
|
-
low: { symbol: '
|
|
225
|
+
critical: { symbol: '[!!]', color: () => colors.brightRed },
|
|
226
|
+
high: { symbol: '[!]', color: () => colors.red },
|
|
227
|
+
medium: { symbol: '[~]', color: () => colors.yellow },
|
|
228
|
+
low: { symbol: '[.]', color: () => colors.green },
|
|
205
229
|
};
|
|
206
230
|
function groupFindingsBySeverity(findings) {
|
|
207
231
|
const grouped = {
|
|
@@ -316,7 +340,7 @@ function generateBenchmarkReport(findings, level, categoryFilter) {
|
|
|
316
340
|
const l3Compliance = l3Total > 0 ? Math.round((l3Passed / l3Total) * 100) : 100;
|
|
317
341
|
const totalScored = l1Total + l2Total + l3Total;
|
|
318
342
|
const totalPassed = l1Passed + l2Passed + l3Passed;
|
|
319
|
-
const overallCompliance = totalScored > 0 ? Math.round((totalPassed / totalScored) * 100) :
|
|
343
|
+
const overallCompliance = totalScored > 0 ? Math.round((totalPassed / totalScored) * 100) : 0;
|
|
320
344
|
// Group results by category
|
|
321
345
|
const categoryResults = [];
|
|
322
346
|
for (const category of index_1.OASB_1_CATEGORIES) {
|
|
@@ -328,7 +352,7 @@ function generateBenchmarkReport(findings, level, categoryFilter) {
|
|
|
328
352
|
const passed = catControls.filter((r) => r.status === 'passed').length;
|
|
329
353
|
const failed = catControls.filter((r) => r.status === 'failed').length;
|
|
330
354
|
const unverified = catControls.filter((r) => r.status === 'unverified').length;
|
|
331
|
-
const compliance = (passed + failed) > 0 ? Math.round((passed / (passed + failed)) * 100) :
|
|
355
|
+
const compliance = (passed + failed) > 0 ? Math.round((passed / (passed + failed)) * 100) : 0;
|
|
332
356
|
categoryResults.push({
|
|
333
357
|
category: category.name,
|
|
334
358
|
compliance,
|
|
@@ -442,14 +466,14 @@ function generateHtmlReport(result) {
|
|
|
442
466
|
'Compliant': '#22c55e',
|
|
443
467
|
'Passing': '#eab308',
|
|
444
468
|
'Needs Improvement': '#f97316',
|
|
445
|
-
'
|
|
469
|
+
'Not Passing': '#ef4444',
|
|
446
470
|
}[result.rating] || '#94a3b8';
|
|
447
471
|
const ratingBg = {
|
|
448
472
|
'Certified': 'rgba(34, 197, 94, 0.15)',
|
|
449
473
|
'Compliant': 'rgba(34, 197, 94, 0.15)',
|
|
450
474
|
'Passing': 'rgba(234, 179, 8, 0.15)',
|
|
451
475
|
'Needs Improvement': 'rgba(249, 115, 22, 0.15)',
|
|
452
|
-
'
|
|
476
|
+
'Not Passing': 'rgba(239, 68, 68, 0.15)',
|
|
453
477
|
}[result.rating] || 'rgba(148, 163, 184, 0.15)';
|
|
454
478
|
// Generate donut chart SVG
|
|
455
479
|
const donutRadius = 70;
|
|
@@ -1390,10 +1414,10 @@ function printBenchmarkReport(result, verbose) {
|
|
|
1390
1414
|
'Compliant': colors.green,
|
|
1391
1415
|
'Passing': colors.yellow,
|
|
1392
1416
|
'Needs Improvement': colors.yellow,
|
|
1393
|
-
'
|
|
1417
|
+
'Not Passing': colors.red,
|
|
1394
1418
|
};
|
|
1395
1419
|
// Header
|
|
1396
|
-
console.log(`\n
|
|
1420
|
+
console.log(`\n${result.benchmark} v${result.version}`);
|
|
1397
1421
|
console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`);
|
|
1398
1422
|
// Level and rating
|
|
1399
1423
|
const levelNames = {
|
|
@@ -1411,13 +1435,18 @@ function printBenchmarkReport(result, verbose) {
|
|
|
1411
1435
|
// Category breakdown
|
|
1412
1436
|
console.log(`Categories:`);
|
|
1413
1437
|
for (const catResult of result.categories) {
|
|
1414
|
-
const
|
|
1415
|
-
|
|
1438
|
+
const total = catResult.passed + catResult.failed;
|
|
1439
|
+
if (total === 0) {
|
|
1440
|
+
console.log(` [.] ${catResult.category}: N/A (no controls at this level)`);
|
|
1441
|
+
continue;
|
|
1442
|
+
}
|
|
1443
|
+
const statusIcon = catResult.failed === 0 ? '[+]' : (catResult.passed > 0 ? '[~]' : '[-]');
|
|
1444
|
+
console.log(` ${statusIcon} ${catResult.category}: ${catResult.passed}/${total} (${catResult.compliance}%)`);
|
|
1416
1445
|
// Show failed controls
|
|
1417
1446
|
if (verbose || catResult.failed > 0) {
|
|
1418
1447
|
for (const ctrl of catResult.controls) {
|
|
1419
1448
|
if (ctrl.status === 'failed') {
|
|
1420
|
-
console.log(`
|
|
1449
|
+
console.log(` [-] ${ctrl.controlId}: ${ctrl.name}`);
|
|
1421
1450
|
if (verbose) {
|
|
1422
1451
|
for (const finding of ctrl.findings) {
|
|
1423
1452
|
console.log(` └─ ${finding}`);
|
|
@@ -1425,7 +1454,7 @@ function printBenchmarkReport(result, verbose) {
|
|
|
1425
1454
|
}
|
|
1426
1455
|
}
|
|
1427
1456
|
else if (verbose && ctrl.status === 'passed') {
|
|
1428
|
-
console.log(`
|
|
1457
|
+
console.log(` [+] ${ctrl.controlId}: ${ctrl.name}`);
|
|
1429
1458
|
}
|
|
1430
1459
|
else if (verbose && ctrl.status === 'unverified') {
|
|
1431
1460
|
// Look up the original control to determine why it's unverified
|
|
@@ -1435,7 +1464,7 @@ function printBenchmarkReport(result, verbose) {
|
|
|
1435
1464
|
const reason = originalControl && (originalControl.verification === 'manual' || originalControl.verification === 'forward')
|
|
1436
1465
|
? 'manual/forward'
|
|
1437
1466
|
: 'no scanner data';
|
|
1438
|
-
console.log(`
|
|
1467
|
+
console.log(` [?] ${ctrl.controlId}: ${ctrl.name} (${reason})`);
|
|
1439
1468
|
}
|
|
1440
1469
|
}
|
|
1441
1470
|
}
|
|
@@ -1445,7 +1474,7 @@ function printBenchmarkReport(result, verbose) {
|
|
|
1445
1474
|
// Compliance breakdown by level
|
|
1446
1475
|
if (verbose) {
|
|
1447
1476
|
console.log(`\nCompliance by level: L1=${result.l1Compliance}% L2=${result.l2Compliance}% L3=${result.l3Compliance}%`);
|
|
1448
|
-
console.log(`Legend:
|
|
1477
|
+
console.log(`Legend: [?] = Manual/Forward verification required`);
|
|
1449
1478
|
}
|
|
1450
1479
|
// Show appropriate next step based on current level
|
|
1451
1480
|
if (result.level === 'L1') {
|
|
@@ -1472,9 +1501,12 @@ function resolvePackageName(targetDir) {
|
|
|
1472
1501
|
}
|
|
1473
1502
|
}
|
|
1474
1503
|
catch { /* ignore */ }
|
|
1475
|
-
// Fallback: use directory name
|
|
1504
|
+
// Fallback: use directory name, resolving "." to the actual directory name
|
|
1476
1505
|
const path = require('path');
|
|
1477
|
-
|
|
1506
|
+
const resolved = path.resolve(targetDir);
|
|
1507
|
+
const name = path.basename(resolved);
|
|
1508
|
+
// Skip names that are clearly not package names
|
|
1509
|
+
return name && name !== '.' && name !== '..' ? name : null;
|
|
1478
1510
|
}
|
|
1479
1511
|
function resolvePackageVersion(targetDir) {
|
|
1480
1512
|
try {
|
|
@@ -1513,7 +1545,6 @@ Output formats (--format):
|
|
|
1513
1545
|
json Machine-readable JSON
|
|
1514
1546
|
sarif GitHub Security tab / IDE integration
|
|
1515
1547
|
html Shareable compliance report
|
|
1516
|
-
asp Agent Security Profile (our format)
|
|
1517
1548
|
|
|
1518
1549
|
Severities: critical, high, medium, low
|
|
1519
1550
|
Exit code 1 if critical/high issues found (or non-compliant in benchmark mode).
|
|
@@ -1533,7 +1564,7 @@ Examples:
|
|
|
1533
1564
|
.option('--dry-run', 'Preview fixes without applying them (use with --fix)')
|
|
1534
1565
|
.option('--ignore <checks>', 'Comma-separated check IDs to skip (e.g., CRED-001,GIT-002)')
|
|
1535
1566
|
.option('--json', 'Output as JSON (deprecated: use --format json)')
|
|
1536
|
-
.option('-f, --format <format>', 'Output format: text, json, sarif, html
|
|
1567
|
+
.option('-f, --format <format>', 'Output format: text, json, sarif, html (default: text)', 'text')
|
|
1537
1568
|
.option('-o, --output <file>', 'Write output to file instead of stdout')
|
|
1538
1569
|
.option('--fail-below <percent>', 'Exit 1 if compliance below threshold (0-100)')
|
|
1539
1570
|
.option('-v, --verbose', 'Show all checks including passed ones')
|
|
@@ -1587,10 +1618,10 @@ Examples:
|
|
|
1587
1618
|
// Only show progress for text output
|
|
1588
1619
|
if (format === 'text') {
|
|
1589
1620
|
if (options.dryRun) {
|
|
1590
|
-
console.log(`\
|
|
1621
|
+
console.log(`\nScanning ${targetDir} (dry-run)...\n`);
|
|
1591
1622
|
}
|
|
1592
1623
|
else {
|
|
1593
|
-
console.log(`\
|
|
1624
|
+
console.log(`\nScanning ${targetDir}...\n`);
|
|
1594
1625
|
}
|
|
1595
1626
|
}
|
|
1596
1627
|
// Deep mode progress display
|
|
@@ -1624,7 +1655,7 @@ Examples:
|
|
|
1624
1655
|
const govScore = govResult.score;
|
|
1625
1656
|
const compositeScore = Math.round((infraScore + govScore) / 2);
|
|
1626
1657
|
if (format === 'json') {
|
|
1627
|
-
|
|
1658
|
+
const jsonOutput = JSON.stringify({
|
|
1628
1659
|
benchmark: 'OASB-2',
|
|
1629
1660
|
infraScore,
|
|
1630
1661
|
govScore,
|
|
@@ -1632,7 +1663,15 @@ Examples:
|
|
|
1632
1663
|
conformance: govResult.conformance,
|
|
1633
1664
|
infraResult,
|
|
1634
1665
|
govResult,
|
|
1635
|
-
}, null, 2)
|
|
1666
|
+
}, null, 2);
|
|
1667
|
+
if (options.output) {
|
|
1668
|
+
require('fs').writeFileSync(options.output, jsonOutput);
|
|
1669
|
+
console.error(`Report written to ${options.output}`);
|
|
1670
|
+
}
|
|
1671
|
+
else {
|
|
1672
|
+
const fs = require('fs');
|
|
1673
|
+
fs.writeFileSync(1, jsonOutput + '\n');
|
|
1674
|
+
}
|
|
1636
1675
|
}
|
|
1637
1676
|
else {
|
|
1638
1677
|
process.stdout.write('\nOASB v2 Composite Security Assessment\n');
|
|
@@ -1700,13 +1739,22 @@ Examples:
|
|
|
1700
1739
|
process.exit(1);
|
|
1701
1740
|
}
|
|
1702
1741
|
// Exit with non-zero if failing or needs improvement (default behavior)
|
|
1703
|
-
if (failBelow === undefined && (benchmarkResult.rating === '
|
|
1742
|
+
if (failBelow === undefined && (benchmarkResult.rating === 'Not Passing' || benchmarkResult.rating === 'Needs Improvement')) {
|
|
1704
1743
|
process.exit(1);
|
|
1705
1744
|
}
|
|
1706
1745
|
return;
|
|
1707
1746
|
}
|
|
1708
1747
|
if (format === 'json') {
|
|
1709
|
-
|
|
1748
|
+
if (options.output) {
|
|
1749
|
+
require('fs').writeFileSync(options.output, JSON.stringify(result, null, 2) + '\n');
|
|
1750
|
+
console.error(`Report written to ${options.output}`);
|
|
1751
|
+
}
|
|
1752
|
+
else {
|
|
1753
|
+
writeJsonStdout(result);
|
|
1754
|
+
}
|
|
1755
|
+
const critHigh = result.findings.filter((f) => !f.passed && !f.fixed && (f.severity === 'critical' || f.severity === 'high'));
|
|
1756
|
+
if (critHigh.length > 0)
|
|
1757
|
+
process.exitCode = 1;
|
|
1710
1758
|
return;
|
|
1711
1759
|
}
|
|
1712
1760
|
// Handle SARIF/HTML/ASP for non-benchmark mode
|
|
@@ -1763,7 +1811,12 @@ Examples:
|
|
|
1763
1811
|
scoreExtra += ` (${sa.cachedResults} cached)`;
|
|
1764
1812
|
}
|
|
1765
1813
|
}
|
|
1766
|
-
console.log(`${projectTypeLabel} | Score: ${result.score}/${result.maxScore}${scoreExtra}
|
|
1814
|
+
console.log(`${projectTypeLabel} | Score: ${result.score}/${result.maxScore}${scoreExtra}`);
|
|
1815
|
+
if (issues.length > 0) {
|
|
1816
|
+
const recoverable = Math.min(result.maxScore - result.score, result.maxScore);
|
|
1817
|
+
console.log(` Path forward: +${recoverable} recoverable by addressing ${issues.length} issue${issues.length === 1 ? '' : 's'}`);
|
|
1818
|
+
}
|
|
1819
|
+
console.log('');
|
|
1767
1820
|
// No issues? Say so and exit
|
|
1768
1821
|
if (issues.length === 0 && fixedFindings.length === 0) {
|
|
1769
1822
|
console.log(`${colors.green}No issues found.${RESET()}\n`);
|
|
@@ -1786,8 +1839,39 @@ Examples:
|
|
|
1786
1839
|
if (finding.fix) {
|
|
1787
1840
|
console.log(` ${colors.cyan}Fix:${RESET()} ${finding.fix}`);
|
|
1788
1841
|
}
|
|
1842
|
+
if (options.verbose) {
|
|
1843
|
+
console.log(` ${colors.dim}Check: ${finding.checkId} | Category: ${finding.category}${RESET()}`);
|
|
1844
|
+
if (finding.file) {
|
|
1845
|
+
console.log(` ${colors.dim}File: ${finding.file}${finding.line ? ` (line ${finding.line})` : ''}${RESET()}`);
|
|
1846
|
+
}
|
|
1847
|
+
if (finding.message && finding.message !== finding.description) {
|
|
1848
|
+
console.log(` ${colors.dim}Detail: ${finding.message}${RESET()}`);
|
|
1849
|
+
}
|
|
1850
|
+
if (finding.details && Object.keys(finding.details).length > 0) {
|
|
1851
|
+
for (const [key, value] of Object.entries(finding.details)) {
|
|
1852
|
+
console.log(` ${colors.dim}${key}: ${typeof value === 'object' ? JSON.stringify(value) : value}${RESET()}`);
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1789
1856
|
console.log();
|
|
1790
1857
|
}
|
|
1858
|
+
// Severity breakdown summary
|
|
1859
|
+
const severityCounts = { critical: 0, high: 0, medium: 0, low: 0 };
|
|
1860
|
+
for (const f of issues) {
|
|
1861
|
+
severityCounts[f.severity]++;
|
|
1862
|
+
}
|
|
1863
|
+
const summaryParts = [];
|
|
1864
|
+
if (severityCounts.critical > 0)
|
|
1865
|
+
summaryParts.push(`${colors.brightRed}Critical: ${severityCounts.critical}${RESET()}`);
|
|
1866
|
+
if (severityCounts.high > 0)
|
|
1867
|
+
summaryParts.push(`${colors.red}High: ${severityCounts.high}${RESET()}`);
|
|
1868
|
+
if (severityCounts.medium > 0)
|
|
1869
|
+
summaryParts.push(`${colors.yellow}Medium: ${severityCounts.medium}${RESET()}`);
|
|
1870
|
+
if (severityCounts.low > 0)
|
|
1871
|
+
summaryParts.push(`${colors.green}Low: ${severityCounts.low}${RESET()}`);
|
|
1872
|
+
if (summaryParts.length > 0) {
|
|
1873
|
+
console.log(`${summaryParts.join(' | ')}\n`);
|
|
1874
|
+
}
|
|
1791
1875
|
}
|
|
1792
1876
|
// Print fixed findings
|
|
1793
1877
|
if (fixedFindings.length > 0) {
|
|
@@ -1802,9 +1886,9 @@ Examples:
|
|
|
1802
1886
|
console.log(`Undo: hackmyagent rollback ${directory}\n`);
|
|
1803
1887
|
}
|
|
1804
1888
|
}
|
|
1805
|
-
// Registry reporting:
|
|
1806
|
-
|
|
1807
|
-
if (
|
|
1889
|
+
// Registry reporting: only when explicitly requested via --version-id (CI) or --registry-report
|
|
1890
|
+
// Community contributions are handled by the opena2a CLI wrapper, not HMA directly
|
|
1891
|
+
if (options.versionId || options.registryReport) {
|
|
1808
1892
|
try {
|
|
1809
1893
|
const core = await Promise.resolve().then(() => __importStar(require('./index')));
|
|
1810
1894
|
const registryUrl = options.registryUrl || process.env.REGISTRY_URL || 'https://registry.opena2a.org';
|
|
@@ -1841,8 +1925,8 @@ Examples:
|
|
|
1841
1925
|
}
|
|
1842
1926
|
}
|
|
1843
1927
|
}
|
|
1844
|
-
catch (
|
|
1845
|
-
|
|
1928
|
+
catch (_reportErr) {
|
|
1929
|
+
// Silently ignore registry errors - they are not relevant to local scan results
|
|
1846
1930
|
}
|
|
1847
1931
|
}
|
|
1848
1932
|
// Star prompt (interactive TTY only, text format only)
|
|
@@ -1862,10 +1946,10 @@ Examples:
|
|
|
1862
1946
|
});
|
|
1863
1947
|
// Severity display for external scan findings
|
|
1864
1948
|
const FINDING_SEVERITY_DISPLAY = {
|
|
1865
|
-
critical: { symbol: '
|
|
1866
|
-
high: { symbol: '
|
|
1867
|
-
medium: { symbol: '
|
|
1868
|
-
low: { symbol: '
|
|
1949
|
+
critical: { symbol: '[!!]', color: () => colors.brightRed },
|
|
1950
|
+
high: { symbol: '[!]', color: () => colors.red },
|
|
1951
|
+
medium: { symbol: '[~]', color: () => colors.yellow },
|
|
1952
|
+
low: { symbol: '[.]', color: () => colors.green },
|
|
1869
1953
|
};
|
|
1870
1954
|
function groupExternalFindingsBySeverity(findings) {
|
|
1871
1955
|
const grouped = {
|
|
@@ -1916,29 +2000,36 @@ function assessRiskLevel(findings) {
|
|
|
1916
2000
|
const mediumCount = findings.filter((f) => f.severity === 'medium').length;
|
|
1917
2001
|
if (criticalCount > 0) {
|
|
1918
2002
|
return {
|
|
1919
|
-
level: '
|
|
2003
|
+
level: 'Critical',
|
|
1920
2004
|
color: colors.brightRed,
|
|
1921
|
-
description:
|
|
2005
|
+
description: `${criticalCount} critical finding(s) with recommended fixes available.`,
|
|
1922
2006
|
};
|
|
1923
2007
|
}
|
|
1924
2008
|
if (highCount > 0) {
|
|
1925
2009
|
return {
|
|
1926
|
-
level: '
|
|
2010
|
+
level: 'High',
|
|
1927
2011
|
color: colors.red,
|
|
1928
|
-
description:
|
|
2012
|
+
description: `${highCount} high-severity finding(s) detected. Fixes available below.`,
|
|
1929
2013
|
};
|
|
1930
2014
|
}
|
|
1931
2015
|
if (mediumCount > 0) {
|
|
1932
2016
|
return {
|
|
1933
|
-
level: '
|
|
2017
|
+
level: 'Moderate',
|
|
1934
2018
|
color: colors.yellow,
|
|
1935
|
-
description: 'Some
|
|
2019
|
+
description: 'Some findings detected. Review the recommendations below.',
|
|
2020
|
+
};
|
|
2021
|
+
}
|
|
2022
|
+
if (findings.length === 0) {
|
|
2023
|
+
return {
|
|
2024
|
+
level: 'None',
|
|
2025
|
+
color: colors.dim,
|
|
2026
|
+
description: 'No OpenClaw configuration detected. Run `hackmyagent secure` for a full scan.',
|
|
1936
2027
|
};
|
|
1937
2028
|
}
|
|
1938
2029
|
return {
|
|
1939
|
-
level: '
|
|
2030
|
+
level: 'Low',
|
|
1940
2031
|
color: colors.green,
|
|
1941
|
-
description: '
|
|
2032
|
+
description: 'No critical or high findings detected.',
|
|
1942
2033
|
};
|
|
1943
2034
|
}
|
|
1944
2035
|
program
|
|
@@ -1969,13 +2060,13 @@ Examples:
|
|
|
1969
2060
|
try {
|
|
1970
2061
|
const targetDir = detectOpenClawDirectory(directory);
|
|
1971
2062
|
if (!options.json) {
|
|
1972
|
-
console.log(`\
|
|
2063
|
+
console.log(`\nOpenClaw Security Report`);
|
|
1973
2064
|
console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`);
|
|
1974
2065
|
if (options.dryRun) {
|
|
1975
|
-
console.log(
|
|
2066
|
+
console.log(`Scanning ${targetDir} (dry-run - previewing fixes)...\n`);
|
|
1976
2067
|
}
|
|
1977
2068
|
else if (options.fix) {
|
|
1978
|
-
console.log(
|
|
2069
|
+
console.log(`Scanning and fixing ${targetDir}...\n`);
|
|
1979
2070
|
console.log(`${colors.yellow}Auto-fix will:${RESET()}`);
|
|
1980
2071
|
console.log(` • Bind gateway to 127.0.0.1 (local-only)`);
|
|
1981
2072
|
console.log(` • Replace plaintext tokens with env var references`);
|
|
@@ -1984,7 +2075,7 @@ Examples:
|
|
|
1984
2075
|
console.log(`\n${colors.cyan}A backup will be created for rollback if needed.${RESET()}\n`);
|
|
1985
2076
|
}
|
|
1986
2077
|
else {
|
|
1987
|
-
console.log(
|
|
2078
|
+
console.log(`Scanning ${targetDir}...\n`);
|
|
1988
2079
|
}
|
|
1989
2080
|
}
|
|
1990
2081
|
const scanner = new index_1.HardeningScanner();
|
|
@@ -2009,7 +2100,7 @@ Examples:
|
|
|
2009
2100
|
passed: passedFindings.length,
|
|
2010
2101
|
findings: allOpenClawFindings,
|
|
2011
2102
|
};
|
|
2012
|
-
|
|
2103
|
+
writeJsonStdout(jsonOutput);
|
|
2013
2104
|
return;
|
|
2014
2105
|
}
|
|
2015
2106
|
// Risk assessment
|
|
@@ -2020,7 +2111,7 @@ Examples:
|
|
|
2020
2111
|
console.log(`Checks: ${allOpenClawFindings.length} total | ${issues.length} issues | ${fixedFindings.length} fixed | ${passedFindings.length} passed\n`);
|
|
2021
2112
|
// Show issues
|
|
2022
2113
|
if (issues.length > 0) {
|
|
2023
|
-
console.log(`${colors.red}
|
|
2114
|
+
console.log(`${colors.red}Findings:${RESET()}\n`);
|
|
2024
2115
|
for (const finding of issues) {
|
|
2025
2116
|
const display = SEVERITY_DISPLAY[finding.severity];
|
|
2026
2117
|
const location = finding.file
|
|
@@ -2028,13 +2119,14 @@ Examples:
|
|
|
2028
2119
|
? `${finding.file}:${finding.line}`
|
|
2029
2120
|
: finding.file
|
|
2030
2121
|
: '';
|
|
2031
|
-
|
|
2122
|
+
const sevLabel = finding.severity.charAt(0).toUpperCase() + finding.severity.slice(1);
|
|
2123
|
+
console.log(`${display.color()}${display.symbol} [${finding.checkId}] ${sevLabel}${RESET()}`);
|
|
2032
2124
|
console.log(` ${finding.description}`);
|
|
2033
2125
|
if (location) {
|
|
2034
2126
|
console.log(` File: ${location}`);
|
|
2035
2127
|
}
|
|
2036
2128
|
if (finding.fix) {
|
|
2037
|
-
console.log(` ${colors.cyan}
|
|
2129
|
+
console.log(` ${colors.cyan}Recommended fix:${RESET()} ${finding.fix}`);
|
|
2038
2130
|
}
|
|
2039
2131
|
console.log();
|
|
2040
2132
|
}
|
|
@@ -2044,7 +2136,7 @@ Examples:
|
|
|
2044
2136
|
}
|
|
2045
2137
|
// Show fixed findings
|
|
2046
2138
|
if (fixedFindings.length > 0) {
|
|
2047
|
-
console.log(`${colors.green}
|
|
2139
|
+
console.log(`${colors.green}Auto-Remediation Applied:${RESET()}\n`);
|
|
2048
2140
|
for (const finding of fixedFindings) {
|
|
2049
2141
|
console.log(` ${colors.green}✓${RESET()} [${finding.checkId}] ${finding.name}`);
|
|
2050
2142
|
if (finding.fixMessage) {
|
|
@@ -2053,8 +2145,8 @@ Examples:
|
|
|
2053
2145
|
}
|
|
2054
2146
|
console.log();
|
|
2055
2147
|
if (result.backupPath) {
|
|
2056
|
-
console.log(`${colors.yellow}
|
|
2057
|
-
console.log(`${colors.yellow}
|
|
2148
|
+
console.log(`${colors.yellow}Backup created:${RESET()} ${result.backupPath}`);
|
|
2149
|
+
console.log(`${colors.yellow}To rollback:${RESET()} hackmyagent rollback ${targetDir}`);
|
|
2058
2150
|
console.log();
|
|
2059
2151
|
console.log(`${colors.cyan}Note:${RESET()} If you replaced tokens with env vars, set OPENCLAW_AUTH_TOKEN`);
|
|
2060
2152
|
console.log(` in your environment before starting OpenClaw.\n`);
|
|
@@ -2091,7 +2183,7 @@ Detects externally exposed:
|
|
|
2091
2183
|
• API keys in responses
|
|
2092
2184
|
• Debug/admin interfaces
|
|
2093
2185
|
|
|
2094
|
-
Scoring: A (90-100), B (80-89), C (70-79), D (60-69),
|
|
2186
|
+
Scoring: A (90-100), B (80-89), C (70-79), D (60-69), Needs Improvement (<60)
|
|
2095
2187
|
Exit code 1 if critical/high issues found.
|
|
2096
2188
|
|
|
2097
2189
|
Examples:
|
|
@@ -2106,7 +2198,7 @@ Examples:
|
|
|
2106
2198
|
.option('-v, --verbose', 'Show detailed finding information')
|
|
2107
2199
|
.action(async (target, options) => {
|
|
2108
2200
|
try {
|
|
2109
|
-
console.log(`\
|
|
2201
|
+
console.log(`\nScanning ${target}...\n`);
|
|
2110
2202
|
const scanner = new index_1.ExternalScanner();
|
|
2111
2203
|
const customPorts = options.ports
|
|
2112
2204
|
? options.ports.split(',').map((p) => parseInt(p.trim(), 10))
|
|
@@ -2116,7 +2208,7 @@ Examples:
|
|
|
2116
2208
|
timeout: parseInt(options.timeout ?? '5000', 10),
|
|
2117
2209
|
});
|
|
2118
2210
|
if (options.json) {
|
|
2119
|
-
|
|
2211
|
+
writeJsonStdout(result);
|
|
2120
2212
|
return;
|
|
2121
2213
|
}
|
|
2122
2214
|
// Print header
|
|
@@ -2132,7 +2224,7 @@ Examples:
|
|
|
2132
2224
|
console.log(`Open Ports: ${result.openPorts.length > 0 ? result.openPorts.join(', ') : 'None detected'}`);
|
|
2133
2225
|
console.log(`Duration: ${result.duration}ms\n`);
|
|
2134
2226
|
if (result.findings.length === 0) {
|
|
2135
|
-
console.log(`${colors.green}
|
|
2227
|
+
console.log(`${colors.green}[+] No security issues found!${RESET()}\n`);
|
|
2136
2228
|
return;
|
|
2137
2229
|
}
|
|
2138
2230
|
// Group findings by severity
|
|
@@ -2183,10 +2275,10 @@ Examples:
|
|
|
2183
2275
|
.action(async (directory) => {
|
|
2184
2276
|
try {
|
|
2185
2277
|
const targetDir = directory.startsWith('/') ? directory : process.cwd() + '/' + directory;
|
|
2186
|
-
console.log(`\
|
|
2278
|
+
console.log(`\nRolling back changes in ${targetDir}...\n`);
|
|
2187
2279
|
const scanner = new index_1.HardeningScanner();
|
|
2188
2280
|
await scanner.rollback(targetDir);
|
|
2189
|
-
console.log(`${colors.green}
|
|
2281
|
+
console.log(`${colors.green}[+] Rollback successful!${RESET()}`);
|
|
2190
2282
|
console.log(' All auto-fix changes have been reverted.\n');
|
|
2191
2283
|
}
|
|
2192
2284
|
catch (error) {
|
|
@@ -2220,12 +2312,6 @@ Target types:
|
|
|
2220
2312
|
a2a A2A agent messaging endpoint (/a2a/message)
|
|
2221
2313
|
local Local simulation (no API calls)
|
|
2222
2314
|
|
|
2223
|
-
Target types:
|
|
2224
|
-
api OpenAI/Anthropic chat completions (default)
|
|
2225
|
-
mcp MCP JSON-RPC server (tools/call, tools/list)
|
|
2226
|
-
a2a A2A agent messaging endpoint (/a2a/message)
|
|
2227
|
-
local Local simulation (no API calls)
|
|
2228
|
-
|
|
2229
2315
|
Examples:
|
|
2230
2316
|
$ hackmyagent attack https://api.example.com/v1/chat
|
|
2231
2317
|
$ hackmyagent attack https://api.example.com --intensity aggressive
|
|
@@ -2253,6 +2339,7 @@ Examples:
|
|
|
2253
2339
|
.option('--stop-on-success', 'Stop after first successful attack')
|
|
2254
2340
|
.option('--payload-file <path>', 'JSON file with custom attack payloads')
|
|
2255
2341
|
.option('--fail-on-vulnerable [severity]', 'Exit code 1 if vulnerabilities found (optional: critical/high/medium/low)')
|
|
2342
|
+
.option('--json', 'Output as JSON (shorthand for --format json)')
|
|
2256
2343
|
.option('-f, --format <format>', 'Output format: text, json, sarif, html', 'text')
|
|
2257
2344
|
.option('-o, --output <file>', 'Write output to file')
|
|
2258
2345
|
.option('-v, --verbose', 'Show detailed output for each payload')
|
|
@@ -2319,9 +2406,20 @@ Examples:
|
|
|
2319
2406
|
apiFormat = 'a2a';
|
|
2320
2407
|
}
|
|
2321
2408
|
// Build target
|
|
2409
|
+
// When --local is used, treat the argument as a directory path, not a URL
|
|
2410
|
+
let localPath;
|
|
2411
|
+
if (targetType === 'local' && targetUrl) {
|
|
2412
|
+
const path = require('path');
|
|
2413
|
+
const fs = require('fs');
|
|
2414
|
+
const resolved = path.resolve(targetUrl);
|
|
2415
|
+
if (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory()) {
|
|
2416
|
+
localPath = resolved;
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2322
2419
|
const target = {
|
|
2323
|
-
url: targetUrl || '',
|
|
2420
|
+
url: localPath ? '' : (targetUrl || ''),
|
|
2324
2421
|
type: targetType,
|
|
2422
|
+
localPath,
|
|
2325
2423
|
headers: Object.keys(headers).length > 0 ? headers : undefined,
|
|
2326
2424
|
apiFormat: apiFormat,
|
|
2327
2425
|
model: options.model,
|
|
@@ -2330,9 +2428,9 @@ Examples:
|
|
|
2330
2428
|
a2aSender: options.a2aSender,
|
|
2331
2429
|
a2aRecipient: options.a2aRecipient,
|
|
2332
2430
|
};
|
|
2333
|
-
// Validate format
|
|
2431
|
+
// Validate format (--json is shorthand for --format json)
|
|
2334
2432
|
const validFormats = ['text', 'json', 'sarif', 'html'];
|
|
2335
|
-
const format = options.format || 'text';
|
|
2433
|
+
const format = options.json ? 'json' : (options.format || 'text');
|
|
2336
2434
|
if (!validFormats.includes(format)) {
|
|
2337
2435
|
console.error(`Error: Invalid format '${format}'. Use: ${validFormats.join(', ')}`);
|
|
2338
2436
|
process.exit(1);
|
|
@@ -2350,9 +2448,9 @@ Examples:
|
|
|
2350
2448
|
}
|
|
2351
2449
|
// Show header for text output
|
|
2352
2450
|
if (format === 'text') {
|
|
2353
|
-
console.log(`\
|
|
2451
|
+
console.log(`\nHackMyAgent Attack Mode`);
|
|
2354
2452
|
console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`);
|
|
2355
|
-
console.log(`Target: ${target.type === 'local' ? 'Local Simulation' : targetUrl}`);
|
|
2453
|
+
console.log(`Target: ${target.type === 'local' ? (localPath ? `Local Directory: ${localPath}` : 'Local Simulation') : targetUrl}`);
|
|
2356
2454
|
console.log(`Intensity: ${intensity}`);
|
|
2357
2455
|
if (customPayloads) {
|
|
2358
2456
|
console.log(`Payloads: ${customPayloads.length} custom (from file)`);
|
|
@@ -2399,8 +2497,8 @@ Examples:
|
|
|
2399
2497
|
console.log(output);
|
|
2400
2498
|
}
|
|
2401
2499
|
}
|
|
2402
|
-
// Registry reporting:
|
|
2403
|
-
const shouldReport = options.
|
|
2500
|
+
// Registry reporting: only when explicitly requested via --version-id (CI) or --registry-report
|
|
2501
|
+
const shouldReport = targetType !== 'local' && (options.versionId || options.registryReport);
|
|
2404
2502
|
if (shouldReport) {
|
|
2405
2503
|
try {
|
|
2406
2504
|
const core = await Promise.resolve().then(() => __importStar(require('./index')));
|
|
@@ -2433,8 +2531,8 @@ Examples:
|
|
|
2433
2531
|
}
|
|
2434
2532
|
}
|
|
2435
2533
|
}
|
|
2436
|
-
catch (
|
|
2437
|
-
|
|
2534
|
+
catch (_reportErr) {
|
|
2535
|
+
// Silently ignore registry errors - they are not relevant to local scan results
|
|
2438
2536
|
}
|
|
2439
2537
|
}
|
|
2440
2538
|
// Exit with non-zero based on fail policy
|
|
@@ -2469,7 +2567,7 @@ function printAttackReport(report, verbose) {
|
|
|
2469
2567
|
if (stats.total === 0)
|
|
2470
2568
|
continue;
|
|
2471
2569
|
const catInfo = index_1.ATTACK_CATEGORIES[cat];
|
|
2472
|
-
const icon = stats.successful > 0 ? '
|
|
2570
|
+
const icon = stats.successful > 0 ? '[-]' : '[+]';
|
|
2473
2571
|
console.log(` ${icon} ${catInfo.name}: ${stats.successful}/${stats.total} successful`);
|
|
2474
2572
|
}
|
|
2475
2573
|
console.log();
|
|
@@ -2495,14 +2593,28 @@ function printAttackReport(report, verbose) {
|
|
|
2495
2593
|
if (blocked.length > 0) {
|
|
2496
2594
|
console.log(`${colors.green}Blocked Attacks (${blocked.length}):${RESET()}`);
|
|
2497
2595
|
for (const r of blocked) {
|
|
2498
|
-
console.log(`
|
|
2596
|
+
console.log(` [+] ${r.payload.id}: ${r.payload.name}`);
|
|
2499
2597
|
}
|
|
2500
2598
|
console.log();
|
|
2501
2599
|
}
|
|
2502
2600
|
}
|
|
2503
2601
|
console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
|
|
2504
|
-
|
|
2505
|
-
|
|
2602
|
+
// Inconclusive explanation (when there are inconclusive results)
|
|
2603
|
+
if (report.summary.inconclusive > 0) {
|
|
2604
|
+
console.log(`Note: ${report.summary.inconclusive} result(s) were inconclusive -- no clear success or block`);
|
|
2605
|
+
console.log(`indicators matched the simulated response.`);
|
|
2606
|
+
if (report.targetType === 'local') {
|
|
2607
|
+
console.log(`Run against a live endpoint (without --local) for active testing with real responses.`);
|
|
2608
|
+
}
|
|
2609
|
+
console.log();
|
|
2610
|
+
}
|
|
2611
|
+
if (!verbose) {
|
|
2612
|
+
console.log(`\nUse --verbose for detailed attack results.`);
|
|
2613
|
+
}
|
|
2614
|
+
if (report.intensity !== 'aggressive') {
|
|
2615
|
+
console.log(`Use --intensity aggressive for advanced attacks.`);
|
|
2616
|
+
}
|
|
2617
|
+
console.log();
|
|
2506
2618
|
}
|
|
2507
2619
|
// Generate SARIF output for attack results
|
|
2508
2620
|
function generateAttackSarif(report) {
|
|
@@ -3460,7 +3572,7 @@ Examples:
|
|
|
3460
3572
|
remediations: r.remediations,
|
|
3461
3573
|
})),
|
|
3462
3574
|
};
|
|
3463
|
-
|
|
3575
|
+
writeJsonStdout(jsonOutput);
|
|
3464
3576
|
if (pluginErrors > 0)
|
|
3465
3577
|
process.exit(2);
|
|
3466
3578
|
return;
|
|
@@ -3510,7 +3622,7 @@ Examples:
|
|
|
3510
3622
|
console.log(`Run 'hackmyagent secure' for a full hardening scan.\n`);
|
|
3511
3623
|
// Warn if scan is incomplete due to plugin errors
|
|
3512
3624
|
if (pluginErrors > 0) {
|
|
3513
|
-
console.log(`\n${colors.brightRed}[!!]
|
|
3625
|
+
console.log(`\n${colors.brightRed}[!!] Note: ${pluginErrors} plugin(s) failed -- scan results are incomplete${RESET()}`);
|
|
3514
3626
|
console.log(` Re-run with --verbose for details.\n`);
|
|
3515
3627
|
}
|
|
3516
3628
|
// Exit with non-zero if critical/high issues remain or scan is incomplete
|
|
@@ -3635,7 +3747,7 @@ program
|
|
|
3635
3747
|
.description(`Scan behavioral governance coverage
|
|
3636
3748
|
|
|
3637
3749
|
Analyzes SOUL.md (or equivalent governance file) for coverage
|
|
3638
|
-
across
|
|
3750
|
+
across 9 behavioral governance domains with 72 security controls.
|
|
3639
3751
|
|
|
3640
3752
|
Searches for governance files in priority order:
|
|
3641
3753
|
SOUL.md > system-prompt.md > SYSTEM_PROMPT.md > .cursorrules
|
|
@@ -3643,11 +3755,11 @@ Searches for governance files in priority order:
|
|
|
3643
3755
|
> instructions.md > constitution.md > agent-config.yaml
|
|
3644
3756
|
|
|
3645
3757
|
Agent profiles filter domains by agent purpose:
|
|
3646
|
-
conversational: Injection, Hardcoded, Honesty
|
|
3758
|
+
conversational: Injection, Hardcoded, Honesty, Harm Avoidance
|
|
3647
3759
|
code-assistant: + Trust, Data
|
|
3648
3760
|
tool-agent: + Capability, Oversight
|
|
3649
3761
|
autonomous: + Agentic Safety
|
|
3650
|
-
orchestrator: All
|
|
3762
|
+
orchestrator: All 9 domains
|
|
3651
3763
|
|
|
3652
3764
|
Maturity levels:
|
|
3653
3765
|
Hardened (80+), Standard (60-79), Developing (40-59),
|
|
@@ -3684,7 +3796,7 @@ Examples:
|
|
|
3684
3796
|
});
|
|
3685
3797
|
// JSON output
|
|
3686
3798
|
if (options.json) {
|
|
3687
|
-
|
|
3799
|
+
writeJsonStdout(result);
|
|
3688
3800
|
// Check fail threshold
|
|
3689
3801
|
if (options.failBelow) {
|
|
3690
3802
|
const threshold = parseInt(options.failBelow, 10);
|
|
@@ -3721,6 +3833,11 @@ Examples:
|
|
|
3721
3833
|
}
|
|
3722
3834
|
continue;
|
|
3723
3835
|
}
|
|
3836
|
+
if (domain.skippedByTier) {
|
|
3837
|
+
const label = (domain.domain + ':').padEnd(26);
|
|
3838
|
+
process.stdout.write(` ${label}${colors.reset}-- (not applicable at ${result.agentTier} tier)${colors.reset}\n`);
|
|
3839
|
+
continue;
|
|
3840
|
+
}
|
|
3724
3841
|
const pctColor = domainBar(domain.percentage);
|
|
3725
3842
|
const label = (domain.domain + ':').padEnd(26);
|
|
3726
3843
|
process.stdout.write(` ${label}${pctColor}${domain.passed}/${domain.total} (${domain.percentage}%)${colors.reset}\n`);
|
|
@@ -3823,7 +3940,7 @@ Examples:
|
|
|
3823
3940
|
dryRun: result.dryRun,
|
|
3824
3941
|
existedBefore: result.existedBefore,
|
|
3825
3942
|
};
|
|
3826
|
-
|
|
3943
|
+
writeJsonStdout(jsonResult);
|
|
3827
3944
|
return;
|
|
3828
3945
|
}
|
|
3829
3946
|
// Text output
|