hackmyagent 0.14.1 → 0.15.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/dist/.integrity-manifest.json +1 -1
- package/dist/arp/engine/event-engine.d.ts.map +1 -1
- package/dist/arp/engine/event-engine.js +8 -6
- package/dist/arp/engine/event-engine.js.map +1 -1
- package/dist/arp/intelligence/coordinator.d.ts.map +1 -1
- package/dist/arp/intelligence/coordinator.js +17 -12
- package/dist/arp/intelligence/coordinator.js.map +1 -1
- package/dist/arp/intelligence/nanomind-l1.js +2 -2
- package/dist/arp/intelligence/nanomind-l1.js.map +1 -1
- package/dist/arp/interceptors/process.d.ts.map +1 -1
- package/dist/arp/interceptors/process.js +40 -5
- package/dist/arp/interceptors/process.js.map +1 -1
- package/dist/arp/proxy/server.d.ts +6 -0
- package/dist/arp/proxy/server.d.ts.map +1 -1
- package/dist/arp/proxy/server.js +40 -14
- package/dist/arp/proxy/server.js.map +1 -1
- package/dist/arp/telemetry/gtin.d.ts +1 -1
- package/dist/arp/telemetry/gtin.d.ts.map +1 -1
- package/dist/arp/telemetry/gtin.js +4 -0
- package/dist/arp/telemetry/gtin.js.map +1 -1
- package/dist/attack/payloads/index.d.ts +2 -1
- package/dist/attack/payloads/index.d.ts.map +1 -1
- package/dist/attack/payloads/index.js +5 -1
- package/dist/attack/payloads/index.js.map +1 -1
- package/dist/attack/payloads/policy-enforcement-integrity.d.ts +13 -0
- package/dist/attack/payloads/policy-enforcement-integrity.d.ts.map +1 -0
- package/dist/attack/payloads/policy-enforcement-integrity.js +217 -0
- package/dist/attack/payloads/policy-enforcement-integrity.js.map +1 -0
- package/dist/attack/scanner.d.ts.map +1 -1
- package/dist/attack/scanner.js +1 -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 +5 -0
- package/dist/attack/types.js.map +1 -1
- package/dist/attack-engine/types.d.ts +1 -1
- package/dist/attack-engine/types.d.ts.map +1 -1
- package/dist/attack-engine/types.js.map +1 -1
- package/dist/cli.js +279 -20
- package/dist/cli.js.map +1 -1
- package/dist/hardening/scanner.d.ts +18 -4
- package/dist/hardening/scanner.d.ts.map +1 -1
- package/dist/hardening/scanner.js +76 -18
- package/dist/hardening/scanner.js.map +1 -1
- package/dist/nanomind-core/inference/tme-classifier.d.ts.map +1 -1
- package/dist/nanomind-core/inference/tme-classifier.js +0 -1
- package/dist/nanomind-core/inference/tme-classifier.js.map +1 -1
- package/dist/nanomind-core/scanner-bridge.d.ts.map +1 -1
- package/dist/nanomind-core/scanner-bridge.js +8 -0
- package/dist/nanomind-core/scanner-bridge.js.map +1 -1
- package/package.json +1 -1
- package/dist/abgr/controls.d.ts +0 -35
- package/dist/abgr/controls.d.ts.map +0 -1
- package/dist/abgr/controls.js +0 -1058
- package/dist/abgr/controls.js.map +0 -1
- package/dist/abgr/detector.d.ts +0 -45
- package/dist/abgr/detector.d.ts.map +0 -1
- package/dist/abgr/detector.js +0 -175
- package/dist/abgr/detector.js.map +0 -1
- package/dist/abgr/index.d.ts +0 -24
- package/dist/abgr/index.d.ts.map +0 -1
- package/dist/abgr/index.js +0 -50
- package/dist/abgr/index.js.map +0 -1
- package/dist/abgr/scorer.d.ts +0 -36
- package/dist/abgr/scorer.d.ts.map +0 -1
- package/dist/abgr/scorer.js +0 -205
- package/dist/abgr/scorer.js.map +0 -1
- package/dist/abgr/templates.d.ts +0 -35
- package/dist/abgr/templates.d.ts.map +0 -1
- package/dist/abgr/templates.js +0 -668
- package/dist/abgr/templates.js.map +0 -1
- package/dist/abgr/tier.d.ts +0 -27
- package/dist/abgr/tier.d.ts.map +0 -1
- package/dist/abgr/tier.js +0 -115
- package/dist/abgr/tier.js.map +0 -1
- package/dist/abgr/types.d.ts +0 -59
- package/dist/abgr/types.d.ts.map +0 -1
- package/dist/abgr/types.js +0 -10
- package/dist/abgr/types.js.map +0 -1
- package/dist/agent-scan/checks.d.ts +0 -6
- package/dist/agent-scan/checks.d.ts.map +0 -1
- package/dist/agent-scan/checks.js +0 -93
- package/dist/agent-scan/checks.js.map +0 -1
- package/dist/agent-scan/index.d.ts +0 -10
- package/dist/agent-scan/index.d.ts.map +0 -1
- package/dist/agent-scan/index.js +0 -16
- package/dist/agent-scan/index.js.map +0 -1
- package/dist/agent-scan/scanner.d.ts +0 -31
- package/dist/agent-scan/scanner.d.ts.map +0 -1
- package/dist/agent-scan/scanner.js +0 -484
- package/dist/agent-scan/scanner.js.map +0 -1
- package/dist/agent-scan/types.d.ts +0 -63
- package/dist/agent-scan/types.d.ts.map +0 -1
- package/dist/agent-scan/types.js +0 -10
- package/dist/agent-scan/types.js.map +0 -1
- package/dist/hardening/llm-checks.d.ts +0 -18
- package/dist/hardening/llm-checks.d.ts.map +0 -1
- package/dist/hardening/llm-checks.js +0 -434
- package/dist/hardening/llm-checks.js.map +0 -1
- package/dist/hardening/mcp-tool-enum.d.ts +0 -45
- package/dist/hardening/mcp-tool-enum.d.ts.map +0 -1
- package/dist/hardening/mcp-tool-enum.js +0 -315
- package/dist/hardening/mcp-tool-enum.js.map +0 -1
- package/dist/hardening/shell-checks.d.ts +0 -21
- package/dist/hardening/shell-checks.d.ts.map +0 -1
- package/dist/hardening/shell-checks.js +0 -236
- package/dist/hardening/shell-checks.js.map +0 -1
- package/dist/nanomind-core/telemetry/auto-update.d.ts +0 -27
- package/dist/nanomind-core/telemetry/auto-update.d.ts.map +0 -1
- package/dist/nanomind-core/telemetry/auto-update.js +0 -129
- package/dist/nanomind-core/telemetry/auto-update.js.map +0 -1
- package/dist/nanomind-core/telemetry/client.d.ts +0 -66
- package/dist/nanomind-core/telemetry/client.d.ts.map +0 -1
- package/dist/nanomind-core/telemetry/client.js +0 -123
- package/dist/nanomind-core/telemetry/client.js.map +0 -1
- package/dist/nanomind-core/telemetry/config.d.ts +0 -33
- package/dist/nanomind-core/telemetry/config.d.ts.map +0 -1
- package/dist/nanomind-core/telemetry/config.js +0 -119
- package/dist/nanomind-core/telemetry/config.js.map +0 -1
- package/dist/nanomind-core/telemetry/index.d.ts +0 -15
- package/dist/nanomind-core/telemetry/index.d.ts.map +0 -1
- package/dist/nanomind-core/telemetry/index.js +0 -27
- package/dist/nanomind-core/telemetry/index.js.map +0 -1
- package/dist/registry/contribution.d.ts +0 -178
- package/dist/registry/contribution.d.ts.map +0 -1
- package/dist/registry/contribution.js +0 -272
- package/dist/registry/contribution.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -167,21 +167,23 @@ const RISK_DISPLAY = {
|
|
|
167
167
|
const RESET = () => colors.reset;
|
|
168
168
|
program
|
|
169
169
|
.command('check')
|
|
170
|
-
.description(`
|
|
170
|
+
.description(`Check if a package or skill is safe
|
|
171
171
|
|
|
172
|
-
|
|
173
|
-
•
|
|
174
|
-
•
|
|
175
|
-
•
|
|
172
|
+
Accepts npm packages, local paths, or skill identifiers:
|
|
173
|
+
• npm package: downloads and runs full security analysis (204 checks + NanoMind)
|
|
174
|
+
• Local path: runs NanoMind semantic analysis
|
|
175
|
+
• Skill identifier: verifies publisher, permissions, revocation
|
|
176
176
|
|
|
177
177
|
Risk levels: low, medium, high, critical
|
|
178
178
|
Exit code 1 if high/critical risk detected.
|
|
179
179
|
|
|
180
180
|
Examples:
|
|
181
|
-
$ hackmyagent check
|
|
182
|
-
$ hackmyagent check @
|
|
183
|
-
$ hackmyagent check @
|
|
184
|
-
|
|
181
|
+
$ hackmyagent check express
|
|
182
|
+
$ hackmyagent check @modelcontextprotocol/server-filesystem
|
|
183
|
+
$ hackmyagent check @modelcontextprotocol/server-filesystem --json
|
|
184
|
+
$ hackmyagent check ./my-agent/
|
|
185
|
+
$ hackmyagent check @publisher/skill --verbose`)
|
|
186
|
+
.argument('<target>', 'npm package name, local path, or skill identifier')
|
|
185
187
|
.option('-v, --verbose', 'Show detailed verification info')
|
|
186
188
|
.option('--json', 'Output as JSON (for scripting/CI)')
|
|
187
189
|
.option('--offline', 'Skip DNS verification (offline mode)')
|
|
@@ -239,6 +241,11 @@ Examples:
|
|
|
239
241
|
process.exit(1);
|
|
240
242
|
return;
|
|
241
243
|
}
|
|
244
|
+
// npm package name: download, run full HMA scan, clean up
|
|
245
|
+
if (looksLikeNpmPackage(skill)) {
|
|
246
|
+
await checkNpmPackage(skill, options);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
242
249
|
// Registry lookup path (non-local identifier) with 10s timeout
|
|
243
250
|
const checkPromise = (0, index_1.checkSkill)(skill, {
|
|
244
251
|
skipDnsVerification: options.offline,
|
|
@@ -1945,12 +1952,20 @@ Examples:
|
|
|
1945
1952
|
deep: isDeep,
|
|
1946
1953
|
silent: format !== 'text',
|
|
1947
1954
|
});
|
|
1955
|
+
// Re-apply all filters after NanoMind merge (merge uses allFindings which is unfiltered)
|
|
1956
|
+
const refiltered = await scanner.reapplyIgnoreFilters(nmResult.mergedFindings, targetDir);
|
|
1948
1957
|
if (result.allFindings) {
|
|
1949
|
-
result.allFindings =
|
|
1958
|
+
result.allFindings = refiltered;
|
|
1950
1959
|
}
|
|
1951
1960
|
if (result.findings) {
|
|
1952
|
-
|
|
1961
|
+
// Re-apply the same gates as the original filter:
|
|
1962
|
+
// 1. Only failed checks 2. Has file path 3. Applies to project type
|
|
1963
|
+
const projectType = result.projectType || 'library';
|
|
1964
|
+
result.findings = refiltered.filter((f) => !f.passed && f.file && scanner.findingAppliesTo(f, projectType));
|
|
1953
1965
|
}
|
|
1966
|
+
// Recalculate score from filtered findings (score was set pre-NanoMind)
|
|
1967
|
+
const forScore = (result.findings || []).filter((f) => !f.passed && !f.fixed);
|
|
1968
|
+
result.score = scanner.calculateScore(forScore).score;
|
|
1954
1969
|
}
|
|
1955
1970
|
// Behavioral simulation: auto-runs on --deep, or when NanoMind detects ambiguity
|
|
1956
1971
|
if (isDeep && format === 'text') {
|
|
@@ -2658,7 +2673,11 @@ Examples:
|
|
|
2658
2673
|
try {
|
|
2659
2674
|
const { orchestrateNanoMind } = await Promise.resolve().then(() => __importStar(require('./nanomind-core/orchestrate.js')));
|
|
2660
2675
|
const nmResult = await orchestrateNanoMind(targetDir, result.findings, { silent: !!options.json });
|
|
2661
|
-
|
|
2676
|
+
// Re-apply .hmaignore filters and recalculate score after NanoMind merge
|
|
2677
|
+
const hRefiltered = await scanner.reapplyIgnoreFilters(nmResult.mergedFindings, targetDir);
|
|
2678
|
+
result.findings = hRefiltered;
|
|
2679
|
+
const hForScore = hRefiltered.filter((f) => !f.passed && !f.fixed);
|
|
2680
|
+
result.score = scanner.calculateScore(hForScore).score;
|
|
2662
2681
|
}
|
|
2663
2682
|
catch { /* NanoMind unavailable */ }
|
|
2664
2683
|
// Filter to OpenClaw-specific findings
|
|
@@ -3064,14 +3083,8 @@ program
|
|
|
3064
3083
|
.command('attack')
|
|
3065
3084
|
.description(`Adversarial security testing for AI agents
|
|
3066
3085
|
|
|
3067
|
-
Red team your AI agent with ${index_1.PAYLOAD_STATS.total} attack payloads across
|
|
3068
|
-
|
|
3069
|
-
• Jailbreaking: ${index_1.PAYLOAD_STATS.byCategory['jailbreak']} payloads
|
|
3070
|
-
• Data Exfiltration: ${index_1.PAYLOAD_STATS.byCategory['data-exfiltration']} payloads
|
|
3071
|
-
• Capability Abuse: ${index_1.PAYLOAD_STATS.byCategory['capability-abuse']} payloads
|
|
3072
|
-
• Context Manipulation: ${index_1.PAYLOAD_STATS.byCategory['context-manipulation']} payloads
|
|
3073
|
-
• MCP Exploitation: ${index_1.PAYLOAD_STATS.byCategory['mcp-exploitation']} payloads
|
|
3074
|
-
• A2A Attacks: ${index_1.PAYLOAD_STATS.byCategory['a2a-attack']} payloads
|
|
3086
|
+
Red team your AI agent with ${index_1.PAYLOAD_STATS.total} attack payloads across ${Object.keys(index_1.PAYLOAD_STATS.byCategory).length} categories:
|
|
3087
|
+
${Object.entries(index_1.PAYLOAD_STATS.byCategory).map(([cat, count]) => ` • ${index_1.ATTACK_CATEGORIES[cat].name}: ${count} payloads`).join('\n')}
|
|
3075
3088
|
|
|
3076
3089
|
Intensity levels (controls how many payloads run):
|
|
3077
3090
|
passive Observation only (${index_1.PAYLOAD_STATS.byIntensity.passive} payloads)
|
|
@@ -3527,6 +3540,7 @@ function generateAttackHtmlReport(report) {
|
|
|
3527
3540
|
'persistent-agent': 'PERSIST',
|
|
3528
3541
|
'fake-tool': 'FAKETOOL',
|
|
3529
3542
|
'context-lifecycle': 'LIFECYCLE',
|
|
3543
|
+
'policy-enforcement-integrity': 'PEI',
|
|
3530
3544
|
};
|
|
3531
3545
|
// Donut chart for attack results
|
|
3532
3546
|
const donutRadius = 60;
|
|
@@ -5729,6 +5743,251 @@ program
|
|
|
5729
5743
|
}
|
|
5730
5744
|
console.log(`\nYour skill is ready. Verify security with: hackmyagent secure ${outputDir}/`);
|
|
5731
5745
|
});
|
|
5746
|
+
// ============================================================================
|
|
5747
|
+
// npm package scanning helpers (used by `check <package>`)
|
|
5748
|
+
// ============================================================================
|
|
5749
|
+
/**
|
|
5750
|
+
* Detect whether a string looks like an npm package name rather than
|
|
5751
|
+
* a hostname, IP address, or local path.
|
|
5752
|
+
*
|
|
5753
|
+
* npm package names: express, @scope/name, lodash, my-pkg
|
|
5754
|
+
* NOT packages: example.com, 192.168.1.1, ./dir, /path, .
|
|
5755
|
+
*/
|
|
5756
|
+
function looksLikeNpmPackage(target) {
|
|
5757
|
+
// Local paths
|
|
5758
|
+
if (target.startsWith('.') || target.startsWith('/'))
|
|
5759
|
+
return false;
|
|
5760
|
+
// Scoped packages are always npm
|
|
5761
|
+
if (target.startsWith('@') && target.includes('/'))
|
|
5762
|
+
return true;
|
|
5763
|
+
// IPs
|
|
5764
|
+
if (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(target))
|
|
5765
|
+
return false;
|
|
5766
|
+
// Hostnames have dots (example.com, sub.domain.org)
|
|
5767
|
+
if (target.includes('.'))
|
|
5768
|
+
return false;
|
|
5769
|
+
// What's left: bare names like express, lodash, hackmyagent
|
|
5770
|
+
// npm names are lowercase, may contain hyphens and digits
|
|
5771
|
+
return /^[a-z0-9][a-z0-9._-]*$/.test(target);
|
|
5772
|
+
}
|
|
5773
|
+
const REGISTRY_URL = 'https://api.oa2a.org';
|
|
5774
|
+
const STALE_SCAN_DAYS = 3;
|
|
5775
|
+
/**
|
|
5776
|
+
* Query the OpenA2A Registry for existing trust data.
|
|
5777
|
+
* Returns null on any error (network, 404, timeout).
|
|
5778
|
+
*/
|
|
5779
|
+
async function queryRegistry(name) {
|
|
5780
|
+
try {
|
|
5781
|
+
const params = new URLSearchParams({ name, includeProfile: 'true' });
|
|
5782
|
+
const response = await fetch(`${REGISTRY_URL}/api/v1/trust/query?${params}`, {
|
|
5783
|
+
method: 'GET',
|
|
5784
|
+
headers: { 'Accept': 'application/json', 'User-Agent': `hackmyagent/${index_1.VERSION}` },
|
|
5785
|
+
signal: AbortSignal.timeout(5000),
|
|
5786
|
+
});
|
|
5787
|
+
if (!response.ok)
|
|
5788
|
+
return null;
|
|
5789
|
+
const data = await response.json();
|
|
5790
|
+
if (!data.packageId)
|
|
5791
|
+
return null;
|
|
5792
|
+
return {
|
|
5793
|
+
found: true,
|
|
5794
|
+
name: data.name ?? name,
|
|
5795
|
+
trustScore: data.trustScore ?? 0,
|
|
5796
|
+
trustLevel: data.trustLevel ?? 0,
|
|
5797
|
+
verdict: data.verdict ?? 'unknown',
|
|
5798
|
+
scanStatus: data.scanStatus,
|
|
5799
|
+
lastScannedAt: data.lastScannedAt,
|
|
5800
|
+
packageType: data.packageType,
|
|
5801
|
+
};
|
|
5802
|
+
}
|
|
5803
|
+
catch {
|
|
5804
|
+
return null;
|
|
5805
|
+
}
|
|
5806
|
+
}
|
|
5807
|
+
/**
|
|
5808
|
+
* Check if scan data is stale (older than STALE_SCAN_DAYS).
|
|
5809
|
+
*/
|
|
5810
|
+
function isScanStale(lastScannedAt) {
|
|
5811
|
+
if (!lastScannedAt)
|
|
5812
|
+
return true;
|
|
5813
|
+
const scanned = new Date(lastScannedAt);
|
|
5814
|
+
const now = new Date();
|
|
5815
|
+
const days = (now.getTime() - scanned.getTime()) / (1000 * 60 * 60 * 24);
|
|
5816
|
+
return days > STALE_SCAN_DAYS;
|
|
5817
|
+
}
|
|
5818
|
+
/**
|
|
5819
|
+
* Publish scan results to the community registry.
|
|
5820
|
+
*/
|
|
5821
|
+
async function publishToRegistry(name, result) {
|
|
5822
|
+
try {
|
|
5823
|
+
const response = await fetch(`${REGISTRY_URL}/api/v1/trust/scans`, {
|
|
5824
|
+
method: 'POST',
|
|
5825
|
+
headers: {
|
|
5826
|
+
'Content-Type': 'application/json',
|
|
5827
|
+
'User-Agent': `hackmyagent/${index_1.VERSION}`,
|
|
5828
|
+
},
|
|
5829
|
+
body: JSON.stringify({
|
|
5830
|
+
name,
|
|
5831
|
+
score: result.score,
|
|
5832
|
+
maxScore: result.maxScore,
|
|
5833
|
+
projectType: result.projectType,
|
|
5834
|
+
findings: result.findings.map(f => ({
|
|
5835
|
+
checkId: f.checkId,
|
|
5836
|
+
name: f.name,
|
|
5837
|
+
severity: f.severity,
|
|
5838
|
+
passed: f.passed,
|
|
5839
|
+
message: f.message,
|
|
5840
|
+
category: f.category,
|
|
5841
|
+
})),
|
|
5842
|
+
scanTimestamp: new Date().toISOString(),
|
|
5843
|
+
}),
|
|
5844
|
+
signal: AbortSignal.timeout(10000),
|
|
5845
|
+
});
|
|
5846
|
+
return response.ok;
|
|
5847
|
+
}
|
|
5848
|
+
catch {
|
|
5849
|
+
return false;
|
|
5850
|
+
}
|
|
5851
|
+
}
|
|
5852
|
+
/**
|
|
5853
|
+
* Display registry trust data in the terminal.
|
|
5854
|
+
*/
|
|
5855
|
+
function displayRegistryResult(data) {
|
|
5856
|
+
const scoreRatio = data.trustScore;
|
|
5857
|
+
const scoreColor = scoreRatio >= 0.7 ? colors.green : scoreRatio >= 0.4 ? colors.yellow : colors.red;
|
|
5858
|
+
const score = Math.round(scoreRatio * 100);
|
|
5859
|
+
console.log(`\n ${data.name}`);
|
|
5860
|
+
console.log(` Type: ${data.packageType ?? 'unknown'}`);
|
|
5861
|
+
console.log(` Score: ${scoreColor}${score}/100${RESET()} (registry)`);
|
|
5862
|
+
console.log(` Verdict: ${data.verdict}`);
|
|
5863
|
+
if (data.lastScannedAt) {
|
|
5864
|
+
const days = Math.floor((Date.now() - new Date(data.lastScannedAt).getTime()) / (1000 * 60 * 60 * 24));
|
|
5865
|
+
console.log(` Scanned: ${days === 0 ? 'today' : days + ' day(s) ago'}`);
|
|
5866
|
+
}
|
|
5867
|
+
console.log();
|
|
5868
|
+
}
|
|
5869
|
+
/**
|
|
5870
|
+
* Download an npm package, run full HMA secure scan, display results, clean up.
|
|
5871
|
+
* Checks the registry first; only downloads if data is missing or stale.
|
|
5872
|
+
*/
|
|
5873
|
+
async function checkNpmPackage(name, options) {
|
|
5874
|
+
// Step 1: Check registry for existing trust data
|
|
5875
|
+
if (!options.offline) {
|
|
5876
|
+
const registryData = await queryRegistry(name);
|
|
5877
|
+
if (registryData?.found && !isScanStale(registryData.lastScannedAt)) {
|
|
5878
|
+
// Fresh data in registry — show it
|
|
5879
|
+
if (options.json) {
|
|
5880
|
+
writeJsonStdout({ ...registryData, source: 'registry' });
|
|
5881
|
+
return;
|
|
5882
|
+
}
|
|
5883
|
+
displayRegistryResult(registryData);
|
|
5884
|
+
return;
|
|
5885
|
+
}
|
|
5886
|
+
// Stale or missing — tell the user we're scanning
|
|
5887
|
+
if (registryData?.found && registryData.lastScannedAt) {
|
|
5888
|
+
if (!options.json && !globalCiMode) {
|
|
5889
|
+
const days = Math.floor((Date.now() - new Date(registryData.lastScannedAt).getTime()) / (1000 * 60 * 60 * 24));
|
|
5890
|
+
console.error(`\nRegistry data is ${days} day(s) old. Re-scanning...`);
|
|
5891
|
+
}
|
|
5892
|
+
}
|
|
5893
|
+
}
|
|
5894
|
+
// Step 2: Download and scan
|
|
5895
|
+
const { mkdtemp, rm } = await Promise.resolve().then(() => __importStar(require('node:fs/promises')));
|
|
5896
|
+
const { tmpdir } = await Promise.resolve().then(() => __importStar(require('node:os')));
|
|
5897
|
+
const { join } = await Promise.resolve().then(() => __importStar(require('node:path')));
|
|
5898
|
+
const { execFile } = await Promise.resolve().then(() => __importStar(require('node:child_process')));
|
|
5899
|
+
const { promisify } = await Promise.resolve().then(() => __importStar(require('node:util')));
|
|
5900
|
+
const execAsync = promisify(execFile);
|
|
5901
|
+
if (!options.json && !globalCiMode) {
|
|
5902
|
+
console.error(`Downloading ${name} from npm...`);
|
|
5903
|
+
}
|
|
5904
|
+
const tempDir = await mkdtemp(join(tmpdir(), 'hma-check-'));
|
|
5905
|
+
try {
|
|
5906
|
+
// Download and extract
|
|
5907
|
+
const { stdout } = await execAsync('npm', ['pack', name, '--pack-destination', tempDir], { timeout: 60000 });
|
|
5908
|
+
const tarball = stdout.trim().split('\n').pop();
|
|
5909
|
+
await execAsync('tar', ['xzf', join(tempDir, tarball), '-C', tempDir], { timeout: 30000 });
|
|
5910
|
+
const packageDir = join(tempDir, 'package');
|
|
5911
|
+
// Run full HMA scan
|
|
5912
|
+
const scanner = new index_1.HardeningScanner();
|
|
5913
|
+
const result = await scanner.scan({ targetDir: packageDir, autoFix: false });
|
|
5914
|
+
const failed = result.findings.filter(f => !f.passed);
|
|
5915
|
+
const critical = failed.filter(f => f.severity === 'critical');
|
|
5916
|
+
const high = failed.filter(f => f.severity === 'high');
|
|
5917
|
+
const medium = failed.filter(f => f.severity === 'medium');
|
|
5918
|
+
const low = failed.filter(f => f.severity === 'low');
|
|
5919
|
+
if (options.json) {
|
|
5920
|
+
writeJsonStdout({
|
|
5921
|
+
name,
|
|
5922
|
+
type: 'npm-package',
|
|
5923
|
+
source: 'local-scan',
|
|
5924
|
+
projectType: result.projectType,
|
|
5925
|
+
score: result.score,
|
|
5926
|
+
maxScore: result.maxScore,
|
|
5927
|
+
findings: result.findings,
|
|
5928
|
+
});
|
|
5929
|
+
return;
|
|
5930
|
+
}
|
|
5931
|
+
// Display results
|
|
5932
|
+
const scoreRatio = result.score / result.maxScore;
|
|
5933
|
+
const scoreColor = scoreRatio >= 0.7 ? colors.green : scoreRatio >= 0.4 ? colors.yellow : colors.red;
|
|
5934
|
+
console.log(`\n ${name}`);
|
|
5935
|
+
console.log(` Type: ${result.projectType}`);
|
|
5936
|
+
console.log(` Score: ${scoreColor}${result.score}/${result.maxScore}${RESET()}`);
|
|
5937
|
+
console.log(` Findings: ${critical.length} critical, ${high.length} high, ${medium.length} medium, ${low.length} low`);
|
|
5938
|
+
if (failed.length > 0) {
|
|
5939
|
+
console.log();
|
|
5940
|
+
const limit = options.verbose ? failed.length : 15;
|
|
5941
|
+
for (const f of failed.slice(0, limit)) {
|
|
5942
|
+
const sev = SEVERITY_DISPLAY[f.severity];
|
|
5943
|
+
const attackClass = f.attackClass ? ` (${f.attackClass})` : '';
|
|
5944
|
+
console.log(` ${sev.color()}${sev.symbol}${RESET()} ${f.name}: ${f.message}${colors.dim}${attackClass}${RESET()}`);
|
|
5945
|
+
}
|
|
5946
|
+
if (failed.length > limit) {
|
|
5947
|
+
console.log(`\n ... and ${failed.length - limit} more (use --verbose to see all)`);
|
|
5948
|
+
}
|
|
5949
|
+
}
|
|
5950
|
+
else {
|
|
5951
|
+
console.log(`\n ${colors.green}No security issues found.${RESET()}`);
|
|
5952
|
+
}
|
|
5953
|
+
// Step 3: Offer to share with community (interactive only)
|
|
5954
|
+
if (process.stdin.isTTY && !globalCiMode) {
|
|
5955
|
+
const readline = await Promise.resolve().then(() => __importStar(require('node:readline')));
|
|
5956
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
5957
|
+
const answer = await new Promise(resolve => {
|
|
5958
|
+
rl.question(`\n Share this scan with the community? [Y/n] `, resolve);
|
|
5959
|
+
});
|
|
5960
|
+
rl.close();
|
|
5961
|
+
if (answer.trim().toLowerCase() !== 'n') {
|
|
5962
|
+
const ok = await publishToRegistry(name, result);
|
|
5963
|
+
if (ok) {
|
|
5964
|
+
console.error(` ${colors.green}Shared. Thank you for building trust in AI.${RESET()}`);
|
|
5965
|
+
}
|
|
5966
|
+
else {
|
|
5967
|
+
console.error(` ${colors.dim}Could not reach registry. Scan saved locally.${RESET()}`);
|
|
5968
|
+
}
|
|
5969
|
+
}
|
|
5970
|
+
}
|
|
5971
|
+
console.log(`\n Full project scan: ${CLI_PREFIX} secure <dir>`);
|
|
5972
|
+
console.log();
|
|
5973
|
+
if (critical.length > 0 || high.length > 0)
|
|
5974
|
+
process.exit(1);
|
|
5975
|
+
}
|
|
5976
|
+
catch (err) {
|
|
5977
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
5978
|
+
// Clean npm error messages
|
|
5979
|
+
if (message.includes('404') || message.includes('Not Found')) {
|
|
5980
|
+
console.error(`Error: Package "${name}" not found on npm.`);
|
|
5981
|
+
}
|
|
5982
|
+
else {
|
|
5983
|
+
console.error(`Error: ${message}`);
|
|
5984
|
+
}
|
|
5985
|
+
process.exit(1);
|
|
5986
|
+
}
|
|
5987
|
+
finally {
|
|
5988
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
5989
|
+
}
|
|
5990
|
+
}
|
|
5732
5991
|
// Self-securing: verify own integrity before running any command
|
|
5733
5992
|
// A security tool that doesn't verify itself is worse than no security tool
|
|
5734
5993
|
(async () => {
|