@automagik/genie 4.260424.18 → 4.260424.19
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "genie",
|
|
3
|
-
"version": "4.260424.
|
|
3
|
+
"version": "4.260424.19",
|
|
4
4
|
"description": "Human-AI partnership for Claude Code. Share a terminal, orchestrate workers, evolve together. Brainstorm ideas, turn them into wishes, execute with /work, validate with /review, and ship as one team.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Namastex Labs"
|
package/scripts/sec-fix.cjs
CHANGED
|
@@ -365,7 +365,63 @@ function renderAuditRow(severity, verb, target, recovery) {
|
|
|
365
365
|
process.stderr.write(` ${TTY.dim}recovery: ${TTY.reset}${TTY.cyan}${recovery}${TTY.reset}\n\n`);
|
|
366
366
|
}
|
|
367
367
|
|
|
368
|
-
function
|
|
368
|
+
function renderBreachImpact(envelope) {
|
|
369
|
+
const breach = envelope?.breachImpact || { enabled: false };
|
|
370
|
+
if (!breach.enabled || (breach.likelyStolen || []).length === 0) return;
|
|
371
|
+
|
|
372
|
+
process.stderr.write('\n');
|
|
373
|
+
hrule('═', TTY.red + TTY.bold);
|
|
374
|
+
banner('☠ BREACH IMPACT — RETRACING THE WORM ☠', SEVERITY.CRITICAL);
|
|
375
|
+
hrule('═', TTY.red + TTY.bold);
|
|
376
|
+
process.stderr.write('\n');
|
|
377
|
+
|
|
378
|
+
process.stderr.write(
|
|
379
|
+
` ${TTY.dim}Exfil channel:${TTY.reset} ${TTY.red}${TTY.bold}${breach.exfilChannel.host}${TTY.reset} ${breach.exfilChannel.paths.join(', ')}\n`,
|
|
380
|
+
);
|
|
381
|
+
if (breach.compromiseWindow) {
|
|
382
|
+
process.stderr.write(
|
|
383
|
+
` ${TTY.dim}Window:${TTY.reset} ${breach.compromiseWindow.firstEvidence} .. ${breach.compromiseWindow.lastEvidence}\n`,
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
if ((breach.compromisedInstallPaths || []).length > 0) {
|
|
387
|
+
process.stderr.write(` ${TTY.dim}Install path:${TTY.reset} ${breach.compromisedInstallPaths[0]}\n`);
|
|
388
|
+
}
|
|
389
|
+
process.stderr.write('\n');
|
|
390
|
+
process.stderr.write(
|
|
391
|
+
` ${TTY.bold}${TTY.red}The env-compat.cjs payload ran as this user during the window.${TTY.reset}\n`,
|
|
392
|
+
);
|
|
393
|
+
process.stderr.write(` ${TTY.dim}These credentials were readable to it — assume stolen:${TTY.reset}\n\n`);
|
|
394
|
+
|
|
395
|
+
for (const item of breach.rotationChecklist || []) {
|
|
396
|
+
const sev =
|
|
397
|
+
item.severity === 'CRITICAL'
|
|
398
|
+
? SEVERITY.CRITICAL.paint(` ${item.severity} `)
|
|
399
|
+
: SEVERITY.DESTRUCTIVE.paint(` ${item.severity} `);
|
|
400
|
+
process.stderr.write(` ${sev} ${TTY.bold}${item.category}${TTY.reset}\n`);
|
|
401
|
+
for (const p of item.paths || []) process.stderr.write(` ${TTY.dim}path:${TTY.reset} ${p}\n`);
|
|
402
|
+
process.stderr.write(` ${TTY.dim}why:${TTY.reset} ${item.reason}\n`);
|
|
403
|
+
if (item.rotationUrl) {
|
|
404
|
+
process.stderr.write(
|
|
405
|
+
` ${TTY.cyan}rotate:${TTY.reset} ${TTY.underline}${item.rotationUrl}${TTY.reset}\n`,
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
process.stderr.write('\n');
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if ((breach.runningProcessesDuringWindow || []).length > 0) {
|
|
412
|
+
process.stderr.write(` ${TTY.bold}Processes that ran the compromised binary:${TTY.reset}\n`);
|
|
413
|
+
for (const proc of breach.runningProcessesDuringWindow) {
|
|
414
|
+
process.stderr.write(
|
|
415
|
+
` pid=${proc.pid} elapsed=${proc.elapsed} ${TTY.dim}${(proc.command || '').slice(0, 90)}${TTY.reset}\n`,
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
process.stderr.write('\n');
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function showPlanSummary(plan, options, envelope) {
|
|
423
|
+
renderBreachImpact(envelope);
|
|
424
|
+
|
|
369
425
|
process.stderr.write('\n');
|
|
370
426
|
hrule('═', TTY.red + TTY.bold);
|
|
371
427
|
banner('⚠ DESTRUCTIVE OPERATIONS AUDIT — REVIEW BEFORE ACCEPTING ⚠', SEVERITY.DESTRUCTIVE);
|
|
@@ -668,7 +724,7 @@ function main() {
|
|
|
668
724
|
|
|
669
725
|
const envelope = runScan();
|
|
670
726
|
const plan = classifyEnvelope(envelope);
|
|
671
|
-
const somethingToDo = showPlanSummary(plan, options);
|
|
727
|
+
const somethingToDo = showPlanSummary(plan, options, envelope);
|
|
672
728
|
|
|
673
729
|
if (!somethingToDo) {
|
|
674
730
|
if (options.json) {
|
package/scripts/sec-scan.cjs
CHANGED
|
@@ -364,9 +364,24 @@ const TEXT_MATCHERS = [
|
|
|
364
364
|
regex: /\b(?:node|bun|bash|sh)\b[^\n]{0,200}env-compat\.(?:cjs|js)\b/i,
|
|
365
365
|
},
|
|
366
366
|
{
|
|
367
|
-
|
|
367
|
+
// Hard evidence: curl/wget/fetch to the exact exfil endpoint path.
|
|
368
|
+
// This is what the CanisterWorm payload uses to upload stolen data
|
|
369
|
+
// (POST /v1/telemetry and POST /v1/drop). Matching these = compromise.
|
|
370
|
+
label: 'network:curl-wget IOC-exfil',
|
|
368
371
|
category: 'network',
|
|
369
|
-
regex:
|
|
372
|
+
regex:
|
|
373
|
+
/\b(?:curl|wget|fetch|Invoke-WebRequest)\b[^\n]{0,200}(?:telemetry\.api-monitor\.com\/v1\/(?:telemetry|drop)|raw\.icp0\.io\/drop)/i,
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
// Soft evidence: bare-host mention of the exfil domain WITHOUT the
|
|
377
|
+
// /v1/ path. Almost always an incident responder (or documentation)
|
|
378
|
+
// probing the host, not the payload itself — the payload never runs
|
|
379
|
+
// a bare `curl <host>` because there's no endpoint that would accept
|
|
380
|
+
// the uploaded payload. Classify as 'probe' so it shows in the
|
|
381
|
+
// report but doesn't elevate the suspicion score.
|
|
382
|
+
label: 'network:exfil-host-probe',
|
|
383
|
+
category: 'probe',
|
|
384
|
+
regex: /\b(?:curl|wget|fetch|Invoke-WebRequest)\b[^\n]{0,200}telemetry\.api-monitor\.com(?!\/v1\/)/i,
|
|
370
385
|
},
|
|
371
386
|
];
|
|
372
387
|
|
|
@@ -1415,6 +1430,7 @@ function collectTextIndicators(text) {
|
|
|
1415
1430
|
installCommands: [],
|
|
1416
1431
|
executionCommands: [],
|
|
1417
1432
|
networkCommands: [],
|
|
1433
|
+
probeMatches: [],
|
|
1418
1434
|
allMatches: [],
|
|
1419
1435
|
};
|
|
1420
1436
|
|
|
@@ -1427,6 +1443,11 @@ function collectTextIndicators(text) {
|
|
|
1427
1443
|
if (matcher.category === 'install') indicators.installCommands.push(matcher.label);
|
|
1428
1444
|
if (matcher.category === 'execution') indicators.executionCommands.push(matcher.label);
|
|
1429
1445
|
if (matcher.category === 'network') indicators.networkCommands.push(matcher.label);
|
|
1446
|
+
// `probe` is informational-only: it means the text references an
|
|
1447
|
+
// exfil host but WITHOUT the attacker's uploading path. Almost
|
|
1448
|
+
// always a responder probing or documentation. Never elevates
|
|
1449
|
+
// compromise severity.
|
|
1450
|
+
if (matcher.category === 'probe') indicators.probeMatches.push(matcher.label);
|
|
1430
1451
|
}
|
|
1431
1452
|
|
|
1432
1453
|
for (const trackedPackage of TRACKED_PACKAGES) {
|
|
@@ -2639,6 +2660,229 @@ function scanImpactSurface(homes, roots, report, runtime) {
|
|
|
2639
2660
|
report.impactSurfaceFindings = uniq(findings.map((entry) => JSON.stringify(entry))).map((entry) => JSON.parse(entry));
|
|
2640
2661
|
}
|
|
2641
2662
|
|
|
2663
|
+
// ---------------------------------------------------------------------------
|
|
2664
|
+
// Breach-impact analysis — retrace the known CanisterWorm payload behavior
|
|
2665
|
+
// against what actually exists on this host.
|
|
2666
|
+
//
|
|
2667
|
+
// The `env-compat.cjs` payload is a postinstall script that runs under the
|
|
2668
|
+
// installing user's identity. Public research + the IOC strings the scanner
|
|
2669
|
+
// already matches (TEL_ENDPOINT, ICP_CANISTER_ID, pkg-telemetry, AES-256-CBC,
|
|
2670
|
+
// RSA-OAEP-SHA256, pypi-pth-exfil, etc.) give us a concrete list of targets:
|
|
2671
|
+
//
|
|
2672
|
+
// 1. Environment variables visible to `npm run postinstall` at install
|
|
2673
|
+
// time (anything in process.env when `env-compat.cjs` executed). The
|
|
2674
|
+
// scanner cannot read historical env state, but install-time env for
|
|
2675
|
+
// shells is commonly .env files + shell profiles.
|
|
2676
|
+
// 2. Credential files under $HOME that the install-user could read.
|
|
2677
|
+
// 3. Browser login-data / cookie stores (chrome/brave/edge/chromium).
|
|
2678
|
+
// 4. Crypto wallets.
|
|
2679
|
+
// 5. SSH keys + known_hosts (for lateral movement).
|
|
2680
|
+
// 6. Session tokens in ~/.config/gh and ~/.config/gcloud and ~/.aws.
|
|
2681
|
+
//
|
|
2682
|
+
// This phase correlates the scanner's own impactSurfaceFindings (what EXISTS
|
|
2683
|
+
// on this host) with the known CanisterWorm targeting to produce
|
|
2684
|
+
// `breachImpact.likelyStolen` — a structured, actionable checklist for
|
|
2685
|
+
// credential rotation rather than a generic "rotate your tokens" line.
|
|
2686
|
+
//
|
|
2687
|
+
// Only fires if hard compromise evidence is present; otherwise the host is
|
|
2688
|
+
// not believed to have been exposed and we emit an empty/disabled report.
|
|
2689
|
+
// ---------------------------------------------------------------------------
|
|
2690
|
+
|
|
2691
|
+
const CANISTERWORM_TARGETS = [
|
|
2692
|
+
// Package registry tokens — highest priority (full supply-chain impact).
|
|
2693
|
+
{
|
|
2694
|
+
match: /(^|\/)\.npmrc$/,
|
|
2695
|
+
category: 'npm-token',
|
|
2696
|
+
severity: 'CRITICAL',
|
|
2697
|
+
rotationUrl: 'https://www.npmjs.com/settings/~/tokens',
|
|
2698
|
+
reason: 'env-compat.cjs reads ~/.npmrc to exfil auth tokens; compromised npm publish rights = supply-chain risk',
|
|
2699
|
+
},
|
|
2700
|
+
{
|
|
2701
|
+
match: /\.config\/gh\/hosts\.yml$/,
|
|
2702
|
+
category: 'github-pat',
|
|
2703
|
+
severity: 'CRITICAL',
|
|
2704
|
+
rotationUrl: 'https://github.com/settings/tokens',
|
|
2705
|
+
reason:
|
|
2706
|
+
'gh CLI tokens grant repo + workflow-secrets access; public research shows exfil to telemetry.api-monitor.com',
|
|
2707
|
+
},
|
|
2708
|
+
// Cloud IAM — infrastructure access.
|
|
2709
|
+
{
|
|
2710
|
+
match: /\.aws\/(credentials|config)$/,
|
|
2711
|
+
category: 'aws-iam',
|
|
2712
|
+
severity: 'CRITICAL',
|
|
2713
|
+
rotationUrl: 'https://console.aws.amazon.com/iam/home#/security_credentials',
|
|
2714
|
+
reason: 'AWS access keys enable infrastructure takeover',
|
|
2715
|
+
},
|
|
2716
|
+
{
|
|
2717
|
+
match: /\.config\/gcloud\/(application_default_credentials|access_tokens)/,
|
|
2718
|
+
category: 'gcp-iam',
|
|
2719
|
+
severity: 'CRITICAL',
|
|
2720
|
+
rotationUrl: 'https://console.cloud.google.com/iam-admin/serviceaccounts',
|
|
2721
|
+
reason: 'GCP application-default credentials / access tokens enable project takeover',
|
|
2722
|
+
},
|
|
2723
|
+
{
|
|
2724
|
+
match: /\.azure\//,
|
|
2725
|
+
category: 'azure-iam',
|
|
2726
|
+
severity: 'CRITICAL',
|
|
2727
|
+
rotationUrl: 'https://portal.azure.com/#view/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/~/Overview',
|
|
2728
|
+
reason: 'Azure CLI refresh tokens enable subscription access',
|
|
2729
|
+
},
|
|
2730
|
+
// AI provider keys (the payload specifically targets these per pkg-telemetry).
|
|
2731
|
+
{
|
|
2732
|
+
match: /(^|\/)\.env(\.|$)/,
|
|
2733
|
+
category: 'dotenv',
|
|
2734
|
+
severity: 'CRITICAL',
|
|
2735
|
+
rotationUrl: null,
|
|
2736
|
+
reason: 'env-compat.cjs reads .env files for ANTHROPIC_API_KEY / OPENAI_API_KEY / custom secrets',
|
|
2737
|
+
},
|
|
2738
|
+
// SSH — lateral movement.
|
|
2739
|
+
{
|
|
2740
|
+
match: /\.ssh\/(id_rsa|id_ed25519|id_ecdsa|config|known_hosts)/,
|
|
2741
|
+
category: 'ssh-key',
|
|
2742
|
+
severity: 'HIGH',
|
|
2743
|
+
rotationUrl: null,
|
|
2744
|
+
reason: 'SSH private keys + known_hosts enable lateral movement to other hosts',
|
|
2745
|
+
},
|
|
2746
|
+
// Browser login data — session cookies, stored passwords.
|
|
2747
|
+
{
|
|
2748
|
+
match:
|
|
2749
|
+
/(Application Support|\.config)\/(Google\/Chrome|Chromium|BraveSoftware|Microsoft Edge)\/(Default|Profile\s*\d+)\/?/i,
|
|
2750
|
+
category: 'browser-session',
|
|
2751
|
+
severity: 'HIGH',
|
|
2752
|
+
rotationUrl: null,
|
|
2753
|
+
reason: 'browser Login Data + Cookies files contain session tokens that bypass 2FA on re-use',
|
|
2754
|
+
},
|
|
2755
|
+
// Crypto wallets.
|
|
2756
|
+
{
|
|
2757
|
+
match: /(Application Support|\.local\/share|\.config)\/(Ledger Live|Exodus|Electrum|MetaMask)/i,
|
|
2758
|
+
category: 'crypto-wallet',
|
|
2759
|
+
severity: 'CRITICAL',
|
|
2760
|
+
rotationUrl: null,
|
|
2761
|
+
reason: 'crypto wallet data allows direct fund theft if keystore passphrase is also captured',
|
|
2762
|
+
},
|
|
2763
|
+
];
|
|
2764
|
+
|
|
2765
|
+
function classifyCanisterWormTarget(path) {
|
|
2766
|
+
for (const target of CANISTERWORM_TARGETS) {
|
|
2767
|
+
if (target.match.test(path)) return target;
|
|
2768
|
+
}
|
|
2769
|
+
return null;
|
|
2770
|
+
}
|
|
2771
|
+
|
|
2772
|
+
function hasHardCompromiseEvidence(report) {
|
|
2773
|
+
if ((report.installFindings || []).length > 0) return true;
|
|
2774
|
+
if ((report.bunCacheFindings || []).length > 0) return true;
|
|
2775
|
+
if ((report.npmTarballFetches || []).length > 0) return true;
|
|
2776
|
+
// Any temp-artifact with known malware hash.
|
|
2777
|
+
for (const entry of report.tempArtifactFindings || []) {
|
|
2778
|
+
if (entry.knownMalwareHash) return true;
|
|
2779
|
+
if ((entry.iocMatches || []).length > 0) return true;
|
|
2780
|
+
}
|
|
2781
|
+
return false;
|
|
2782
|
+
}
|
|
2783
|
+
|
|
2784
|
+
function scanBreachImpact(report) {
|
|
2785
|
+
const hasEvidence = hasHardCompromiseEvidence(report);
|
|
2786
|
+
const breachImpact = {
|
|
2787
|
+
enabled: hasEvidence,
|
|
2788
|
+
exfilChannel: {
|
|
2789
|
+
host: 'telemetry.api-monitor.com',
|
|
2790
|
+
paths: ['/v1/telemetry', '/v1/drop'],
|
|
2791
|
+
observed: false,
|
|
2792
|
+
},
|
|
2793
|
+
compromiseWindow: null,
|
|
2794
|
+
likelyStolen: [],
|
|
2795
|
+
compromisedInstallPaths: [],
|
|
2796
|
+
runningProcessesDuringWindow: [],
|
|
2797
|
+
rotationChecklist: [],
|
|
2798
|
+
};
|
|
2799
|
+
|
|
2800
|
+
if (!hasEvidence) {
|
|
2801
|
+
report.breachImpact = breachImpact;
|
|
2802
|
+
return;
|
|
2803
|
+
}
|
|
2804
|
+
|
|
2805
|
+
// Determine compromise window from evidence timestamps.
|
|
2806
|
+
const evidenceTimes = [];
|
|
2807
|
+
for (const entry of report.installFindings || []) {
|
|
2808
|
+
const t = entry.modifiedAt || null;
|
|
2809
|
+
if (t) evidenceTimes.push(Date.parse(t));
|
|
2810
|
+
breachImpact.compromisedInstallPaths.push(entry.path);
|
|
2811
|
+
}
|
|
2812
|
+
for (const entry of report.bunCacheFindings || []) {
|
|
2813
|
+
const t = entry.modifiedAt || null;
|
|
2814
|
+
if (t) evidenceTimes.push(Date.parse(t));
|
|
2815
|
+
}
|
|
2816
|
+
for (const entry of report.npmTarballFetches || []) {
|
|
2817
|
+
const t = entry.time || entry.cacheRecordTime || null;
|
|
2818
|
+
if (t) evidenceTimes.push(Date.parse(t));
|
|
2819
|
+
}
|
|
2820
|
+
const validTimes = evidenceTimes.filter((t) => Number.isFinite(t) && t > 0);
|
|
2821
|
+
if (validTimes.length > 0) {
|
|
2822
|
+
breachImpact.compromiseWindow = {
|
|
2823
|
+
firstEvidence: new Date(Math.min(...validTimes)).toISOString(),
|
|
2824
|
+
lastEvidence: new Date(Math.max(...validTimes)).toISOString(),
|
|
2825
|
+
};
|
|
2826
|
+
}
|
|
2827
|
+
|
|
2828
|
+
// Cross-reference impact-surface findings against known CanisterWorm targets.
|
|
2829
|
+
// Every match is a credential the payload, running as the installing user,
|
|
2830
|
+
// had read access to during the compromise window.
|
|
2831
|
+
for (const entry of report.impactSurfaceFindings || []) {
|
|
2832
|
+
const target = classifyCanisterWormTarget(entry.path);
|
|
2833
|
+
if (!target) continue;
|
|
2834
|
+
breachImpact.likelyStolen.push({
|
|
2835
|
+
category: target.category,
|
|
2836
|
+
severity: target.severity,
|
|
2837
|
+
path: entry.path,
|
|
2838
|
+
reason: target.reason,
|
|
2839
|
+
rotationUrl: target.rotationUrl,
|
|
2840
|
+
});
|
|
2841
|
+
}
|
|
2842
|
+
|
|
2843
|
+
// Also include .env files discovered in scanImpactSurface (they're in
|
|
2844
|
+
// impactSurfaceFindings with kind='secret-store').
|
|
2845
|
+
for (const entry of report.impactSurfaceFindings || []) {
|
|
2846
|
+
if (entry.kind !== 'secret-store') continue;
|
|
2847
|
+
if (breachImpact.likelyStolen.some((s) => s.path === entry.path)) continue;
|
|
2848
|
+
const target = classifyCanisterWormTarget(entry.path);
|
|
2849
|
+
if (target) continue; // already classified above
|
|
2850
|
+
breachImpact.likelyStolen.push({
|
|
2851
|
+
category: 'dotenv',
|
|
2852
|
+
severity: 'CRITICAL',
|
|
2853
|
+
path: entry.path,
|
|
2854
|
+
reason: 'env-compat.cjs reads .env files for API keys (ANTHROPIC, OPENAI, custom secrets)',
|
|
2855
|
+
rotationUrl: null,
|
|
2856
|
+
});
|
|
2857
|
+
}
|
|
2858
|
+
|
|
2859
|
+
// Correlate live processes with the compromise window: any process whose
|
|
2860
|
+
// elapsed time overlaps the window and which was spawned by the compromised
|
|
2861
|
+
// install path is part of the breach footprint.
|
|
2862
|
+
for (const entry of report.liveProcessFindings || []) {
|
|
2863
|
+
if (!entry.matchedInstallPaths || entry.matchedInstallPaths.length === 0) continue;
|
|
2864
|
+
breachImpact.runningProcessesDuringWindow.push({
|
|
2865
|
+
pid: entry.pid,
|
|
2866
|
+
elapsed: entry.elapsed,
|
|
2867
|
+
command: (entry.command || '').slice(0, 160),
|
|
2868
|
+
});
|
|
2869
|
+
}
|
|
2870
|
+
|
|
2871
|
+
// Build a priority-sorted rotation checklist (deduped by category).
|
|
2872
|
+
const severityRank = { CRITICAL: 0, HIGH: 1, MEDIUM: 2, LOW: 3 };
|
|
2873
|
+
const byCategory = new Map();
|
|
2874
|
+
for (const item of breachImpact.likelyStolen) {
|
|
2875
|
+
const existing = byCategory.get(item.category);
|
|
2876
|
+
if (!existing) byCategory.set(item.category, { ...item, paths: [item.path] });
|
|
2877
|
+
else existing.paths.push(item.path);
|
|
2878
|
+
}
|
|
2879
|
+
breachImpact.rotationChecklist = [...byCategory.values()].sort(
|
|
2880
|
+
(a, b) => (severityRank[a.severity] ?? 99) - (severityRank[b.severity] ?? 99),
|
|
2881
|
+
);
|
|
2882
|
+
|
|
2883
|
+
report.breachImpact = breachImpact;
|
|
2884
|
+
}
|
|
2885
|
+
|
|
2642
2886
|
function collectTempRoots(platformInfo, homes, roots) {
|
|
2643
2887
|
const tempRoots = new Set();
|
|
2644
2888
|
|
|
@@ -3359,6 +3603,35 @@ function printHumanReport(report) {
|
|
|
3359
3603
|
}
|
|
3360
3604
|
}
|
|
3361
3605
|
|
|
3606
|
+
const breach = report.breachImpact || { enabled: false };
|
|
3607
|
+
if (breach.enabled && (breach.likelyStolen || []).length > 0) {
|
|
3608
|
+
console.log('');
|
|
3609
|
+
console.log('BREACH IMPACT — retracing CanisterWorm exfil behavior against this host:');
|
|
3610
|
+
if (breach.compromiseWindow) {
|
|
3611
|
+
console.log(
|
|
3612
|
+
` compromise window: ${breach.compromiseWindow.firstEvidence} .. ${breach.compromiseWindow.lastEvidence}`,
|
|
3613
|
+
);
|
|
3614
|
+
}
|
|
3615
|
+
console.log(
|
|
3616
|
+
` exfil channel: ${breach.exfilChannel.host} ${breach.exfilChannel.paths.join(', ')}${breach.exfilChannel.observed ? ' (observed)' : ' (not observed in logs — channel targeting only)'}`,
|
|
3617
|
+
);
|
|
3618
|
+
console.log('');
|
|
3619
|
+
console.log(' likely-stolen credentials (grouped, priority-sorted):');
|
|
3620
|
+
for (const item of breach.rotationChecklist || []) {
|
|
3621
|
+
console.log(` [${item.severity}] ${item.category}`);
|
|
3622
|
+
for (const p of item.paths || []) console.log(` path: ${p}`);
|
|
3623
|
+
console.log(` why: ${item.reason}`);
|
|
3624
|
+
if (item.rotationUrl) console.log(` rotate at: ${item.rotationUrl}`);
|
|
3625
|
+
}
|
|
3626
|
+
if ((breach.runningProcessesDuringWindow || []).length > 0) {
|
|
3627
|
+
console.log('');
|
|
3628
|
+
console.log(' processes that ran the compromised binary:');
|
|
3629
|
+
for (const proc of breach.runningProcessesDuringWindow) {
|
|
3630
|
+
console.log(` pid=${proc.pid} elapsed=${proc.elapsed} cmd=${proc.command}`);
|
|
3631
|
+
}
|
|
3632
|
+
}
|
|
3633
|
+
}
|
|
3634
|
+
|
|
3362
3635
|
if (report.npmCacheMetadata.length > 0) {
|
|
3363
3636
|
console.log('');
|
|
3364
3637
|
console.log('npm cache metadata observations:');
|
|
@@ -3523,6 +3796,7 @@ async function main() {
|
|
|
3523
3796
|
tempArtifactFindings: [],
|
|
3524
3797
|
liveProcessFindings: [],
|
|
3525
3798
|
impactSurfaceFindings: [],
|
|
3799
|
+
breachImpact: { enabled: false, likelyStolen: [], rotationChecklist: [] },
|
|
3526
3800
|
timeline: [],
|
|
3527
3801
|
errors: [],
|
|
3528
3802
|
};
|
|
@@ -3621,6 +3895,14 @@ async function main() {
|
|
|
3621
3895
|
() => scanLiveProcesses(report),
|
|
3622
3896
|
report,
|
|
3623
3897
|
);
|
|
3898
|
+
await runPhase(
|
|
3899
|
+
runtime,
|
|
3900
|
+
'scanBreachImpact',
|
|
3901
|
+
'breach-impact',
|
|
3902
|
+
'(breach analysis)',
|
|
3903
|
+
() => scanBreachImpact(report),
|
|
3904
|
+
report,
|
|
3905
|
+
);
|
|
3624
3906
|
|
|
3625
3907
|
report.timeline = sortTimeline(report.timeline);
|
|
3626
3908
|
report.summary = summarize(report);
|