@blamejs/exceptd-skills 0.16.22 → 0.16.24

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.
Files changed (62) hide show
  1. package/ARCHITECTURE.md +2 -2
  2. package/CHANGELOG.md +42 -0
  3. package/CONTEXT.md +9 -9
  4. package/README.md +3 -3
  5. package/agents/report-generator.md +2 -2
  6. package/agents/skill-updater.md +1 -1
  7. package/agents/source-validator.md +3 -4
  8. package/agents/threat-researcher.md +1 -1
  9. package/bin/exceptd.js +91 -32
  10. package/data/_indexes/_meta.json +10 -10
  11. package/data/_indexes/activity-feed.json +12 -12
  12. package/data/_indexes/chains.json +70435 -4026
  13. package/data/_indexes/frequency.json +492 -163
  14. package/data/_indexes/section-offsets.json +51 -51
  15. package/data/_indexes/summary-cards.json +272 -106
  16. package/data/_indexes/token-budget.json +10 -10
  17. package/data/_indexes/trigger-table.json +15 -6
  18. package/data/_indexes/xref.json +218 -26
  19. package/data/cve-catalog.json +10 -10
  20. package/data/cwe-catalog.json +1 -0
  21. package/lib/auto-discovery.js +39 -1
  22. package/lib/collectors/ai-api.js +112 -7
  23. package/lib/collectors/citation-hygiene.js +27 -0
  24. package/lib/collectors/crypto-codebase.js +25 -0
  25. package/lib/collectors/kernel.js +32 -2
  26. package/lib/collectors/library-author.js +30 -0
  27. package/lib/collectors/runtime.js +38 -3
  28. package/lib/collectors/sbom.js +21 -2
  29. package/lib/collectors/scan-excludes.js +4 -1
  30. package/lib/collectors/secrets.js +125 -0
  31. package/lib/cve-cli.js +9 -1
  32. package/lib/cve-curation.js +8 -1
  33. package/lib/cve-regression-watcher.js +5 -2
  34. package/lib/exit-codes.js +2 -0
  35. package/lib/flag-suggest.js +1 -1
  36. package/lib/lint-skills.js +70 -0
  37. package/lib/playbook-runner.js +75 -14
  38. package/lib/prefetch.js +24 -1
  39. package/lib/refresh-external.js +32 -3
  40. package/lib/rfc-cli.js +8 -1
  41. package/lib/scoring.js +36 -8
  42. package/lib/validate-cve-catalog.js +36 -14
  43. package/lib/validate-package.js +8 -0
  44. package/lib/validate-playbooks.js +42 -0
  45. package/lib/verify.js +4 -3
  46. package/manifest-snapshot.json +4 -2
  47. package/manifest-snapshot.sha256 +1 -1
  48. package/manifest.json +57 -54
  49. package/orchestrator/README.md +1 -1
  50. package/orchestrator/index.js +65 -7
  51. package/orchestrator/scanner.js +53 -5
  52. package/package.json +1 -1
  53. package/sbom.cdx.json +110 -110
  54. package/scripts/build-indexes.js +42 -8
  55. package/scripts/builders/cwe-chains.js +1 -0
  56. package/scripts/builders/section-offsets.js +10 -2
  57. package/scripts/builders/token-budget.js +3 -3
  58. package/scripts/check-changelog-extract.js +38 -1
  59. package/scripts/check-sbom-currency.js +72 -0
  60. package/scripts/check-version-tags.js +5 -0
  61. package/scripts/release.js +22 -15
  62. package/skills/exploit-scoring/skill.md +8 -8
package/ARCHITECTURE.md CHANGED
@@ -176,7 +176,7 @@ Tracks PoC status, weaponization stage, and AI-assist factor per CVE. Updated wh
176
176
 
177
177
  ### `data/cwe-catalog.json`
178
178
 
179
- 173 CWE entries pinned to **CWE v4.20**. Covers the Top 25 Most Dangerous Software Weaknesses (2024 release) plus AI- and supply-chain-relevant weakness classes (prompt-injection-as-trust-boundary failure, training data integrity, dependency confusion, untrusted artifact ingestion). Each entry records root-cause description, common consequences, mitigation patterns, and the CVEs in `cve-catalog.json` that instantiate the weakness. Skills cite CWE IDs in `cwe_refs` to anchor a finding to a stable weakness taxonomy rather than to a single CVE; the CWE provides the durable root-cause lens that survives across exploit generations.
179
+ 177 CWE entries pinned to **CWE v4.20**. Covers the Top 25 Most Dangerous Software Weaknesses (2024 release) plus AI- and supply-chain-relevant weakness classes (prompt-injection-as-trust-boundary failure, training data integrity, dependency confusion, untrusted artifact ingestion). Each entry records root-cause description, common consequences, mitigation patterns, and the CVEs in `cve-catalog.json` that instantiate the weakness. Skills cite CWE IDs in `cwe_refs` to anchor a finding to a stable weakness taxonomy rather than to a single CVE; the CWE provides the durable root-cause lens that survives across exploit generations.
180
180
 
181
181
  `_meta.cwe_version` pins the version; on a CWE release, audit IDs for renames or deprecations, bump `last_threat_review` on affected skills, and update `_meta`.
182
182
 
@@ -213,7 +213,7 @@ cisa_kev +25 (binary)
213
213
  poc_available +20 (binary)
214
214
  ai_assisted_weapon +15 (binary)
215
215
  active_exploitation +20 (binary)
216
- blast_radius +15 (0–15 scaled)
216
+ blast_radius +30 (0–30 scaled)
217
217
  patch_available -15 (binary)
218
218
  live_patch_available -10 (binary: additional reduction if no reboot required)
219
219
  reboot_required +5 (binary penalty: patch exists but requires reboot)
package/CHANGELOG.md CHANGED
@@ -1,5 +1,45 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.16.24 — 2026-06-05
4
+
5
+ Attestation replay tamper-gating, collector detection integrity, and lock-lifecycle fixes.
6
+
7
+ ### Bugs
8
+
9
+ A host whose `keys/public.pem` fails the `EXPECTED_FINGERPRINT` pin is now a first-class tamper state on the replay path: `reattest` refuses with exit 6 (TAMPERED) unless `--force-replay`, the same way `attest verify` already refused. Previously the pin mismatch — the exact key-swap attack the pin exists to surface — fell through to a benign "unsigned attestations are an operator config issue" note and the replay proceeded. The sidecar audit classification gains a matching `fingerprint-mismatch` label, and the replay-refusal predicate now lives in one shared helper so a future tamper class has exactly one place to extend.
10
+
11
+ Piping a collector into a run — `exceptd collect secrets | exceptd run secrets --evidence -` — now classifies real findings as detected instead of silently inconclusive. Collectors flipped false-positive-gated indicators to "hit" without attesting the FP checks they had actually performed, so the engine's honest-downgrade gate demoted every such hit. Eight collectors now perform and attest their deterministic FP checks (example-key demotion, placeholder and entropy floors, context co-occurrence, test-path exclusion, kernel-config reads, sticky-bit/socket classification, registry-tarball and mutable-ref analysis); indicators whose remaining checks genuinely require a network probe or operator judgement stay inconclusive by design, and a guard test enforces that any collector flipping a gated indicator either attests its checks or documents why it abstains.
12
+
13
+ A run invoked with `--bundle-deterministic` against pathologically nested evidence no longer leaks the playbook's cross-process mutex: the depth-limit refusal threw after the lock was taken but before the release path was armed, so every later run of that playbook on the same host was blocked with a phantom "concurrent run holds the mutex" until the process exited. Attestation creation no longer strands an orphaned, unsigned attestation body holding the session slot when the body lands but its signature sidecar cannot be placed — the slot is released and the create can be retried; the unsafe-filename refusal also now returns its structured envelope instead of crashing. `doctor --ai-config --fix` no longer builds an `icacls` grant from an unset `USERNAME` (which stripped ACL inheritance and then failed on the unresolvable account, locking the file out); it resolves the user via the OS and withholds the automatic fix when no name is resolvable. The watch daemon's lockfile stale-reclaim race now exits 75 (WATCH_LOCK_CONTENTION) like every other contention path instead of a generic failure, and the CVE regression watcher stamps reports with the run's clock rather than a module-load timestamp.
14
+
15
+ End-to-end detection scenarios now assert that the staged indicator actually fired instead of accepting the operator-supplied verdict alone, one scenario no longer masks the engine's computed classification with an override, and RWEP floors are pinned at the computed values so a per-indicator weight regression trips them. The derived-index builders count `###` headings fence-aware (code-block lines no longer inflate section offsets), emit the documented `dlp_refs` key on CWE chains, and describe their real output shape; the zero-day response report template and remaining docs now state the blast-radius ceiling as 0-30.
16
+
17
+ ## 0.16.23 — 2026-06-05
18
+
19
+ A broad correctness and hardening pass across the runtime, the data-refresh pipeline, the catalogs, and the release tooling.
20
+
21
+ ### Bugs
22
+
23
+ Escalation conditions written against the analyze result — `analyze.compliance_theater_check.verdict == 'theater'` and `finding.*` paths — now actually fire: the escalation evaluator previously resolved only the flat context keys, so a playbook's theater-verdict or finding-keyed escalation (for example cloud-iam-incident's notify_legal) could never trigger. The evaluation context now exposes the assembled analyze result and the derived finding shape — the same roots feeds_into conditions already resolve — and the playbook validator rejects any condition rooted at a phase result that can never resolve in its context.
24
+
25
+ A declared regulatory clock can no longer drop out of a run's close-phase output. A govern obligation with a notification duty but no matching close notification action — fourteen playbooks carried at least one, such as cloud-iam-incident's AU Privacy Act NDB 720-hour window — now yields a synthesized, fully enriched record in `jurisdiction_notifications`, marked `synthesized_from_obligation` and carrying the computed deadline and the obligation's evidence checklist.
26
+
27
+ `refresh --from-cache` is now actually offline: the GHSA and OSV sources, which have no cache layer, fell through to live network fetches; they now return a structured skip instead. The cache-consume signature gate additionally pins the publisher key fingerprint like every other verification site, auto-discovery verifies each cached payload's sha256 against the signed cache index before deriving draft fields from it, and a KEV de-listing now clears the stale `cisa_kev_date` alongside zeroing the factor.
28
+
29
+ CLI failure paths emit the documented structured envelope instead of raw stack traces: `exceptd cve` and `exceptd rfc` against a corrupt catalog, curation apply-path write failures (disk full, read-only filesystem), the orchestrator's fatal handler, and framework-gap catalog-read failures under `--json`. `ci --max-rwep` with a missing value now errors instead of silently setting the gate cap to 1. `collect` failure bodies no longer advertise an exit code different from the one the process exits with. The watch daemon's lock-contention exit (75) is documented in `doctor --exit-codes`. Collector artifact summaries emit forward-slash paths on Windows, matching their SARIF evidence locations. Attestation diff and reattest regain their signal-side comparison fallback — it read a playbook path that never exists, so the fallback was dead. The typo-suggester's flag inventory for `run` now includes `--directive`, `--explain`, and `--signal-list`.
30
+
31
+ The MCP-config scanner now redacts secret-shaped values recursively before anything is emitted or persisted; previously only top-level fields were redacted, so an API key nested inside a server's config object leaked into scan output verbatim.
32
+
33
+ Four catalog entries carried RWEP factor values contradicting their own flags (CVE-2026-46333, CVE-2009-3459, CVE-2023-43472, and CVE-2026-31635 — the last with two compensating errors that cancelled in the aggregate tolerance). Their factors and scores are corrected, and the validator now checks every stored factor against the weight its source field implies, so compensating per-factor drift can no longer ship. The factor-shape detector also recognizes the catalog's canonical `ai_factor` and `reboot_required` keys, closing a gap where a mixed-shape factor block on exactly those keys passed undetected, and the required-field list the scorer validates against is loaded from the catalog schema itself (it had drifted: five schema-required fields unchecked, one optional field over-required).
34
+
35
+ The documented RWEP formula understated its heaviest factor: blast radius contributes up to 30 points, not the 15 stated in the README, the architecture notes, and the exploit-scoring skill's formula text. The docs now match the scorer, which was always correct — its worked examples already used 30. The derived skill indexes are rebuilt from skill frontmatter rather than a stale manifest cache, restoring dropped UK CAF and AU ISM control mappings and D3FEND references to the chain and cross-reference surfaces, and the ai-c2-detection skill's CWE-918 link is restored to the manifest and every index built from it. The pinned CWE catalog version in `sources/index.json` was three minor versions behind the shipped catalog and is corrected, with the source-currency guard extended to cover the CWE and D3FEND rows the way it already covered ATT&CK and ATLAS.
36
+
37
+ The release orchestrator could report a release complete while the publish was unconfirmed — a failed registry query short-circuited the final verification, and a non-success publish workflow only warned. Both now fail the phase: success requires the registry to positively report the just-published version.
38
+
39
+ ### Features
40
+
41
+ The secret-scanning configuration no longer allowlists the entire playbooks directory by path — targeted patterns cover the legitimate example values instead, so a real credential committed into a playbook file is detectable again. Workflow version comments now match their pinned action SHAs, the publish workflow pins the same exact Node version the gates run on (held in lockstep by a three-way test against CI and the Docker harness), the ATLAS-currency workflow detects stale skills structurally instead of grepping a prose string, and the automated data-refresh PR body describes the structural gate it actually runs. The packaging gate pins every module the CLI requires at launch, the SBOM gate verifies the entry counts embedded in the published description against the live catalogs, skill frontmatter is validated against the shipped schema (previously decorative), and a new guard asserts that every byte-hashed shipped file type carries an LF-forcing `.gitattributes` rule. The catalog inventory in CONTEXT.md and the counts in the package description are pinned by tests so they fail loudly instead of rotting silently.
42
+
3
43
  ## 0.16.22 — 2026-06-05
4
44
 
5
45
  When the automated external-data refresh applies a CISA KEV listing change to a catalog entry, it now updates the entry's RWEP factor and score in the same write, honouring whichever factor shape the entry stores. Previously only the flag flipped, leaving the stored score failing the factor-sum invariant the catalog enforces — the first real KEV listing the refresh applied surfaced this as a 25-point mismatch. A first listing also now carries its KEV date: the drift check emitted a date change only when the local entry already had one, so a newly listed CVE arrived dated null. The refresh workflow's post-apply gate now runs structural validation (catalog schema and RWEP coherence, cross-references, index freshness, skill lint) instead of the full test suite: the suite's curation-completeness checks assert guarantees that human curation supplies after import, so they gate the automated data PR's merge rather than its creation. The workflow also regenerates the SBOM over freshly applied data and consumes its own just-built prefetch cache on runners that have no signing key.
@@ -2128,6 +2168,8 @@ Documentation accuracy pass. README.md + ARCHITECTURE.md were still pinning ATLA
2128
2168
  **`tests/docs-catalog-counts-pinned.test.js`** — new contract test asserts that README.md and ARCHITECTURE.md text matches the live catalog state for: ATLAS version (`data/atlas-ttps.json._meta.atlas_version`), ATT&CK version (`data/attack-techniques.json._meta.attack_version`), skill count (`manifest.json.skills.length`), D3FEND entry count, CVE catalog count, framework-gap entry count. Any future PR that bumps a catalog without updating the operator-facing docs fails the gate at CI time — eliminates the silent-drift class that v0.12.34 cleaned up.
2129
2169
 
2130
2170
 
2171
+ ## 0.12.33 — 2026-05-15
2172
+
2131
2173
  Same-day CVE intake (node-ipc supply-chain compromise) + cleanup of the long-standing `cred-stores` skill-vs-playbook semantic confusion.
2132
2174
 
2133
2175
  ### Features
package/CONTEXT.md CHANGED
@@ -113,18 +113,18 @@ Skills and playbooks read from `data/`. Authoritative catalog inventory:
113
113
 
114
114
  | File | Entries | Purpose |
115
115
  |------|---------|---------|
116
- | `cve-catalog.json` | 10 | CVEs with CVSS, RWEP score, EPSS estimates, CISA KEV flags, PoC and live-patch availability |
117
- | `atlas-ttps.json` | 15 | MITRE ATLAS v5.6.0 (May 2026) techniques with framework gap flags |
118
- | `attack-techniques.json` | 79 | MITRE ATT&CK techniques with framework coverage mappings |
119
- | `framework-control-gaps.json` | 62 | Framework control gap entries: designed-for vs. what each control misses |
120
- | `exploit-availability.json` | 10 | Per-CVE PoC locations, weaponization stage, AI-acceleration factor, live-patch status |
116
+ | `cve-catalog.json` | 439 | CVEs with CVSS, RWEP score, EPSS estimates, CISA KEV flags, PoC and live-patch availability |
117
+ | `atlas-ttps.json` | 170 | MITRE ATLAS v5.6.0 (May 2026) techniques with framework gap flags |
118
+ | `attack-techniques.json` | 805 | MITRE ATT&CK techniques with framework coverage mappings |
119
+ | `framework-control-gaps.json` | 194 | Framework control gap entries: designed-for vs. what each control misses |
120
+ | `exploit-availability.json` | 28 | Per-CVE PoC locations, weaponization stage, AI-acceleration factor, live-patch status |
121
121
  | `global-frameworks.json` | 35 jurisdictions | Patch SLAs and notification windows across global regulatory regimes |
122
- | `zeroday-lessons.json` | 10 | Learning-loop entries: zero-day → attack vector → control gap → framework gap → new control |
123
- | `cwe-catalog.json` | 171 | CWE v4.20 entries (Top 25 2024 plus AI- and supply-chain-relevant weaknesses) |
122
+ | `zeroday-lessons.json` | 439 | Learning-loop entries: zero-day → attack vector → control gap → framework gap → new control |
123
+ | `cwe-catalog.json` | 177 | CWE v4.20 entries (Top 25 2024 plus AI- and supply-chain-relevant weaknesses) |
124
124
  | `d3fend-catalog.json` | 468 | MITRE D3FEND v1.3.0 defensive techniques for offensive → defensive mapping |
125
- | `rfc-references.json` | 31 | IETF RFC / Internet-Draft references with status, errata count, replaces / replaced-by, `last_verified` dates |
125
+ | `rfc-references.json` | 8888 | IETF RFC / Internet-Draft references with status, errata count, replaces / replaced-by, `last_verified` dates |
126
126
  | `dlp-controls.json` | 22 | DLP control entries indexed by channel, classifier, surface, enforcement mode, evidence type |
127
- | `playbooks/` | 13 | Playbook specifications (see above) |
127
+ | `playbooks/` | 33 | Playbook specifications (see above) |
128
128
  | `_indexes/` | 17 derived files | Pre-computed indexes built by `npm run build-indexes` |
129
129
 
130
130
  ---
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  <div align="center">
2
2
 
3
3
  <picture>
4
- <source media="(prefers-color-scheme: dark)" srcset="public/img/logo/exceptd-logo-dark.svg">
5
- <img src="public/img/logo/exceptd-logo-primary.svg" alt="exceptd" width="220" />
4
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/blamejs/exceptd-skills/main/public/img/logo/exceptd-logo-dark.svg">
5
+ <img src="https://raw.githubusercontent.com/blamejs/exceptd-skills/main/public/img/logo/exceptd-logo-primary.svg" alt="exceptd" width="220" />
6
6
  </picture>
7
7
 
8
8
  # exceptd Security
@@ -77,7 +77,7 @@ Generate defensible policy exceptions for architectural realities frameworks don
77
77
  ### Risk Intelligence
78
78
 
79
79
  **[exploit-scoring](skills/exploit-scoring/skill.md)**
80
- Real-World Exploit Priority (RWEP) scoring beyond CVSS. Factors: CISA KEV status (0.25), public PoC (0.20), AI-assisted weaponization (0.15), active exploitation (0.20), patch availability (-0.15), live-patch availability (-0.10), blast radius (0.15). Pre-calculated RWEP scores for all CVEs in `data/cve-catalog.json`. Outputs RWEP alongside CVSS with plain-language priority guidance.
80
+ Real-World Exploit Priority (RWEP) scoring beyond CVSS. Factors: CISA KEV status (0.25), public PoC (0.20), AI-assisted weaponization (0.15), active exploitation (0.20), patch availability (-0.15), live-patch availability (-0.10), blast radius (0.30). Pre-calculated RWEP scores for all CVEs in `data/cve-catalog.json`. Outputs RWEP alongside CVSS with plain-language priority guidance.
81
81
 
82
82
  **[threat-model-currency](skills/threat-model-currency/skill.md)**
83
83
  Score how current an organization's threat model is against 2026 threat reality. Checklist of 14 current threat classes against documented model coverage. Outputs: currency percentage, specific missing threat classes, recommended additions with ATLAS/ATT&CK references, prioritized update roadmap.
@@ -36,7 +36,7 @@ Generate structured, audience-appropriate reports from skill outputs. Translates
36
36
  ### 2. Technical Assessment Report
37
37
 
38
38
  **Audience:** Security engineers, DevOps, Platform teams
39
- **Template:** `reports/templates/technical-assessment.md`
39
+ **Template:** No canonical template — assemble from the content list below.
40
40
  **Content:**
41
41
  - Full CVE inventory with CVSS + RWEP
42
42
  - Specific remediation commands and configurations
@@ -74,7 +74,7 @@ Generate structured, audience-appropriate reports from skill outputs. Translates
74
74
  ### 4. Threat Model Update Report
75
75
 
76
76
  **Audience:** Security architects, threat modeling teams
77
- **Template:** `reports/templates/threat-model-update.md`
77
+ **Template:** No canonical template — assemble from the content list below.
78
78
  **Content:**
79
79
  - Currency score before and after update
80
80
  - Specific threat classes added
@@ -96,7 +96,7 @@ For each affected skill:
96
96
  ## Quality Rules
97
97
 
98
98
  - Never write to data files without a source-validator approval
99
- - Never modify the interpretation of a framework control without a framework-analyst package
99
+ - Never modify the interpretation of a framework control without a threat-researcher handoff package containing the framework gap update, verified by source-validator
100
100
  - Never delete entries from zeroday-lessons.json or framework-control-gaps.json — mark as superseded, not deleted
101
101
  - Always update `last_threat_review` in skill frontmatter after changes
102
102
  - If a RWEP score changes by more than 15 points: flag for review rather than auto-applying
@@ -2,14 +2,13 @@
2
2
 
3
3
  ## Role
4
4
 
5
- Cross-check all claims in a threat-researcher or framework-analyst handoff package against primary sources. Flag unverifiable claims. Produce a verification report that the skill-updater uses to decide what to accept.
5
+ Cross-check all claims in a threat-researcher handoff package against primary sources. Flag unverifiable claims. Produce a verification report that the skill-updater uses to decide what to accept.
6
6
 
7
7
  This agent is the quality gate. It prevents bad data from entering the skill catalog.
8
8
 
9
9
  ## When to spawn
10
10
 
11
- - After threat-researcher produces an intelligence package
12
- - After framework-analyst produces a gap update
11
+ - After threat-researcher produces an intelligence package (including a framework gap update)
13
12
  - On-demand audit of existing data/cve-catalog.json entries
14
13
  - Before any new skill that contains specific CVE or TTP claims is merged
15
14
 
@@ -53,7 +52,7 @@ This agent is the quality gate. It prevents bad data from entering the skill cat
53
52
  "agent": "source-validator",
54
53
  "run_id": "[matches threat-researcher run_id]",
55
54
  "timestamp": "[ISO 8601]",
56
- "input_from": "threat-researcher | framework-analyst",
55
+ "input_from": "threat-researcher",
57
56
  "verification_results": {
58
57
  "passed": [
59
58
  {
@@ -145,5 +145,5 @@ Research and validate new threat intelligence — CVEs, attack campaigns, new AT
145
145
 
146
146
  - Does not write directly to data files — that is the skill-updater's job after source-validator approval
147
147
  - Does not include direct exploit links in output
148
- - Does not make compliance recommendationsthat is the framework-analyst's job
148
+ - Does not apply framework gap updates itself gap findings ship as part of the handoff package; the skill-updater applies them after source-validator approval
149
149
  - Does not score risk — RWEP calculation is in lib/scoring.js
package/bin/exceptd.js CHANGED
@@ -1776,8 +1776,9 @@ function dispatchPlaybook(cmd, argv) {
1776
1776
  } catch (e) {
1777
1777
  // v0.11.14 (#131): when the operator typed a skill name (kernel-lpe-triage)
1778
1778
  // and got "Playbook not found," surface the playbooks that load that skill.
1779
- // 13 playbooks vs 38 skills with many-to-many: operators routinely confuse
1780
- // the two because the website (and AGENTS.md) describe both as runnable.
1779
+ // Playbooks and skills are a many-to-many mapping: operators routinely
1780
+ // confuse the two because the website (and AGENTS.md) describe both as
1781
+ // runnable.
1781
1782
  const m = e && e.message && e.message.match(/^Playbook not found: ([^\s(]+)/);
1782
1783
  if (m) {
1783
1784
  const wanted = m[1];
@@ -2448,10 +2449,10 @@ async function cmdCollect(runner, args, runOpts, pretty) {
2448
2449
  let mod;
2449
2450
  try { mod = require(collectorPath); }
2450
2451
  catch (e) {
2451
- return emitError(`collect: failed to load collector ${path.relative(PKG_ROOT, collectorPath)}: ${e.message}`, { verb: "collect", playbook_id: playbookId, exit_code: 2 }, pretty);
2452
+ return emitError(`collect: failed to load collector ${path.relative(PKG_ROOT, collectorPath)}: ${e.message}`, { verb: "collect", playbook_id: playbookId }, pretty);
2452
2453
  }
2453
2454
  if (typeof mod.collect !== "function") {
2454
- return emitError(`collect: collector at ${path.relative(PKG_ROOT, collectorPath)} does not export a collect() function`, { verb: "collect", playbook_id: playbookId, exit_code: 2 }, pretty);
2455
+ return emitError(`collect: collector at ${path.relative(PKG_ROOT, collectorPath)} does not export a collect() function`, { verb: "collect", playbook_id: playbookId }, pretty);
2455
2456
  }
2456
2457
 
2457
2458
  // --cwd <path> overrides process.cwd(). Validated as an existing
@@ -2476,7 +2477,7 @@ async function cmdCollect(runner, args, runOpts, pretty) {
2476
2477
  } catch (e) {
2477
2478
  return emitError(
2478
2479
  `collect: collector for "${playbookId}" threw an unhandled exception: ${e.message}. File a bug — collectors must catch their own errors and surface them via collector_errors[].`,
2479
- { verb: "collect", playbook_id: playbookId, stack: e.stack || null, exit_code: 2 },
2480
+ { verb: "collect", playbook_id: playbookId, stack: e.stack || null },
2480
2481
  pretty,
2481
2482
  );
2482
2483
  }
@@ -2538,7 +2539,7 @@ async function cmdCollect(runner, args, runOpts, pretty) {
2538
2539
  } catch (e) {
2539
2540
  return emitError(
2540
2541
  `collect: --resolve failed for "${playbookId}": ${e.message}`,
2541
- { verb: "collect", playbook_id: playbookId, exit_code: 2 },
2542
+ { verb: "collect", playbook_id: playbookId },
2542
2543
  pretty,
2543
2544
  );
2544
2545
  }
@@ -4448,7 +4449,7 @@ function persistAttestation(args) {
4448
4449
  if (!/^[A-Za-z0-9._-]{1,64}\.json$/.test(filename || "")) {
4449
4450
  return {
4450
4451
  ok: false,
4451
- error: `Refusing to persist attestation with unsafe filename: ${JSON.stringify(filename).slice(0, 80)}.`,
4452
+ error: `Refusing to persist attestation with unsafe filename: ${String(JSON.stringify(filename)).slice(0, 80)}.`,
4452
4453
  existingPath: null,
4453
4454
  };
4454
4455
  }
@@ -4525,7 +4526,17 @@ function persistAttestation(args) {
4525
4526
  fs.renameSync(jsonTmp, filePath);
4526
4527
  }
4527
4528
  // Slot won — place the sidecar (sigPath is fresh on a create).
4528
- fs.renameSync(sigTmp, sigPath);
4529
+ try {
4530
+ fs.renameSync(sigTmp, sigPath);
4531
+ } catch (sigErr) {
4532
+ // The body landed but its sidecar did not. Left in place, the
4533
+ // orphaned unsigned body would hold the slot forever: every
4534
+ // retry collides with EEXIST and verification reports the
4535
+ // attestation unsigned. Release the slot before rethrowing so
4536
+ // the create can be retried cleanly.
4537
+ try { fs.unlinkSync(filePath); } catch { /* best-effort slot release */ }
4538
+ throw sigErr;
4539
+ }
4529
4540
  try { fs.unlinkSync(jsonTmp); } catch { /* hard-link path leaves a second name */ }
4530
4541
  } else {
4531
4542
  // Force-overwrite, under the persist lock: atomic replace of both.
@@ -4912,7 +4923,12 @@ function verifyAttestationSidecar(attFile) {
4912
4923
  if (pubKey) {
4913
4924
  const pinError = assertExpectedFingerprint(pubKey);
4914
4925
  if (pinError) {
4915
- return { file: attFile, signed: false, verified: false, reason: pinError };
4926
+ // A pin mismatch means the host's public key is NOT the published
4927
+ // one — verification against it would prove nothing. Carry a tamper
4928
+ // class so consumers (reattest's refusal predicate, the sidecar
4929
+ // classifier) treat this as tamper evidence, not a benign
4930
+ // unsigned-attestation config state.
4931
+ return { file: attFile, signed: false, verified: false, tamper_class: "fingerprint-mismatch", reason: pinError };
4916
4932
  }
4917
4933
  }
4918
4934
  if (!fs.existsSync(sigPath)) {
@@ -5136,25 +5152,7 @@ function cmdReattest(runner, args, runOpts, pretty) {
5136
5152
  // tampering. `verified === false && signed === true` is the real tamper
5137
5153
  // signal.
5138
5154
  const verify = verifyAttestationSidecar(attFile);
5139
- // Collapse tamper-class detection. Any non-benign sidecar state
5140
- // (signed-but-invalid, sidecar-corrupt, unsigned-substitution) refuses
5141
- // replay unless --force-replay is set. A predicate of only
5142
- // `verify.signed && !verify.verified` would miss corrupt-JSON sidecars
5143
- // and substituted "unsigned" sidecars on a host WITH a private key —
5144
- // both of which let replay proceed against forged input.
5145
- const isSignedTamper = verify.signed && !verify.verified;
5146
- const isClassTamper = !verify.signed && (
5147
- verify.tamper_class === "sidecar-corrupt"
5148
- || verify.tamper_class === "unsigned-substitution"
5149
- // Extend tamper-class refusal to algorithm-unsupported sidecars —
5150
- // anything other than "Ed25519" or "unsigned". Without explicit
5151
- // refusal, a sidecar that throws inside crypto.verify (e.g.
5152
- // signature_base64 missing on a downgrade-bait shape) emerges as
5153
- // signed:true + verified:false through the catch block by accident.
5154
- // The strict pre-check surfaces the class directly; refuse on it too.
5155
- || verify.tamper_class === "algorithm-unsupported"
5156
- );
5157
- if ((isSignedTamper || isClassTamper) && !args["force-replay"]) {
5155
+ if (isTamperedSidecarVerify(verify) && !args["force-replay"]) {
5158
5156
  process.stderr.write(`[exceptd reattest] TAMPERED: attestation at ${attFile} failed Ed25519 verification (${verify.reason}). Refusing to replay against forged input. Pass --force-replay to override (the replay output records sidecar_verify so the audit trail captures the override).\n`);
5159
5157
  const body = {
5160
5158
  ok: false,
@@ -5169,7 +5167,7 @@ function cmdReattest(runner, args, runOpts, pretty) {
5169
5167
  process.exitCode = EXIT_CODES.TAMPERED;
5170
5168
  return;
5171
5169
  }
5172
- if ((isSignedTamper || isClassTamper) && args["force-replay"]) {
5170
+ if (isTamperedSidecarVerify(verify) && args["force-replay"]) {
5173
5171
  process.stderr.write(`[exceptd reattest] WARNING: --force-replay overriding failed signature verification on ${attFile} (${verify.reason}). The replay output records sidecar_verify so the override is audit-visible.\n`);
5174
5172
  } else if (!verify.signed && verify.reason && verify.reason.includes("no .sig sidecar") && !args["force-replay"]) {
5175
5173
  // missing-sidecar is NOT benign. The previous flow accepted
@@ -5455,6 +5453,39 @@ function cmdReattest(runner, args, runOpts, pretty) {
5455
5453
  * sidecar_verify object so auditors can filter override events by class
5456
5454
  * without regexing the human-readable reason string.
5457
5455
  */
5456
+ /**
5457
+ * Tamper predicate over a verifyAttestationSidecar() result. Collapses
5458
+ * tamper-class detection: any non-benign sidecar state refuses replay
5459
+ * unless --force-replay is set. A predicate of only
5460
+ * `verify.signed && !verify.verified` would miss corrupt-JSON sidecars,
5461
+ * substituted "unsigned" sidecars on a host WITH a private key,
5462
+ * downgrade-bait algorithm shapes, and a keys/public.pem failing the
5463
+ * EXPECTED_FINGERPRINT pin — each of which would let replay proceed
5464
+ * against forged input. Keeping the class list HERE (one shared helper)
5465
+ * means a new tamper class added to the verifier has exactly one refusal
5466
+ * site to extend.
5467
+ */
5468
+ function isTamperedSidecarVerify(verify) {
5469
+ if (!verify || typeof verify !== "object") return false;
5470
+ const isSignedTamper = verify.signed && !verify.verified;
5471
+ const isClassTamper = !verify.signed && (
5472
+ verify.tamper_class === "sidecar-corrupt"
5473
+ || verify.tamper_class === "unsigned-substitution"
5474
+ // Anything other than "Ed25519" or "unsigned": a sidecar that throws
5475
+ // inside crypto.verify (e.g. signature_base64 missing on a
5476
+ // downgrade-bait shape) would otherwise emerge as signed:true +
5477
+ // verified:false through the catch block by accident. The strict
5478
+ // pre-check surfaces the class directly; refuse on it too.
5479
+ || verify.tamper_class === "algorithm-unsupported"
5480
+ // A keys/public.pem that fails the EXPECTED_FINGERPRINT pin is the
5481
+ // key-swap attack the pin exists for — verification "against" the
5482
+ // swapped key proves nothing, so replay refuses exactly like the
5483
+ // other tamper classes (attest verify already refuses on this).
5484
+ || verify.tamper_class === "fingerprint-mismatch"
5485
+ );
5486
+ return Boolean(isSignedTamper || isClassTamper);
5487
+ }
5488
+
5458
5489
  function classifySidecarVerify(verify) {
5459
5490
  if (!verify || typeof verify !== "object") return "unknown";
5460
5491
  if (verify.signed && verify.verified) return "verified";
@@ -5464,6 +5495,7 @@ function classifySidecarVerify(verify) {
5464
5495
  // `algorithm-unsupported` is its own class label so log scrapers /
5465
5496
  // dashboards can filter downgrade-bait events without parsing the reason.
5466
5497
  if (verify.tamper_class === "algorithm-unsupported") return "algorithm-unsupported";
5498
+ if (verify.tamper_class === "fingerprint-mismatch") return "fingerprint-mismatch";
5467
5499
  if (typeof verify.reason === "string" && verify.reason.startsWith("attestation explicitly unsigned")) return "explicitly-unsigned";
5468
5500
  if (typeof verify.reason === "string" && verify.reason.includes("no .sig sidecar")) return "no-sidecar";
5469
5501
  if (typeof verify.reason === "string" && verify.reason.includes("no public key")) return "no-public-key";
@@ -6045,7 +6077,7 @@ function _playbookSignalCatalog(runner, playbookId) {
6045
6077
  try {
6046
6078
  const pb = runner.loadPlaybook ? runner.loadPlaybook(playbookId) : null;
6047
6079
  if (!pb) return null;
6048
- const inds = (pb.phases?.look?.indicators || []).filter(i => i && i.id);
6080
+ const inds = (pb.phases?.detect?.indicators || []).filter(i => i && i.id);
6049
6081
  if (inds.length === 0) return null;
6050
6082
  return Object.fromEntries(inds.map(i => [i.id, 'inconclusive']));
6051
6083
  } catch { return null; }
@@ -6943,6 +6975,14 @@ function cmdDoctor(runner, args, runOpts, pretty) {
6943
6975
  // /grant:r <USER>:F` to strip inherited entries.
6944
6976
  const aclCheck = checkWindowsAcl(childAbs);
6945
6977
  if (aclCheck.ok) continue;
6978
+ // Resolve the grant principal defensively: an unset USERNAME
6979
+ // would otherwise interpolate "undefined:F", and icacls applies
6980
+ // /inheritance:r BEFORE failing on the unresolvable account —
6981
+ // stripping every inherited entry and locking the file out.
6982
+ // os.userInfo() works even when the env var is absent.
6983
+ const aclUser = process.env.USERNAME || (() => {
6984
+ try { return require('os').userInfo().username; } catch { return null; }
6985
+ })();
6946
6986
  findings.push({
6947
6987
  path: `${displayRoot}/${childRel}`,
6948
6988
  mode: null,
@@ -6950,7 +6990,9 @@ function cmdDoctor(runner, args, runOpts, pretty) {
6950
6990
  issue: 'broader_than_user_only_acl',
6951
6991
  acl_extra_principals: aclCheck.extraPrincipals,
6952
6992
  hint: `icacls "${childAbs}" /inheritance:r /grant:r %USERNAME%:F # NEW-CTRL-050: AI-assistant configs holding MCP tokens / API keys must restrict ACL to the workstation user`,
6953
- fix_command: ['icacls', childAbs, '/inheritance:r', '/grant:r', `${process.env.USERNAME}:F`],
6993
+ ...(aclUser
6994
+ ? { fix_command: ['icacls', childAbs, '/inheritance:r', '/grant:r', `${aclUser}:F`] }
6995
+ : { fix_unavailable: 'current user name unresolvable (USERNAME unset); apply the hint manually' }),
6954
6996
  });
6955
6997
  continue;
6956
6998
  }
@@ -8369,6 +8411,17 @@ function cmdAsk(runner, args, runOpts, pretty) {
8369
8411
  */
8370
8412
  function cmdCi(runner, args, runOpts, pretty) {
8371
8413
  const scope = args.scope;
8414
+ // A value-less `--max-rwep` parses as boolean true and would coerce to
8415
+ // Number(true) === 1 — a finite, non-negative cap that slips past the
8416
+ // numeric guard below and silently sets an extraordinarily strict gate.
8417
+ // Treat the forgotten value as a usage error, same as a non-numeric one.
8418
+ if (args["max-rwep"] === true) {
8419
+ return emitError(
8420
+ "ci: --max-rwep requires a non-negative number.",
8421
+ { verb: "ci", flag: "max-rwep" },
8422
+ pretty,
8423
+ );
8424
+ }
8372
8425
  const maxRwep = args["max-rwep"] !== undefined ? Number(args["max-rwep"]) : null;
8373
8426
  // Reject a non-numeric / negative cap rather than silently coercing it.
8374
8427
  // `--max-rwep abc` previously became Number→NaN→0, degenerating the gate to
@@ -9009,4 +9062,10 @@ function cmdCi(runner, args, runOpts, pretty) {
9009
9062
 
9010
9063
  if (require.main === module) main();
9011
9064
 
9012
- module.exports = { COMMANDS, PKG_ROOT, PLAYBOOK_VERBS, persistAttestation };
9065
+ module.exports = {
9066
+ COMMANDS, PKG_ROOT, PLAYBOOK_VERBS, persistAttestation,
9067
+ // internal helpers exposed for tests
9068
+ _isTamperedSidecarVerify: isTamperedSidecarVerify,
9069
+ _classifySidecarVerify: classifySidecarVerify,
9070
+ _verifyAttestationSidecar: verifyAttestationSidecar,
9071
+ };
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "schema_version": "1.1.0",
3
- "generated_at": "2026-06-05T14:37:44.664Z",
3
+ "generated_at": "2026-06-05T22:20:42.479Z",
4
4
  "generator": "scripts/build-indexes.js",
5
5
  "source_count": 63,
6
6
  "source_hashes": {
7
- "manifest.json": "0d633a546c8d60d593625a6ddc140c6bd6a8c1f940ea29cf0dc4dfddb72b6b39",
7
+ "manifest.json": "fe65c62f46bbb3c069247eb19169ac4ab99bf0354e3e9cfe432782bf402a918a",
8
8
  "data/atlas-ttps.json": "f66b456cf82a3c20575d8479de41f7b11b7ee5693eb1fcf64a67e162ae1b88a2",
9
9
  "data/attack-techniques.json": "c39f28e3402ef13ad9b7076819f63fda67a22f97e3e375cfe01c4a4e0beff7c9",
10
- "data/cve-catalog.json": "8264da4534d39c9493cfcd18acf7e38ed47ce2a81be15afd5a3f4baf1d504929",
11
- "data/cwe-catalog.json": "feadd8497221c097d8237fb93d9557c4dbdd70434097da8debd6f5e50ede1b24",
10
+ "data/cve-catalog.json": "51d8425a49e5cc0375d0a154a83a16816e99c3141a5bbafe6383607ca11be240",
11
+ "data/cwe-catalog.json": "b398003b68b0d9539d13a5536e933005149fd05f3d33978297c870987542cd86",
12
12
  "data/d3fend-catalog.json": "9a54bccb9f24f84b32024216cc3f53819a053721ac8ab43c326859e68fc0ffaf",
13
13
  "data/dlp-controls.json": "d2406c482dddd30e49203879999dc4b3a7fd4d0494d6a61d86b91ee76415df19",
14
14
  "data/exploit-availability.json": "ec2656f0d9a893610e27b43eb6035fe9b18e057c9f6dfaac7e7d4959bbcbb795",
@@ -21,7 +21,7 @@
21
21
  "skills/mcp-agent-trust/skill.md": "0752834acde0303d6d1e36be4b320eac3d34fde715bb8d71f3ad9e801d701482",
22
22
  "skills/framework-gap-analysis/skill.md": "c2da8cc184ac4277309896bf4fe6afe23c419575b297734a8fc168f42d1805e9",
23
23
  "skills/compliance-theater/skill.md": "02b51b932ded4b9b74844ecf842ee3f4d6b09699ebe9cdba457e2c9b7da10ebd",
24
- "skills/exploit-scoring/skill.md": "2bb77e1f667bc47c6ab1c7715071d9fe8da2697f9bbbf7914c7e9fd231ad67f1",
24
+ "skills/exploit-scoring/skill.md": "628f4853e8d6b6ac2ad8ad159f1c9d74c392727f4ebf9bc1d7d8e43074b08a3a",
25
25
  "skills/rag-pipeline-security/skill.md": "49b1910e996f01df1382714f9307ead98028fb5b6911bb9022cb8e5c0edf0723",
26
26
  "skills/ai-c2-detection/skill.md": "08fee5607e4972a3c0e1e31d58d4ddfeffeae1302df22416f3e24c20229fb782",
27
27
  "skills/policy-exception-gen/skill.md": "c549f48c2e44759a74c2c6306f0eb34ea26152fb2a3b0e7e96505d5b174f1bd4",
@@ -73,21 +73,21 @@
73
73
  "index_stats": {
74
74
  "xref_entries": {
75
75
  "cwe_refs": 53,
76
- "d3fend_refs": 21,
77
- "framework_gaps": 87,
76
+ "d3fend_refs": 22,
77
+ "framework_gaps": 96,
78
78
  "atlas_refs": 10,
79
79
  "attack_refs": 51,
80
- "rfc_refs": 23,
80
+ "rfc_refs": 35,
81
81
  "dlp_refs": 0
82
82
  },
83
- "trigger_table_entries": 685,
83
+ "trigger_table_entries": 688,
84
84
  "chains_cve_entries": 426,
85
85
  "chains_cwe_entries": 177,
86
86
  "jurisdictions_indexed": 29,
87
87
  "handoff_dag_nodes": 51,
88
88
  "summary_cards": 51,
89
89
  "section_offsets_skills": 51,
90
- "token_budget_total_approx": 435954,
90
+ "token_budget_total_approx": 435955,
91
91
  "recipes": 8,
92
92
  "jurisdiction_clocks": 29,
93
93
  "did_ladders": 8,
@@ -112,35 +112,35 @@
112
112
  "type": "skill_review",
113
113
  "artifact": "compliance-theater",
114
114
  "path": "skills/compliance-theater/skill.md",
115
- "note": "Detect where an organization passes an audit but remains exposed — seven documented compliance theater patterns"
115
+ "note": "Detect where an organization passes an audit but remains exposed — seven documented compliance theater patterns with specific detection tests"
116
116
  },
117
117
  {
118
118
  "date": "2026-05-22",
119
119
  "type": "skill_review",
120
120
  "artifact": "rag-pipeline-security",
121
121
  "path": "skills/rag-pipeline-security/skill.md",
122
- "note": "RAG-specific threat model — embedding manipulation, vector store poisoning, retrieval filter bypass, indirect prompt injection"
122
+ "note": "RAG-specific threat model — embedding manipulation, vector store poisoning, retrieval filter bypass, indirect prompt injection — no current framework coverage"
123
123
  },
124
124
  {
125
125
  "date": "2026-05-22",
126
126
  "type": "skill_review",
127
127
  "artifact": "policy-exception-gen",
128
128
  "path": "skills/policy-exception-gen/skill.md",
129
- "note": "Generate defensible policy exceptions for architectural realities — ephemeral infra, AI pipelines, ZTA, no-reboot patching"
129
+ "note": "Generate defensible policy exceptions for architectural realities — ephemeral infra, AI pipelines, ZTA, no-reboot patching, with compensating controls and auditor-ready justification"
130
130
  },
131
131
  {
132
132
  "date": "2026-05-22",
133
133
  "type": "skill_review",
134
134
  "artifact": "pqc-first",
135
135
  "path": "skills/pqc-first/skill.md",
136
- "note": "Post-quantum cryptography first mentality — hard version gates (OpenSSL 3.5+), algorithm sunset tracking, HNDL assessment, loopback learning for NIST/IETF evolution"
136
+ "note": "Post-quantum cryptography first mentality — hard version gates, algorithm sunset tracking, loopback learning for NIST/IETF standards evolution"
137
137
  },
138
138
  {
139
139
  "date": "2026-05-22",
140
140
  "type": "skill_review",
141
141
  "artifact": "skill-update-loop",
142
142
  "path": "skills/skill-update-loop/skill.md",
143
- "note": "Meta-skill for keeping all exceptd skills current — CISA KEV triggers, ATLAS version updates, framework amendments, forward_watch resolution, currency scoring"
143
+ "note": "Meta-skill for keeping all exceptd skills current — fires on new CVEs, ATLAS updates, framework changes, and forward_watch triggers"
144
144
  },
145
145
  {
146
146
  "date": "2026-05-22",
@@ -161,7 +161,7 @@
161
161
  "type": "skill_review",
162
162
  "artifact": "ransomware-response",
163
163
  "path": "skills/ransomware-response/skill.md",
164
- "note": "Ransomware-specific incident response — OFAC SDN sanctions screening as payment-posture blocker, EU Reg 2014/833 + UK OFSI + AU DFAT + JP MOF cross-jurisdiction sanctions lookups, decryptor availability via No More Ransom + vendor-specific catalogs, cyber-insurance carrier 24h notification, negotiator-engagement legal posture, immutable-backup viability test, PHI exfil-before-encrypt as distinct breach class, parallel jurisdiction clocks"
164
+ "note": "Ransomware-specific incident response — OFAC sanctions screening as payment-posture blocker, EU Reg 2014/833 + UK OFSI + AU DFAT + JP MOF cross-jurisdiction sanctions lookups, decryptor availability via No More Ransom + vendor-specific catalogs, cyber-insurance carrier 24h notification, negotiator-engagement legal posture, immutable-backup viability test, PHI exfil-before-encrypt as distinct breach class, parallel jurisdiction clocks (NIS2 24h / DORA 4h / GDPR 72h / SEC 8-K 96h / HIPAA 60d / CIRCIA 72h / NYDFS 500.17 24h ransom-payment)"
165
165
  },
166
166
  {
167
167
  "date": "2026-05-19",
@@ -235,7 +235,7 @@
235
235
  "type": "skill_review",
236
236
  "artifact": "ai-attack-surface",
237
237
  "path": "skills/ai-attack-surface/skill.md",
238
- "note": "Comprehensive AI/ML attack surface assessment mapped to MITRE ATLAS v5.6.0 with gap flags"
238
+ "note": "Comprehensive AI/ML attack surface assessment mapped to MITRE ATLAS v5.6.0 with explicit framework gap flags"
239
239
  },
240
240
  {
241
241
  "date": "2026-05-17",
@@ -249,14 +249,14 @@
249
249
  "type": "skill_review",
250
250
  "artifact": "ai-c2-detection",
251
251
  "path": "skills/ai-c2-detection/skill.md",
252
- "note": "Detect adversary use of AI APIs as covert C2 — SesameOp pattern, PROMPTFLUX/PROMPTSTEAL behavioral signatures"
252
+ "note": "Detect adversary use of AI APIs as covert C2 — SesameOp pattern, PROMPTFLUX/PROMPTSTEAL behavioral signatures, response playbook"
253
253
  },
254
254
  {
255
255
  "date": "2026-05-15",
256
256
  "type": "skill_review",
257
257
  "artifact": "kernel-lpe-triage",
258
258
  "path": "skills/kernel-lpe-triage/skill.md",
259
- "note": "Assess Linux kernel LPE exposure — Copy Fail, Dirty Frag, live-patch vs. reboot remediation"
259
+ "note": "Assess Linux kernel LPE exposure — Copy Fail, Dirty Frag, Fragnesia, live-patch vs. reboot remediation paths, framework gap declarations"
260
260
  },
261
261
  {
262
262
  "date": "2026-05-15",
@@ -291,7 +291,7 @@
291
291
  "type": "skill_review",
292
292
  "artifact": "sector-telecom",
293
293
  "path": "skills/sector-telecom/skill.md",
294
- "note": "Telecom and 5G security for mid-2026 — Salt Typhoon, Volt Typhoon, CALEA / IPA-LI gateway compromise, signaling-protocol abuse (SS7 / Diameter / GTP), 5G N6 / N9 isolation, gNB / DU / CU integrity, OEM-equipment supply-chain compromise, AI-RAN / O-RAN security"
294
+ "note": "Telecom and 5G security for mid-2026 — Salt Typhoon, Volt Typhoon, CALEA / IPA-LI gateway compromise, signaling-protocol abuse (SS7 / Diameter / GTP), 5G N6 / N9 isolation, gNB / DU / CU integrity, OEM-equipment supply-chain compromise, AI-RAN / O-RAN security; FCC CPNI + 4-business-day notification, NIS2 Annex I telecom essential entities, UK TSA 2021 + Ofcom, AU SOCI / TSSR, GSMA NESAS, 3GPP TR 33.926 + TS 33.501, ITU-T X.805."
295
295
  },
296
296
  {
297
297
  "date": "2026-05-15",
@@ -305,7 +305,7 @@
305
305
  "type": "skill_review",
306
306
  "artifact": "cloud-iam-incident",
307
307
  "path": "skills/cloud-iam-incident/skill.md",
308
- "note": "Cloud-IAM incident response for AWS / GCP / Azure — account takeover, IAM role assumption abuse, access-key compromise, cross-account assume-role chains, federated-trust attacks, IMDS metadata exfiltration, and Snowflake-AA24-class IdP-to-cloud credential reuse"
308
+ "note": "Cloud-IAM incident response for AWS / GCP / Azure — account takeover, IAM role assumption abuse, access-key compromise, cross-account assume-role chains, federated-trust attacks (IAM Identity Center / Workload Identity Federation / Azure managed identity), IMDS metadata exfiltration, and Snowflake-AA24-class IdP-to-cloud credential reuse"
309
309
  },
310
310
  {
311
311
  "date": "2026-05-15",
@@ -455,7 +455,7 @@
455
455
  "type": "skill_review",
456
456
  "artifact": "security-maturity-tiers",
457
457
  "path": "skills/security-maturity-tiers/skill.md",
458
- "note": "Three-tier implementation roadmap — MVP (ship this week), Practical (scalable today), Overkill (defense-in-depth)"
458
+ "note": "Three-tier implementation roadmap — MVP you can ship today, practical best practices useable now, overkill gold standard for defense-in-depth"
459
459
  }
460
460
  ]
461
461
  }