hackmyagent 0.11.2 → 0.11.4
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 +51 -61
- package/dist/cli.js.map +1 -1
- package/dist/hardening/scanner.d.ts.map +1 -1
- package/dist/hardening/scanner.js +107 -112
- package/dist/hardening/scanner.js.map +1 -1
- package/dist/hardening/taxonomy.d.ts.map +1 -1
- package/dist/hardening/taxonomy.js +67 -66
- package/dist/hardening/taxonomy.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/telemetry/contribute.d.ts +5 -0
- package/dist/telemetry/contribute.d.ts.map +1 -1
- package/dist/telemetry/contribute.js +34 -0
- package/dist/telemetry/contribute.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1538,43 +1538,22 @@ function resolvePackageVersion(targetDir) {
|
|
|
1538
1538
|
catch { /* ignore */ }
|
|
1539
1539
|
return null;
|
|
1540
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
|
-
}
|
|
1561
1541
|
/**
|
|
1562
1542
|
* Handle community contribution after a scan completes.
|
|
1563
1543
|
*
|
|
1564
1544
|
* Determines whether to contribute based on:
|
|
1565
1545
|
* 1. --contribute / --no-contribute CLI flags (highest priority)
|
|
1566
|
-
* 2.
|
|
1567
|
-
* 3.
|
|
1568
|
-
* 4. Interactive opt-in prompt (first scan or scan #10)
|
|
1546
|
+
* 2. ~/.opena2a/config.json contribute.enabled setting
|
|
1547
|
+
* 3. Interactive opt-in prompt (first scan or scan #10)
|
|
1569
1548
|
*
|
|
1570
|
-
*
|
|
1549
|
+
* If contributing, builds an anonymized payload and submits it
|
|
1550
|
+
* asynchronously (non-blocking). Failures are logged as warnings.
|
|
1571
1551
|
*/
|
|
1572
|
-
async function handleContribution(contributeFlag, targetDir, findings, registryUrl, format
|
|
1552
|
+
async function handleContribution(contributeFlag, targetDir, findings, registryUrl, format) {
|
|
1573
1553
|
try {
|
|
1574
1554
|
const { isContributeEnabled, shouldPromptContribute, showContributePrompt, incrementScanCount, buildContributionPayloadFromDir, submitContribution, } = await Promise.resolve().then(() => __importStar(require('./telemetry')));
|
|
1575
1555
|
// Always increment scan count
|
|
1576
1556
|
incrementScanCount();
|
|
1577
|
-
const isCi = contributionOptions?.ci === true;
|
|
1578
1557
|
// Determine whether to contribute
|
|
1579
1558
|
let shouldContribute;
|
|
1580
1559
|
if (contributeFlag === true) {
|
|
@@ -1585,10 +1564,6 @@ async function handleContribution(contributeFlag, targetDir, findings, registryU
|
|
|
1585
1564
|
// --no-contribute flag: skip this scan
|
|
1586
1565
|
shouldContribute = false;
|
|
1587
1566
|
}
|
|
1588
|
-
else if (isCi) {
|
|
1589
|
-
// CI mode: never prompt, only contribute if --contribute was explicit
|
|
1590
|
-
shouldContribute = false;
|
|
1591
|
-
}
|
|
1592
1567
|
else {
|
|
1593
1568
|
// Check config
|
|
1594
1569
|
const configSetting = isContributeEnabled();
|
|
@@ -1599,7 +1574,7 @@ async function handleContribution(contributeFlag, targetDir, findings, registryU
|
|
|
1599
1574
|
shouldContribute = false;
|
|
1600
1575
|
}
|
|
1601
1576
|
else {
|
|
1602
|
-
// Not configured -- prompt (interactive TTY only)
|
|
1577
|
+
// Not configured -- prompt after 3 scans (interactive TTY only)
|
|
1603
1578
|
if (format === 'text' && process.stdout.isTTY && shouldPromptContribute()) {
|
|
1604
1579
|
shouldContribute = await showContributePrompt();
|
|
1605
1580
|
}
|
|
@@ -1610,21 +1585,6 @@ async function handleContribution(contributeFlag, targetDir, findings, registryU
|
|
|
1610
1585
|
}
|
|
1611
1586
|
if (!shouldContribute)
|
|
1612
1587
|
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
|
-
}
|
|
1628
1588
|
// Build and submit contribution (non-blocking)
|
|
1629
1589
|
const packageName = resolvePackageName(targetDir);
|
|
1630
1590
|
if (!packageName)
|
|
@@ -1646,7 +1606,7 @@ async function handleContribution(contributeFlag, targetDir, findings, registryU
|
|
|
1646
1606
|
* Converts SoulScanResult controls into SecurityFinding-like objects
|
|
1647
1607
|
* for the contribution module, then delegates to handleContribution.
|
|
1648
1608
|
*/
|
|
1649
|
-
async function handleSoulContribution(contributeFlag, targetDir, result, registryUrl, format
|
|
1609
|
+
async function handleSoulContribution(contributeFlag, targetDir, result, registryUrl, format) {
|
|
1650
1610
|
// Convert soul controls into SecurityFinding-shaped objects
|
|
1651
1611
|
const findings = [];
|
|
1652
1612
|
for (const domain of result.domains) {
|
|
@@ -1665,11 +1625,7 @@ async function handleSoulContribution(contributeFlag, targetDir, result, registr
|
|
|
1665
1625
|
});
|
|
1666
1626
|
}
|
|
1667
1627
|
}
|
|
1668
|
-
await handleContribution(contributeFlag, targetDir, findings, registryUrl, format
|
|
1669
|
-
ci: contributionOptions?.ci,
|
|
1670
|
-
score: result.score,
|
|
1671
|
-
projectType: 'soul',
|
|
1672
|
-
});
|
|
1628
|
+
await handleContribution(contributeFlag, targetDir, findings, registryUrl, format);
|
|
1673
1629
|
}
|
|
1674
1630
|
program
|
|
1675
1631
|
.command('secure')
|
|
@@ -1730,10 +1686,18 @@ Examples:
|
|
|
1730
1686
|
.option('--registry-key <key>', 'Registry API key (default: REGISTRY_API_KEY env)')
|
|
1731
1687
|
.option('--contribute', 'Share anonymized scan findings with OpenA2A Registry (overrides config)')
|
|
1732
1688
|
.option('--no-contribute', 'Do not share findings for this scan (overrides config)')
|
|
1733
|
-
.option('--ci', 'CI mode: suppress interactive prompts,
|
|
1689
|
+
.option('--ci', 'CI mode: suppress interactive prompts, exit non-zero on findings')
|
|
1734
1690
|
.action(async (directory, options) => {
|
|
1735
1691
|
try {
|
|
1736
1692
|
const targetDir = directory.startsWith('/') ? directory : process.cwd() + '/' + directory;
|
|
1693
|
+
// CI mode: force non-interactive defaults
|
|
1694
|
+
if (options.ci) {
|
|
1695
|
+
if (!options.format && !options.json)
|
|
1696
|
+
options.format = 'text';
|
|
1697
|
+
// In CI, never prompt -- only contribute if explicitly --contribute
|
|
1698
|
+
if (options.contribute === undefined)
|
|
1699
|
+
options.contribute = false;
|
|
1700
|
+
}
|
|
1737
1701
|
// Check if directory exists
|
|
1738
1702
|
if (!require('fs').existsSync(targetDir)) {
|
|
1739
1703
|
console.error(`Error: Directory '${targetDir}' does not exist.`);
|
|
@@ -1935,7 +1899,7 @@ Examples:
|
|
|
1935
1899
|
writeJsonStdout(jsonOutput);
|
|
1936
1900
|
}
|
|
1937
1901
|
// Community contribution (non-blocking, runs in JSON mode too)
|
|
1938
|
-
await handleContribution(options.contribute, targetDir, result.findings, options.registryUrl, format
|
|
1902
|
+
await handleContribution(options.contribute, targetDir, result.findings, options.registryUrl, format);
|
|
1939
1903
|
const critHigh = result.findings.filter((f) => !f.passed && !f.fixed && (f.severity === 'critical' || f.severity === 'high'));
|
|
1940
1904
|
if (critHigh.length > 0)
|
|
1941
1905
|
process.exitCode = 1;
|
|
@@ -2015,10 +1979,11 @@ Examples:
|
|
|
2015
1979
|
? `${finding.file}:${finding.line}`
|
|
2016
1980
|
: finding.file
|
|
2017
1981
|
: '';
|
|
2018
|
-
// Format: SEVERITY file:line
|
|
1982
|
+
// Format: SEVERITY [DRY RUN] Would fix: file:line
|
|
2019
1983
|
// Description
|
|
2020
1984
|
// Fix: command
|
|
2021
|
-
|
|
1985
|
+
const dryRunPrefix = finding.wouldFix ? `${colors.cyan}[DRY RUN] Would fix: ${RESET()}` : '';
|
|
1986
|
+
console.log(`${display.color()}${display.symbol} ${finding.severity.toUpperCase()}${RESET()} ${dryRunPrefix}${location}`);
|
|
2022
1987
|
console.log(` ${finding.description}`);
|
|
2023
1988
|
if (finding.fix) {
|
|
2024
1989
|
console.log(` ${colors.cyan}Fix:${RESET()} ${finding.fix}`);
|
|
@@ -2056,6 +2021,14 @@ Examples:
|
|
|
2056
2021
|
if (summaryParts.length > 0) {
|
|
2057
2022
|
console.log(`${summaryParts.join(' | ')}\n`);
|
|
2058
2023
|
}
|
|
2024
|
+
// Dry-run summary
|
|
2025
|
+
if (result.dryRun) {
|
|
2026
|
+
const wouldFixCount = issues.filter((f) => f.wouldFix).length;
|
|
2027
|
+
if (wouldFixCount > 0) {
|
|
2028
|
+
console.log(`${colors.cyan}Dry run complete:${RESET()} ${wouldFixCount} issue${wouldFixCount === 1 ? '' : 's'} auto-fixable. Run without --dry-run to apply.`);
|
|
2029
|
+
}
|
|
2030
|
+
console.log(` No changes were made.\n`);
|
|
2031
|
+
}
|
|
2059
2032
|
}
|
|
2060
2033
|
// Print fixed findings
|
|
2061
2034
|
if (fixedFindings.length > 0) {
|
|
@@ -2155,12 +2128,15 @@ Examples:
|
|
|
2155
2128
|
}
|
|
2156
2129
|
}
|
|
2157
2130
|
// Community contribution: share anonymized findings with OpenA2A Registry
|
|
2158
|
-
await handleContribution(options.contribute, targetDir, result.findings, options.registryUrl, format
|
|
2131
|
+
await handleContribution(options.contribute, targetDir, result.findings, options.registryUrl, format);
|
|
2159
2132
|
// Star prompt (interactive TTY only, text format only)
|
|
2160
2133
|
if (process.stdout.isTTY) {
|
|
2161
2134
|
console.log(`${colors.cyan}Helpful?${RESET()} Star the project: https://github.com/opena2a-org/opena2a\n`);
|
|
2162
2135
|
}
|
|
2163
|
-
// Exit with non-zero if critical/high issues remain
|
|
2136
|
+
// Exit with non-zero if critical/high issues remain (or any issues in --ci mode)
|
|
2137
|
+
if (options.ci && issues.length > 0) {
|
|
2138
|
+
process.exit(1);
|
|
2139
|
+
}
|
|
2164
2140
|
const criticalOrHigh = issues.filter((f) => f.severity === 'critical' || f.severity === 'high');
|
|
2165
2141
|
if (criticalOrHigh.length > 0) {
|
|
2166
2142
|
process.exit(1);
|
|
@@ -2426,7 +2402,9 @@ Examples:
|
|
|
2426
2402
|
.option('-v, --verbose', 'Show detailed finding information')
|
|
2427
2403
|
.action(async (target, options) => {
|
|
2428
2404
|
try {
|
|
2429
|
-
|
|
2405
|
+
if (!options.json) {
|
|
2406
|
+
console.log(`\nScanning ${target}...\n`);
|
|
2407
|
+
}
|
|
2430
2408
|
const scanner = new index_1.ExternalScanner();
|
|
2431
2409
|
const customPorts = options.ports
|
|
2432
2410
|
? options.ports.split(',').map((p) => parseInt(p.trim(), 10))
|
|
@@ -4054,10 +4032,15 @@ Examples:
|
|
|
4054
4032
|
.option('--registry-url <url>', 'Registry URL (default: REGISTRY_URL env)', process.env.REGISTRY_URL || 'https://api.oa2a.org')
|
|
4055
4033
|
.option('--contribute', 'Share anonymized scan findings with OpenA2A Registry (overrides config)')
|
|
4056
4034
|
.option('--no-contribute', 'Do not share findings for this scan (overrides config)')
|
|
4057
|
-
.option('--ci', 'CI mode: suppress interactive prompts,
|
|
4035
|
+
.option('--ci', 'CI mode: suppress interactive prompts, exit non-zero on findings')
|
|
4058
4036
|
.action(async (directory, options) => {
|
|
4059
4037
|
try {
|
|
4060
4038
|
const targetDir = directory.startsWith('/') ? directory : process.cwd() + '/' + directory;
|
|
4039
|
+
// CI mode: force non-interactive defaults
|
|
4040
|
+
if (options.ci) {
|
|
4041
|
+
if (options.contribute === undefined)
|
|
4042
|
+
options.contribute = false;
|
|
4043
|
+
}
|
|
4061
4044
|
if (!require('fs').existsSync(targetDir)) {
|
|
4062
4045
|
process.stderr.write(`Error: Directory '${targetDir}' does not exist.\n`);
|
|
4063
4046
|
process.exit(1);
|
|
@@ -4220,7 +4203,14 @@ Examples:
|
|
|
4220
4203
|
}
|
|
4221
4204
|
// Community contribution: share anonymized findings with OpenA2A Registry
|
|
4222
4205
|
const soulFormat = options.json ? 'json' : 'text';
|
|
4223
|
-
await handleSoulContribution(options.contribute, targetDir, result, options.registryUrl, soulFormat
|
|
4206
|
+
await handleSoulContribution(options.contribute, targetDir, result, options.registryUrl, soulFormat);
|
|
4207
|
+
// In CI mode, exit non-zero if any controls failed
|
|
4208
|
+
if (options.ci) {
|
|
4209
|
+
const failedControls = result.domains.flatMap(d => d.controls).filter(c => !c.passed);
|
|
4210
|
+
if (failedControls.length > 0) {
|
|
4211
|
+
process.exit(1);
|
|
4212
|
+
}
|
|
4213
|
+
}
|
|
4224
4214
|
// Check fail threshold
|
|
4225
4215
|
if (options.failBelow) {
|
|
4226
4216
|
const threshold = parseInt(options.failBelow, 10);
|