@blamejs/exceptd-skills 0.11.13 → 0.11.15

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.
@@ -492,5 +492,119 @@
492
492
  }
493
493
  ],
494
494
  "last_updated": "2026-05-11"
495
+ },
496
+ "CVE-2026-45321": {
497
+ "name": "Mini Shai-Hulud TanStack npm worm",
498
+ "type": "supply-chain-worm",
499
+ "cvss_score": 9.6,
500
+ "cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H",
501
+ "cisa_kev": false,
502
+ "cisa_kev_date": null,
503
+ "cisa_kev_pending": true,
504
+ "cisa_kev_pending_reason": "Attack disclosed 2026-05-11. Active in-the-wild exploitation of 42 @tanstack/* packages with combined ~150M weekly downloads. CISA KEV listing expected within standard review window.",
505
+ "poc_available": true,
506
+ "poc_description": "Confirmed in-the-wild — 84 malicious versions published across 42 @tanstack/* packages between 2026-05-11 19:20-19:26 UTC. The worm itself IS the PoC; payload analysis published by multiple researchers within 20 minutes.",
507
+ "ai_discovered": false,
508
+ "ai_assisted_weaponization": false,
509
+ "ai_assisted_notes": "Attack methodology is engineering-grade — chained primitives across CI/CD, pnpm cache, and OIDC token handling. No evidence of AI-assisted exploit development; attribution: TeamPCP.",
510
+ "active_exploitation": "confirmed",
511
+ "affected": "Anyone consuming any of 42 @tanstack/* npm packages (router, table, form, store, virtual, etc.) — combined ~150M+ weekly downloads. @tanstack/react-router alone ships to ~12M weekly. Excludes @tanstack/react-query (not in the affected set).",
512
+ "affected_versions": [
513
+ "84 specific malicious versions published 2026-05-11 19:20-19:26 UTC across 42 @tanstack/* packages — all yanked; check `npm view <pkg> time` for the publish-time window. Any package-lock.json or pnpm-lock.yaml resolved during that window is suspect."
514
+ ],
515
+ "vector": "Three chained primitives — none sufficient alone: (1) pull_request_target on TanStack's bundle-size.yml ran fork-PR code with base-repo permissions (classic Pwn Request); (2) that run wrote poison into the actions/cache pnpm-store under the key Linux-pnpm-store-${hashFiles('**/pnpm-lock.yaml')} that release.yml later restored; (3) on next main push, release.yml (id-token: write for npm publish) restored the poisoned cache, attacker code read /proc/<runner.worker>/mem to lift the OIDC token before the Publish step touched it, and published directly to npm — bypassing the workflow's own publish step. Result: malicious tarballs shipped with VALID SLSA provenance.",
516
+ "complexity": "high",
517
+ "complexity_notes": "Requires upstream maintainer to have (a) pull_request_target trigger on a non-publishing workflow with sufficient permissions, (b) cache that publish workflow later consumes, and (c) id-token: write scoped broadly enough that an in-process actor can scrape it. Each link is fixable individually; the chain is what's novel.",
518
+ "patch_available": true,
519
+ "patch_required_reboot": false,
520
+ "live_patch_available": true,
521
+ "live_patch_tools": [
522
+ "npm yank — registry has removed the malicious versions",
523
+ "Pin or rollback affected @tanstack/* packages in package-lock.json / pnpm-lock.yaml to a pre-2026-05-11-19:20Z resolved version",
524
+ "Set npm registry cooldown: .npmrc `before=72h` (npm 11+) or `minimumReleaseAge=4320` to refuse any fresh-publish under 72 hours"
525
+ ],
526
+ "framework_control_gaps": {
527
+ "SLSA-L3": "FIRST documented npm package shipping valid SLSA provenance while being malicious — provenance only proves WHICH pipeline built the artifact, not that the pipeline BEHAVED AS INTENDED. SLSA L3 build integrity is necessary but insufficient against cache-poisoning attacks within the build.",
528
+ "NIST-800-53-SA-12": "Supply chain protection treats provenance + signing as the trust anchor. CVE-2026-45321 demonstrates both can be intact on a malicious package.",
529
+ "NIST-800-218-SSDF": "PS.3 + PO.3 don't address cache poisoning between sibling workflows in the same repo. SSDF presumes per-workflow trust isolation that GitHub Actions' shared actions/cache breaks.",
530
+ "EU-CRA-Art13": "Required vulnerability handling doesn't cover the case where the upstream maintainer is unwitting — the maintainer was a victim, not a participant.",
531
+ "NIS2-Art21-2d": "Supply chain risk management presumes detectable signal at consumption. Valid provenance neutralizes the standard consumer-side check.",
532
+ "DORA-Art28": "ICT third-party risk doesn't cover transitive cache poisoning in upstream CI/CD."
533
+ },
534
+ "atlas_refs": [
535
+ "AML.T0010",
536
+ "AML.T0018",
537
+ "AML.T0048"
538
+ ],
539
+ "attack_refs": [
540
+ "T1195.002",
541
+ "T1078.004",
542
+ "T1574",
543
+ "T1059.007"
544
+ ],
545
+ "rwep_score": 45,
546
+ "rwep_factors": {
547
+ "cisa_kev": 0,
548
+ "poc_available": 20,
549
+ "ai_factor": 0,
550
+ "active_exploitation": 20,
551
+ "blast_radius": 30,
552
+ "patch_available": -15,
553
+ "live_patch_available": -10,
554
+ "reboot_required": 0
555
+ },
556
+ "rwep_notes": "RWEP cap of 30 on blast_radius understates the real exposure (42 packages, ~150M+ weekly downloads combined). Operationally treat as P0; the formula caps blast_radius regardless of magnitude. Once CISA KEV-lists this CVE, the +25 boost will lift score to 70 (P1 territory).",
557
+ "epss_score": 0.78,
558
+ "epss_percentile": 0.97,
559
+ "epss_date": "2026-05-13",
560
+ "epss_source": "https://api.first.org/data/v1/epss?cve=CVE-2026-45321",
561
+ "source_verified": "2026-05-13",
562
+ "verification_sources": [
563
+ "https://nvd.nist.gov/vuln/detail/CVE-2026-45321",
564
+ "https://github.com/advisories?query=CVE-2026-45321",
565
+ "https://www.npmjs.com/advisories?search=tanstack"
566
+ ],
567
+ "vendor_advisories": [
568
+ {
569
+ "vendor": "TanStack",
570
+ "advisory_id": null,
571
+ "url": "https://github.com/TanStack/query/security/advisories",
572
+ "severity": "critical",
573
+ "published_date": "2026-05-11"
574
+ },
575
+ {
576
+ "vendor": "npm Inc.",
577
+ "advisory_id": null,
578
+ "url": "https://www.npmjs.com/advisories?search=CVE-2026-45321",
579
+ "severity": "critical",
580
+ "published_date": "2026-05-11"
581
+ },
582
+ {
583
+ "vendor": "GitHub Security Advisories",
584
+ "advisory_id": null,
585
+ "url": "https://github.com/advisories?query=CVE-2026-45321",
586
+ "severity": "critical",
587
+ "published_date": "2026-05-11"
588
+ }
589
+ ],
590
+ "iocs": {
591
+ "payload_artifacts": [
592
+ "node_modules/@tanstack/*/router_init.js",
593
+ "node_modules/@tanstack/*/router_runtime.js"
594
+ ],
595
+ "persistence_artifacts": [
596
+ ".claude/settings.json hooks.SessionStart entry running `node .vscode/setup.mjs`",
597
+ ".vscode/tasks.json folder-open task pointing at .vscode/setup.mjs",
598
+ "~/Library/LaunchAgents/com.tanstack.*.plist (macOS persistence)",
599
+ "~/.config/systemd/user/*.service referencing the staged setup.mjs (Linux systemd-user persistence)"
600
+ ],
601
+ "behavioral": [
602
+ "Build job restores actions/cache key matching Linux-pnpm-store-<hash> written by a non-publishing workflow",
603
+ "Same repo has pull_request_target trigger anywhere AND id-token: write anywhere AND actions/cache used by both",
604
+ "@tanstack/* package resolved within publish window 2026-05-11T19:20Z..2026-05-11T19:26Z"
605
+ ],
606
+ "destructive": "Payload triggers wipe on token-revocation — operators rotating npm tokens after suspected exposure should snapshot affected hosts first."
607
+ },
608
+ "last_updated": "2026-05-13"
495
609
  }
496
610
  }
@@ -1,10 +1,22 @@
1
1
  {
2
2
  "_meta": {
3
3
  "id": "mcp",
4
- "version": "1.0.0",
5
- "last_threat_review": "2026-05-11",
6
- "threat_currency_score": 96,
4
+ "version": "1.1.0",
5
+ "last_threat_review": "2026-05-13",
6
+ "threat_currency_score": 97,
7
7
  "changelog": [
8
+ {
9
+ "version": "1.1.0",
10
+ "date": "2026-05-13",
11
+ "summary": "Cross-cuts CVE-2026-45321 (Mini Shai-Hulud TanStack npm worm) on the agent-persistence side. The worm installs SessionStart hooks in .claude/settings.json + folder-open tasks in .vscode/tasks.json + OS-level LaunchAgents/systemd-user units, all of which re-arm the credential-harvesting payload on every agent or IDE restart. Detect path adds: SessionStart-hook-not-in-allowlist, vscode-folder-open-hook-not-in-allowlist, agent-persistence-os-level. The primary supply-chain detection lives in sbom; this playbook covers the agentic-tooling persistence vector.",
12
+ "cves_added": [
13
+ "CVE-2026-45321"
14
+ ],
15
+ "framework_gaps_updated": [
16
+ "nist-800-53-AC-2-AI-hook-allowlist",
17
+ "eu-ai-act-art15-agent-persistence"
18
+ ]
19
+ },
8
20
  {
9
21
  "version": "1.0.0",
10
22
  "date": "2026-05-11",
@@ -68,7 +80,8 @@
68
80
  "T1190"
69
81
  ],
70
82
  "cve_refs": [
71
- "CVE-2026-30615"
83
+ "CVE-2026-30615",
84
+ "CVE-2026-45321"
72
85
  ],
73
86
  "cwe_refs": [
74
87
  "CWE-345",
@@ -1,10 +1,22 @@
1
1
  {
2
2
  "_meta": {
3
3
  "id": "sbom",
4
- "version": "1.0.0",
5
- "last_threat_review": "2026-05-11",
6
- "threat_currency_score": 95,
4
+ "version": "1.1.0",
5
+ "last_threat_review": "2026-05-13",
6
+ "threat_currency_score": 97,
7
7
  "changelog": [
8
+ {
9
+ "version": "1.1.0",
10
+ "date": "2026-05-13",
11
+ "summary": "Adds CVE-2026-45321 (Mini Shai-Hulud TanStack npm worm, 2026-05-11). Novel category: FIRST documented npm package shipping valid SLSA provenance while being malicious — provenance proves which pipeline built it, not that the pipeline behaved as intended. Detect path includes chained-primitives signature (pull_request_target + actions/cache + id-token:write co-residency), IoC sweep for .claude/settings.json SessionStart hooks + .vscode/tasks.json folder-open hooks + LaunchAgent / systemd-user persistence, registry-cooldown mitigation (.npmrc before=72h or minimumReleaseAge=4320).",
12
+ "cves_added": [
13
+ "CVE-2026-45321"
14
+ ],
15
+ "framework_gaps_updated": [
16
+ "slsa-l3-insufficient-vs-cache-poisoning",
17
+ "nist-800-218-SSDF-PS3-PO3"
18
+ ]
19
+ },
8
20
  {
9
21
  "version": "1.0.0",
10
22
  "date": "2026-05-11",
@@ -80,7 +92,8 @@
80
92
  "CVE-2026-43284",
81
93
  "CVE-2026-43500",
82
94
  "CVE-2025-53773",
83
- "CVE-2026-30615"
95
+ "CVE-2026-30615",
96
+ "CVE-2026-45321"
84
97
  ],
85
98
  "cwe_refs": [
86
99
  "CWE-1357",
@@ -505,6 +518,48 @@
505
518
  "description": "Authoritative catalog for matched-CVE correlation.",
506
519
  "required": true,
507
520
  "air_gap_alternative": "Catalog is shipped with exceptd; available offline."
521
+ },
522
+ {
523
+ "id": "tanstack-payload-sweep",
524
+ "type": "file_path",
525
+ "source": "find node_modules -path '*/@tanstack/*' \\( -name 'router_init.js' -o -name 'router_runtime.js' \\) 2>/dev/null",
526
+ "description": "CVE-2026-45321 IoC sweep — payload markers inside any installed @tanstack/* package. Captures both flat npm and pnpm-style nested layouts.",
527
+ "required": false
528
+ },
529
+ {
530
+ "id": "agent-persistence-claude-settings",
531
+ "type": "config_file",
532
+ "source": ".claude/settings.json and $HOME/.claude/settings.json — read `hooks` keys, in particular SessionStart entries",
533
+ "description": "CVE-2026-45321 persistence vector — read every Claude Code settings file in scope to inspect hook entries.",
534
+ "required": false
535
+ },
536
+ {
537
+ "id": "agent-persistence-vscode-tasks",
538
+ "type": "config_file",
539
+ "source": ".vscode/tasks.json — read `tasks[].runOptions.runOn` for any folderOpen entries",
540
+ "description": "CVE-2026-45321 persistence vector — VS Code folder-open hooks re-arm the worm on every IDE re-open.",
541
+ "required": false
542
+ },
543
+ {
544
+ "id": "agent-persistence-os-level",
545
+ "type": "config_file",
546
+ "source": "$HOME/Library/LaunchAgents/*.plist (macOS) AND $HOME/.config/systemd/user/*.service (Linux) — list and read",
547
+ "description": "CVE-2026-45321 OS-level persistence — outlives any IDE/agent restart.",
548
+ "required": false
549
+ },
550
+ {
551
+ "id": "npmrc-cooldown-policy",
552
+ "type": "config_file",
553
+ "source": "Read .npmrc (project) and $HOME/.npmrc (user) — look for `before=` or `minimumReleaseAge=` settings",
554
+ "description": "Mitigation status for CVE-2026-45321 and similar fresh-publish worms. Absence is a high-confidence finding for any project that consumes npm packages.",
555
+ "required": false
556
+ },
557
+ {
558
+ "id": "github-workflows",
559
+ "type": "config_file",
560
+ "source": "Read .github/workflows/*.yml and *.yaml — extract `on:` triggers, `permissions:`, and `uses: actions/cache@*` step references",
561
+ "description": "CVE-2026-45321 architectural pre-condition check — detects pull_request_target + id-token:write + shared actions/cache co-residency in the same repo.",
562
+ "required": false
508
563
  }
509
564
  ],
510
565
  "collection_scope": {
@@ -628,6 +683,68 @@
628
683
  "description": "KEV-listed match — fast-path escalation required.",
629
684
  "confidence": "deterministic",
630
685
  "deterministic": true
686
+ },
687
+ {
688
+ "id": "tanstack-worm-payload-files",
689
+ "type": "file_path",
690
+ "value": "node_modules/@tanstack/*/router_init.js exists OR node_modules/@tanstack/*/router_runtime.js exists",
691
+ "description": "CVE-2026-45321 (Mini Shai-Hulud) payload markers — these files do not exist in clean TanStack packages.",
692
+ "confidence": "deterministic",
693
+ "deterministic": true,
694
+ "attack_ref": "T1195.002"
695
+ },
696
+ {
697
+ "id": "tanstack-worm-resolved-during-publish-window",
698
+ "type": "log_pattern",
699
+ "value": "Lockfile entry for any @tanstack/* package resolved within 2026-05-11T19:20Z..2026-05-11T19:26Z (the malicious publish window)",
700
+ "description": "CVE-2026-45321 timing match — any @tanstack/* package whose lockfile-recorded resolution timestamp falls inside the 6-minute attacker publish window is suspect even if the payload markers were since cleaned.",
701
+ "confidence": "high",
702
+ "deterministic": false,
703
+ "attack_ref": "T1195.002"
704
+ },
705
+ {
706
+ "id": "agent-persistence-claude-session-start-hook",
707
+ "type": "file_path",
708
+ "value": ".claude/settings.json contains hooks.SessionStart referencing .vscode/setup.mjs OR any non-blamejs-installed script",
709
+ "description": "CVE-2026-45321 persistence vector — worm installs a SessionStart hook to re-arm on next Claude Code launch. Any SessionStart hook running an in-repo .mjs that the operator didn't author is suspect.",
710
+ "confidence": "deterministic",
711
+ "deterministic": true,
712
+ "attack_ref": "T1574"
713
+ },
714
+ {
715
+ "id": "agent-persistence-vscode-folder-open-task",
716
+ "type": "file_path",
717
+ "value": ".vscode/tasks.json contains a runOptions.runOn=folderOpen task pointing at .vscode/setup.mjs or similar",
718
+ "description": "CVE-2026-45321 persistence vector — folder-open hook re-arms on every VS Code re-open of the directory.",
719
+ "confidence": "deterministic",
720
+ "deterministic": true,
721
+ "attack_ref": "T1547"
722
+ },
723
+ {
724
+ "id": "agent-persistence-os-level",
725
+ "type": "file_path",
726
+ "value": "~/Library/LaunchAgents/com.tanstack.*.plist exists (macOS) OR ~/.config/systemd/user/*.service references an in-repo staged setup.mjs (Linux)",
727
+ "description": "CVE-2026-45321 OS-level persistence — outlives any IDE/agent restart. Targets macOS LaunchAgents + Linux systemd-user units.",
728
+ "confidence": "deterministic",
729
+ "deterministic": true,
730
+ "attack_ref": "T1547"
731
+ },
732
+ {
733
+ "id": "ci-cache-poisoning-co-residency",
734
+ "type": "log_pattern",
735
+ "value": "Repo .github/workflows/ contains BOTH (a) a workflow with `on: pull_request_target` AND (b) any workflow with `permissions: id-token: write` AND (c) any actions/cache step shared between the two",
736
+ "description": "Architectural pre-condition for CVE-2026-45321-style chained-primitives attacks. Even without the payload, this co-residency means any successful fork-PR exploit can poison the cache that the publish workflow restores. Mitigation: separate cache namespaces, or remove pull_request_target.",
737
+ "confidence": "deterministic",
738
+ "deterministic": true,
739
+ "attack_ref": "T1195.002"
740
+ },
741
+ {
742
+ "id": "npm-registry-no-cooldown",
743
+ "type": "file_path",
744
+ "value": ".npmrc and ~/.npmrc both lack `before=` or `minimumReleaseAge=` settings, AND project consumes any npm package",
745
+ "description": "Mitigation gap for CVE-2026-45321 and similar fresh-publish worms. Without a registry cooldown, `npm install` will accept a version published seconds ago. Recommended: `before=72h` (npm 11+) or `minimumReleaseAge=4320` minutes. The worm was caught publicly within 20 minutes; 72h is overkill-safe.",
746
+ "confidence": "high",
747
+ "deterministic": false
631
748
  }
632
749
  ],
633
750
  "false_positive_profile": [
@@ -373,5 +373,98 @@
373
373
  "basis": "No vendor management or supply chain control covers MCP servers. 150M+ affected downloads suggests extremely broad exposure.",
374
374
  "theater_pattern": "vendor_management_ai"
375
375
  }
376
+ },
377
+ "CVE-2026-45321": {
378
+ "name": "Mini Shai-Hulud TanStack npm worm",
379
+ "lesson_date": "2026-05-13",
380
+ "attack_vector": {
381
+ "description": "Three chained primitives across one repository's CI: (1) pull_request_target on a non-publishing workflow ran fork-PR code with base-repo permissions, (2) that run poisoned actions/cache under a key the publish workflow would later restore, (3) on next main push the publish workflow restored the poisoned cache, attacker code read /proc/<runner>/mem to lift the OIDC token before the official publish step, and shipped malicious tarballs to npm with VALID SLSA provenance. 84 versions across 42 @tanstack/* packages published in a 6-minute window 2026-05-11 19:20-19:26 UTC.",
382
+ "privileges_required": "Any GitHub account that can open a pull request to the target repository (no maintainer access required).",
383
+ "complexity": "engineering-grade — chained primitives, deep CI knowledge, /proc/<pid>/mem token-scraping under id-token:write",
384
+ "ai_factor": "None observed. Engineering-grade tradecraft attributed to TeamPCP. Notable for what it didn't need: AI didn't make this attack possible. CI-trust-boundary misuse + cache co-residency made it possible."
385
+ },
386
+ "defense_chain": {
387
+ "prevention": {
388
+ "what_would_have_worked": "Forbid pull_request_target co-residency with id-token:write workflows in the same repo, OR isolate actions/cache namespaces per trigger class so fork-PR runs cannot write to a key any tag/main run will read.",
389
+ "was_this_required": false,
390
+ "framework_requiring_it": null,
391
+ "adequacy": "Architectural — eliminates the chain. Hardest part is auditing every repo for the architectural pre-condition; this is what the sbom playbook's `ci-cache-poisoning-co-residency` indicator checks."
392
+ },
393
+ "detection": {
394
+ "what_would_have_worked": "Consumer-side fresh-publish cooldown (.npmrc before=72h or minimumReleaseAge=4320 minutes). External researchers caught this worm within 20 minutes of publish; 72h is overkill-safe.",
395
+ "was_this_required": false,
396
+ "framework_requiring_it": null,
397
+ "adequacy": "Defense in depth — operators who would have installed the malicious version on 2026-05-11 19:25Z would not have, because the cooldown would have rejected it."
398
+ },
399
+ "response": {
400
+ "what_would_have_worked": "Token rotation triggered by the npm yank notice, paired with host-snapshot BEFORE rotation (the worm payload carries a destructive wipe on token-revocation).",
401
+ "was_this_required": false,
402
+ "framework_requiring_it": null,
403
+ "adequacy": "Reduces blast radius post-exploitation. The destructive-on-revocation property means hasty rotation can lose evidence."
404
+ }
405
+ },
406
+ "framework_coverage": {
407
+ "SLSA-L3": {
408
+ "covered": true,
409
+ "adequate": false,
410
+ "gap": "SLSA L3 build-integrity is necessary but insufficient against cache-poisoning attacks within the build. The malicious tarballs shipped with VALID SLSA provenance — provenance proves which pipeline built the artifact, not that the pipeline behaved as intended."
411
+ },
412
+ "NIST-800-218-SSDF": {
413
+ "covered": true,
414
+ "adequate": false,
415
+ "gap": "PS.3 + PO.3 don't address cache poisoning between sibling workflows in the same repo. SSDF presumes per-workflow trust isolation that GitHub Actions' shared actions/cache breaks."
416
+ },
417
+ "NIST-800-53-SA-12": {
418
+ "covered": true,
419
+ "adequate": false,
420
+ "gap": "Supply chain protection treats provenance + signing as the trust anchor. CVE-2026-45321 demonstrates both can be intact on a malicious package."
421
+ },
422
+ "EU-CRA-Art13": {
423
+ "covered": true,
424
+ "adequate": false,
425
+ "gap": "Vulnerability-handling provisions presume detectable signal at consumption. Valid provenance neutralizes the standard consumer-side check."
426
+ },
427
+ "NIS2-Art21-2d": {
428
+ "covered": true,
429
+ "adequate": false,
430
+ "gap": "Supply chain risk management presumes detectable signal at consumption."
431
+ }
432
+ },
433
+ "new_control_requirements": [
434
+ {
435
+ "id": "NEW-CTRL-008",
436
+ "name": "CI-WORKFLOW-TRUST-BOUNDARY-ISOLATION",
437
+ "description": "Forbid pull_request_target co-residency with id-token:write workflows in the same repository. If co-residency is required, isolate actions/cache namespaces per trigger class.",
438
+ "evidence": "CVE-2026-45321 — chained primitives required exactly this co-residency to succeed",
439
+ "gap_closes": [
440
+ "NIST-800-218-SSDF",
441
+ "SLSA-L3"
442
+ ]
443
+ },
444
+ {
445
+ "id": "NEW-CTRL-009",
446
+ "name": "REGISTRY-COOLDOWN-POLICY",
447
+ "description": "Consumer-side registry cooldown (.npmrc before=72h or minimumReleaseAge=4320 minutes) refuses to install any version published within the last 72 hours.",
448
+ "evidence": "CVE-2026-45321 — caught publicly within 20 minutes; 72h is overkill-safe",
449
+ "gap_closes": [
450
+ "NIST-800-53-SA-12",
451
+ "NIS2-Art21-2d"
452
+ ]
453
+ },
454
+ {
455
+ "id": "NEW-CTRL-010",
456
+ "name": "AGENT-PERSISTENCE-HOOK-ALLOWLIST",
457
+ "description": "AI coding assistants must allowlist hooks. SessionStart hooks in .claude/settings.json + folder-open tasks in .vscode/tasks.json + OS-level LaunchAgents/systemd-user units that reference in-repo staged scripts must be approved by the operator before execution.",
458
+ "evidence": "CVE-2026-45321 — the worm installs persistence via all three hook surfaces",
459
+ "gap_closes": [
460
+ "ALL-AI-AGENT-PERSISTENCE"
461
+ ]
462
+ }
463
+ ],
464
+ "compliance_exposure_score": {
465
+ "percent_audit_passing_orgs_still_exposed": 95,
466
+ "basis": "SLSA L3 + provenance + signing all pass on the malicious package. Standard supply-chain audits (SBOM check, provenance verify, signature verify) all give green. The architectural pre-condition (pull_request_target + id-token:write + shared actions/cache) is not in any compliance framework's control catalog. Combined ~150M+ weekly downloads across 42 packages = extremely broad exposure.",
467
+ "theater_pattern": "provenance_signed_therefore_safe"
468
+ }
376
469
  }
377
470
  }
package/keys/public.pem CHANGED
@@ -1,3 +1,3 @@
1
1
  -----BEGIN PUBLIC KEY-----
2
- MCowBQYDK2VwAyEABzYdqZx/L/XFA8w/EHgjXi/WMpUO5qJg9lrDMgiG2q4=
2
+ MCowBQYDK2VwAyEAc7dTqpdkqSacW3fFwlplSF3i9c845VcTA118wKCxuvE=
3
3
  -----END PUBLIC KEY-----
@@ -77,22 +77,38 @@ function parseArgs(argv) {
77
77
  }
78
78
 
79
79
  function printHelp() {
80
- console.log(`refresh-external — pull latest upstream data, optionally upsert into local catalogs.
80
+ console.log(`refresh — pull latest upstream data, optionally upsert into local catalogs.
81
+
82
+ Default behavior is to actually fetch from the network in dry-run mode and write
83
+ refresh-report.json. Use --apply to upsert findings into local catalogs.
81
84
 
82
85
  Modes:
83
- (default) dry-run all sources, write refresh-report.json
84
- --apply apply diffs and rebuild indexes
85
- --source kev,epss scope to a comma-separated list (kev|epss|nvd|rfc|pins)
86
- --from-fixture <p> use frozen fixture payloads (tests use this path)
86
+ (default) fetch all sources from network, dry-run, write refresh-report.json
87
+ --apply apply diffs and rebuild indexes (default also fetches; combine)
88
+ --network fetch the latest signed catalog snapshot from the
89
+ maintainer's npm-published tarball, verify every skill
90
+ signature against the local public.pem, swap data/ in
91
+ place. Same trust anchor as \`npm update -g\`, only the
92
+ data slice changes — useful when you want fresher
93
+ intel without re-resolving CLI/lib code.
94
+ --prefetch (alias: --no-network) populate the cache for offline use.
95
+ Equivalent to \`exceptd prefetch\`.
87
96
  --from-cache [<p>] read from prefetch cache (default .cache/upstream).
88
97
  Combine with --apply to upsert against cached data
89
- entirely offline.
90
- --swarm fan out sources across worker threads. Source fetches
91
- run in parallel rather than sequentially. Best when
92
- paired with --from-cache (no rate-limit contention).
98
+ entirely offline. Cache must be pre-populated via --prefetch.
99
+ --source kev,epss scope to a comma-separated list (kev|epss|nvd|rfc|pins)
100
+ --from-fixture <p> use frozen fixture payloads (tests use this path)
101
+ --indexes-only rebuild data/_indexes/ only; no network. Equivalent to
102
+ \`exceptd refresh --indexes-only\`.
103
+ --swarm fan out sources across worker threads. Best with --from-cache.
104
+
105
+ Air-gap workflow:
106
+ 1. On a connected host: \`exceptd refresh --prefetch\`
107
+ 2. Copy .cache/upstream/ across the boundary
108
+ 3. On the offline host: \`exceptd refresh --from-cache --apply\`
93
109
 
94
110
  Outputs:
95
- refresh-report.json (gitignored) — summary of every diff + per-source status.
111
+ refresh-report.json (gitignored) — per-source status + every diff
96
112
 
97
113
  This module never auto-applies version-pin bumps — those require audit per
98
114
  AGENTS.md Hard Rule #12 and are surfaced as report-only findings.
@@ -642,7 +658,19 @@ function loadCtx(opts) {
642
658
  const abs = path.resolve(opts.fromCache);
643
659
  ctx.cacheDir = abs;
644
660
  if (!fs.existsSync(abs)) {
645
- throw new Error(`refresh-external: --from-cache path does not exist: ${abs}`);
661
+ // v0.11.14 (#129): operators following the website's air-gap workflow
662
+ // hit this with an unhelpful "path does not exist" stack trace. The
663
+ // cache is populated by `exceptd refresh --no-network` (which routes
664
+ // to prefetch). Tell them exactly that, and emit a structured JSON
665
+ // error to stderr instead of a fatal stack trace.
666
+ const err = new Error(
667
+ `refresh: --from-cache path does not exist: ${abs}\n` +
668
+ `Hint: the cache is populated by running \`exceptd refresh --no-network\` (or \`exceptd refresh --prefetch\`) ` +
669
+ `on a connected host first. Air-gap workflow: (1) on connected host: \`exceptd refresh --no-network\`, ` +
670
+ `(2) copy .cache/upstream/ across the boundary, (3) on offline host: \`exceptd refresh --from-cache --apply\`.`
671
+ );
672
+ err._exceptd_hint = true;
673
+ throw err;
646
674
  }
647
675
  }
648
676
  return ctx;
@@ -769,7 +797,14 @@ async function sequential(items, fn) {
769
797
 
770
798
  if (require.main === module) {
771
799
  main().catch((err) => {
772
- console.error(`refresh-external: fatal: ${err && err.stack ? err.stack : err}`);
800
+ // v0.11.14 (#129): hinted errors print the hint message + a structured
801
+ // JSON line on stderr instead of a fatal stack trace.
802
+ if (err && err._exceptd_hint) {
803
+ console.error(err.message);
804
+ console.error(JSON.stringify({ ok: false, error: err.message.split("\n")[0], hint: err.message.split("\n").slice(1).join(" ").trim(), verb: "refresh" }));
805
+ } else {
806
+ console.error(`refresh-external: fatal: ${err && err.stack ? err.stack : err}`);
807
+ }
773
808
  process.exit(2);
774
809
  });
775
810
  }