hackmyagent 0.11.1 → 0.11.2
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/cli.js +68 -16
- package/dist/cli.js.map +1 -1
- package/dist/hardening/scanner.d.ts.map +1 -1
- package/dist/hardening/scanner.js +146 -12
- package/dist/hardening/scanner.js.map +1 -1
- package/dist/hardening/taxonomy.d.ts.map +1 -1
- package/dist/hardening/taxonomy.js +1 -0
- package/dist/hardening/taxonomy.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -120,9 +120,15 @@ Examples:
|
|
|
120
120
|
$ hackmyagent secure --fix Fix issues automatically
|
|
121
121
|
$ hackmyagent fix-all Run all security plugins
|
|
122
122
|
$ hackmyagent scan example.com Scan external infrastructure`)
|
|
123
|
-
.version(index_1.VERSION, '-v, --version', 'Output the version number')
|
|
124
|
-
.option('--no-color', 'Disable colored output (also respects NO_COLOR env)')
|
|
125
|
-
|
|
123
|
+
.version('hackmyagent ' + index_1.VERSION, '-v, --version', 'Output the version number')
|
|
124
|
+
.option('--no-color', 'Disable colored output (also respects NO_COLOR env)');
|
|
125
|
+
program.addHelpText('beforeAll', `
|
|
126
|
+
Quick start:
|
|
127
|
+
$ hackmyagent secure Scan current directory (147+ checks)
|
|
128
|
+
$ hackmyagent fix-all --with-aim Auto-fix + create agent identity
|
|
129
|
+
$ hackmyagent attack Red-team your agent
|
|
130
|
+
`);
|
|
131
|
+
program.hook('preAction', (thisCommand) => {
|
|
126
132
|
const opts = thisCommand.opts();
|
|
127
133
|
if (opts.color === false) {
|
|
128
134
|
colors = { green: '', yellow: '', red: '', brightRed: '', cyan: '', dim: '', reset: '' };
|
|
@@ -1532,22 +1538,43 @@ function resolvePackageVersion(targetDir) {
|
|
|
1532
1538
|
catch { /* ignore */ }
|
|
1533
1539
|
return null;
|
|
1534
1540
|
}
|
|
1541
|
+
/**
|
|
1542
|
+
* Build an anonymized contribution summary from scan findings.
|
|
1543
|
+
* Contains only aggregate data -- no file paths, no code, no project names.
|
|
1544
|
+
*/
|
|
1545
|
+
function buildContributionSummary(findings, score, projectType) {
|
|
1546
|
+
const failed = findings.filter((f) => !f.passed);
|
|
1547
|
+
const categories = [...new Set(failed.map((f) => f.category).filter(Boolean))].sort();
|
|
1548
|
+
const counts = { critical: 0, high: 0, medium: 0, low: 0 };
|
|
1549
|
+
for (const f of failed) {
|
|
1550
|
+
if (f.severity in counts)
|
|
1551
|
+
counts[f.severity]++;
|
|
1552
|
+
}
|
|
1553
|
+
return {
|
|
1554
|
+
packageType: projectType,
|
|
1555
|
+
findingCategories: categories,
|
|
1556
|
+
findingCounts: counts,
|
|
1557
|
+
score,
|
|
1558
|
+
toolVersion: index_1.VERSION,
|
|
1559
|
+
};
|
|
1560
|
+
}
|
|
1535
1561
|
/**
|
|
1536
1562
|
* Handle community contribution after a scan completes.
|
|
1537
1563
|
*
|
|
1538
1564
|
* Determines whether to contribute based on:
|
|
1539
1565
|
* 1. --contribute / --no-contribute CLI flags (highest priority)
|
|
1540
|
-
* 2.
|
|
1541
|
-
* 3.
|
|
1566
|
+
* 2. --ci mode: skip prompt, only contribute if --contribute is explicit
|
|
1567
|
+
* 3. ~/.opena2a/config.json contribute.enabled setting
|
|
1568
|
+
* 4. Interactive opt-in prompt (first scan or scan #10)
|
|
1542
1569
|
*
|
|
1543
|
-
*
|
|
1544
|
-
* asynchronously (non-blocking). Failures are logged as warnings.
|
|
1570
|
+
* Shows what will be sent before sending (transparency).
|
|
1545
1571
|
*/
|
|
1546
|
-
async function handleContribution(contributeFlag, targetDir, findings, registryUrl, format) {
|
|
1572
|
+
async function handleContribution(contributeFlag, targetDir, findings, registryUrl, format, contributionOptions) {
|
|
1547
1573
|
try {
|
|
1548
1574
|
const { isContributeEnabled, shouldPromptContribute, showContributePrompt, incrementScanCount, buildContributionPayloadFromDir, submitContribution, } = await Promise.resolve().then(() => __importStar(require('./telemetry')));
|
|
1549
1575
|
// Always increment scan count
|
|
1550
1576
|
incrementScanCount();
|
|
1577
|
+
const isCi = contributionOptions?.ci === true;
|
|
1551
1578
|
// Determine whether to contribute
|
|
1552
1579
|
let shouldContribute;
|
|
1553
1580
|
if (contributeFlag === true) {
|
|
@@ -1558,6 +1585,10 @@ async function handleContribution(contributeFlag, targetDir, findings, registryU
|
|
|
1558
1585
|
// --no-contribute flag: skip this scan
|
|
1559
1586
|
shouldContribute = false;
|
|
1560
1587
|
}
|
|
1588
|
+
else if (isCi) {
|
|
1589
|
+
// CI mode: never prompt, only contribute if --contribute was explicit
|
|
1590
|
+
shouldContribute = false;
|
|
1591
|
+
}
|
|
1561
1592
|
else {
|
|
1562
1593
|
// Check config
|
|
1563
1594
|
const configSetting = isContributeEnabled();
|
|
@@ -1568,7 +1599,7 @@ async function handleContribution(contributeFlag, targetDir, findings, registryU
|
|
|
1568
1599
|
shouldContribute = false;
|
|
1569
1600
|
}
|
|
1570
1601
|
else {
|
|
1571
|
-
// Not configured -- prompt
|
|
1602
|
+
// Not configured -- prompt (interactive TTY only)
|
|
1572
1603
|
if (format === 'text' && process.stdout.isTTY && shouldPromptContribute()) {
|
|
1573
1604
|
shouldContribute = await showContributePrompt();
|
|
1574
1605
|
}
|
|
@@ -1579,6 +1610,21 @@ async function handleContribution(contributeFlag, targetDir, findings, registryU
|
|
|
1579
1610
|
}
|
|
1580
1611
|
if (!shouldContribute)
|
|
1581
1612
|
return;
|
|
1613
|
+
// Build the anonymized contribution summary for transparency display
|
|
1614
|
+
const score = contributionOptions?.score ?? 0;
|
|
1615
|
+
const projectType = contributionOptions?.projectType ?? 'unknown';
|
|
1616
|
+
const summary = buildContributionSummary(findings, score, projectType);
|
|
1617
|
+
// Transparency: show what will be sent before sending
|
|
1618
|
+
if (format === 'text' && !isCi) {
|
|
1619
|
+
console.log('');
|
|
1620
|
+
console.log('Contributing anonymized scan summary to OpenA2A Registry:');
|
|
1621
|
+
console.log(` Package type: ${summary.packageType}`);
|
|
1622
|
+
console.log(` Categories: ${summary.findingCategories.join(', ') || '(none)'}`);
|
|
1623
|
+
console.log(` Findings: ${summary.findingCounts.critical} critical, ${summary.findingCounts.high} high, ${summary.findingCounts.medium} medium, ${summary.findingCounts.low} low`);
|
|
1624
|
+
console.log(` Score: ${summary.score}`);
|
|
1625
|
+
console.log(` Tool version: ${summary.toolVersion}`);
|
|
1626
|
+
console.log(' (No file paths, code, or project names are sent)');
|
|
1627
|
+
}
|
|
1582
1628
|
// Build and submit contribution (non-blocking)
|
|
1583
1629
|
const packageName = resolvePackageName(targetDir);
|
|
1584
1630
|
if (!packageName)
|
|
@@ -1600,7 +1646,7 @@ async function handleContribution(contributeFlag, targetDir, findings, registryU
|
|
|
1600
1646
|
* Converts SoulScanResult controls into SecurityFinding-like objects
|
|
1601
1647
|
* for the contribution module, then delegates to handleContribution.
|
|
1602
1648
|
*/
|
|
1603
|
-
async function handleSoulContribution(contributeFlag, targetDir, result, registryUrl, format) {
|
|
1649
|
+
async function handleSoulContribution(contributeFlag, targetDir, result, registryUrl, format, contributionOptions) {
|
|
1604
1650
|
// Convert soul controls into SecurityFinding-shaped objects
|
|
1605
1651
|
const findings = [];
|
|
1606
1652
|
for (const domain of result.domains) {
|
|
@@ -1619,7 +1665,11 @@ async function handleSoulContribution(contributeFlag, targetDir, result, registr
|
|
|
1619
1665
|
});
|
|
1620
1666
|
}
|
|
1621
1667
|
}
|
|
1622
|
-
await handleContribution(contributeFlag, targetDir, findings, registryUrl, format
|
|
1668
|
+
await handleContribution(contributeFlag, targetDir, findings, registryUrl, format, {
|
|
1669
|
+
ci: contributionOptions?.ci,
|
|
1670
|
+
score: result.score,
|
|
1671
|
+
projectType: 'soul',
|
|
1672
|
+
});
|
|
1623
1673
|
}
|
|
1624
1674
|
program
|
|
1625
1675
|
.command('secure')
|
|
@@ -1680,6 +1730,7 @@ Examples:
|
|
|
1680
1730
|
.option('--registry-key <key>', 'Registry API key (default: REGISTRY_API_KEY env)')
|
|
1681
1731
|
.option('--contribute', 'Share anonymized scan findings with OpenA2A Registry (overrides config)')
|
|
1682
1732
|
.option('--no-contribute', 'Do not share findings for this scan (overrides config)')
|
|
1733
|
+
.option('--ci', 'CI mode: suppress interactive prompts, machine-friendly output')
|
|
1683
1734
|
.action(async (directory, options) => {
|
|
1684
1735
|
try {
|
|
1685
1736
|
const targetDir = directory.startsWith('/') ? directory : process.cwd() + '/' + directory;
|
|
@@ -1884,7 +1935,7 @@ Examples:
|
|
|
1884
1935
|
writeJsonStdout(jsonOutput);
|
|
1885
1936
|
}
|
|
1886
1937
|
// Community contribution (non-blocking, runs in JSON mode too)
|
|
1887
|
-
await handleContribution(options.contribute, targetDir, result.findings, options.registryUrl, format);
|
|
1938
|
+
await handleContribution(options.contribute, targetDir, result.findings, options.registryUrl, format, { ci: options.ci, score: result.score, projectType: result.projectType });
|
|
1888
1939
|
const critHigh = result.findings.filter((f) => !f.passed && !f.fixed && (f.severity === 'critical' || f.severity === 'high'));
|
|
1889
1940
|
if (critHigh.length > 0)
|
|
1890
1941
|
process.exitCode = 1;
|
|
@@ -1935,9 +1986,9 @@ Examples:
|
|
|
1935
1986
|
let scoreExtra = '';
|
|
1936
1987
|
if (result.semanticAnalysis) {
|
|
1937
1988
|
const sa = result.semanticAnalysis;
|
|
1938
|
-
scoreExtra = ` |
|
|
1989
|
+
scoreExtra = ` | ${sa.layer2Findings} deep analysis finding${sa.layer2Findings === 1 ? '' : 's'}`;
|
|
1939
1990
|
if (sa.layer3Findings > 0) {
|
|
1940
|
-
scoreExtra += `, ${sa.layer3Findings}
|
|
1991
|
+
scoreExtra += `, ${sa.layer3Findings} AI-assisted`;
|
|
1941
1992
|
if (sa.llmCost !== undefined)
|
|
1942
1993
|
scoreExtra += ` ($${sa.llmCost.toFixed(3)})`;
|
|
1943
1994
|
if (sa.cachedResults)
|
|
@@ -2104,7 +2155,7 @@ Examples:
|
|
|
2104
2155
|
}
|
|
2105
2156
|
}
|
|
2106
2157
|
// Community contribution: share anonymized findings with OpenA2A Registry
|
|
2107
|
-
await handleContribution(options.contribute, targetDir, result.findings, options.registryUrl, format);
|
|
2158
|
+
await handleContribution(options.contribute, targetDir, result.findings, options.registryUrl, format, { ci: options.ci, score: result.score, projectType: result.projectType });
|
|
2108
2159
|
// Star prompt (interactive TTY only, text format only)
|
|
2109
2160
|
if (process.stdout.isTTY) {
|
|
2110
2161
|
console.log(`${colors.cyan}Helpful?${RESET()} Star the project: https://github.com/opena2a-org/opena2a\n`);
|
|
@@ -4003,6 +4054,7 @@ Examples:
|
|
|
4003
4054
|
.option('--registry-url <url>', 'Registry URL (default: REGISTRY_URL env)', process.env.REGISTRY_URL || 'https://api.oa2a.org')
|
|
4004
4055
|
.option('--contribute', 'Share anonymized scan findings with OpenA2A Registry (overrides config)')
|
|
4005
4056
|
.option('--no-contribute', 'Do not share findings for this scan (overrides config)')
|
|
4057
|
+
.option('--ci', 'CI mode: suppress interactive prompts, machine-friendly output')
|
|
4006
4058
|
.action(async (directory, options) => {
|
|
4007
4059
|
try {
|
|
4008
4060
|
const targetDir = directory.startsWith('/') ? directory : process.cwd() + '/' + directory;
|
|
@@ -4168,7 +4220,7 @@ Examples:
|
|
|
4168
4220
|
}
|
|
4169
4221
|
// Community contribution: share anonymized findings with OpenA2A Registry
|
|
4170
4222
|
const soulFormat = options.json ? 'json' : 'text';
|
|
4171
|
-
await handleSoulContribution(options.contribute, targetDir, result, options.registryUrl, soulFormat);
|
|
4223
|
+
await handleSoulContribution(options.contribute, targetDir, result, options.registryUrl, soulFormat, { ci: options.ci });
|
|
4172
4224
|
// Check fail threshold
|
|
4173
4225
|
if (options.failBelow) {
|
|
4174
4226
|
const threshold = parseInt(options.failBelow, 10);
|