hackmyagent 0.10.0 → 0.11.0
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 +111 -257
- package/dist/arp/index.d.ts +5 -1
- package/dist/arp/index.d.ts.map +1 -1
- package/dist/arp/index.js +38 -1
- package/dist/arp/index.js.map +1 -1
- package/dist/arp/monitors/skill-capability-monitor.d.ts +119 -0
- package/dist/arp/monitors/skill-capability-monitor.d.ts.map +1 -0
- package/dist/arp/monitors/skill-capability-monitor.js +258 -0
- package/dist/arp/monitors/skill-capability-monitor.js.map +1 -0
- package/dist/arp/telemetry/forwarder.d.ts +62 -0
- package/dist/arp/telemetry/forwarder.d.ts.map +1 -0
- package/dist/arp/telemetry/forwarder.js +106 -0
- package/dist/arp/telemetry/forwarder.js.map +1 -0
- package/dist/arp/telemetry/gtin.d.ts +87 -0
- package/dist/arp/telemetry/gtin.d.ts.map +1 -0
- package/dist/arp/telemetry/gtin.js +239 -0
- package/dist/arp/telemetry/gtin.js.map +1 -0
- package/dist/arp/telemetry/index.d.ts +6 -0
- package/dist/arp/telemetry/index.d.ts.map +1 -0
- package/dist/arp/telemetry/index.js +17 -0
- package/dist/arp/telemetry/index.js.map +1 -0
- package/dist/arp/types.d.ts +10 -0
- package/dist/arp/types.d.ts.map +1 -1
- package/dist/attack/index.d.ts +1 -1
- package/dist/attack/index.d.ts.map +1 -1
- package/dist/attack/index.js +5 -1
- package/dist/attack/index.js.map +1 -1
- package/dist/attack/payloads/context-window.d.ts +7 -0
- package/dist/attack/payloads/context-window.d.ts.map +1 -0
- package/dist/attack/payloads/context-window.js +110 -0
- package/dist/attack/payloads/context-window.js.map +1 -0
- package/dist/attack/payloads/index.d.ts +5 -1
- package/dist/attack/payloads/index.d.ts.map +1 -1
- package/dist/attack/payloads/index.js +17 -1
- package/dist/attack/payloads/index.js.map +1 -1
- package/dist/attack/payloads/memory-weaponization.d.ts +7 -0
- package/dist/attack/payloads/memory-weaponization.d.ts.map +1 -0
- package/dist/attack/payloads/memory-weaponization.js +110 -0
- package/dist/attack/payloads/memory-weaponization.js.map +1 -0
- package/dist/attack/payloads/supply-chain.d.ts +7 -0
- package/dist/attack/payloads/supply-chain.d.ts.map +1 -0
- package/dist/attack/payloads/supply-chain.js +110 -0
- package/dist/attack/payloads/supply-chain.js.map +1 -0
- package/dist/attack/payloads/tool-shadow.d.ts +8 -0
- package/dist/attack/payloads/tool-shadow.d.ts.map +1 -0
- package/dist/attack/payloads/tool-shadow.js +209 -0
- package/dist/attack/payloads/tool-shadow.js.map +1 -0
- package/dist/attack/scanner.d.ts.map +1 -1
- package/dist/attack/scanner.js +4 -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 +20 -0
- package/dist/attack/types.js.map +1 -1
- package/dist/checker/index.d.ts +2 -0
- package/dist/checker/index.d.ts.map +1 -1
- package/dist/checker/index.js +8 -1
- package/dist/checker/index.js.map +1 -1
- package/dist/checker/skill-dependency-graph.d.ts +55 -0
- package/dist/checker/skill-dependency-graph.d.ts.map +1 -0
- package/dist/checker/skill-dependency-graph.js +288 -0
- package/dist/checker/skill-dependency-graph.js.map +1 -0
- package/dist/cli.js +481 -66
- package/dist/cli.js.map +1 -1
- package/dist/hardening/index.d.ts +5 -0
- package/dist/hardening/index.d.ts.map +1 -1
- package/dist/hardening/index.js +11 -1
- package/dist/hardening/index.js.map +1 -1
- package/dist/hardening/scanner.d.ts +40 -0
- package/dist/hardening/scanner.d.ts.map +1 -1
- package/dist/hardening/scanner.js +988 -11
- package/dist/hardening/scanner.js.map +1 -1
- package/dist/hardening/security-check.d.ts +2 -0
- package/dist/hardening/security-check.d.ts.map +1 -1
- package/dist/hardening/skill-capability-validator.d.ts +31 -0
- package/dist/hardening/skill-capability-validator.d.ts.map +1 -0
- package/dist/hardening/skill-capability-validator.js +237 -0
- package/dist/hardening/skill-capability-validator.js.map +1 -0
- package/dist/hardening/skill-context.d.ts +22 -0
- package/dist/hardening/skill-context.d.ts.map +1 -0
- package/dist/hardening/skill-context.js +127 -0
- package/dist/hardening/skill-context.js.map +1 -0
- package/dist/hardening/taxonomy.d.ts +17 -0
- package/dist/hardening/taxonomy.d.ts.map +1 -0
- package/dist/hardening/taxonomy.js +152 -0
- package/dist/hardening/taxonomy.js.map +1 -0
- package/dist/index.d.ts +12 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +36 -3
- package/dist/index.js.map +1 -1
- package/dist/plugins/credvault.js +2 -2
- package/dist/plugins/credvault.js.map +1 -1
- package/dist/plugins/secretless.d.ts +15 -0
- package/dist/plugins/secretless.d.ts.map +1 -0
- package/dist/plugins/secretless.js +199 -0
- package/dist/plugins/secretless.js.map +1 -0
- package/dist/plugins/signcrypt.js +2 -2
- package/dist/plugins/signcrypt.js.map +1 -1
- package/dist/plugins/skillguard.js +2 -2
- package/dist/plugins/skillguard.js.map +1 -1
- package/dist/registry/client.d.ts +1 -1
- package/dist/registry/client.d.ts.map +1 -1
- package/dist/registry/client.js +4 -1
- package/dist/registry/client.js.map +1 -1
- package/dist/registry/publish.d.ts.map +1 -1
- package/dist/registry/publish.js +7 -1
- package/dist/registry/publish.js.map +1 -1
- package/dist/resolve-mcp.d.ts +21 -0
- package/dist/resolve-mcp.d.ts.map +1 -0
- package/dist/resolve-mcp.js +42 -0
- package/dist/resolve-mcp.js.map +1 -0
- package/dist/scanner/external-scanner.d.ts.map +1 -1
- package/dist/scanner/external-scanner.js +48 -14
- package/dist/scanner/external-scanner.js.map +1 -1
- package/dist/scanner/types.d.ts +1 -0
- package/dist/scanner/types.d.ts.map +1 -1
- package/dist/soul/scanner.d.ts.map +1 -1
- package/dist/soul/scanner.js +2 -1
- package/dist/soul/scanner.js.map +1 -1
- package/dist/telemetry/contribute.d.ts +60 -0
- package/dist/telemetry/contribute.d.ts.map +1 -0
- package/dist/telemetry/contribute.js +169 -0
- package/dist/telemetry/contribute.js.map +1 -0
- package/dist/telemetry/index.d.ts +6 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/index.js +18 -0
- package/dist/telemetry/index.js.map +1 -0
- package/dist/telemetry/opt-in.d.ts +46 -0
- package/dist/telemetry/opt-in.d.ts.map +1 -0
- package/dist/telemetry/opt-in.js +220 -0
- package/dist/telemetry/opt-in.js.map +1 -0
- package/package.json +9 -3
package/dist/cli.js
CHANGED
|
@@ -40,6 +40,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
40
40
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
41
|
const commander_1 = require("commander");
|
|
42
42
|
const index_1 = require("./index");
|
|
43
|
+
const resolve_mcp_1 = require("./resolve-mcp");
|
|
43
44
|
const program = new commander_1.Command();
|
|
44
45
|
// Write JSON to stdout synchronously with retry for pipe backpressure.
|
|
45
46
|
// process.stdout.write() is async and gets truncated when process.exit()
|
|
@@ -80,7 +81,7 @@ function resolveCliPrefix() {
|
|
|
80
81
|
}
|
|
81
82
|
const CLI_PREFIX = resolveCliPrefix();
|
|
82
83
|
// Check for NO_COLOR env or non-TTY to disable colors by default
|
|
83
|
-
const noColorEnv = process.env.NO_COLOR !== undefined || process.stdout.isTTY
|
|
84
|
+
const noColorEnv = process.env.NO_COLOR !== undefined || !process.stdout.isTTY;
|
|
84
85
|
// Color codes - will be cleared if --no-color is passed
|
|
85
86
|
let colors = {
|
|
86
87
|
green: '\x1b[32m',
|
|
@@ -102,7 +103,7 @@ program
|
|
|
102
103
|
.name('hackmyagent')
|
|
103
104
|
.description(`Find it. Break it. Fix it.
|
|
104
105
|
|
|
105
|
-
The hacker's toolkit for AI agents. 147+ security checks,
|
|
106
|
+
The hacker's toolkit for AI agents. 147+ security checks, 115 attack
|
|
106
107
|
payloads, auto-fix with rollback, and OASB benchmark compliance.
|
|
107
108
|
|
|
108
109
|
Documentation: https://hackmyagent.com/docs
|
|
@@ -115,11 +116,11 @@ Updates (v${index_1.VERSION}):
|
|
|
115
116
|
|
|
116
117
|
Examples:
|
|
117
118
|
$ hackmyagent secure Find vulnerabilities (147+ checks)
|
|
118
|
-
$ hackmyagent attack --local Break it with
|
|
119
|
+
$ hackmyagent attack --local Break it with 115 attack payloads
|
|
119
120
|
$ hackmyagent secure --fix Fix issues automatically
|
|
120
121
|
$ hackmyagent fix-all Run all security plugins
|
|
121
122
|
$ hackmyagent scan example.com Scan external infrastructure`)
|
|
122
|
-
.version(index_1.VERSION, '-
|
|
123
|
+
.version(index_1.VERSION, '-v, --version', 'Output the version number')
|
|
123
124
|
.option('--no-color', 'Disable colored output (also respects NO_COLOR env)')
|
|
124
125
|
.hook('preAction', (thisCommand) => {
|
|
125
126
|
const opts = thisCommand.opts();
|
|
@@ -572,21 +573,15 @@ function generateHtmlReport(result) {
|
|
|
572
573
|
.sort((a, b) => a.compliance - b.compliance)[0];
|
|
573
574
|
// Security grade based on compliance
|
|
574
575
|
const getGrade = (pct) => {
|
|
575
|
-
if (pct >= 95)
|
|
576
|
-
return { letter: 'A+', color: '#22c55e' };
|
|
577
576
|
if (pct >= 90)
|
|
578
|
-
return { letter: '
|
|
579
|
-
if (pct >= 85)
|
|
580
|
-
return { letter: 'B+', color: '#84cc16' };
|
|
577
|
+
return { letter: 'strong', color: '#22c55e' };
|
|
581
578
|
if (pct >= 80)
|
|
582
|
-
return { letter: '
|
|
583
|
-
if (pct >= 75)
|
|
584
|
-
return { letter: 'C+', color: '#eab308' };
|
|
579
|
+
return { letter: 'good', color: '#84cc16' };
|
|
585
580
|
if (pct >= 70)
|
|
586
|
-
return { letter: '
|
|
581
|
+
return { letter: 'moderate', color: '#eab308' };
|
|
587
582
|
if (pct >= 60)
|
|
588
|
-
return { letter: '
|
|
589
|
-
return { letter: '
|
|
583
|
+
return { letter: 'improving', color: '#f97316' };
|
|
584
|
+
return { letter: 'needs-attention', color: '#ef4444' };
|
|
590
585
|
};
|
|
591
586
|
const grade = getGrade(result.compliance);
|
|
592
587
|
// Generate executive summary items
|
|
@@ -766,15 +761,15 @@ function generateHtmlReport(result) {
|
|
|
766
761
|
border-bottom: 1px solid var(--border);
|
|
767
762
|
}
|
|
768
763
|
.score-grade {
|
|
769
|
-
width:
|
|
770
|
-
height:
|
|
764
|
+
width: 72px;
|
|
765
|
+
height: 72px;
|
|
771
766
|
border-radius: 12px;
|
|
772
767
|
border: 2px solid;
|
|
773
768
|
display: flex;
|
|
774
769
|
align-items: center;
|
|
775
770
|
justify-content: center;
|
|
776
771
|
}
|
|
777
|
-
.grade-letter { font-size:
|
|
772
|
+
.grade-letter { font-size: 0.65rem; font-weight: 800; text-transform: uppercase; text-align: center; line-height: 1.2; }
|
|
778
773
|
.score-main { flex: 1; }
|
|
779
774
|
.score-pct { font-size: 2rem; font-weight: 700; color: var(--text-primary); line-height: 1; }
|
|
780
775
|
.score-label { font-size: 0.75rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.05em; margin-top: 0.25rem; }
|
|
@@ -1233,7 +1228,7 @@ function generateScanHtmlReport(scanResult, targetDir) {
|
|
|
1233
1228
|
const fixedFindings = scanResult.findings.filter(f => f.fixed);
|
|
1234
1229
|
const score = scanResult.score;
|
|
1235
1230
|
const scoreColor = score >= 90 ? '#22c55e' : score >= 70 ? '#eab308' : score >= 50 ? '#f97316' : '#ef4444';
|
|
1236
|
-
const gradeLetters = score >= 90 ? '
|
|
1231
|
+
const gradeLetters = score >= 90 ? 'strong' : score >= 80 ? 'good' : score >= 70 ? 'moderate' : score >= 60 ? 'improving' : 'needs-attention';
|
|
1237
1232
|
const severityOrder = ['critical', 'high', 'medium', 'low'];
|
|
1238
1233
|
const severityColors = {
|
|
1239
1234
|
critical: '#ef4444', high: '#f97316', medium: '#eab308', low: '#22c55e',
|
|
@@ -1274,7 +1269,7 @@ function generateScanHtmlReport(scanResult, targetDir) {
|
|
|
1274
1269
|
h1 { font-size: 1.5rem; margin-bottom: 0.5rem; }
|
|
1275
1270
|
.meta { color: var(--text-secondary); margin-bottom: 2rem; }
|
|
1276
1271
|
.score-card { display: flex; align-items: center; gap: 2rem; background: var(--bg-secondary); border: 1px solid var(--border); border-radius: 12px; padding: 1.5rem; margin-bottom: 2rem; }
|
|
1277
|
-
.grade { font-size:
|
|
1272
|
+
.grade { font-size: 0.75rem; font-weight: 700; width: 100px; height: 100px; display: flex; align-items: center; justify-content: center; border-radius: 50%; border: 3px solid ${scoreColor}; text-transform: uppercase; text-align: center; line-height: 1.2; padding: 0.5rem; }
|
|
1278
1273
|
.score-details { flex: 1; }
|
|
1279
1274
|
.score-num { font-size: 2rem; font-weight: 700; }
|
|
1280
1275
|
.stats { display: flex; gap: 2rem; margin-top: 0.5rem; }
|
|
@@ -1537,6 +1532,95 @@ function resolvePackageVersion(targetDir) {
|
|
|
1537
1532
|
catch { /* ignore */ }
|
|
1538
1533
|
return null;
|
|
1539
1534
|
}
|
|
1535
|
+
/**
|
|
1536
|
+
* Handle community contribution after a scan completes.
|
|
1537
|
+
*
|
|
1538
|
+
* Determines whether to contribute based on:
|
|
1539
|
+
* 1. --contribute / --no-contribute CLI flags (highest priority)
|
|
1540
|
+
* 2. ~/.opena2a/config.json contribute.enabled setting
|
|
1541
|
+
* 3. Interactive opt-in prompt (first scan or scan #10)
|
|
1542
|
+
*
|
|
1543
|
+
* If contributing, builds an anonymized payload and submits it
|
|
1544
|
+
* asynchronously (non-blocking). Failures are logged as warnings.
|
|
1545
|
+
*/
|
|
1546
|
+
async function handleContribution(contributeFlag, targetDir, findings, registryUrl, format) {
|
|
1547
|
+
try {
|
|
1548
|
+
const { isContributeEnabled, shouldPromptContribute, showContributePrompt, incrementScanCount, buildContributionPayloadFromDir, submitContribution, } = await Promise.resolve().then(() => __importStar(require('./telemetry')));
|
|
1549
|
+
// Always increment scan count
|
|
1550
|
+
incrementScanCount();
|
|
1551
|
+
// Determine whether to contribute
|
|
1552
|
+
let shouldContribute;
|
|
1553
|
+
if (contributeFlag === true) {
|
|
1554
|
+
// --contribute flag: always contribute this scan
|
|
1555
|
+
shouldContribute = true;
|
|
1556
|
+
}
|
|
1557
|
+
else if (contributeFlag === false) {
|
|
1558
|
+
// --no-contribute flag: skip this scan
|
|
1559
|
+
shouldContribute = false;
|
|
1560
|
+
}
|
|
1561
|
+
else {
|
|
1562
|
+
// Check config
|
|
1563
|
+
const configSetting = isContributeEnabled();
|
|
1564
|
+
if (configSetting === true) {
|
|
1565
|
+
shouldContribute = true;
|
|
1566
|
+
}
|
|
1567
|
+
else if (configSetting === false) {
|
|
1568
|
+
shouldContribute = false;
|
|
1569
|
+
}
|
|
1570
|
+
else {
|
|
1571
|
+
// Not configured -- prompt after 3 scans (interactive TTY only)
|
|
1572
|
+
if (format === 'text' && process.stdout.isTTY && shouldPromptContribute()) {
|
|
1573
|
+
shouldContribute = await showContributePrompt();
|
|
1574
|
+
}
|
|
1575
|
+
else {
|
|
1576
|
+
shouldContribute = false;
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
if (!shouldContribute)
|
|
1581
|
+
return;
|
|
1582
|
+
// Build and submit contribution (non-blocking)
|
|
1583
|
+
const packageName = resolvePackageName(targetDir);
|
|
1584
|
+
if (!packageName)
|
|
1585
|
+
return;
|
|
1586
|
+
const payload = buildContributionPayloadFromDir(packageName, targetDir, findings);
|
|
1587
|
+
const result = await submitContribution(payload, registryUrl);
|
|
1588
|
+
if (result.success && format === 'text') {
|
|
1589
|
+
console.log('Contributed anonymized scan summary to OpenA2A Registry (--no-contribute to opt out)');
|
|
1590
|
+
}
|
|
1591
|
+
// Failures are silently ignored -- contribution is best-effort
|
|
1592
|
+
}
|
|
1593
|
+
catch {
|
|
1594
|
+
// Non-fatal: contribution failure must never crash the scan
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
/**
|
|
1598
|
+
* Handle community contribution for scan-soul results.
|
|
1599
|
+
*
|
|
1600
|
+
* Converts SoulScanResult controls into SecurityFinding-like objects
|
|
1601
|
+
* for the contribution module, then delegates to handleContribution.
|
|
1602
|
+
*/
|
|
1603
|
+
async function handleSoulContribution(contributeFlag, targetDir, result, registryUrl, format) {
|
|
1604
|
+
// Convert soul controls into SecurityFinding-shaped objects
|
|
1605
|
+
const findings = [];
|
|
1606
|
+
for (const domain of result.domains) {
|
|
1607
|
+
if (domain.skippedByProfile || domain.skippedByTier)
|
|
1608
|
+
continue;
|
|
1609
|
+
for (const ctrl of domain.controls) {
|
|
1610
|
+
findings.push({
|
|
1611
|
+
checkId: ctrl.id,
|
|
1612
|
+
name: ctrl.name,
|
|
1613
|
+
description: '',
|
|
1614
|
+
category: domain.domain,
|
|
1615
|
+
severity: 'medium',
|
|
1616
|
+
passed: ctrl.passed,
|
|
1617
|
+
message: '',
|
|
1618
|
+
fixable: false,
|
|
1619
|
+
});
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
await handleContribution(contributeFlag, targetDir, findings, registryUrl, format);
|
|
1623
|
+
}
|
|
1540
1624
|
program
|
|
1541
1625
|
.command('secure')
|
|
1542
1626
|
.description(`Scan and harden your agent setup
|
|
@@ -1592,8 +1676,10 @@ Examples:
|
|
|
1592
1676
|
.option('--registry-report', 'Post results to OpenA2A Registry')
|
|
1593
1677
|
.option('--no-registry', 'Skip auto-publishing results to OpenA2A Registry')
|
|
1594
1678
|
.option('--version-id <id>', 'Registry version ID to report against')
|
|
1595
|
-
.option('--registry-url <url>', 'Registry URL (default: REGISTRY_URL env)', process.env.REGISTRY_URL || 'https://
|
|
1679
|
+
.option('--registry-url <url>', 'Registry URL (default: REGISTRY_URL env)', process.env.REGISTRY_URL || 'https://api.oa2a.org')
|
|
1596
1680
|
.option('--registry-key <key>', 'Registry API key (default: REGISTRY_API_KEY env)')
|
|
1681
|
+
.option('--contribute', 'Share anonymized scan findings with OpenA2A Registry (overrides config)')
|
|
1682
|
+
.option('--no-contribute', 'Do not share findings for this scan (overrides config)')
|
|
1597
1683
|
.action(async (directory, options) => {
|
|
1598
1684
|
try {
|
|
1599
1685
|
const targetDir = directory.startsWith('/') ? directory : process.cwd() + '/' + directory;
|
|
@@ -1768,7 +1854,7 @@ Examples:
|
|
|
1768
1854
|
if (options.publish && options.registry !== false) {
|
|
1769
1855
|
try {
|
|
1770
1856
|
const { publishScanResults } = await Promise.resolve().then(() => __importStar(require('./registry/publish')));
|
|
1771
|
-
const registryUrl = options.registryUrl || process.env.REGISTRY_URL || 'https://
|
|
1857
|
+
const registryUrl = options.registryUrl || process.env.REGISTRY_URL || 'https://api.oa2a.org';
|
|
1772
1858
|
const packageName = resolvePackageName(targetDir);
|
|
1773
1859
|
if (packageName) {
|
|
1774
1860
|
const publishData = {
|
|
@@ -1797,6 +1883,8 @@ Examples:
|
|
|
1797
1883
|
else {
|
|
1798
1884
|
writeJsonStdout(jsonOutput);
|
|
1799
1885
|
}
|
|
1886
|
+
// Community contribution (non-blocking, runs in JSON mode too)
|
|
1887
|
+
await handleContribution(options.contribute, targetDir, result.findings, options.registryUrl, format);
|
|
1800
1888
|
const critHigh = result.findings.filter((f) => !f.passed && !f.fixed && (f.severity === 'critical' || f.severity === 'high'));
|
|
1801
1889
|
if (critHigh.length > 0)
|
|
1802
1890
|
process.exitCode = 1;
|
|
@@ -1936,7 +2024,7 @@ Examples:
|
|
|
1936
2024
|
if (options.versionId || options.registryReport) {
|
|
1937
2025
|
try {
|
|
1938
2026
|
const core = await Promise.resolve().then(() => __importStar(require('./index')));
|
|
1939
|
-
const registryUrl = options.registryUrl || process.env.REGISTRY_URL || 'https://
|
|
2027
|
+
const registryUrl = options.registryUrl || process.env.REGISTRY_URL || 'https://api.oa2a.org';
|
|
1940
2028
|
if (options.versionId) {
|
|
1941
2029
|
// Authenticated path: existing behavior (version-id + API key)
|
|
1942
2030
|
const registryKey = options.registryKey || process.env.REGISTRY_API_KEY;
|
|
@@ -1983,7 +2071,7 @@ Examples:
|
|
|
1983
2071
|
else if (options.publish) {
|
|
1984
2072
|
try {
|
|
1985
2073
|
const { publishScanResults, formatPublishOutput } = await Promise.resolve().then(() => __importStar(require('./registry/publish')));
|
|
1986
|
-
const registryUrl = options.registryUrl || process.env.REGISTRY_URL || 'https://
|
|
2074
|
+
const registryUrl = options.registryUrl || process.env.REGISTRY_URL || 'https://api.oa2a.org';
|
|
1987
2075
|
const packageName = resolvePackageName(targetDir);
|
|
1988
2076
|
if (!packageName) {
|
|
1989
2077
|
console.error('\nCould not determine package name. Publish requires a package.json with a name field.');
|
|
@@ -2015,6 +2103,8 @@ Examples:
|
|
|
2015
2103
|
console.error('Scan results are still available locally.');
|
|
2016
2104
|
}
|
|
2017
2105
|
}
|
|
2106
|
+
// Community contribution: share anonymized findings with OpenA2A Registry
|
|
2107
|
+
await handleContribution(options.contribute, targetDir, result.findings, options.registryUrl, format);
|
|
2018
2108
|
// Star prompt (interactive TTY only, text format only)
|
|
2019
2109
|
if (process.stdout.isTTY) {
|
|
2020
2110
|
console.log(`${colors.cyan}Helpful?${RESET()} Star the project: https://github.com/opena2a-org/opena2a\n`);
|
|
@@ -2270,7 +2360,7 @@ Detects externally exposed:
|
|
|
2270
2360
|
• API keys in responses
|
|
2271
2361
|
• Debug/admin interfaces
|
|
2272
2362
|
|
|
2273
|
-
Scoring:
|
|
2363
|
+
Scoring: strong (90-100), good (80-89), moderate (70-79), improving (60-69), needs-attention (<60)
|
|
2274
2364
|
Exit code 1 if critical/high issues found.
|
|
2275
2365
|
|
|
2276
2366
|
Examples:
|
|
@@ -2299,13 +2389,11 @@ Examples:
|
|
|
2299
2389
|
return;
|
|
2300
2390
|
}
|
|
2301
2391
|
// Print header
|
|
2302
|
-
const gradeColor = result.grade === '
|
|
2392
|
+
const gradeColor = result.grade === 'strong' || result.grade === 'good'
|
|
2303
2393
|
? colors.green
|
|
2304
|
-
: result.grade === '
|
|
2305
|
-
? colors.
|
|
2306
|
-
:
|
|
2307
|
-
? colors.yellow
|
|
2308
|
-
: colors.red;
|
|
2394
|
+
: result.grade === 'moderate'
|
|
2395
|
+
? colors.yellow
|
|
2396
|
+
: colors.red;
|
|
2309
2397
|
console.log(`Target: ${result.target}`);
|
|
2310
2398
|
console.log(`Score: ${gradeColor}${result.score}/100 (${result.grade})${RESET()}`);
|
|
2311
2399
|
console.log(`Open Ports: ${result.openPorts.length > 0 ? result.openPorts.join(', ') : 'None detected'}`);
|
|
@@ -2435,7 +2523,7 @@ Examples:
|
|
|
2435
2523
|
.option('--registry-report', 'Post results to OpenA2A Registry')
|
|
2436
2524
|
.option('--no-registry', 'Skip auto-publishing results to OpenA2A Registry')
|
|
2437
2525
|
.option('--version-id <id>', 'Registry version ID to report against')
|
|
2438
|
-
.option('--registry-url <url>', 'Registry URL (default: REGISTRY_URL env)', process.env.REGISTRY_URL || 'https://
|
|
2526
|
+
.option('--registry-url <url>', 'Registry URL (default: REGISTRY_URL env)', process.env.REGISTRY_URL || 'https://api.oa2a.org')
|
|
2439
2527
|
.option('--registry-key <key>', 'Registry API key (default: REGISTRY_API_KEY env)')
|
|
2440
2528
|
.action(async (targetUrl, options) => {
|
|
2441
2529
|
try {
|
|
@@ -2591,7 +2679,7 @@ Examples:
|
|
|
2591
2679
|
if (shouldReport) {
|
|
2592
2680
|
try {
|
|
2593
2681
|
const core = await Promise.resolve().then(() => __importStar(require('./index')));
|
|
2594
|
-
const registryUrl = options.registryUrl || process.env.REGISTRY_URL || 'https://
|
|
2682
|
+
const registryUrl = options.registryUrl || process.env.REGISTRY_URL || 'https://api.oa2a.org';
|
|
2595
2683
|
if (options.versionId) {
|
|
2596
2684
|
// Authenticated path: existing behavior (version-id + API key)
|
|
2597
2685
|
const registryKey = options.registryKey || process.env.REGISTRY_API_KEY;
|
|
@@ -2638,7 +2726,7 @@ Examples:
|
|
|
2638
2726
|
else if (options.publish && targetType !== 'local') {
|
|
2639
2727
|
try {
|
|
2640
2728
|
const { publishScanResults, formatPublishOutput } = await Promise.resolve().then(() => __importStar(require('./registry/publish')));
|
|
2641
|
-
const regUrl = options.registryUrl || process.env.REGISTRY_URL || 'https://
|
|
2729
|
+
const regUrl = options.registryUrl || process.env.REGISTRY_URL || 'https://api.oa2a.org';
|
|
2642
2730
|
const packageName = target.url || targetUrl || 'unknown';
|
|
2643
2731
|
if (format === 'text') {
|
|
2644
2732
|
console.log('\nPublishing results to registry...\n');
|
|
@@ -2792,14 +2880,14 @@ function generateAttackHtmlReport(report) {
|
|
|
2792
2880
|
// Risk grade based on score
|
|
2793
2881
|
const getGrade = (score) => {
|
|
2794
2882
|
if (score <= 10)
|
|
2795
|
-
return { letter: '
|
|
2883
|
+
return { letter: 'strong', color: '#22c55e' };
|
|
2796
2884
|
if (score <= 25)
|
|
2797
|
-
return { letter: '
|
|
2885
|
+
return { letter: 'good', color: '#84cc16' };
|
|
2798
2886
|
if (score <= 50)
|
|
2799
|
-
return { letter: '
|
|
2887
|
+
return { letter: 'moderate', color: '#eab308' };
|
|
2800
2888
|
if (score <= 70)
|
|
2801
|
-
return { letter: '
|
|
2802
|
-
return { letter: '
|
|
2889
|
+
return { letter: 'improving', color: '#f97316' };
|
|
2890
|
+
return { letter: 'needs-attention', color: '#ef4444' };
|
|
2803
2891
|
};
|
|
2804
2892
|
const grade = getGrade(report.riskScore);
|
|
2805
2893
|
const ratingColor = {
|
|
@@ -2834,6 +2922,10 @@ function generateAttackHtmlReport(report) {
|
|
|
2834
2922
|
'context-manipulation': 'CM',
|
|
2835
2923
|
'mcp-exploitation': 'MCP',
|
|
2836
2924
|
'a2a-attack': 'A2A',
|
|
2925
|
+
'memory-weaponization': 'MEM',
|
|
2926
|
+
'context-window': 'CTX',
|
|
2927
|
+
'supply-chain': 'SUP',
|
|
2928
|
+
'tool-shadow': 'SHADOW',
|
|
2837
2929
|
};
|
|
2838
2930
|
// Donut chart for attack results
|
|
2839
2931
|
const donutRadius = 60;
|
|
@@ -3069,15 +3161,15 @@ function generateAttackHtmlReport(report) {
|
|
|
3069
3161
|
border-bottom: 1px solid var(--border);
|
|
3070
3162
|
}
|
|
3071
3163
|
.score-grade {
|
|
3072
|
-
width:
|
|
3073
|
-
height:
|
|
3164
|
+
width: 72px;
|
|
3165
|
+
height: 72px;
|
|
3074
3166
|
border-radius: 12px;
|
|
3075
3167
|
border: 2px solid;
|
|
3076
3168
|
display: flex;
|
|
3077
3169
|
align-items: center;
|
|
3078
3170
|
justify-content: center;
|
|
3079
3171
|
}
|
|
3080
|
-
.grade-letter { font-size:
|
|
3172
|
+
.grade-letter { font-size: 0.65rem; font-weight: 800; text-transform: uppercase; text-align: center; line-height: 1.2; }
|
|
3081
3173
|
.score-main { flex: 1; }
|
|
3082
3174
|
.score-pct { font-size: 2rem; font-weight: 700; color: var(--text-primary); line-height: 1; }
|
|
3083
3175
|
.score-label { font-size: 0.75rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.05em; margin-top: 0.25rem; }
|
|
@@ -3524,6 +3616,7 @@ function generateAttackHtmlReport(report) {
|
|
|
3524
3616
|
}
|
|
3525
3617
|
// --- fix-all: Run all OpenClaw plugins to scan and remediate ---
|
|
3526
3618
|
const credvault_1 = require("./plugins/credvault");
|
|
3619
|
+
const secretless_1 = require("./plugins/secretless");
|
|
3527
3620
|
const signcrypt_1 = require("./plugins/signcrypt");
|
|
3528
3621
|
const skillguard_1 = require("./plugins/skillguard");
|
|
3529
3622
|
const aim_core_1 = require("@opena2a/aim-core");
|
|
@@ -3539,27 +3632,35 @@ program
|
|
|
3539
3632
|
.description(`Run all OpenA2A security plugins to scan and auto-fix agent issues
|
|
3540
3633
|
|
|
3541
3634
|
Runs the full plugin suite in order:
|
|
3542
|
-
1.
|
|
3543
|
-
2.
|
|
3544
|
-
3.
|
|
3635
|
+
1. Credential Protection — find hardcoded secrets, replace with env vars
|
|
3636
|
+
2. AI Visibility Protection — block .env from AI tools, encrypt MCP keys
|
|
3637
|
+
3. File Signing — sign skills and heartbeats with Ed25519
|
|
3638
|
+
4. Skill Safety Scanner — detect dangerous patterns, pin hashes
|
|
3545
3639
|
|
|
3546
3640
|
Each plugin scans for findings, then auto-fixes what it can.
|
|
3547
3641
|
Dangerous patterns (reverse shells, exfil, etc.) require manual review.
|
|
3548
3642
|
|
|
3643
|
+
Step 2 requires secretless-ai (npm install -g secretless-ai). If not
|
|
3644
|
+
installed, the plugin reports this and continues with the remaining steps.
|
|
3645
|
+
|
|
3646
|
+
Use --with-aim to create a cryptographic identity for your agent.
|
|
3647
|
+
This enables automatic file signing, audit logging, and trust scoring
|
|
3648
|
+
so you don't need to manage keys or track files manually.
|
|
3649
|
+
|
|
3549
3650
|
Exit code 1 if critical/high issues remain after fixing.
|
|
3550
3651
|
|
|
3551
3652
|
Examples:
|
|
3552
3653
|
$ hackmyagent fix-all Scan and fix current directory
|
|
3553
3654
|
$ hackmyagent fix-all ./my-agent Scan specific directory
|
|
3655
|
+
$ hackmyagent fix-all --with-aim Create identity + sign + audit (recommended)
|
|
3554
3656
|
$ hackmyagent fix-all --dry-run Preview fixes without applying
|
|
3555
3657
|
$ hackmyagent fix-all --scan-only Scan without fixing
|
|
3556
|
-
$ hackmyagent fix-all --json JSON output for CI
|
|
3557
|
-
$ hackmyagent fix-all --with-aim Enable AIM identity and audit`)
|
|
3658
|
+
$ hackmyagent fix-all --json JSON output for CI`)
|
|
3558
3659
|
.argument('[directory]', 'Agent directory to scan (default: current directory)', '')
|
|
3559
3660
|
.option('--dry-run', 'Preview fixes without applying them')
|
|
3560
3661
|
.option('--scan-only', 'Only scan, do not fix')
|
|
3561
3662
|
.option('--json', 'Output as JSON (for scripting/CI)')
|
|
3562
|
-
.option('--with-aim', '
|
|
3663
|
+
.option('--with-aim', 'Create agent identity for automatic signing, audit logging, and trust scoring')
|
|
3563
3664
|
.option('-v, --verbose', 'Show all findings including passed plugins')
|
|
3564
3665
|
.action(async (directory, options) => {
|
|
3565
3666
|
try {
|
|
@@ -3607,12 +3708,15 @@ Examples:
|
|
|
3607
3708
|
});
|
|
3608
3709
|
}
|
|
3609
3710
|
// Create and initialize plugins in execution order
|
|
3610
|
-
//
|
|
3611
|
-
//
|
|
3711
|
+
// 1. CredVault finds hardcoded secrets, replaces with ${VAR}
|
|
3712
|
+
// 2. Secretless blocks .env from AI visibility (completes the credential lifecycle)
|
|
3713
|
+
// 3. SignCrypt signs skill and heartbeat files
|
|
3714
|
+
// 4. SkillGuard pins hashes last so they reflect the final file state
|
|
3612
3715
|
const pluginFactories = [
|
|
3613
|
-
{ name: '
|
|
3614
|
-
{ name: '
|
|
3615
|
-
{ name: '
|
|
3716
|
+
{ name: 'Credential Protection', create: credvault_1.createPlugin },
|
|
3717
|
+
{ name: 'AI Visibility Protection', create: secretless_1.createPlugin },
|
|
3718
|
+
{ name: 'File Signing', create: signcrypt_1.createPlugin },
|
|
3719
|
+
{ name: 'Skill Safety Scanner', create: skillguard_1.createPlugin },
|
|
3616
3720
|
];
|
|
3617
3721
|
const plugins = [];
|
|
3618
3722
|
for (const factory of pluginFactories) {
|
|
@@ -3818,16 +3922,6 @@ Examples:
|
|
|
3818
3922
|
process.exit(1);
|
|
3819
3923
|
}
|
|
3820
3924
|
});
|
|
3821
|
-
// Grade display colors
|
|
3822
|
-
function gradeColor(grade) {
|
|
3823
|
-
switch (grade) {
|
|
3824
|
-
case 'A': return colors.green;
|
|
3825
|
-
case 'B': return colors.green;
|
|
3826
|
-
case 'C': return colors.yellow;
|
|
3827
|
-
case 'D': return colors.red;
|
|
3828
|
-
case 'F': return colors.brightRed;
|
|
3829
|
-
}
|
|
3830
|
-
}
|
|
3831
3925
|
function levelColor(level) {
|
|
3832
3926
|
switch (level) {
|
|
3833
3927
|
case 'hardened': return colors.green;
|
|
@@ -3906,7 +4000,9 @@ Examples:
|
|
|
3906
4000
|
.option('--fail-below <score>', 'Exit 1 if score below threshold (0-100)')
|
|
3907
4001
|
.option('--deep', 'Enable LLM semantic analysis for ambiguous controls (requires claude CLI or ANTHROPIC_API_KEY)')
|
|
3908
4002
|
.option('--publish', 'Push scan results to the OpenA2A Registry')
|
|
3909
|
-
.option('--registry-url <url>', 'Registry URL (default: REGISTRY_URL env)', process.env.REGISTRY_URL || 'https://
|
|
4003
|
+
.option('--registry-url <url>', 'Registry URL (default: REGISTRY_URL env)', process.env.REGISTRY_URL || 'https://api.oa2a.org')
|
|
4004
|
+
.option('--contribute', 'Share anonymized scan findings with OpenA2A Registry (overrides config)')
|
|
4005
|
+
.option('--no-contribute', 'Do not share findings for this scan (overrides config)')
|
|
3910
4006
|
.action(async (directory, options) => {
|
|
3911
4007
|
try {
|
|
3912
4008
|
const targetDir = directory.startsWith('/') ? directory : process.cwd() + '/' + directory;
|
|
@@ -3929,7 +4025,7 @@ Examples:
|
|
|
3929
4025
|
if (options.publish) {
|
|
3930
4026
|
try {
|
|
3931
4027
|
const { publishScanResults } = await Promise.resolve().then(() => __importStar(require('./registry/publish')));
|
|
3932
|
-
const registryUrl = options.registryUrl || process.env.REGISTRY_URL || 'https://
|
|
4028
|
+
const registryUrl = options.registryUrl || process.env.REGISTRY_URL || 'https://api.oa2a.org';
|
|
3933
4029
|
const packageName = resolvePackageName(targetDir);
|
|
3934
4030
|
if (packageName) {
|
|
3935
4031
|
const publishData = {
|
|
@@ -4043,7 +4139,7 @@ Examples:
|
|
|
4043
4139
|
if (options.publish) {
|
|
4044
4140
|
try {
|
|
4045
4141
|
const { publishScanResults, formatPublishOutput } = await Promise.resolve().then(() => __importStar(require('./registry/publish')));
|
|
4046
|
-
const registryUrl = options.registryUrl || process.env.REGISTRY_URL || 'https://
|
|
4142
|
+
const registryUrl = options.registryUrl || process.env.REGISTRY_URL || 'https://api.oa2a.org';
|
|
4047
4143
|
const packageName = resolvePackageName(targetDir);
|
|
4048
4144
|
if (!packageName) {
|
|
4049
4145
|
process.stderr.write('Could not determine package name. Publish requires a package.json with a name field.\n');
|
|
@@ -4070,6 +4166,9 @@ Examples:
|
|
|
4070
4166
|
process.stderr.write('Scan results are still available locally.\n');
|
|
4071
4167
|
}
|
|
4072
4168
|
}
|
|
4169
|
+
// Community contribution: share anonymized findings with OpenA2A Registry
|
|
4170
|
+
const soulFormat = options.json ? 'json' : 'text';
|
|
4171
|
+
await handleSoulContribution(options.contribute, targetDir, result, options.registryUrl, soulFormat);
|
|
4073
4172
|
// Check fail threshold
|
|
4074
4173
|
if (options.failBelow) {
|
|
4075
4174
|
const threshold = parseInt(options.failBelow, 10);
|
|
@@ -4175,5 +4274,321 @@ Examples:
|
|
|
4175
4274
|
process.exit(1);
|
|
4176
4275
|
}
|
|
4177
4276
|
});
|
|
4277
|
+
// ---------------------------------------------------------------------------
|
|
4278
|
+
// trust — Trust verification via OpenA2A Registry (powered by ai-trust)
|
|
4279
|
+
// ---------------------------------------------------------------------------
|
|
4280
|
+
const REGISTRY_DEFAULT_URL = 'https://api.oa2a.org';
|
|
4281
|
+
async function trustCheck(name, registryUrl, type) {
|
|
4282
|
+
const params = new URLSearchParams({ name, includeProfile: 'true', includeDeps: 'true' });
|
|
4283
|
+
if (type)
|
|
4284
|
+
params.set('type', type);
|
|
4285
|
+
const url = `${registryUrl}/api/v1/trust/query?${params.toString()}`;
|
|
4286
|
+
const res = await fetch(url, {
|
|
4287
|
+
method: 'GET',
|
|
4288
|
+
headers: { 'Accept': 'application/json', 'User-Agent': `hackmyagent/${index_1.VERSION}` },
|
|
4289
|
+
});
|
|
4290
|
+
if (!res.ok) {
|
|
4291
|
+
if (res.status === 404) {
|
|
4292
|
+
throw new Error(`Package "${name}" not found in the OpenA2A Registry.`);
|
|
4293
|
+
}
|
|
4294
|
+
const body = await res.text();
|
|
4295
|
+
throw new Error(`Registry API returned ${res.status}: ${body}`);
|
|
4296
|
+
}
|
|
4297
|
+
const data = (await res.json());
|
|
4298
|
+
data.found = !!data.packageId;
|
|
4299
|
+
return data;
|
|
4300
|
+
}
|
|
4301
|
+
async function trustBatch(packages, registryUrl) {
|
|
4302
|
+
const url = `${registryUrl}/api/v1/trust/batch`;
|
|
4303
|
+
const res = await fetch(url, {
|
|
4304
|
+
method: 'POST',
|
|
4305
|
+
headers: {
|
|
4306
|
+
'Content-Type': 'application/json',
|
|
4307
|
+
'Accept': 'application/json',
|
|
4308
|
+
'User-Agent': `hackmyagent/${index_1.VERSION}`,
|
|
4309
|
+
},
|
|
4310
|
+
body: JSON.stringify({ packages }),
|
|
4311
|
+
});
|
|
4312
|
+
if (!res.ok) {
|
|
4313
|
+
if (res.status === 404) {
|
|
4314
|
+
throw new Error('Registry batch endpoint not found. The registry may be unavailable.');
|
|
4315
|
+
}
|
|
4316
|
+
const body = await res.text();
|
|
4317
|
+
throw new Error(`Registry API returned ${res.status}: ${body}`);
|
|
4318
|
+
}
|
|
4319
|
+
const raw = (await res.json());
|
|
4320
|
+
const NULL_UUID = '00000000-0000-0000-0000-000000000000';
|
|
4321
|
+
for (const r of raw.results) {
|
|
4322
|
+
r.found = !!r.packageId && r.packageId !== NULL_UUID;
|
|
4323
|
+
}
|
|
4324
|
+
const found = raw.results.filter((r) => r.found).length;
|
|
4325
|
+
return {
|
|
4326
|
+
results: raw.results,
|
|
4327
|
+
meta: { total: raw.total, found, notFound: raw.total - found },
|
|
4328
|
+
};
|
|
4329
|
+
}
|
|
4330
|
+
function trustLevelLabel(level) {
|
|
4331
|
+
switch (level) {
|
|
4332
|
+
case 0: return 'Blocked';
|
|
4333
|
+
case 1: return 'Warning';
|
|
4334
|
+
case 2: return 'Listed';
|
|
4335
|
+
case 3: return 'Scanned';
|
|
4336
|
+
case 4: return 'Verified';
|
|
4337
|
+
default: return `Unknown (${level})`;
|
|
4338
|
+
}
|
|
4339
|
+
}
|
|
4340
|
+
function trustLevelColor(level) {
|
|
4341
|
+
if (level >= 3)
|
|
4342
|
+
return colors.green;
|
|
4343
|
+
if (level >= 1)
|
|
4344
|
+
return colors.yellow;
|
|
4345
|
+
return colors.red;
|
|
4346
|
+
}
|
|
4347
|
+
function trustVerdictColor(verdict) {
|
|
4348
|
+
switch (verdict) {
|
|
4349
|
+
case 'safe': return colors.green;
|
|
4350
|
+
case 'warning': return colors.yellow;
|
|
4351
|
+
case 'blocked': return colors.red;
|
|
4352
|
+
default: return colors.dim;
|
|
4353
|
+
}
|
|
4354
|
+
}
|
|
4355
|
+
function formatTrustCheck(answer) {
|
|
4356
|
+
if (!answer.found) {
|
|
4357
|
+
return [
|
|
4358
|
+
'',
|
|
4359
|
+
` ${answer.name}`,
|
|
4360
|
+
` ${colors.dim}Type: ${answer.packageType || 'unknown'}${colors.reset}`,
|
|
4361
|
+
` ${colors.dim}Status: Not found in registry${colors.reset}`,
|
|
4362
|
+
'',
|
|
4363
|
+
].join('\n');
|
|
4364
|
+
}
|
|
4365
|
+
const vc = trustVerdictColor(answer.verdict);
|
|
4366
|
+
const tc = trustLevelColor(answer.trustLevel);
|
|
4367
|
+
const lines = [
|
|
4368
|
+
'',
|
|
4369
|
+
` ${answer.name}`,
|
|
4370
|
+
` Type: ${answer.packageType || 'unknown'}`,
|
|
4371
|
+
` Verdict: ${vc}${answer.verdict.toUpperCase()}${colors.reset}`,
|
|
4372
|
+
` Trust Level: ${tc}${trustLevelLabel(answer.trustLevel)}${colors.reset} (${answer.trustLevel}/4)`,
|
|
4373
|
+
` Trust Score: ${Math.round(answer.trustScore * 100)}/100`,
|
|
4374
|
+
` Scan Status: ${answer.scanStatus || 'unknown'}`,
|
|
4375
|
+
];
|
|
4376
|
+
if (answer.dependencies && answer.dependencies.totalDeps > 0) {
|
|
4377
|
+
const deps = answer.dependencies;
|
|
4378
|
+
lines.push('');
|
|
4379
|
+
lines.push(' Dependencies');
|
|
4380
|
+
lines.push(` Total: ${deps.totalDeps}`);
|
|
4381
|
+
lines.push(` Vulnerable: ${deps.vulnerableDeps > 0 ? colors.red + deps.vulnerableDeps + colors.reset : colors.green + '0' + colors.reset}`);
|
|
4382
|
+
lines.push(` Min Trust: ${deps.minTrustLevel}/4`);
|
|
4383
|
+
}
|
|
4384
|
+
lines.push('');
|
|
4385
|
+
return lines.join('\n');
|
|
4386
|
+
}
|
|
4387
|
+
function formatTrustBatch(response, minTrust) {
|
|
4388
|
+
const lines = [];
|
|
4389
|
+
lines.push('');
|
|
4390
|
+
lines.push(` Trust Audit: ${response.meta.total} packages queried, ${response.meta.found} found, ${response.meta.notFound} not found`);
|
|
4391
|
+
lines.push('');
|
|
4392
|
+
const nameW = 40, typeW = 14, verdictW = 10, levelW = 12, scoreW = 8, scanW = 10;
|
|
4393
|
+
lines.push(' ' +
|
|
4394
|
+
'PACKAGE'.padEnd(nameW) +
|
|
4395
|
+
'TYPE'.padEnd(typeW) +
|
|
4396
|
+
'VERDICT'.padEnd(verdictW) +
|
|
4397
|
+
'TRUST'.padEnd(levelW) +
|
|
4398
|
+
'SCORE'.padEnd(scoreW) +
|
|
4399
|
+
'SCAN'.padEnd(scanW));
|
|
4400
|
+
lines.push(' ' + '-'.repeat(nameW + typeW + verdictW + levelW + scoreW + scanW));
|
|
4401
|
+
for (const result of response.results) {
|
|
4402
|
+
const vc = trustVerdictColor(result.verdict);
|
|
4403
|
+
const tc = trustLevelColor(result.trustLevel);
|
|
4404
|
+
const name = result.name.length > nameW - 2
|
|
4405
|
+
? result.name.substring(0, nameW - 5) + '...'
|
|
4406
|
+
: result.name;
|
|
4407
|
+
lines.push(' ' +
|
|
4408
|
+
name.padEnd(nameW) +
|
|
4409
|
+
(result.packageType || '-').padEnd(typeW) +
|
|
4410
|
+
vc + result.verdict.toUpperCase().padEnd(verdictW) + colors.reset +
|
|
4411
|
+
tc + trustLevelLabel(result.trustLevel).padEnd(levelW) + colors.reset +
|
|
4412
|
+
(result.found ? `${Math.round(result.trustScore * 100)}/100` : '-').padEnd(scoreW) +
|
|
4413
|
+
(result.scanStatus || '-').padEnd(scanW));
|
|
4414
|
+
}
|
|
4415
|
+
const belowThreshold = response.results.filter((r) => r.found && r.trustLevel < minTrust);
|
|
4416
|
+
const notFound = response.results.filter((r) => !r.found);
|
|
4417
|
+
lines.push('');
|
|
4418
|
+
if (belowThreshold.length > 0) {
|
|
4419
|
+
lines.push(` ${colors.yellow}[!] ${belowThreshold.length} package(s) below minimum trust level ${minTrust}:${colors.reset}`);
|
|
4420
|
+
for (const pkg of belowThreshold) {
|
|
4421
|
+
lines.push(` ${colors.yellow} - ${pkg.name} (trust level ${pkg.trustLevel}, verdict: ${pkg.verdict})${colors.reset}`);
|
|
4422
|
+
}
|
|
4423
|
+
}
|
|
4424
|
+
if (notFound.length > 0) {
|
|
4425
|
+
lines.push(` ${colors.dim}[?] ${notFound.length} package(s) not found in registry:${colors.reset}`);
|
|
4426
|
+
for (const pkg of notFound) {
|
|
4427
|
+
lines.push(` ${colors.dim} - ${pkg.name}${colors.reset}`);
|
|
4428
|
+
}
|
|
4429
|
+
}
|
|
4430
|
+
if (belowThreshold.length === 0 && notFound.length === 0) {
|
|
4431
|
+
lines.push(` ${colors.green}All ${response.meta.found} packages meet minimum trust level ${minTrust}.${colors.reset}`);
|
|
4432
|
+
}
|
|
4433
|
+
lines.push('');
|
|
4434
|
+
return lines.join('\n');
|
|
4435
|
+
}
|
|
4436
|
+
async function parseDepsFile(filePath) {
|
|
4437
|
+
const fs = require('fs');
|
|
4438
|
+
const path = require('path');
|
|
4439
|
+
let content;
|
|
4440
|
+
try {
|
|
4441
|
+
content = fs.readFileSync(filePath, 'utf-8');
|
|
4442
|
+
}
|
|
4443
|
+
catch (err) {
|
|
4444
|
+
if (err && typeof err === 'object' && 'code' in err && err.code === 'ENOENT') {
|
|
4445
|
+
throw new Error(`File not found: ${filePath}`);
|
|
4446
|
+
}
|
|
4447
|
+
throw err;
|
|
4448
|
+
}
|
|
4449
|
+
const fileName = path.basename(filePath);
|
|
4450
|
+
if (fileName === 'package.json') {
|
|
4451
|
+
const pkg = JSON.parse(content);
|
|
4452
|
+
const packages = [];
|
|
4453
|
+
const seen = new Set();
|
|
4454
|
+
for (const deps of [pkg.dependencies, pkg.devDependencies]) {
|
|
4455
|
+
if (!deps)
|
|
4456
|
+
continue;
|
|
4457
|
+
for (const name of Object.keys(deps)) {
|
|
4458
|
+
if (!seen.has(name)) {
|
|
4459
|
+
seen.add(name);
|
|
4460
|
+
packages.push({ name });
|
|
4461
|
+
}
|
|
4462
|
+
}
|
|
4463
|
+
}
|
|
4464
|
+
return packages;
|
|
4465
|
+
}
|
|
4466
|
+
if (fileName === 'requirements.txt') {
|
|
4467
|
+
const packages = [];
|
|
4468
|
+
const seen = new Set();
|
|
4469
|
+
for (const rawLine of content.split('\n')) {
|
|
4470
|
+
const line = rawLine.trim();
|
|
4471
|
+
if (!line || line.startsWith('#') || line.startsWith('-'))
|
|
4472
|
+
continue;
|
|
4473
|
+
const match = line.match(/^([a-zA-Z0-9_-]+(?:\[[a-zA-Z0-9_,-]+\])?)/);
|
|
4474
|
+
if (match) {
|
|
4475
|
+
const name = match[1].replace(/\[.*\]/, '');
|
|
4476
|
+
if (!seen.has(name)) {
|
|
4477
|
+
seen.add(name);
|
|
4478
|
+
packages.push({ name });
|
|
4479
|
+
}
|
|
4480
|
+
}
|
|
4481
|
+
}
|
|
4482
|
+
return packages;
|
|
4483
|
+
}
|
|
4484
|
+
throw new Error(`Unsupported dependency file: ${fileName}. Supported: package.json, requirements.txt`);
|
|
4485
|
+
}
|
|
4486
|
+
program
|
|
4487
|
+
.command('trust')
|
|
4488
|
+
.description(`Check trust level for AI packages before installing
|
|
4489
|
+
|
|
4490
|
+
Query the OpenA2A Registry to verify trust scores, vulnerability status,
|
|
4491
|
+
and dependency risk for MCP servers, A2A agents, and AI tools.
|
|
4492
|
+
|
|
4493
|
+
Modes:
|
|
4494
|
+
trust <package> Single package lookup
|
|
4495
|
+
trust --audit <file> Audit a dependency file (package.json, requirements.txt)
|
|
4496
|
+
trust --batch pkg1 pkg2 Batch lookup for multiple packages
|
|
4497
|
+
|
|
4498
|
+
Examples:
|
|
4499
|
+
$ ${CLI_PREFIX} trust @anthropic/claude-mcp
|
|
4500
|
+
$ ${CLI_PREFIX} trust server-filesystem (resolves to @modelcontextprotocol/server-filesystem)
|
|
4501
|
+
$ ${CLI_PREFIX} trust mcp-server-fetch (resolves to @modelcontextprotocol/server-fetch)
|
|
4502
|
+
$ ${CLI_PREFIX} trust my-mcp-server --type mcp_server
|
|
4503
|
+
$ ${CLI_PREFIX} trust --audit package.json
|
|
4504
|
+
$ ${CLI_PREFIX} trust --audit requirements.txt --min-trust 3
|
|
4505
|
+
$ ${CLI_PREFIX} trust --batch langchain openai anthropic`)
|
|
4506
|
+
.argument('[package]', 'Package name to look up')
|
|
4507
|
+
.option('-t, --type <type>', 'Package type (mcp_server, a2a_agent, ai_tool, etc.)')
|
|
4508
|
+
.option('--audit <file>', 'Audit a dependency file (package.json or requirements.txt)')
|
|
4509
|
+
.option('--batch <names...>', 'Batch trust lookup for multiple packages')
|
|
4510
|
+
.option('--min-trust <level>', 'Minimum trust level threshold (0-4)', '3')
|
|
4511
|
+
.option('--registry-url <url>', 'Registry base URL', REGISTRY_DEFAULT_URL)
|
|
4512
|
+
.option('--json', 'Output as JSON')
|
|
4513
|
+
.action(async (packageName, opts) => {
|
|
4514
|
+
const registryUrl = opts.registryUrl.replace(/\/+$/, '');
|
|
4515
|
+
const minTrust = parseInt(opts.minTrust, 10);
|
|
4516
|
+
if (isNaN(minTrust) || minTrust < 0 || minTrust > 4) {
|
|
4517
|
+
process.stderr.write('Error: --min-trust must be a number between 0 and 4\n');
|
|
4518
|
+
process.exit(1);
|
|
4519
|
+
}
|
|
4520
|
+
try {
|
|
4521
|
+
// Mode: audit a dependency file
|
|
4522
|
+
if (opts.audit) {
|
|
4523
|
+
const rawPackages = await parseDepsFile(opts.audit);
|
|
4524
|
+
const packages = rawPackages.map((pkg) => ({
|
|
4525
|
+
...pkg,
|
|
4526
|
+
name: (0, resolve_mcp_1.resolveAndLogMcpShorthand)(pkg.name),
|
|
4527
|
+
}));
|
|
4528
|
+
if (packages.length === 0) {
|
|
4529
|
+
process.stdout.write('No dependencies found in the specified file.\n');
|
|
4530
|
+
return;
|
|
4531
|
+
}
|
|
4532
|
+
if (packages.length > 100) {
|
|
4533
|
+
process.stderr.write(`Error: Too many dependencies (${packages.length}). Maximum 100 per request.\n`);
|
|
4534
|
+
process.exit(1);
|
|
4535
|
+
}
|
|
4536
|
+
const response = await trustBatch(packages, registryUrl);
|
|
4537
|
+
if (opts.json) {
|
|
4538
|
+
writeJsonStdout(response);
|
|
4539
|
+
}
|
|
4540
|
+
else {
|
|
4541
|
+
process.stdout.write(formatTrustBatch(response, minTrust));
|
|
4542
|
+
}
|
|
4543
|
+
const belowThreshold = response.results.some((r) => r.found && r.trustLevel < minTrust);
|
|
4544
|
+
if (belowThreshold)
|
|
4545
|
+
process.exitCode = 1;
|
|
4546
|
+
return;
|
|
4547
|
+
}
|
|
4548
|
+
// Mode: batch lookup
|
|
4549
|
+
if (opts.batch && opts.batch.length > 0) {
|
|
4550
|
+
if (opts.batch.length > 100) {
|
|
4551
|
+
process.stderr.write(`Error: Too many packages (${opts.batch.length}). Maximum 100 per request.\n`);
|
|
4552
|
+
process.exit(1);
|
|
4553
|
+
}
|
|
4554
|
+
const packages = opts.batch.map((name) => ({
|
|
4555
|
+
name: (0, resolve_mcp_1.resolveAndLogMcpShorthand)(name),
|
|
4556
|
+
...(opts.type ? { type: opts.type } : {}),
|
|
4557
|
+
}));
|
|
4558
|
+
const response = await trustBatch(packages, registryUrl);
|
|
4559
|
+
if (opts.json) {
|
|
4560
|
+
writeJsonStdout(response);
|
|
4561
|
+
}
|
|
4562
|
+
else {
|
|
4563
|
+
process.stdout.write(formatTrustBatch(response, minTrust));
|
|
4564
|
+
}
|
|
4565
|
+
const belowThreshold = response.results.some((r) => r.found && r.trustLevel < minTrust);
|
|
4566
|
+
if (belowThreshold)
|
|
4567
|
+
process.exitCode = 1;
|
|
4568
|
+
return;
|
|
4569
|
+
}
|
|
4570
|
+
// Mode: single package lookup
|
|
4571
|
+
if (!packageName) {
|
|
4572
|
+
process.stderr.write(`Error: Provide a package name or use --audit/--batch.\n`);
|
|
4573
|
+
process.stderr.write(`Usage: ${CLI_PREFIX} trust <package>\n`);
|
|
4574
|
+
process.exit(1);
|
|
4575
|
+
}
|
|
4576
|
+
packageName = (0, resolve_mcp_1.resolveAndLogMcpShorthand)(packageName);
|
|
4577
|
+
const result = await trustCheck(packageName, registryUrl, opts.type);
|
|
4578
|
+
if (opts.json) {
|
|
4579
|
+
writeJsonStdout(result);
|
|
4580
|
+
}
|
|
4581
|
+
else {
|
|
4582
|
+
process.stdout.write(formatTrustCheck(result));
|
|
4583
|
+
}
|
|
4584
|
+
if (result.found && (result.verdict === 'blocked' || result.verdict === 'warning')) {
|
|
4585
|
+
process.exitCode = 1;
|
|
4586
|
+
}
|
|
4587
|
+
}
|
|
4588
|
+
catch (error) {
|
|
4589
|
+
process.stderr.write(`Error: ${error instanceof Error ? error.message : 'Unknown error'}\n`);
|
|
4590
|
+
process.exit(1);
|
|
4591
|
+
}
|
|
4592
|
+
});
|
|
4178
4593
|
program.parse();
|
|
4179
4594
|
//# sourceMappingURL=cli.js.map
|