hackmyagent 0.9.9 → 0.10.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 CHANGED
@@ -12,7 +12,7 @@ Security scanner and red-team toolkit for AI agents — 147 checks, 55 adversari
12
12
 
13
13
  Works with Claude Code, Cursor, VS Code, and any MCP server setup.
14
14
 
15
- [Website](https://hackmyagent.com) | [Security Checks Reference](docs/SECURITY_CHECKS.md) | [OpenA2A CLI](https://github.com/opena2a-org/opena2a)
15
+ [Website](https://hackmyagent.com) | [Security Checks Reference](docs/SECURITY_CHECKS.md) | [Demos](https://opena2a.org/demos) | [OpenA2A CLI](https://github.com/opena2a-org/opena2a)
16
16
 
17
17
  ---
18
18
 
@@ -38,7 +38,7 @@ That's it. No config files, no setup, no flags needed.
38
38
 
39
39
  ```
40
40
  ┌──────────────────────────────────────────────────┐
41
- │ HackMyAgent v0.9.8 — Security Scanner
41
+ │ HackMyAgent v0.10.0 — Security Scanner
42
42
  │ Found: 3 critical · 5 high · 12 medium │
43
43
  │ │
44
44
  │ CRED-001 critical Hardcoded API key in .env │
@@ -50,6 +50,10 @@ That's it. No config files, no setup, no flags needed.
50
50
  └──────────────────────────────────────────────────┘
51
51
  ```
52
52
 
53
+ ![HackMyAgent Demo](docs/hackmyagent-demo.gif)
54
+
55
+ > See all demos at [opena2a.org/demos](https://opena2a.org/demos)
56
+
53
57
  ---
54
58
 
55
59
  ## Installation
package/dist/cli.js CHANGED
@@ -1573,7 +1573,8 @@ Examples:
1573
1573
  $ hackmyagent secure -b oasb-1 -f sarif SARIF for GitHub
1574
1574
  $ hackmyagent secure -b oasb-1 -f html -o report.html
1575
1575
  $ hackmyagent secure -b oasb-1 --fail-below 80 CI threshold
1576
- $ hackmyagent secure -b oasb-2 OASB-2 composite (infra + governance)`)
1576
+ $ hackmyagent secure -b oasb-2 OASB-2 composite (infra + governance)
1577
+ $ hackmyagent secure ./my-agent --publish Scan and publish results to registry`)
1577
1578
  .argument('[directory]', 'Directory to scan (defaults to current directory)', '.')
1578
1579
  .option('--fix', 'Automatically fix issues where possible')
1579
1580
  .option('--dry-run', 'Preview fixes without applying them (use with --fix)')
@@ -1587,10 +1588,11 @@ Examples:
1587
1588
  .option('-l, --level <level>', 'Benchmark level: L1 (Essential), L2 (Standard), L3 (Hardened)', 'L1')
1588
1589
  .option('-c, --category <name>', 'Filter to specific benchmark category')
1589
1590
  .option('--deep', 'Enable LLM-powered semantic analysis (requires ANTHROPIC_API_KEY)')
1591
+ .option('--publish', 'Push scan results to the OpenA2A Registry')
1590
1592
  .option('--registry-report', 'Post results to OpenA2A Registry')
1591
1593
  .option('--no-registry', 'Skip auto-publishing results to OpenA2A Registry')
1592
1594
  .option('--version-id <id>', 'Registry version ID to report against')
1593
- .option('--registry-url <url>', 'Registry URL (default: REGISTRY_URL env)')
1595
+ .option('--registry-url <url>', 'Registry URL (default: REGISTRY_URL env)', process.env.REGISTRY_URL || 'https://registry.opena2a.org')
1594
1596
  .option('--registry-key <key>', 'Registry API key (default: REGISTRY_API_KEY env)')
1595
1597
  .action(async (directory, options) => {
1596
1598
  try {
@@ -1761,12 +1763,39 @@ Examples:
1761
1763
  return;
1762
1764
  }
1763
1765
  if (format === 'json') {
1766
+ // Run publish in JSON mode and include result in output
1767
+ let publishStatus;
1768
+ if (options.publish && options.registry !== false) {
1769
+ try {
1770
+ const { publishScanResults } = await Promise.resolve().then(() => __importStar(require('./registry/publish')));
1771
+ const registryUrl = options.registryUrl || process.env.REGISTRY_URL || 'https://registry.opena2a.org';
1772
+ const packageName = resolvePackageName(targetDir);
1773
+ if (packageName) {
1774
+ const publishData = {
1775
+ packageName,
1776
+ packageVersion: resolvePackageVersion(targetDir) ?? undefined,
1777
+ directory: targetDir,
1778
+ hardeningFindings: result.findings,
1779
+ };
1780
+ const publishResult = await publishScanResults(publishData, registryUrl);
1781
+ publishStatus = { ...publishResult, registryUrl };
1782
+ }
1783
+ else {
1784
+ publishStatus = { success: false, error: 'Could not determine package name' };
1785
+ }
1786
+ }
1787
+ catch (publishErr) {
1788
+ const msg = publishErr instanceof Error ? publishErr.message : 'unknown error';
1789
+ publishStatus = { success: false, error: msg };
1790
+ }
1791
+ }
1792
+ const jsonOutput = publishStatus ? { ...result, publish: publishStatus } : result;
1764
1793
  if (options.output) {
1765
- require('fs').writeFileSync(options.output, JSON.stringify(result, null, 2) + '\n');
1794
+ require('fs').writeFileSync(options.output, JSON.stringify(jsonOutput, null, 2) + '\n');
1766
1795
  console.error(`Report written to ${options.output}`);
1767
1796
  }
1768
1797
  else {
1769
- writeJsonStdout(result);
1798
+ writeJsonStdout(jsonOutput);
1770
1799
  }
1771
1800
  const critHigh = result.findings.filter((f) => !f.passed && !f.fixed && (f.severity === 'critical' || f.severity === 'high'));
1772
1801
  if (critHigh.length > 0)
@@ -1945,6 +1974,47 @@ Examples:
1945
1974
  // Silently ignore registry errors - they are not relevant to local scan results
1946
1975
  }
1947
1976
  }
1977
+ // Publish: push results to registry when --publish is used
1978
+ if (options.publish && options.registry === false) {
1979
+ if (format === 'text') {
1980
+ console.log('\nPublish skipped: --no-registry flag is active.');
1981
+ }
1982
+ }
1983
+ else if (options.publish) {
1984
+ try {
1985
+ const { publishScanResults, formatPublishOutput } = await Promise.resolve().then(() => __importStar(require('./registry/publish')));
1986
+ const registryUrl = options.registryUrl || process.env.REGISTRY_URL || 'https://registry.opena2a.org';
1987
+ const packageName = resolvePackageName(targetDir);
1988
+ if (!packageName) {
1989
+ console.error('\nCould not determine package name. Publish requires a package.json with a name field.');
1990
+ }
1991
+ else {
1992
+ if (format === 'text') {
1993
+ console.log('\nPublishing results to registry...\n');
1994
+ }
1995
+ const publishData = {
1996
+ packageName,
1997
+ packageVersion: resolvePackageVersion(targetDir) ?? undefined,
1998
+ directory: targetDir,
1999
+ hardeningFindings: result.findings,
2000
+ };
2001
+ const publishResult = await publishScanResults(publishData, registryUrl);
2002
+ if (format === 'text') {
2003
+ console.log(formatPublishOutput(publishResult, publishData, registryUrl));
2004
+ console.log();
2005
+ }
2006
+ else if (format === 'json') {
2007
+ // Append publish result to JSON output in a separate log
2008
+ console.error(JSON.stringify({ publish: publishResult }, null, 2));
2009
+ }
2010
+ }
2011
+ }
2012
+ catch (publishErr) {
2013
+ const msg = publishErr instanceof Error ? publishErr.message : 'unknown error';
2014
+ console.error(`\nFailed to publish to registry: ${msg}`);
2015
+ console.error('Scan results are still available locally.');
2016
+ }
2017
+ }
1948
2018
  // Star prompt (interactive TTY only, text format only)
1949
2019
  if (process.stdout.isTTY) {
1950
2020
  console.log(`${colors.cyan}Helpful?${RESET()} Star the project: https://github.com/opena2a-org/opena2a\n`);
@@ -2338,7 +2408,8 @@ Examples:
2338
2408
  $ hackmyagent attack https://api.example.com --payload-file custom.json
2339
2409
  $ hackmyagent attack https://api.example.com --fail-on-vulnerable medium
2340
2410
  $ hackmyagent attack http://localhost:3010 --target-type mcp --category mcp-exploitation
2341
- $ hackmyagent attack http://localhost:3020 --target-type a2a --category a2a-attack`)
2411
+ $ hackmyagent attack http://localhost:3020 --target-type a2a --category a2a-attack
2412
+ $ hackmyagent attack https://api.example.com --publish Attack and publish results to registry`)
2342
2413
  .argument('[target]', 'API endpoint to test (or use --local for simulation)')
2343
2414
  .option('-i, --intensity <level>', 'Attack intensity: passive, active, aggressive', 'active')
2344
2415
  .option('-c, --category <categories>', 'Comma-separated categories to test')
@@ -2360,10 +2431,11 @@ Examples:
2360
2431
  .option('-f, --format <format>', 'Output format: text, json, sarif, html', 'text')
2361
2432
  .option('-o, --output <file>', 'Write output to file')
2362
2433
  .option('-v, --verbose', 'Show detailed output for each payload')
2434
+ .option('--publish', 'Push scan results to the OpenA2A Registry')
2363
2435
  .option('--registry-report', 'Post results to OpenA2A Registry')
2364
2436
  .option('--no-registry', 'Skip auto-publishing results to OpenA2A Registry')
2365
2437
  .option('--version-id <id>', 'Registry version ID to report against')
2366
- .option('--registry-url <url>', 'Registry URL (default: REGISTRY_URL env)')
2438
+ .option('--registry-url <url>', 'Registry URL (default: REGISTRY_URL env)', process.env.REGISTRY_URL || 'https://registry.opena2a.org')
2367
2439
  .option('--registry-key <key>', 'Registry API key (default: REGISTRY_API_KEY env)')
2368
2440
  .action(async (targetUrl, options) => {
2369
2441
  try {
@@ -2552,6 +2624,42 @@ Examples:
2552
2624
  // Silently ignore registry errors - they are not relevant to local scan results
2553
2625
  }
2554
2626
  }
2627
+ // Publish: push attack results to registry when --publish is used
2628
+ if (options.publish && options.registry === false) {
2629
+ if (format === 'text') {
2630
+ console.log('\nPublish skipped: --no-registry flag is active.');
2631
+ }
2632
+ }
2633
+ else if (options.publish && targetType === 'local') {
2634
+ if (format === 'text') {
2635
+ console.log('\nPublish skipped: only available for live target scans.');
2636
+ }
2637
+ }
2638
+ else if (options.publish && targetType !== 'local') {
2639
+ try {
2640
+ const { publishScanResults, formatPublishOutput } = await Promise.resolve().then(() => __importStar(require('./registry/publish')));
2641
+ const regUrl = options.registryUrl || process.env.REGISTRY_URL || 'https://registry.opena2a.org';
2642
+ const packageName = target.url || targetUrl || 'unknown';
2643
+ if (format === 'text') {
2644
+ console.log('\nPublishing results to registry...\n');
2645
+ }
2646
+ const publishData = {
2647
+ packageName,
2648
+ directory: process.cwd(),
2649
+ attackReport: report,
2650
+ };
2651
+ const publishResult = await publishScanResults(publishData, regUrl);
2652
+ if (format === 'text') {
2653
+ console.log(formatPublishOutput(publishResult, publishData, regUrl));
2654
+ console.log();
2655
+ }
2656
+ }
2657
+ catch (publishErr) {
2658
+ const msg = publishErr instanceof Error ? publishErr.message : 'unknown error';
2659
+ console.error(`\nFailed to publish to registry: ${msg}`);
2660
+ console.error('Scan results are still available locally.');
2661
+ }
2662
+ }
2555
2663
  // Exit with non-zero based on fail policy
2556
2664
  if ((0, index_1.shouldFail)(report, options.failOnVulnerable)) {
2557
2665
  process.exit(1);
@@ -3788,7 +3896,8 @@ Examples:
3788
3896
  $ hackmyagent scan-soul --json Machine-readable output
3789
3897
  $ hackmyagent scan-soul --verbose Show all controls
3790
3898
  $ hackmyagent scan-soul --profile conversational Override profile
3791
- $ hackmyagent scan-soul --deep Enable LLM semantic analysis`)
3899
+ $ hackmyagent scan-soul --deep Enable LLM semantic analysis
3900
+ $ hackmyagent scan-soul ./my-agent --publish Scan and publish results to registry`)
3792
3901
  .argument('[directory]', 'Directory to scan (defaults to current directory)', '.')
3793
3902
  .option('--json', 'Output as JSON')
3794
3903
  .option('-v, --verbose', 'Show individual control results')
@@ -3796,6 +3905,8 @@ Examples:
3796
3905
  .option('--profile <profile>', 'Override agent profile (conversational, code-assistant, tool-agent, autonomous, orchestrator, custom)')
3797
3906
  .option('--fail-below <score>', 'Exit 1 if score below threshold (0-100)')
3798
3907
  .option('--deep', 'Enable LLM semantic analysis for ambiguous controls (requires claude CLI or ANTHROPIC_API_KEY)')
3908
+ .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://registry.opena2a.org')
3799
3910
  .action(async (directory, options) => {
3800
3911
  try {
3801
3912
  const targetDir = directory.startsWith('/') ? directory : process.cwd() + '/' + directory;
@@ -3813,7 +3924,34 @@ Examples:
3813
3924
  });
3814
3925
  // JSON output
3815
3926
  if (options.json) {
3816
- writeJsonStdout(result);
3927
+ // Run publish in JSON mode and include result in output
3928
+ let publishStatus;
3929
+ if (options.publish) {
3930
+ try {
3931
+ const { publishScanResults } = await Promise.resolve().then(() => __importStar(require('./registry/publish')));
3932
+ const registryUrl = options.registryUrl || process.env.REGISTRY_URL || 'https://registry.opena2a.org';
3933
+ const packageName = resolvePackageName(targetDir);
3934
+ if (packageName) {
3935
+ const publishData = {
3936
+ packageName,
3937
+ packageVersion: resolvePackageVersion(targetDir) ?? undefined,
3938
+ directory: targetDir,
3939
+ soulResult: result,
3940
+ };
3941
+ const publishResult = await publishScanResults(publishData, registryUrl);
3942
+ publishStatus = { ...publishResult, registryUrl };
3943
+ }
3944
+ else {
3945
+ publishStatus = { success: false, error: 'Could not determine package name' };
3946
+ }
3947
+ }
3948
+ catch (publishErr) {
3949
+ const msg = publishErr instanceof Error ? publishErr.message : 'unknown error';
3950
+ publishStatus = { success: false, error: msg };
3951
+ }
3952
+ }
3953
+ const jsonOutput = publishStatus ? { ...result, publish: publishStatus } : result;
3954
+ writeJsonStdout(jsonOutput);
3817
3955
  // Check fail threshold
3818
3956
  if (options.failBelow) {
3819
3957
  const threshold = parseInt(options.failBelow, 10);
@@ -3901,6 +4039,37 @@ Examples:
3901
4039
  process.stdout.write(`\n${colors.green}All ${result.totalControls} governance controls covered.${colors.reset}\n`);
3902
4040
  }
3903
4041
  process.stdout.write('\n');
4042
+ // Publish: push SOUL results to registry when --publish is used
4043
+ if (options.publish) {
4044
+ try {
4045
+ const { publishScanResults, formatPublishOutput } = await Promise.resolve().then(() => __importStar(require('./registry/publish')));
4046
+ const registryUrl = options.registryUrl || process.env.REGISTRY_URL || 'https://registry.opena2a.org';
4047
+ const packageName = resolvePackageName(targetDir);
4048
+ if (!packageName) {
4049
+ process.stderr.write('Could not determine package name. Publish requires a package.json with a name field.\n');
4050
+ }
4051
+ else {
4052
+ if (!options.json) {
4053
+ process.stdout.write('Publishing results to registry...\n\n');
4054
+ }
4055
+ const publishData = {
4056
+ packageName,
4057
+ packageVersion: resolvePackageVersion(targetDir) ?? undefined,
4058
+ directory: targetDir,
4059
+ soulResult: result,
4060
+ };
4061
+ const publishResult = await publishScanResults(publishData, registryUrl);
4062
+ if (!options.json) {
4063
+ process.stdout.write(formatPublishOutput(publishResult, publishData, registryUrl) + '\n\n');
4064
+ }
4065
+ }
4066
+ }
4067
+ catch (publishErr) {
4068
+ const msg = publishErr instanceof Error ? publishErr.message : 'unknown error';
4069
+ process.stderr.write(`Failed to publish to registry: ${msg}\n`);
4070
+ process.stderr.write('Scan results are still available locally.\n');
4071
+ }
4072
+ }
3904
4073
  // Check fail threshold
3905
4074
  if (options.failBelow) {
3906
4075
  const threshold = parseInt(options.failBelow, 10);