hackmyagent 0.17.0 → 0.17.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/.integrity-manifest.json +1 -1
- package/dist/checker/skill-identifier.d.ts.map +1 -1
- package/dist/checker/skill-identifier.js +4 -3
- package/dist/checker/skill-identifier.js.map +1 -1
- package/dist/cli.js +201 -58
- package/dist/cli.js.map +1 -1
- package/dist/hardening/index.d.ts +1 -1
- package/dist/hardening/index.d.ts.map +1 -1
- package/dist/hardening/index.js +2 -1
- package/dist/hardening/index.js.map +1 -1
- package/dist/hardening/scanner.d.ts +16 -0
- package/dist/hardening/scanner.d.ts.map +1 -1
- package/dist/hardening/scanner.js +28 -28
- package/dist/hardening/scanner.js.map +1 -1
- package/dist/hardening/taxonomy.d.ts +5 -0
- package/dist/hardening/taxonomy.d.ts.map +1 -1
- package/dist/hardening/taxonomy.js +66 -0
- package/dist/hardening/taxonomy.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/nanomind-core/analyzers/credential-analyzer.js +6 -0
- package/dist/nanomind-core/analyzers/credential-analyzer.js.map +1 -1
- package/dist/nanomind-core/compiler/semantic-compiler.js +7 -2
- package/dist/nanomind-core/compiler/semantic-compiler.js.map +1 -1
- package/dist/nanomind-core/index.d.ts +2 -0
- package/dist/nanomind-core/index.d.ts.map +1 -1
- package/dist/nanomind-core/index.js +12 -2
- package/dist/nanomind-core/index.js.map +1 -1
- package/dist/nanomind-core/inference/analm-infer.py +104 -0
- package/dist/nanomind-core/inference/security-analyst.d.ts +95 -0
- package/dist/nanomind-core/inference/security-analyst.d.ts.map +1 -0
- package/dist/nanomind-core/inference/security-analyst.js +372 -0
- package/dist/nanomind-core/inference/security-analyst.js.map +1 -0
- package/dist/nanomind-core/orchestrate.d.ts +7 -0
- package/dist/nanomind-core/orchestrate.d.ts.map +1 -1
- package/dist/nanomind-core/orchestrate.js +68 -2
- package/dist/nanomind-core/orchestrate.js.map +1 -1
- package/dist/nanomind-core/scanner-bridge.d.ts.map +1 -1
- package/dist/nanomind-core/scanner-bridge.js +33 -5
- package/dist/nanomind-core/scanner-bridge.js.map +1 -1
- package/dist/registry/client.d.ts +5 -0
- package/dist/registry/client.d.ts.map +1 -1
- package/dist/registry/client.js +10 -1
- package/dist/registry/client.js.map +1 -1
- package/dist/registry/publish.d.ts.map +1 -1
- package/dist/registry/publish.js +3 -4
- package/dist/registry/publish.js.map +1 -1
- package/dist/scanner/external-scanner.js +2 -2
- package/dist/scanner/external-scanner.js.map +1 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -267,9 +267,23 @@ Examples:
|
|
|
267
267
|
return;
|
|
268
268
|
}
|
|
269
269
|
// npm package name: download, run full HMA scan, clean up
|
|
270
|
+
// On npm 404, fall through to skill check (skill identifiers look like @scope/name)
|
|
270
271
|
if (looksLikeNpmPackage(skill)) {
|
|
271
|
-
|
|
272
|
-
|
|
272
|
+
try {
|
|
273
|
+
await checkNpmPackage(skill, options);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
catch (npmErr) {
|
|
277
|
+
if (npmErr instanceof Error && npmErr.name === 'NpmNotFoundError') {
|
|
278
|
+
// Not on npm — fall through to skill check
|
|
279
|
+
if (!options.json && !globalCiMode) {
|
|
280
|
+
console.error(`Package "${skill}" not found on npm. Trying as skill identifier...`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
throw npmErr; // Re-throw non-404 errors
|
|
285
|
+
}
|
|
286
|
+
}
|
|
273
287
|
}
|
|
274
288
|
// --rescan only applies to targets that otherwise hit the registry cache.
|
|
275
289
|
// For skill identifiers we fall through to the registry lookup below.
|
|
@@ -494,22 +508,10 @@ function displayUnifiedCheck(opts) {
|
|
|
494
508
|
guidance: f.guidance,
|
|
495
509
|
attackClass: f.attackClass,
|
|
496
510
|
}));
|
|
497
|
-
//
|
|
498
|
-
const
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
return cat !== 'governance' && cat !== 'injection-hardening' && cat !== 'trust-hierarchy'
|
|
502
|
-
&& !id.startsWith('AST-GOV') && !id.startsWith('AST-GOVERN')
|
|
503
|
-
&& !id.startsWith('AST-PROMPT') && !id.startsWith('AST-HEARTBEAT');
|
|
504
|
-
});
|
|
505
|
-
if (hasCodeFindings) {
|
|
506
|
-
score = critical > 0 ? Math.max(10, 100 - critical * 20 - high * 10 - medium * 5) : high > 0 ? Math.max(30, 100 - high * 10 - medium * 5) : Math.max(50, 100 - medium * 5 - low * 2);
|
|
507
|
-
}
|
|
508
|
-
else {
|
|
509
|
-
// Governance-only: floor at 25, each finding costs less
|
|
510
|
-
score = Math.max(25, 100 - critical * 8 - high * 5 - medium * 3 - low * 1);
|
|
511
|
-
}
|
|
512
|
-
maxScore = 100;
|
|
511
|
+
// Use the canonical scoring formula (exponential decay + 0.4x governance weight)
|
|
512
|
+
const scoreResult = (0, index_1.calculateSecurityScore)(issues);
|
|
513
|
+
score = scoreResult.score;
|
|
514
|
+
maxScore = scoreResult.maxScore;
|
|
513
515
|
}
|
|
514
516
|
else if (registry?.found) {
|
|
515
517
|
score = Math.round(registry.trustScore * 100);
|
|
@@ -2266,6 +2268,7 @@ Examples:
|
|
|
2266
2268
|
.option('-l, --level <level>', 'Benchmark level: L1 (Essential), L2 (Standard), L3 (Hardened)', 'L1')
|
|
2267
2269
|
.option('-c, --category <name>', 'Filter to specific benchmark category')
|
|
2268
2270
|
.option('--deep', 'Maximum analysis: static + semantic + behavioral simulation + adaptive attacks (~30s per file)')
|
|
2271
|
+
.option('--analm', 'AI-powered threat analysis using AnaLM (requires analm setup)')
|
|
2269
2272
|
.option('--static-only', 'Disable semantic analysis and simulation (static checks only, fast, deterministic)')
|
|
2270
2273
|
.option('--scan-depth <depth>', 'CAAT scan depth: quick (config+creds only), standard (default), deep (+ simulation)', 'standard')
|
|
2271
2274
|
.option('--ci-publish', 'Submit scan results to registry CI endpoint (requires CI_SCAN_HMAC_SECRET env)')
|
|
@@ -2358,7 +2361,7 @@ Examples:
|
|
|
2358
2361
|
catch { /* daemon not installed */ }
|
|
2359
2362
|
}
|
|
2360
2363
|
const onProgress = format === 'text'
|
|
2361
|
-
? (msg) => process.stdout.write(msg)
|
|
2364
|
+
? (msg) => process.stdout.write(msg.endsWith('\n') ? msg : msg + '\n')
|
|
2362
2365
|
: undefined;
|
|
2363
2366
|
// Show analysis mode to user
|
|
2364
2367
|
if (format === 'text') {
|
|
@@ -2394,15 +2397,16 @@ Examples:
|
|
|
2394
2397
|
const scanDurationMs = Date.now() - scanStartMs;
|
|
2395
2398
|
// NanoMind Semantic Compiler: AST-based analysis runs alongside static checks
|
|
2396
2399
|
// Defense-in-depth: static findings can NEVER be suppressed, only upgraded
|
|
2400
|
+
const { orchestrateNanoMind } = await Promise.resolve().then(() => __importStar(require('./nanomind-core/orchestrate.js')));
|
|
2401
|
+
const existingFindings = result.allFindings || result.findings || [];
|
|
2402
|
+
const nmResult = await orchestrateNanoMind(targetDir, existingFindings, {
|
|
2403
|
+
staticOnly: isStaticOnly,
|
|
2404
|
+
ci: options.ci,
|
|
2405
|
+
deep: isDeep,
|
|
2406
|
+
analm: options.analm,
|
|
2407
|
+
silent: format !== 'text',
|
|
2408
|
+
});
|
|
2397
2409
|
{
|
|
2398
|
-
const { orchestrateNanoMind } = await Promise.resolve().then(() => __importStar(require('./nanomind-core/orchestrate.js')));
|
|
2399
|
-
const existingFindings = result.allFindings || result.findings || [];
|
|
2400
|
-
const nmResult = await orchestrateNanoMind(targetDir, existingFindings, {
|
|
2401
|
-
staticOnly: isStaticOnly,
|
|
2402
|
-
ci: options.ci,
|
|
2403
|
-
deep: isDeep,
|
|
2404
|
-
silent: format !== 'text',
|
|
2405
|
-
});
|
|
2406
2410
|
// Re-apply all filters after NanoMind merge (merge uses allFindings which is unfiltered)
|
|
2407
2411
|
const refiltered = await scanner.reapplyIgnoreFilters(nmResult.mergedFindings, targetDir);
|
|
2408
2412
|
if (result.allFindings) {
|
|
@@ -2591,7 +2595,10 @@ Examples:
|
|
|
2591
2595
|
publishStatus = { success: false, error: msg };
|
|
2592
2596
|
}
|
|
2593
2597
|
}
|
|
2594
|
-
const
|
|
2598
|
+
const jsonBase = nmResult.analystFindings?.length
|
|
2599
|
+
? { ...result, analystFindings: nmResult.analystFindings }
|
|
2600
|
+
: result;
|
|
2601
|
+
const jsonOutput = publishStatus ? { ...jsonBase, publish: publishStatus } : jsonBase;
|
|
2595
2602
|
if (options.output) {
|
|
2596
2603
|
require('fs').writeFileSync(options.output, JSON.stringify(jsonOutput, null, 2) + '\n');
|
|
2597
2604
|
console.error(`Report written to ${options.output}`);
|
|
@@ -2746,6 +2753,42 @@ Examples:
|
|
|
2746
2753
|
if (summaryParts.length > 0) {
|
|
2747
2754
|
console.log(`${summaryParts.join(' | ')}\n`);
|
|
2748
2755
|
}
|
|
2756
|
+
// Analyst findings (--analyze)
|
|
2757
|
+
if (nmResult.analystFindings && nmResult.analystFindings.length > 0) {
|
|
2758
|
+
console.log(`${colors.cyan}--- AnaLM Analysis ---${RESET()}\n`);
|
|
2759
|
+
for (const af of nmResult.analystFindings) {
|
|
2760
|
+
const r = af.result;
|
|
2761
|
+
if (af.taskType === 'threatAnalysis') {
|
|
2762
|
+
const level = String(r.threatLevel ?? 'unknown').toUpperCase();
|
|
2763
|
+
const levelColor = level === 'CRITICAL' || level === 'HIGH' ? colors.red : level === 'MEDIUM' ? colors.yellow : colors.dim;
|
|
2764
|
+
console.log(` ${levelColor}${level}${RESET()} ${r.attackVector ?? ''}`);
|
|
2765
|
+
if (r.description)
|
|
2766
|
+
console.log(` ${r.description}`);
|
|
2767
|
+
if (Array.isArray(r.mitigations) && r.mitigations.length > 0) {
|
|
2768
|
+
for (const m of r.mitigations) {
|
|
2769
|
+
console.log(` ${colors.cyan}Fix:${RESET()} ${m}`);
|
|
2770
|
+
}
|
|
2771
|
+
}
|
|
2772
|
+
}
|
|
2773
|
+
else if (af.taskType === 'credentialContextClassification') {
|
|
2774
|
+
const cls = String(r.classification ?? 'unknown');
|
|
2775
|
+
const clsColor = cls === 'real' ? colors.red : cls === 'test' || cls === 'example' ? colors.green : colors.yellow;
|
|
2776
|
+
console.log(` Credential: ${clsColor}${cls}${RESET()}`);
|
|
2777
|
+
if (r.reasoning)
|
|
2778
|
+
console.log(` ${r.reasoning}`);
|
|
2779
|
+
}
|
|
2780
|
+
else {
|
|
2781
|
+
// Generic display for other task types
|
|
2782
|
+
console.log(` ${af.taskType}: ${JSON.stringify(r)}`);
|
|
2783
|
+
}
|
|
2784
|
+
console.log(` ${colors.dim}Confidence: ${Math.round(af.confidence * 100)}% | ${af.modelVersion} (${af.durationMs}ms)${RESET()}`);
|
|
2785
|
+
console.log();
|
|
2786
|
+
}
|
|
2787
|
+
}
|
|
2788
|
+
// Analyst hint (shown when model is available but --analyze not used)
|
|
2789
|
+
if (nmResult.analystHint && issues.length > 0) {
|
|
2790
|
+
console.log(`${colors.dim}Tip: ${nmResult.analystHint}${RESET()}\n`);
|
|
2791
|
+
}
|
|
2749
2792
|
// Dry-run summary
|
|
2750
2793
|
if (result.dryRun) {
|
|
2751
2794
|
const wouldFixCount = issues.filter((f) => f.wouldFix).length;
|
|
@@ -2796,7 +2839,8 @@ Examples:
|
|
|
2796
2839
|
console.error('Error: --registry-key or REGISTRY_API_KEY env is required when using --version-id');
|
|
2797
2840
|
process.exit(1);
|
|
2798
2841
|
}
|
|
2799
|
-
const
|
|
2842
|
+
const atcToken = process.env.ATC_TOKEN;
|
|
2843
|
+
const client = new core.RegistryClient({ registryUrl, apiKey: registryKey, atcToken });
|
|
2800
2844
|
const payload = core.buildScanReport(options.versionId, result.findings);
|
|
2801
2845
|
await client.reportScanResult(payload);
|
|
2802
2846
|
console.log(`Registry: scan results reported for version ${options.versionId}`);
|
|
@@ -3240,7 +3284,7 @@ Examples:
|
|
|
3240
3284
|
.argument('<target>', 'Target hostname or IP address')
|
|
3241
3285
|
.option('--json', 'Output as JSON (for scripting/CI)')
|
|
3242
3286
|
.option('-p, --ports <ports>', 'Comma-separated ports to scan (default: common MCP ports)')
|
|
3243
|
-
.option('-t, --timeout <ms>', 'Connection timeout in milliseconds', '
|
|
3287
|
+
.option('-t, --timeout <ms>', 'Connection timeout in milliseconds', '2000')
|
|
3244
3288
|
.option('-v, --verbose', 'Show detailed finding information')
|
|
3245
3289
|
.action(async (target, options) => {
|
|
3246
3290
|
try {
|
|
@@ -3256,11 +3300,11 @@ Examples:
|
|
|
3256
3300
|
`\n`);
|
|
3257
3301
|
process.exit(1);
|
|
3258
3302
|
}
|
|
3259
|
-
const timeoutMs = parseInt(options.timeout ?? '
|
|
3303
|
+
const timeoutMs = parseInt(options.timeout ?? '2000', 10);
|
|
3260
3304
|
const customPorts = options.ports
|
|
3261
3305
|
? options.ports.split(',').map((p) => parseInt(p.trim(), 10))
|
|
3262
3306
|
: undefined;
|
|
3263
|
-
const portCount = customPorts?.length ??
|
|
3307
|
+
const portCount = customPorts?.length ?? 2;
|
|
3264
3308
|
if (!options.json) {
|
|
3265
3309
|
console.log(`\nScanning ${target} (${portCount} ports, ${timeoutMs}ms timeout)...\n`);
|
|
3266
3310
|
}
|
|
@@ -3566,7 +3610,8 @@ Examples:
|
|
|
3566
3610
|
console.error('Error: --registry-key or REGISTRY_API_KEY env is required when using --version-id');
|
|
3567
3611
|
process.exit(1);
|
|
3568
3612
|
}
|
|
3569
|
-
const
|
|
3613
|
+
const atcToken = process.env.ATC_TOKEN;
|
|
3614
|
+
const client = new core.RegistryClient({ registryUrl, apiKey: registryKey, atcToken });
|
|
3570
3615
|
const payload = core.buildAttackReport(options.versionId, report);
|
|
3571
3616
|
await client.reportScanResult(payload);
|
|
3572
3617
|
console.log(`Registry: attack results reported for version ${options.versionId}`);
|
|
@@ -5575,6 +5620,28 @@ Examples:
|
|
|
5575
5620
|
if (opts.json) {
|
|
5576
5621
|
writeJsonStdout(result);
|
|
5577
5622
|
}
|
|
5623
|
+
else if (result.found) {
|
|
5624
|
+
// Use the unified display (same as `check --no-scan`) for visual consistency
|
|
5625
|
+
const registryData = {
|
|
5626
|
+
found: true,
|
|
5627
|
+
name: result.name,
|
|
5628
|
+
trustScore: result.trustScore,
|
|
5629
|
+
trustLevel: result.trustLevel,
|
|
5630
|
+
verdict: result.verdict,
|
|
5631
|
+
scanStatus: result.scanStatus,
|
|
5632
|
+
lastScannedAt: result.lastScannedAt,
|
|
5633
|
+
packageType: result.packageType,
|
|
5634
|
+
recommendation: result.recommendation,
|
|
5635
|
+
cveCount: result.cveCount,
|
|
5636
|
+
communityScans: result.communityScans,
|
|
5637
|
+
dependencies: result.dependencies ? {
|
|
5638
|
+
totalDeps: result.dependencies.totalDeps,
|
|
5639
|
+
vulnerableDeps: result.dependencies.vulnerableDeps,
|
|
5640
|
+
minTrustLevel: result.dependencies.minTrustLevel,
|
|
5641
|
+
} : undefined,
|
|
5642
|
+
};
|
|
5643
|
+
displayUnifiedCheck({ name: packageName, registry: registryData, verbose: false });
|
|
5644
|
+
}
|
|
5578
5645
|
else {
|
|
5579
5646
|
process.stdout.write(formatTrustCheck(result));
|
|
5580
5647
|
}
|
|
@@ -5593,7 +5660,7 @@ program
|
|
|
5593
5660
|
.option('-d, --directory <dir>', 'Scan a specific directory to collect check metadata from findings')
|
|
5594
5661
|
.option('--json', 'Output as JSON (default)')
|
|
5595
5662
|
.action(async (options) => {
|
|
5596
|
-
const { getAttackClass, getTaxonomyMap } = require('./hardening/taxonomy');
|
|
5663
|
+
const { getAttackClass, getTaxonomyMap, getCheckSeverity } = require('./hardening/taxonomy');
|
|
5597
5664
|
// Build static registry from taxonomy map (covers all known checks)
|
|
5598
5665
|
const taxMap = getTaxonomyMap();
|
|
5599
5666
|
const metadata = {};
|
|
@@ -5605,7 +5672,7 @@ program
|
|
|
5605
5672
|
name: checkId,
|
|
5606
5673
|
category: prefix.toLowerCase(),
|
|
5607
5674
|
attackClass: taxMap[checkId] || '',
|
|
5608
|
-
severity:
|
|
5675
|
+
severity: getCheckSeverity(checkId),
|
|
5609
5676
|
};
|
|
5610
5677
|
}
|
|
5611
5678
|
// If a directory is provided, enrich with actual finding data (names, severity, etc.)
|
|
@@ -6094,6 +6161,63 @@ program
|
|
|
6094
6161
|
}
|
|
6095
6162
|
console.log(`\nYour skill is ready. Verify security with: hackmyagent secure ${outputDir}/`);
|
|
6096
6163
|
});
|
|
6164
|
+
// analm: manage the AnaLM generative model
|
|
6165
|
+
const analmCmd = program
|
|
6166
|
+
.command('analm')
|
|
6167
|
+
.description('Manage the AnaLM model for AI-powered security analysis');
|
|
6168
|
+
analmCmd
|
|
6169
|
+
.command('setup')
|
|
6170
|
+
.description('Download the AnaLM model')
|
|
6171
|
+
.action(async () => {
|
|
6172
|
+
const { getAnalystStatus, setupAnalystModel } = await Promise.resolve().then(() => __importStar(require('./nanomind-core/inference/security-analyst.js')));
|
|
6173
|
+
const status = await getAnalystStatus();
|
|
6174
|
+
console.log('AnaLM (NanoMind Security Analyst)');
|
|
6175
|
+
console.log(` Platform: ${status.platform}`);
|
|
6176
|
+
console.log(` Backend: ${status.backend === 'none' ? 'not available' : status.backend}`);
|
|
6177
|
+
console.log(` Model: ${status.modelCached ? 'cached' : 'not downloaded'}`);
|
|
6178
|
+
console.log('');
|
|
6179
|
+
if (status.backend === 'none') {
|
|
6180
|
+
console.log('No supported inference backend found.');
|
|
6181
|
+
if (process.platform !== 'darwin') {
|
|
6182
|
+
console.log('Currently requires Apple Silicon Mac with MLX.');
|
|
6183
|
+
console.log('Cross-platform support (llama.cpp/GGUF) coming soon.');
|
|
6184
|
+
}
|
|
6185
|
+
else {
|
|
6186
|
+
console.log('Install uv: curl -LsSf https://astral.sh/uv/install.sh | sh');
|
|
6187
|
+
}
|
|
6188
|
+
process.exit(1);
|
|
6189
|
+
}
|
|
6190
|
+
if (status.modelCached) {
|
|
6191
|
+
console.log('Model already downloaded. Use --analm with any scan command.');
|
|
6192
|
+
return;
|
|
6193
|
+
}
|
|
6194
|
+
const ok = await setupAnalystModel(false);
|
|
6195
|
+
if (!ok)
|
|
6196
|
+
process.exit(1);
|
|
6197
|
+
});
|
|
6198
|
+
analmCmd
|
|
6199
|
+
.command('status')
|
|
6200
|
+
.description('Check the status of AnaLM model and runtime')
|
|
6201
|
+
.action(async () => {
|
|
6202
|
+
const { getAnalystStatus } = await Promise.resolve().then(() => __importStar(require('./nanomind-core/inference/security-analyst.js')));
|
|
6203
|
+
const status = await getAnalystStatus();
|
|
6204
|
+
console.log('AnaLM (NanoMind Security Analyst)');
|
|
6205
|
+
console.log(` Platform: ${status.platform}`);
|
|
6206
|
+
console.log(` Backend: ${status.backend === 'none' ? `${colors.red}not available${RESET()}` : `${colors.green}${status.backend}${RESET()}`}`);
|
|
6207
|
+
console.log(` Model: ${status.modelCached ? `${colors.green}cached${RESET()}` : `${colors.yellow}not downloaded${RESET()}`}`);
|
|
6208
|
+
console.log(` Ready: ${status.available ? `${colors.green}yes${RESET()}` : `${colors.yellow}no${RESET()}`}`);
|
|
6209
|
+
console.log('');
|
|
6210
|
+
if (status.available) {
|
|
6211
|
+
console.log('Use --analm with any scan command for AI-powered analysis.');
|
|
6212
|
+
console.log(` Example: hackmyagent secure ./my-agent --analm`);
|
|
6213
|
+
}
|
|
6214
|
+
else if (status.backend !== 'none') {
|
|
6215
|
+
console.log(`Run: hackmyagent analm setup`);
|
|
6216
|
+
}
|
|
6217
|
+
else if (process.platform !== 'darwin') {
|
|
6218
|
+
console.log('Cross-platform support (llama.cpp/GGUF) coming soon.');
|
|
6219
|
+
}
|
|
6220
|
+
});
|
|
6097
6221
|
// ============================================================================
|
|
6098
6222
|
// npm package scanning helpers (used by `check <package>`)
|
|
6099
6223
|
// ============================================================================
|
|
@@ -6438,6 +6562,9 @@ const AI_TOOLING_PATH_PATTERNS = [
|
|
|
6438
6562
|
/^\.aider/,
|
|
6439
6563
|
/^\.copilot\//,
|
|
6440
6564
|
/^\.github\/copilot/,
|
|
6565
|
+
/\.env\.example$/i, // Example env files are not real credentials
|
|
6566
|
+
/\.env\.sample$/i,
|
|
6567
|
+
/\.env\.template$/i,
|
|
6441
6568
|
];
|
|
6442
6569
|
/** Governance-related categories/checkId prefixes that are noise on AI tooling files */
|
|
6443
6570
|
const GOVERNANCE_CATEGORIES = new Set([
|
|
@@ -6473,13 +6600,12 @@ function filterLocalOnlyFindings(result, scanner) {
|
|
|
6473
6600
|
// Remove local-only categories (git, permissions, env, etc.)
|
|
6474
6601
|
if (PACKAGE_SCAN_LOCAL_ONLY_CATEGORIES.has(f.category))
|
|
6475
6602
|
return false;
|
|
6476
|
-
//
|
|
6477
|
-
|
|
6478
|
-
|
|
6479
|
-
|
|
6480
|
-
|
|
6481
|
-
|
|
6482
|
-
}
|
|
6603
|
+
// Exclude ALL findings on AI tooling files (CLAUDE.md, .claude/, .cursorrules, etc.)
|
|
6604
|
+
// These files contain instructions to AI assistants, not package source code.
|
|
6605
|
+
// Credential patterns, injection patterns, and governance findings in these
|
|
6606
|
+
// files are false positives — they describe security practices, not vulnerabilities.
|
|
6607
|
+
if (f.file && isAiToolingFile(f.file))
|
|
6608
|
+
return false;
|
|
6483
6609
|
return true;
|
|
6484
6610
|
});
|
|
6485
6611
|
// Demote test file findings to low severity (test code patterns are
|
|
@@ -7054,7 +7180,35 @@ async function checkNpmPackage(name, options) {
|
|
|
7054
7180
|
const { stdout } = await execAsync('npm', ['pack', name, '--pack-destination', tempDir], { timeout: 60000 });
|
|
7055
7181
|
const tarball = stdout.trim().split('\n').pop();
|
|
7056
7182
|
await execAsync('tar', ['xzf', join(tempDir, tarball), '-C', tempDir], { timeout: 30000 });
|
|
7057
|
-
|
|
7183
|
+
// npm tarballs normally extract to 'package/', but some packages (e.g. @types/*)
|
|
7184
|
+
// may use a different directory name. Detect the actual extracted directory.
|
|
7185
|
+
const { readdir, stat } = await Promise.resolve().then(() => __importStar(require('node:fs/promises')));
|
|
7186
|
+
let packageDir = join(tempDir, 'package');
|
|
7187
|
+
try {
|
|
7188
|
+
await stat(packageDir);
|
|
7189
|
+
}
|
|
7190
|
+
catch {
|
|
7191
|
+
// 'package/' doesn't exist — find the extracted directory (skip the .tgz file)
|
|
7192
|
+
const entries = await readdir(tempDir);
|
|
7193
|
+
const dirs = [];
|
|
7194
|
+
for (const entry of entries) {
|
|
7195
|
+
if (entry.endsWith('.tgz') || entry.endsWith('.tar.gz'))
|
|
7196
|
+
continue;
|
|
7197
|
+
const s = await stat(join(tempDir, entry));
|
|
7198
|
+
if (s.isDirectory())
|
|
7199
|
+
dirs.push(entry);
|
|
7200
|
+
}
|
|
7201
|
+
if (dirs.length === 1) {
|
|
7202
|
+
packageDir = join(tempDir, dirs[0]);
|
|
7203
|
+
}
|
|
7204
|
+
else if (dirs.length === 0) {
|
|
7205
|
+
throw new Error(`Tarball extraction produced no directory in ${tempDir}`);
|
|
7206
|
+
}
|
|
7207
|
+
else {
|
|
7208
|
+
// Multiple dirs — pick the first non-hidden one
|
|
7209
|
+
packageDir = join(tempDir, dirs.find(d => !d.startsWith('.')) || dirs[0]);
|
|
7210
|
+
}
|
|
7211
|
+
}
|
|
7058
7212
|
// Run full HMA scan + NanoMind (same pipeline as `secure`)
|
|
7059
7213
|
const scanner = new index_1.HardeningScanner();
|
|
7060
7214
|
const result = await scanner.scan({ targetDir: packageDir, autoFix: false });
|
|
@@ -7137,21 +7291,10 @@ async function checkNpmPackage(name, options) {
|
|
|
7137
7291
|
const message = err instanceof Error ? err.message : String(err);
|
|
7138
7292
|
// Clean npm error messages
|
|
7139
7293
|
if (message.includes('404') || message.includes('Not Found')) {
|
|
7140
|
-
|
|
7141
|
-
|
|
7142
|
-
|
|
7143
|
-
|
|
7144
|
-
if (suggestions.length > 0) {
|
|
7145
|
-
console.error(`\nDid you mean?`);
|
|
7146
|
-
for (const s of suggestions) {
|
|
7147
|
-
console.error(` ${s}`);
|
|
7148
|
-
}
|
|
7149
|
-
console.error();
|
|
7150
|
-
}
|
|
7151
|
-
}
|
|
7152
|
-
catch {
|
|
7153
|
-
// Search failed — just show the original error
|
|
7154
|
-
}
|
|
7294
|
+
// Throw a typed error so the router can fall through to skill check
|
|
7295
|
+
const notFound = new Error(`NPM_NOT_FOUND:${name}`);
|
|
7296
|
+
notFound.name = 'NpmNotFoundError';
|
|
7297
|
+
throw notFound;
|
|
7155
7298
|
}
|
|
7156
7299
|
else {
|
|
7157
7300
|
console.error(`Error: ${message}`);
|