@blamejs/exceptd-skills 0.11.0 → 0.11.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.11.1 — 2026-05-12
4
+
5
+ **Patch: operator-reported items 47-57.**
6
+
7
+ ### Bugs
8
+
9
+ - **#48 report self-describing header.** `report executive` / `technical` / `compliance` previously emitted identical `# exceptd Security Assessment Report` headers — only stderr (`[orchestrator] Generating <X> report`) distinguished them, so a piped-to-file report had no internal provenance. Now: `# exceptd Executive Report` / `Technical Report` / `Compliance Report` + an HTML-comment marker (`<!-- exceptd-report:flavor=<x> version=<v> -->`) inside the body. Saved files are self-describing.
10
+ - **#50 mutex cross-process enforcement.** `_meta.mutex` was documented but only enforced intra-process (in-memory `_activeRuns` Set). Two parallel `exceptd run kernel` + `exceptd run hardening` invocations in separate shells would race. Now: runner writes a `.exceptd/locks/<playbook>.lock` JSON file (pid + started_at) for the duration of the run; preflight rejects with `blocked_by: mutex` when a non-stale lock exists. Stale locks (dead pid) are auto-GC'd. Released in `finally`.
11
+ - **#51 deprecation message version-aware.** The banner used to say "Prefer `brief --all` (v0.11.0)" unconditionally; operators on v0.10.x reading it would find no `brief` command in their install. Now: banner shows the installed version explicitly and conditionally emits "available in this install" vs "upgrade to v0.11.0+ first."
12
+ - **#47 / #49 exit-code + skill-not-found shapes.** Verified still correct in v0.11.0 — exit 1 on `ok:false`, JSON shape for `skill <missing>`. No regression; added regression test coverage.
13
+
14
+ ### Features
15
+
16
+ - **#54 `--json-stdout-only`** — silences ALL stderr emissions (deprecation banners, unsigned-attestation warnings, hook output). Operators piping JSON results through `jq` or scripting exit codes get clean stdout exclusively. Real errors (uncaught exceptions starting with "Error") still pass through.
17
+ - **#55 `report csaf`** — emits a CSAF 2.0 envelope of the full assessment (findings + dispatch plan + skill currency + host context). Pipes directly into VEX downstream tooling.
18
+ - **#57 default-stdin on pipe.** `exceptd run <playbook>` now auto-detects piped stdin (`process.stdin.isTTY === false`) and assumes `--evidence -`. Operators forgetting the flag no longer hit a precondition halt.
19
+
20
+ ### Already-existing surface (cross-referenced in operator report)
21
+
22
+ - #52 brief lands before deprecating look — already shipped in v0.11.0
23
+ - #53 doctor verb — already shipped in v0.11.0
24
+ - #56 cross-session diff — already exists as `attest diff <a-sid> --against <b-sid>` (v0.11.0)
25
+
3
26
  ## 0.11.0 — 2026-05-12
4
27
 
5
28
  **Minor: architectural CLI redesign — 21 verbs collapsed to 11. Plus operator-reported items 31-46.**
package/README.md CHANGED
@@ -32,7 +32,9 @@ This platform surfaces what is actually happening right now. Every skill explici
32
32
 
33
33
  Pre-1.0. Latest release lives on [GitHub Releases](https://github.com/blamejs/exceptd-skills/releases) and on npm as [`@blamejs/exceptd-skills`](https://www.npmjs.com/package/@blamejs/exceptd-skills) (signed npm provenance attestation). 38 skills across kernel LPE, AI attack surface, MCP trust, RAG security, AI-API C2 detection, PQC migration, framework gap analysis, compliance theater, exploit scoring, threat-model currency, zero-day learning, global GRC, policy exception generation, security maturity tiers, skill update loop, attack-surface pen testing, fuzz testing, DLP gap analysis, supply-chain integrity, defensive-countermeasure mapping, identity assurance, OT/ICS security, coordinated vulnerability disclosure, threat-modeling methodology, child-safety age gates, plus sector packs (federal, financial, healthcare, energy) — and a `researcher` triage dispatcher. 10 data catalogs cover CVE / ATLAS / ATT&CK / CWE / D3FEND / DLP / RFC / framework gaps / global frameworks / zero-day lessons. 34 jurisdictions tracked. AI-consumer ergonomics: `data/_indexes/` ships 17 pre-computed indexes (xref / chains / dispatch / DiD ladders / theater fingerprints / recipes / token budget / currency / activity feed) regenerated by `npm run build-indexes`. External-data refresh is automated nightly via `.github/workflows/refresh.yml` — KEV/EPSS/NVD/RFC drift opens an auto-PR with deltas pre-applied; KEV adds new CVEs and IETF discovery auto-imports new RFCs across 48 project-relevant working groups (`_auto_imported` annotation flags entries for human curation); ATLAS/ATT&CK/CWE/D3FEND version bumps open an issue (audit required per AGENTS.md Hard Rule #12). `exceptd verify` prints dual SHA-256 + SHA3-512 public-key fingerprints for out-of-band key pinning. `exceptd scan` probes 22 PQC algorithms across the full NIST + IETF emerging landscape. `exceptd framework-gap <framework> <scenario>` provides a non-AI programmatic runner for the framework-gap skill.
34
34
 
35
- **v0.10.0 introduces the seven-phase playbook contract** — exceptd ships playbooks under `data/playbooks/*.json` that host AIs (Claude Code, Cursor, Gemini CLI, Codex) execute through seven phases: `govern → direct → look → detect → analyze → validate → close`. exceptd owns govern / direct / analyze / validate / close (knowledge + GRC layer); the host AI owns look / detect (artifact collection + indicator evaluation with its native Bash/Read/Grep/Glob). The runner enforces `threat_currency_score` gates, evaluates per-playbook preconditions, honors mutex sets, picks priority-ordered remediation paths, builds CSAF-2.0 evidence bundles (Ed25519-signed by default), computes ISO 8601 notification deadlines from jurisdiction `clock_starts` events, and generates auditor-ready policy-exception language when remediation cannot complete within compliance windows. See `lib/schemas/playbook.schema.json` for the contract and AGENTS.md "Seven-phase playbook contract" for the host-AI invocation guide. Legacy `exceptd scan` remains for non-AI operators with a deprecation banner — to be removed in v1.0.
35
+ **v0.10.0 introduced the seven-phase playbook contract** — exceptd ships playbooks under `data/playbooks/*.json` that host AIs (Claude Code, Cursor, Gemini CLI, Codex) execute through seven phases: `govern → direct → look → detect → analyze → validate → close`. exceptd owns govern / direct / analyze / validate / close (knowledge + GRC layer); the host AI owns look / detect (artifact collection + indicator evaluation with its native Bash/Read/Grep/Glob).
36
+
37
+ **v0.11.0 collapses the 21-verb CLI into 11 canonical verbs** + flips the default output to human-readable. The new surface: `discover` (scan cwd → recommend playbooks), `brief` (unified info doc, replaces plan + govern + direct + look), `run` (phases 4-7, with flat or nested submission shape, auto-detect cwd context), `ai-run` (JSONL streaming variant for AI conversational flow), `attest` (subverbs: list / show / export / verify / diff — replaces reattest + list-attestations), `doctor` (one-shot health check — signatures + currency + cve/rfc validation + signing status), `ci` (one-shot CI gate, exit-2 on detected or rwep ≥ escalate), `ask` (plain-English routing), `lint` (pre-flight submission shape check). Attestation root moved from cwd-relative `.exceptd/` to `~/.exceptd/attestations/<repo-or-host-tag>/`. v0.10.x verbs (`plan`/`govern`/`direct`/`look`/`scan`/`dispatch`/`currency`/`verify`/`validate-cves`/`validate-rfcs`/`watchlist`/`prefetch`/`build-indexes`/`ingest`/`reattest`/`list-attestations`) still work via one-time deprecation banner — removed in v0.12.
36
38
 
37
39
  ---
38
40
 
@@ -174,71 +176,141 @@ Direct invocations also available: `npm run verify`, `node lib/sign.js sign-all`
174
176
 
175
177
  Every command works the same via `npx @blamejs/exceptd-skills`, a global install (`exceptd`), or a local `node bin/exceptd.js`.
176
178
 
177
- ```
178
- exceptd path Print absolute path to the installed package.
179
-
180
- exceptd prefetch [args] Warm local cache of upstream artifacts.
181
- --max-age 24h Skip entries fresher than this.
182
- --source kev,nvd Comma-separated source filter.
183
- --force Ignore freshness; refetch everything.
184
- --no-network Dry-run plan; do not actually fetch.
185
-
186
- exceptd refresh [args] Refresh upstream data; optionally apply upserts.
187
- --apply Write diffs back to data/*.json and rebuild indexes.
188
- --from-cache [<dir>] Read from prefetch cache instead of upstream.
189
- --swarm Fan-out across worker threads.
190
- --source kev,epss,nvd,rfc,pins Scope by source.
191
- --from-fixture <dir> Test mode — read frozen fixtures.
192
- --report-out <path> Redirect refresh-report.json output.
193
-
194
- exceptd build-indexes [args] Rebuild data/_indexes/*.json (17 outputs).
195
- --only <names> Comma-separated subset (auto-pulls in dependencies).
196
- --changed Rebuild only outputs whose deps changed.
197
- --parallel Run independent outputs concurrently.
198
-
199
- exceptd verify Verify Ed25519 signature on every skill.
200
- Prints dual SHA-256 + SHA3-512 fingerprint
201
- of keys/public.pem so operators can pin
202
- the key out-of-band.
203
- exceptd scan Scan environment for findings — includes
204
- a 22-algorithm PQC probe (NIST finalized
205
- ML-KEM/ML-DSA/SLH-DSA, draft FN-DSA + HQC,
206
- Round-4 alternates Frodo/NTRU/McEliece/BIKE,
207
- signature on-ramp Hawk/Mayo/SQIsign/CROSS/
208
- UOV/SDitH/Mirath/FAEST/Perk, stateful
209
- LMS/XMSS/HSS, IETF composite sigs + KEMs).
210
- exceptd dispatch Scan then route findings to skills. Plan
211
- entries surface per-CVE evidence (IDs +
212
- RWEP scores), not aggregate counts.
213
- exceptd skill <name> Show context for a specific skill.
214
- exceptd currency Skill currency report. Score is age-only
215
- (forward_watch count does NOT reduce score
216
- — it's a maintenance signal).
217
- exceptd report [executive|technical|compliance] Generate report. Executive
218
- summary splits currency into named tiers
219
- (critical-stale <50%, stale 50-69%).
220
- exceptd framework-gap <FW> <SCENARIO> [--json]
221
- Programmatic runner for the framework-gap
222
- skill. Lists matching control gaps,
223
- universal gaps, and theater-risk controls.
224
- Examples:
225
- exceptd framework-gap NIST-800-53 CVE-2026-31431
226
- exceptd framework-gap PCI-DSS-4.0 "prompt injection"
227
- exceptd framework-gap all CVE-2025-53773 --json
228
- exceptd validate-cves [args] Cross-check CVE catalog vs NVD/KEV/EPSS.
229
- --offline Local view only; no network.
230
- --from-cache [<dir>] Cache-first lookups with live fallback.
231
- --no-fail Report drift without failing exit code.
232
- exceptd validate-rfcs [args] Cross-check RFC catalog vs IETF Datatracker.
233
- --offline Local view only; no network.
234
- --from-cache [<dir>] Cache-first lookups with live fallback.
235
- --no-fail Report drift without failing exit code.
236
- exceptd watchlist [--by-skill] Forward-watch aggregator across skills.
179
+ ### v0.11.0 canonical verbs
237
180
 
181
+ ```
182
+ exceptd First-run welcome — two ways to start
183
+ (discover / ask) plus common starting
184
+ playbooks for code / Linux / service contexts.
185
+
186
+ exceptd discover Scan cwd → recommend playbooks based on
187
+ detected files (.git, package.json,
188
+ Dockerfile, requirements.txt, etc) + host
189
+ platform. Replaces scan + dispatch.
190
+ --scan-only Also include legacy host scan findings.
191
+ --json | --pretty Machine output (default is human checklist).
192
+
193
+ exceptd brief [playbook] Unified info doc — jurisdictions + threat
194
+ context + RWEP thresholds + preconditions
195
+ + artifacts + indicators. Replaces plan +
196
+ govern + direct + look.
197
+ --all Every playbook (replaces `plan`).
198
+ --scope <type> system | code | service | cross-cutting.
199
+ --directives Expand directive metadata per playbook.
200
+ --phase <name> Emit only one phase (legacy compat).
201
+
202
+ exceptd run [playbook] Phases 4-7. Auto-detects cwd context when
203
+ no playbook positional.
204
+ --evidence <file|-> Submission JSON (flat or nested shape).
205
+ --evidence-dir <dir> Per-playbook submission files (cron-friendly).
206
+ --scope <type> | --all Multi-playbook run.
207
+ --vex <file> CycloneDX / OpenVEX filter (drop not_affected).
208
+ --format <fmt> ... csaf-2.0 | sarif | openvex | markdown | summary.
209
+ Repeatable. CSAF is primary; extras go to
210
+ close.evidence_package.bundles_by_format.
211
+ --diff-from-latest Drift vs prior attestation for same playbook.
212
+ --ci Exit-code gate (use `exceptd ci` instead).
213
+ --operator <name> Bind attestation to identity.
214
+ --ack Explicit jurisdiction-obligation consent.
215
+ --session-id <id> Reuse session id (collision refused).
216
+ --force-overwrite Override session collision refusal.
217
+ --session-key <hex> HMAC sign evidence_package (≥ 16 hex chars).
218
+ --attestation-root <path> Override ~/.exceptd/attestations/ root.
219
+ --explain Dry-run: preconditions + artifacts +
220
+ signal keys + submission skeleton.
221
+ --signal-list Lighter than --explain; enumerate signal
222
+ keys only.
223
+ --force-stale Override threat_currency_score < 50 gate.
224
+ --air-gap Honor air_gap_alternative paths.
225
+
226
+ exceptd ai-run <playbook> JSONL streaming variant of run. AI emits
227
+ evidence events on stdin; runner streams
228
+ phase events on stdout. One pipe, no
229
+ file handoff.
230
+ --no-stream Single-shot mode (emit one combined JSON).
231
+
232
+ exceptd attest <subverb> [<sid>] Auditor-facing operations.
233
+ attest list Inventory all sessions across both
234
+ ~/.exceptd and cwd-legacy roots.
235
+ attest show <sid> Full (unredacted) attestation.
236
+ attest export <sid> Redacted bundle for audit submission.
237
+ Strips raw artifact values; preserves
238
+ evidence_hash + signature + verdict.
239
+ --format csaf wraps in CSAF envelope.
240
+ attest verify <sid> Ed25519 .sig sidecar verification.
241
+ attest diff <sid> Drift replay (= reattest default).
242
+ --against <other-sid> compares two
243
+ sessions side-by-side with per-artifact
244
+ diff (added / removed / changed).
245
+ --playbook <id> Filter (list / diff).
246
+ --since <ISO> Filter list / diff to entries after date.
247
+
248
+ exceptd discover / doctor / ci See above for doctor and ci.
249
+
250
+ exceptd doctor One-shot health check.
251
+ --signatures Only Ed25519 skill verification.
252
+ --currency Only skill currency report.
253
+ --cves Only CVE catalog drift check.
254
+ --rfcs Only RFC catalog drift check.
255
+
256
+ exceptd ci One-shot CI gate. Exits 2 on detected or
257
+ rwep ≥ rwep_threshold.escalate.
258
+ --all | --scope <type> Pick playbooks; auto-detect if neither.
259
+ --max-rwep <n> Cap below playbook default.
260
+ --block-on-jurisdiction-clock Fail when notification clock fires.
261
+ --evidence / --evidence-dir Per-playbook submission files.
262
+
263
+ exceptd ask "<question>" Plain-English routing to playbook(s).
264
+ Returns ranked playbook IDs based on
265
+ keyword overlap with each playbook's
266
+ domain.name + attack_class + threat_context.
267
+
268
+ exceptd lint <pb> <evidence> Pre-flight check submission shape vs
269
+ playbook (preconditions / artifacts /
270
+ indicators) without executing phases 4-7.
271
+
272
+ exceptd refresh Refresh upstream catalogs + indexes.
273
+ Replaces prefetch + refresh + build-indexes.
274
+ --apply Write diffs back + rebuild indexes.
275
+ --from-cache [<dir>] Read from prefetch cache.
276
+ --no-network Dry-run.
277
+ --indexes-only Rebuild data/_indexes/*.json only.
278
+
279
+ exceptd skill <name> Show context for one skill.
280
+ exceptd framework-gap <FW> <ref> One framework + one CVE/scenario, JSON
281
+ or human. (Operates outside the seven-
282
+ phase contract for ad-hoc gap analysis.)
283
+ exceptd path Absolute path to the installed package.
238
284
  exceptd version Package version.
239
285
  exceptd help This help.
286
+ exceptd <verb> --help Per-verb usage with flag descriptions.
240
287
  ```
241
288
 
289
+ ### Legacy v0.10.x verbs (deprecated, removed in v0.12)
290
+
291
+ These still work but emit a one-time deprecation banner per process:
292
+
293
+ | Legacy verb | v0.11.0 replacement |
294
+ |---|---|
295
+ | `plan` | `brief --all` |
296
+ | `govern <pb>` | `brief <pb> --phase govern` |
297
+ | `direct <pb>` | `brief <pb> --phase direct` |
298
+ | `look <pb>` | `brief <pb> --phase look` |
299
+ | `scan` | `discover --scan-only` |
300
+ | `dispatch` | `discover` |
301
+ | `currency` | `doctor --currency` |
302
+ | `verify` | `doctor --signatures` |
303
+ | `validate-cves` | `doctor --cves` |
304
+ | `validate-rfcs` | `doctor --rfcs` |
305
+ | `ingest` | `run` |
306
+ | `reattest <sid>` | `attest diff <sid>` |
307
+ | `list-attestations` | `attest list` |
308
+ | `watchlist` | (no replacement yet — kept) |
309
+ | `prefetch` | `refresh --no-network` |
310
+ | `build-indexes` | `refresh --indexes-only` |
311
+
312
+ Suppress the deprecation banner: `EXCEPTD_DEPRECATION_SHOWN=1`.
313
+
242
314
  ## Invoking a skill from your AI assistant
243
315
 
244
316
  Once your assistant has loaded `AGENTS.md`, type a trigger phrase or skill name:
package/bin/exceptd.js CHANGED
@@ -275,6 +275,25 @@ Project rules: ${PKG_ROOT}/AGENTS.md
275
275
 
276
276
  function main() {
277
277
  const argv = process.argv.slice(2);
278
+
279
+ // --json-stdout-only: silence ALL stderr emissions (deprecation banners,
280
+ // unsigned-attestation warnings, hook output). Operators piping the JSON
281
+ // result through `jq` or scripting around exit codes want clean stdout
282
+ // exclusively. Handled here at top of main so the deprecation banner +
283
+ // unsigned warning are suppressed before they fire.
284
+ if (argv.includes("--json-stdout-only")) {
285
+ process.env.EXCEPTD_DEPRECATION_SHOWN = "1";
286
+ process.env.EXCEPTD_UNSIGNED_WARNED = "1";
287
+ const origStderrWrite = process.stderr.write.bind(process.stderr);
288
+ process.stderr.write = (chunk, encoding, cb) => {
289
+ // Let actual error frames through (uncaught exceptions need to surface
290
+ // for debugging); suppress framework stderr.
291
+ if (typeof chunk === "string" && chunk.startsWith("Error")) return origStderrWrite(chunk, encoding, cb);
292
+ if (typeof cb === "function") cb();
293
+ return true;
294
+ };
295
+ }
296
+
278
297
  if (argv.length === 0) {
279
298
  printWelcome();
280
299
  process.exit(0);
@@ -300,8 +319,17 @@ function main() {
300
319
  if (PLAYBOOK_VERBS.has(cmd)) {
301
320
  // One-time deprecation banner per process when a legacy verb is invoked.
302
321
  if (LEGACY_VERB_REPLACEMENTS[cmd] && !process.env.EXCEPTD_DEPRECATION_SHOWN) {
322
+ // Mention the installed version explicitly so an operator on v0.10.x
323
+ // who reads "Prefer brief..." doesn't go looking for a verb that
324
+ // doesn't exist in their install. v0.11.0+ has the replacement; v0.10.x
325
+ // users see this with the explicit "upgrade to v0.11.0 first" note.
326
+ const ver = readPkgVersion();
327
+ const haveBrief = ver !== "unknown" && ver.match(/^(\d+)\.(\d+)/) && (parseInt(RegExp.$1, 10) > 0 || parseInt(RegExp.$2, 10) >= 11);
303
328
  process.stderr.write(
304
- `[exceptd] DEPRECATION: \`${cmd}\` is a v0.10.x verb. Prefer \`${LEGACY_VERB_REPLACEMENTS[cmd]}\` (v0.11.0). ` +
329
+ `[exceptd] DEPRECATION: \`${cmd}\` is a v0.10.x verb. ` +
330
+ (haveBrief
331
+ ? `Prefer \`${LEGACY_VERB_REPLACEMENTS[cmd]}\` (available in this install, v${ver}). `
332
+ : `Upgrade to v0.11.0+ then use \`${LEGACY_VERB_REPLACEMENTS[cmd]}\` (currently installed: v${ver}). `) +
305
333
  `Legacy verbs remain functional through this release; they will be removed in v0.12. ` +
306
334
  `Suppress: export EXCEPTD_DEPRECATION_SHOWN=1.\n`
307
335
  );
@@ -429,7 +457,8 @@ function dispatchPlaybook(cmd, argv) {
429
457
  const args = parseArgs(argv, {
430
458
  bool: ["pretty", "air-gap", "force-stale", "all", "flat", "directives",
431
459
  "ci", "latest", "diff-from-latest", "explain", "signal-list", "ack",
432
- "force-overwrite", "no-stream", "block-on-jurisdiction-clock"],
460
+ "force-overwrite", "no-stream", "block-on-jurisdiction-clock",
461
+ "json-stdout-only"],
433
462
  multi: ["playbook", "format"],
434
463
  });
435
464
  const pretty = !!args.pretty;
@@ -1080,6 +1109,13 @@ function cmdRun(runner, args, runOpts, pretty) {
1080
1109
  }
1081
1110
 
1082
1111
  let submission = {};
1112
+ // v0.11.1: auto-detect piped stdin (process.stdin.isTTY === false means
1113
+ // something is piping into us). If no --evidence flag and stdin is a pipe,
1114
+ // assume `--evidence -`. Operators forgetting the flag previously got a
1115
+ // confusing precondition halt; now the common case "just works."
1116
+ if (!args.evidence && process.stdin.isTTY === false) {
1117
+ args.evidence = "-";
1118
+ }
1083
1119
  if (args.evidence) {
1084
1120
  try {
1085
1121
  submission = readEvidence(args.evidence);
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "schema_version": "1.1.0",
3
- "generated_at": "2026-05-12T14:47:57.871Z",
3
+ "generated_at": "2026-05-12T15:12:14.697Z",
4
4
  "generator": "scripts/build-indexes.js",
5
5
  "source_count": 49,
6
6
  "source_hashes": {
7
- "manifest.json": "f4243591af627c02011ae29d97eb702764e495522188a4d8fcab7b58950d941c",
7
+ "manifest.json": "c9d65112c41d74b26c533cc37160fe6d450789ad8910fcb8cc4dae6225940ef3",
8
8
  "data/atlas-ttps.json": "1500b5830dab070c4252496964a8c0948e1052a656e2c7c6e1efaf0350645e13",
9
9
  "data/cve-catalog.json": "a81d3e4b491b27ccc084596b063a6108ff10c9eb01d7776922fc393980b534fe",
10
10
  "data/cwe-catalog.json": "c3367d469b4b3d31e4c56397dd7a8305a0be338ecd85afa27804c0c9ce12157b",
@@ -143,16 +143,69 @@ function preflight(playbook, runOpts = {}) {
143
143
  }
144
144
  }
145
145
 
146
- // 3. Mutex
146
+ // 3. Mutex — both intra-process (in-memory Set) AND cross-process
147
+ // (filesystem lockfile under .exceptd/locks/<playbook>.lock). v0.11.0 only
148
+ // enforced intra-process; v0.11.1 adds cross-process so two parallel CLI
149
+ // invocations of mutex-conflicting playbooks correctly race-detect.
147
150
  for (const conflictId of meta.mutex || []) {
148
151
  if (_activeRuns.has(conflictId)) {
149
- return { ok: false, blocked_by: 'mutex', reason: `Mutex conflict: playbook ${conflictId} is currently active and listed in this playbook's mutex set.`, issues };
152
+ return { ok: false, blocked_by: 'mutex', reason: `Mutex conflict (intra-process): playbook ${conflictId} is currently active and listed in this playbook's mutex set.`, issues };
153
+ }
154
+ const lockPath = lockFilePath(conflictId);
155
+ if (lockPath && fs.existsSync(lockPath)) {
156
+ // Stale-lock detection: if the recorded PID is dead, ignore the lock.
157
+ try {
158
+ const lock = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
159
+ if (lock.pid && !pidAlive(lock.pid)) {
160
+ fs.unlinkSync(lockPath); // GC stale
161
+ } else {
162
+ return {
163
+ ok: false,
164
+ blocked_by: 'mutex',
165
+ reason: `Mutex conflict (cross-process): playbook ${conflictId} has an active lock at ${lockPath} (pid ${lock.pid}, started ${lock.started_at}).`,
166
+ issues,
167
+ };
168
+ }
169
+ } catch { /* malformed lockfile — treat as stale and remove */
170
+ try { fs.unlinkSync(lockPath); } catch {}
171
+ }
150
172
  }
151
173
  }
152
174
 
153
175
  return { ok: true, issues };
154
176
  }
155
177
 
178
+ function lockDir() {
179
+ const dir = path.join(process.cwd(), '.exceptd', 'locks');
180
+ try { fs.mkdirSync(dir, { recursive: true }); } catch {}
181
+ return dir;
182
+ }
183
+
184
+ function lockFilePath(playbookId) {
185
+ try { return path.join(lockDir(), `${playbookId}.lock`); }
186
+ catch { return null; }
187
+ }
188
+
189
+ function acquireLock(playbookId) {
190
+ const p = lockFilePath(playbookId);
191
+ if (!p) return null;
192
+ try {
193
+ fs.writeFileSync(p, JSON.stringify({ pid: process.pid, started_at: new Date().toISOString(), playbook: playbookId }, null, 2), { flag: 'wx' });
194
+ return p;
195
+ } catch { return null; /* already locked or unwritable */ }
196
+ }
197
+
198
+ function releaseLock(lockPath) {
199
+ if (!lockPath) return;
200
+ try { fs.unlinkSync(lockPath); } catch {}
201
+ }
202
+
203
+ function pidAlive(pid) {
204
+ if (typeof pid !== 'number') return false;
205
+ try { process.kill(pid, 0); return true; }
206
+ catch (e) { return e.code !== 'ESRCH'; }
207
+ }
208
+
156
209
  // --- phase 1: govern ---
157
210
 
158
211
  /**
@@ -915,6 +968,9 @@ function run(playbookId, directiveId, agentSubmission = {}, runOpts = {}) {
915
968
  }
916
969
 
917
970
  _activeRuns.add(playbookId);
971
+ // Cross-process mutex lock for this run. preflight verified no other lock
972
+ // exists; we acquire ours and release in the finally block.
973
+ const lockPath = acquireLock(playbookId);
918
974
  try {
919
975
  const phases = {
920
976
  govern: govern(playbookId, directiveId, runOpts),
@@ -947,6 +1003,7 @@ function run(playbookId, directiveId, agentSubmission = {}, runOpts = {}) {
947
1003
  };
948
1004
  } finally {
949
1005
  _activeRuns.delete(playbookId);
1006
+ releaseLock(lockPath);
950
1007
  }
951
1008
  }
952
1009
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "_comment": "Auto-generated by scripts/refresh-manifest-snapshot.js — do not hand-edit. Public skill surface used by check-manifest-snapshot.js to detect breaking removals.",
3
- "_generated_at": "2026-05-12T14:46:11.640Z",
3
+ "_generated_at": "2026-05-12T15:10:59.836Z",
4
4
  "atlas_version": "5.1.0",
5
5
  "skill_count": 38,
6
6
  "skills": [
package/manifest.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "exceptd-security",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "description": "AI security skills grounded in mid-2026 threat reality, not stale framework documentation",
5
5
  "homepage": "https://exceptd.com",
6
6
  "license": "Apache-2.0",
@@ -52,7 +52,7 @@
52
52
  ],
53
53
  "last_threat_review": "2026-05-01",
54
54
  "signature": "WprHkO1KOjQtCBj6/EJghBTNyNKJhn7O2HDbAQZPi5jn4flwHpSrtP8LC15a4Unoh+xiIIgGhvTHZIQFHGMpBQ==",
55
- "signed_at": "2026-05-12T14:46:11.163Z",
55
+ "signed_at": "2026-05-12T15:10:59.392Z",
56
56
  "cwe_refs": [
57
57
  "CWE-125",
58
58
  "CWE-362",
@@ -116,7 +116,7 @@
116
116
  ],
117
117
  "last_threat_review": "2026-05-01",
118
118
  "signature": "fg20bOXGRkPUdLmegeXpTM4hnzl/ArgcVc88rItZN5DdsnFnzPgUU1PwCI82zooyj2GfxJHYjxNkq5qd2zNPBg==",
119
- "signed_at": "2026-05-12T14:46:11.165Z",
119
+ "signed_at": "2026-05-12T15:10:59.394Z",
120
120
  "cwe_refs": [
121
121
  "CWE-1039",
122
122
  "CWE-1426",
@@ -179,7 +179,7 @@
179
179
  ],
180
180
  "last_threat_review": "2026-05-01",
181
181
  "signature": "6JuSzkSSFzFHEZ3ANzqjtIbKPOkwJeKhQ+8WAPB4+dTRvDSeg46n3D88XfGaNd2z7pmg/i8p9ZoImQcHFS4BCg==",
182
- "signed_at": "2026-05-12T14:46:11.166Z",
182
+ "signed_at": "2026-05-12T15:10:59.395Z",
183
183
  "cwe_refs": [
184
184
  "CWE-22",
185
185
  "CWE-345",
@@ -225,7 +225,7 @@
225
225
  "framework_gaps": [],
226
226
  "last_threat_review": "2026-05-01",
227
227
  "signature": "PYSw9abiYfW+y7IkY8udJG5LSds2a4rMimlw3rrdD0zE3vunEeV/y7oTmDD4o83OqHSCKNzF/7vMhvd/noqICQ==",
228
- "signed_at": "2026-05-12T14:46:11.166Z"
228
+ "signed_at": "2026-05-12T15:10:59.395Z"
229
229
  },
230
230
  {
231
231
  "name": "compliance-theater",
@@ -256,7 +256,7 @@
256
256
  ],
257
257
  "last_threat_review": "2026-05-01",
258
258
  "signature": "BMFmmJYP3HsHIjUqnhw8E3MiMGZJsI/eDq51we+nxUicZ8nFUQT9DhmRntAqOs6BUnsfiQNNLc/rrsNh8yg1CQ==",
259
- "signed_at": "2026-05-12T14:46:11.167Z"
259
+ "signed_at": "2026-05-12T15:10:59.396Z"
260
260
  },
261
261
  {
262
262
  "name": "exploit-scoring",
@@ -285,7 +285,7 @@
285
285
  ],
286
286
  "last_threat_review": "2026-05-01",
287
287
  "signature": "VGPyDwy5BRlpn1lZthhPB6ytb4ZcU2j0KtCZbaMkyLdMugQJtK2yEuwrsDH4yEtAhTB6/A4B3eSygJckum49Ag==",
288
- "signed_at": "2026-05-12T14:46:11.167Z"
288
+ "signed_at": "2026-05-12T15:10:59.396Z"
289
289
  },
290
290
  {
291
291
  "name": "rag-pipeline-security",
@@ -322,7 +322,7 @@
322
322
  ],
323
323
  "last_threat_review": "2026-05-01",
324
324
  "signature": "XkFGpsNnXBVslkQ48usEu9l1LjPiV2ppW+M4B63zXFBP2Puh52qYCffEPjUHYhoO5bjgTM7yCbK8XF/Dzk5wBw==",
325
- "signed_at": "2026-05-12T14:46:11.167Z",
325
+ "signed_at": "2026-05-12T15:10:59.396Z",
326
326
  "cwe_refs": [
327
327
  "CWE-1395",
328
328
  "CWE-1426"
@@ -379,7 +379,7 @@
379
379
  ],
380
380
  "last_threat_review": "2026-05-01",
381
381
  "signature": "1Xqy7Kxxy6GpTvuYJPdllPzVDRFxb7N6AuxKuoaO4v91CiZLmiXt0sTIWImKJ3p9Eup6rJNDdsY71dolFhHNBA==",
382
- "signed_at": "2026-05-12T14:46:11.168Z",
382
+ "signed_at": "2026-05-12T15:10:59.396Z",
383
383
  "d3fend_refs": [
384
384
  "D3-CA",
385
385
  "D3-CSPP",
@@ -414,7 +414,7 @@
414
414
  "framework_gaps": [],
415
415
  "last_threat_review": "2026-05-01",
416
416
  "signature": "QNLOmAL54S/Cmk4cdO4L2BCGkqZ/FgY4UBsKWtg/EEW+YXF5ev+a8XsUT8q5veuUa2VYcYna7rD1iAnE+2PDBA==",
417
- "signed_at": "2026-05-12T14:46:11.168Z",
417
+ "signed_at": "2026-05-12T15:10:59.397Z",
418
418
  "cwe_refs": [
419
419
  "CWE-1188"
420
420
  ]
@@ -442,7 +442,7 @@
442
442
  "framework_gaps": [],
443
443
  "last_threat_review": "2026-05-01",
444
444
  "signature": "aFHq4cSl3CKchnVITxx+BrAEWD33WtFFJoQtwAug5g9R3/3ABtjaXYGVQaZcdcG1AIZkMoGSPywgLQWDY7ZDCw==",
445
- "signed_at": "2026-05-12T14:46:11.168Z"
445
+ "signed_at": "2026-05-12T15:10:59.397Z"
446
446
  },
447
447
  {
448
448
  "name": "global-grc",
@@ -474,7 +474,7 @@
474
474
  "framework_gaps": [],
475
475
  "last_threat_review": "2026-05-01",
476
476
  "signature": "viCTUWdy6euvd2KTAo6sLvarK/FZkDtYGocxBt0H+fY94kLQGW8K5cSpqIWdUF5NUytSHBCiG4YcSze8P9Z/BQ==",
477
- "signed_at": "2026-05-12T14:46:11.169Z"
477
+ "signed_at": "2026-05-12T15:10:59.398Z"
478
478
  },
479
479
  {
480
480
  "name": "zeroday-gap-learn",
@@ -501,7 +501,7 @@
501
501
  "framework_gaps": [],
502
502
  "last_threat_review": "2026-05-01",
503
503
  "signature": "6PkUaHQi3Hxuqq/Jp4GYckvfqVEofmeT87NUH0T+pwyjlc+xZkoqNPn65f7ldciEPL86JIPi3/dDTKQbIFFBCw==",
504
- "signed_at": "2026-05-12T14:46:11.169Z"
504
+ "signed_at": "2026-05-12T15:10:59.398Z"
505
505
  },
506
506
  {
507
507
  "name": "pqc-first",
@@ -553,7 +553,7 @@
553
553
  ],
554
554
  "last_threat_review": "2026-05-01",
555
555
  "signature": "ZenFTEzWx+DzrSXlNXhbZ70vOdJSXfrnKkAwqMlBf5nlDf38V1/hG4XCKj43snQXWr4mVJOX6ilqFLTYNIjnBw==",
556
- "signed_at": "2026-05-12T14:46:11.169Z",
556
+ "signed_at": "2026-05-12T15:10:59.398Z",
557
557
  "cwe_refs": [
558
558
  "CWE-327"
559
559
  ],
@@ -600,7 +600,7 @@
600
600
  ],
601
601
  "last_threat_review": "2026-05-01",
602
602
  "signature": "ih0vpd2v2zS31JSJv7SnABoya8JlJdrXZXx4rBnrsV3Assj+dbjAP0pQ1HMT/5RX8yTTswRQsg0bJV3qmbJ3Bw==",
603
- "signed_at": "2026-05-12T14:46:11.170Z"
603
+ "signed_at": "2026-05-12T15:10:59.399Z"
604
604
  },
605
605
  {
606
606
  "name": "security-maturity-tiers",
@@ -637,7 +637,7 @@
637
637
  ],
638
638
  "last_threat_review": "2026-05-01",
639
639
  "signature": "Lv8dHiwIqUbNsywCCB/+pYWGF+MHCvxVn1IAvR7Cnif5fy0sICv0N4SVsSb621qAAkHNshpfxqwuhbuQnE1TBA==",
640
- "signed_at": "2026-05-12T14:46:11.170Z",
640
+ "signed_at": "2026-05-12T15:10:59.399Z",
641
641
  "cwe_refs": [
642
642
  "CWE-1188"
643
643
  ]
@@ -672,7 +672,7 @@
672
672
  "framework_gaps": [],
673
673
  "last_threat_review": "2026-05-11",
674
674
  "signature": "BS+wrL28HHYhBpe+v84VLoq9KPBXu6alfG968katfGIoLNYQueaHP931bRmlkrjfeb6qbDf067GWdPEh7nroAw==",
675
- "signed_at": "2026-05-12T14:46:11.170Z"
675
+ "signed_at": "2026-05-12T15:10:59.399Z"
676
676
  },
677
677
  {
678
678
  "name": "attack-surface-pentest",
@@ -743,7 +743,7 @@
743
743
  "PTES revision incorporating AI-surface enumeration"
744
744
  ],
745
745
  "signature": "vLhIYT/CC3IzxMRa+UPeqGSZTvthuwUeTMGNFMm37+TaEk0TtfwPrPyrBJLHw4W6Wt7+pufjHs46X3nTgzoRAg==",
746
- "signed_at": "2026-05-12T14:46:11.171Z"
746
+ "signed_at": "2026-05-12T15:10:59.399Z"
747
747
  },
748
748
  {
749
749
  "name": "fuzz-testing-strategy",
@@ -803,7 +803,7 @@
803
803
  "OSS-Fuzz-Gen / AI-assisted harness generation becoming the default expectation for OSS maintainers"
804
804
  ],
805
805
  "signature": "TOcQLy/427cuf0Lw90J7A0oIeuhUmf9NXb6tOUS5K3SazCKTJujPgYSVAPZOYf1zZrRAY/aq0iqELd5cLyk5DA==",
806
- "signed_at": "2026-05-12T14:46:11.171Z"
806
+ "signed_at": "2026-05-12T15:10:59.400Z"
807
807
  },
808
808
  {
809
809
  "name": "dlp-gap-analysis",
@@ -878,7 +878,7 @@
878
878
  "Quebec Law 25, India DPDPA, KSA PDPL enforcement actions naming AI-tool prompt data as in-scope personal information"
879
879
  ],
880
880
  "signature": "u4IN7escQa5V+OgdtaJXLdvhmNiGZsdmGOvebTLZ30WoImT+WiksvaqSa0POGdbr6HzFkALe2RrZEH9Tr0U6Dg==",
881
- "signed_at": "2026-05-12T14:46:11.171Z"
881
+ "signed_at": "2026-05-12T15:10:59.400Z"
882
882
  },
883
883
  {
884
884
  "name": "supply-chain-integrity",
@@ -955,7 +955,7 @@
955
955
  "OpenSSF model-signing — emerging Sigstore-based signing standard for ML model weights; track for production adoption"
956
956
  ],
957
957
  "signature": "eTGQJ3gnG24WggfwuFNNIFOWV/ttPxTa3pvx9OH28m5KDS1a4ZmOR7K8y01wk/su8bH0ClYYRfoBfKQOtRswAg==",
958
- "signed_at": "2026-05-12T14:46:11.172Z"
958
+ "signed_at": "2026-05-12T15:10:59.401Z"
959
959
  },
960
960
  {
961
961
  "name": "defensive-countermeasure-mapping",
@@ -1012,7 +1012,7 @@
1012
1012
  ],
1013
1013
  "last_threat_review": "2026-05-11",
1014
1014
  "signature": "q7gFLPoqf/8bqATR6gt/nj0EoyUOlfzi+bZ0bT3pC9KW7O6M/ji9fT+AXSGNp6PKd+70ACb3mkMGmWgjLpQXCg==",
1015
- "signed_at": "2026-05-12T14:46:11.172Z"
1015
+ "signed_at": "2026-05-12T15:10:59.402Z"
1016
1016
  },
1017
1017
  {
1018
1018
  "name": "identity-assurance",
@@ -1079,7 +1079,7 @@
1079
1079
  "d3fend_refs": [],
1080
1080
  "last_threat_review": "2026-05-11",
1081
1081
  "signature": "pX8rhrrzuyG3iRrPORLqTZAjzGdWK/bKPUGJG5WHSZcv4LB0kQXOit4sHG0exdXxI6HY8jyX67QY4r5vEHHACw==",
1082
- "signed_at": "2026-05-12T14:46:11.172Z"
1082
+ "signed_at": "2026-05-12T15:10:59.402Z"
1083
1083
  },
1084
1084
  {
1085
1085
  "name": "ot-ics-security",
@@ -1135,7 +1135,7 @@
1135
1135
  "d3fend_refs": [],
1136
1136
  "last_threat_review": "2026-05-11",
1137
1137
  "signature": "ypb8kNZQRdyu5mWeveB7sjCjNKXS1yXvjDJv88muzwhOs/a4Fu/Gb532js5NKyy+eCw/emrphpTZaL8R9a2lBA==",
1138
- "signed_at": "2026-05-12T14:46:11.172Z"
1138
+ "signed_at": "2026-05-12T15:10:59.402Z"
1139
1139
  },
1140
1140
  {
1141
1141
  "name": "coordinated-vuln-disclosure",
@@ -1187,7 +1187,7 @@
1187
1187
  "NYDFS 23 NYCRR 500 amendments potentially adding explicit CVD program requirements"
1188
1188
  ],
1189
1189
  "signature": "346Lt+277ycRNsyAOGwLSONi4awgxKy3hP9G+BWjwaa8ySmTeqbYsbyyhtxjeohk9bV2SF+Hl2q4JdSvc/2qCQ==",
1190
- "signed_at": "2026-05-12T14:46:11.173Z"
1190
+ "signed_at": "2026-05-12T15:10:59.402Z"
1191
1191
  },
1192
1192
  {
1193
1193
  "name": "threat-modeling-methodology",
@@ -1237,7 +1237,7 @@
1237
1237
  "PASTA v2 updates incorporating AI/ML application threats"
1238
1238
  ],
1239
1239
  "signature": "ewTvG5vu3ngFHyXgBur5vSKDFQsOZx0x79djGMricl7LCvQf5//OG6LZKXa+AOuEq58prRS+HgzrFA1DiTfeCQ==",
1240
- "signed_at": "2026-05-12T14:46:11.173Z"
1240
+ "signed_at": "2026-05-12T15:10:59.403Z"
1241
1241
  },
1242
1242
  {
1243
1243
  "name": "webapp-security",
@@ -1311,7 +1311,7 @@
1311
1311
  "d3fend_refs": [],
1312
1312
  "last_threat_review": "2026-05-11",
1313
1313
  "signature": "ZHjbKu0Em92Kimr2esL1g93mf9TmcsChBhVEMWf/lFrjeLcg8nyHEIcDstIZ3FWYgc6MQNHnc3Rup3Xp/Za1Cw==",
1314
- "signed_at": "2026-05-12T14:46:11.173Z"
1314
+ "signed_at": "2026-05-12T15:10:59.403Z"
1315
1315
  },
1316
1316
  {
1317
1317
  "name": "ai-risk-management",
@@ -1361,7 +1361,7 @@
1361
1361
  "d3fend_refs": [],
1362
1362
  "last_threat_review": "2026-05-11",
1363
1363
  "signature": "1KRxjCbAX0Rs5NTOioi1w/f1SOzDQrtRoXjTDtzEwJ+d1QzFf9cqmBlp0uXmGpL0bzEaHWIctjigSychmoL2Dw==",
1364
- "signed_at": "2026-05-12T14:46:11.174Z"
1364
+ "signed_at": "2026-05-12T15:10:59.403Z"
1365
1365
  },
1366
1366
  {
1367
1367
  "name": "sector-healthcare",
@@ -1421,7 +1421,7 @@
1421
1421
  "d3fend_refs": [],
1422
1422
  "last_threat_review": "2026-05-11",
1423
1423
  "signature": "eiajFh7w7d4g+/crGalTtw9Qsu0deVsdHkdthZSy595ifGmgu0zaFD8usKThbPhOdUCCclTYkZYz5GalQmkhCw==",
1424
- "signed_at": "2026-05-12T14:46:11.174Z"
1424
+ "signed_at": "2026-05-12T15:10:59.404Z"
1425
1425
  },
1426
1426
  {
1427
1427
  "name": "sector-financial",
@@ -1502,7 +1502,7 @@
1502
1502
  "TIBER-EU framework v2.0 alignment with DORA TLPT RTS (JC 2024/40); cross-recognition with CBEST and iCAST"
1503
1503
  ],
1504
1504
  "signature": "iSZR/fYESQVyjkcqj+O+yzU0BQfaELH5s7WizzUTWvDPDTD2ZyOnZTT1r/Zfx2l4mbPmVeFGWdYnnVFTk/i3Aw==",
1505
- "signed_at": "2026-05-12T14:46:11.174Z"
1505
+ "signed_at": "2026-05-12T15:10:59.404Z"
1506
1506
  },
1507
1507
  {
1508
1508
  "name": "sector-federal-government",
@@ -1571,7 +1571,7 @@
1571
1571
  "Australia PSPF 2024 revision and ISM quarterly updates — track for Essential Eight Maturity Level requirements for federal entities"
1572
1572
  ],
1573
1573
  "signature": "Wjdo5YXEL8XeNZkaEueG1DOUoyalstNPzQkxD/cwP5iMrJWg/Ly+sC0Oluuqm3aU7d63z55PrbGQCJD0XVZqBg==",
1574
- "signed_at": "2026-05-12T14:46:11.175Z"
1574
+ "signed_at": "2026-05-12T15:10:59.404Z"
1575
1575
  },
1576
1576
  {
1577
1577
  "name": "sector-energy",
@@ -1636,7 +1636,7 @@
1636
1636
  "ICS-CERT advisory feed (https://www.cisa.gov/news-events/cybersecurity-advisories/ics-advisories) for vendor CVEs in Siemens, Rockwell, Schneider Electric, ABB, GE Vernova, Hitachi Energy, AVEVA / OSIsoft PI"
1637
1637
  ],
1638
1638
  "signature": "c/l7dOHe0Zj6Ag3abUaEie6o0f8M4rhY5aPI9/wG4z6FDue9PzCVw8vUGoITFgg89g97lMfy2C3CE2PegQoFCw==",
1639
- "signed_at": "2026-05-12T14:46:11.175Z"
1639
+ "signed_at": "2026-05-12T15:10:59.405Z"
1640
1640
  },
1641
1641
  {
1642
1642
  "name": "api-security",
@@ -1705,7 +1705,7 @@
1705
1705
  "d3fend_refs": [],
1706
1706
  "last_threat_review": "2026-05-11",
1707
1707
  "signature": "9FgcJvYeo07QxQ+mnVRQk4jYLDMO/AVSXMs8cueO2f/qMOTQmrhBMVhj5ze7hzvXpGkp7EK/3Q1XKqde61JMAg==",
1708
- "signed_at": "2026-05-12T14:46:11.175Z"
1708
+ "signed_at": "2026-05-12T15:10:59.405Z"
1709
1709
  },
1710
1710
  {
1711
1711
  "name": "cloud-security",
@@ -1786,7 +1786,7 @@
1786
1786
  "CISA KEV additions for cloud-control-plane CVEs (IMDSv1 abuses, federation token mishandling, cross-tenant boundary failures); CISA Cybersecurity Advisories for cross-cloud advisories"
1787
1787
  ],
1788
1788
  "signature": "xRA0XZf7VPtuBtbsm41bay9yBLphw/hlL3YxIUrpko5g9ldM3oJe9o1qSwzIj/wSnQSI29qqPpNsnlks+HEOCA==",
1789
- "signed_at": "2026-05-12T14:46:11.176Z"
1789
+ "signed_at": "2026-05-12T15:10:59.405Z"
1790
1790
  },
1791
1791
  {
1792
1792
  "name": "container-runtime-security",
@@ -1848,7 +1848,7 @@
1848
1848
  "d3fend_refs": [],
1849
1849
  "last_threat_review": "2026-05-11",
1850
1850
  "signature": "GcU50DStuN1gU/Evm/sFRgeieQbqffVp12rgbGnasRX89Q7kM4ltFXB+bgCXHIvICzYb78hPIifWQb9UVupWBQ==",
1851
- "signed_at": "2026-05-12T14:46:11.176Z"
1851
+ "signed_at": "2026-05-12T15:10:59.406Z"
1852
1852
  },
1853
1853
  {
1854
1854
  "name": "mlops-security",
@@ -1919,7 +1919,7 @@
1919
1919
  "MITRE ATLAS v5.2 — track AML.T0010 sub-technique expansion and any new MLOps-pipeline-specific TTPs"
1920
1920
  ],
1921
1921
  "signature": "onIazpFoL1t4PMNRsoF06ggnl7BzCKjt0x+ZmVfWfyt1V06DgllsrbN3AAz4+g4jW2Sc71q0vIFKfwEUWpGVAQ==",
1922
- "signed_at": "2026-05-12T14:46:11.176Z"
1922
+ "signed_at": "2026-05-12T15:10:59.406Z"
1923
1923
  },
1924
1924
  {
1925
1925
  "name": "incident-response-playbook",
@@ -1981,7 +1981,7 @@
1981
1981
  "NYDFS 23 NYCRR 500.17 amendments tightening ransom-payment 24h disclosure operationalization"
1982
1982
  ],
1983
1983
  "signature": "P0Yv4CtqbnBNP6nSIxQUYYHL7T7ci+iE7iE2UXVfnMPeWVdKG2nvRePjBXc3JZTLima1Txn/I5ocDNhLTIeUAQ==",
1984
- "signed_at": "2026-05-12T14:46:11.177Z"
1984
+ "signed_at": "2026-05-12T15:10:59.406Z"
1985
1985
  },
1986
1986
  {
1987
1987
  "name": "email-security-anti-phishing",
@@ -2034,7 +2034,7 @@
2034
2034
  "d3fend_refs": [],
2035
2035
  "last_threat_review": "2026-05-11",
2036
2036
  "signature": "2pv81lLRbazpHqundCANb3YiLB4lkVsYctIDvI8rxSvHxhPS9jYXqmAoB5APSdDuOaew6XqpfZOehQUj9WmyBw==",
2037
- "signed_at": "2026-05-12T14:46:11.177Z"
2037
+ "signed_at": "2026-05-12T15:10:59.407Z"
2038
2038
  },
2039
2039
  {
2040
2040
  "name": "age-gates-child-safety",
@@ -2102,7 +2102,7 @@
2102
2102
  "US state adult-site age-verification laws — 19+ states by mid-2026 (TX HB 18 upheld by SCOTUS June 2025 in Free Speech Coalition v. Paxton); track ongoing challenges in remaining states"
2103
2103
  ],
2104
2104
  "signature": "BJ/YYnGVXeSBaR9oWAVrcNX7Wz+kE8R4CghX6+XEI/qY89fyrkKNNwo2veqqf49wffJhHVJ1wTp8ZDECjNp+Dw==",
2105
- "signed_at": "2026-05-12T14:46:11.177Z"
2105
+ "signed_at": "2026-05-12T15:10:59.407Z"
2106
2106
  }
2107
2107
  ]
2108
2108
  }
@@ -304,12 +304,62 @@ function runCurrency() {
304
304
  }
305
305
 
306
306
  async function runReport(format) {
307
+ // v0.11.1 feature #55: `report csaf` emits a CSAF 2.0 envelope covering
308
+ // every scanned finding + dispatched plan + currency posture. Useful for
309
+ // VEX downstreams that ingest CSAF JSON.
310
+ if (format === 'csaf') {
311
+ const scanResult = await scan();
312
+ const plan = dispatch(scanResult.findings);
313
+ const { currency_report } = currencyCheck();
314
+ const ver = (function(){try{return require('../package.json').version;}catch{return 'unknown';}})();
315
+ const csaf = {
316
+ document: {
317
+ category: 'csaf_security_advisory',
318
+ csaf_version: '2.0',
319
+ publisher: { category: 'vendor', name: 'exceptd', namespace: 'https://exceptd.com' },
320
+ title: `exceptd assessment report — ${scanResult.summary.total_findings} finding(s) across ${plan.plan.length} skill(s)`,
321
+ tracking: {
322
+ id: `exceptd-report-${Date.now()}`,
323
+ status: 'final',
324
+ version: ver,
325
+ initial_release_date: new Date().toISOString(),
326
+ revision_history: [{ number: '1', date: new Date().toISOString(), summary: 'Initial report emission' }],
327
+ },
328
+ },
329
+ vulnerabilities: scanResult.findings
330
+ .filter(f => f.cve_id)
331
+ .map(f => ({
332
+ cve: f.cve_id,
333
+ notes: [{ category: 'description', text: f.action_required || f.signal }],
334
+ scores: [{ products: [], cvss_v3: { base_score: 0 } }],
335
+ threats: f.severity === 'critical' ? [{ category: 'exploit_status', details: f.action_required }] : [],
336
+ })),
337
+ exceptd_extension: {
338
+ scan_summary: scanResult.summary,
339
+ dispatch_plan: plan,
340
+ skill_currency: currency_report,
341
+ host: scanResult.host,
342
+ },
343
+ };
344
+ process.stdout.write(JSON.stringify(csaf, null, 2) + '\n');
345
+ return;
346
+ }
347
+
307
348
  console.log(`[orchestrator] Generating ${format} report...\n`);
308
349
  const scanResult = await scan();
309
350
  const plan = dispatch(scanResult.findings);
310
351
  const { currency_report } = currencyCheck();
311
352
 
312
- console.log('# exceptd Security Assessment Report');
353
+ // Bug #48: header now self-describes the report flavor so a piped-to-file
354
+ // report carries its provenance internally. Previously only stderr
355
+ // (`[orchestrator] Generating <X> report`) distinguished the three.
356
+ const flavorTitle = {
357
+ executive: 'Executive Report',
358
+ technical: 'Technical Report',
359
+ compliance: 'Compliance Report',
360
+ }[format] || 'Report';
361
+ console.log(`# exceptd ${flavorTitle}`);
362
+ console.log(`<!-- exceptd-report:flavor=${format} version=${(function(){try{return require('../package.json').version;}catch{return 'unknown';}})()} -->`);
313
363
  console.log(`Generated: ${new Date().toISOString()}\n`);
314
364
 
315
365
  console.log('## Executive Summary');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/exceptd-skills",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "description": "AI security skills grounded in mid-2026 threat reality, not stale framework documentation. 38 skills, 10 catalogs, 34 jurisdictions, pre-computed indexes, Ed25519-signed.",
5
5
  "keywords": [
6
6
  "ai-security",
package/sbom.cdx.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "bomFormat": "CycloneDX",
3
3
  "specVersion": "1.6",
4
- "serialNumber": "urn:uuid:0e46fda6-1345-4ea4-ad34-0bfaf842d658",
4
+ "serialNumber": "urn:uuid:8c82a24f-1d6f-4e1f-80ad-08975a8a86ea",
5
5
  "version": 1,
6
6
  "metadata": {
7
- "timestamp": "2026-05-12T14:46:12.106Z",
7
+ "timestamp": "2026-05-12T15:11:00.344Z",
8
8
  "tools": [
9
9
  {
10
10
  "name": "hand-written",
@@ -13,10 +13,10 @@
13
13
  }
14
14
  ],
15
15
  "component": {
16
- "bom-ref": "pkg:npm/@blamejs/exceptd-skills@0.11.0",
16
+ "bom-ref": "pkg:npm/@blamejs/exceptd-skills@0.11.1",
17
17
  "type": "application",
18
18
  "name": "@blamejs/exceptd-skills",
19
- "version": "0.11.0",
19
+ "version": "0.11.1",
20
20
  "description": "AI security skills grounded in mid-2026 threat reality, not stale framework documentation. 38 skills, 10 catalogs, 34 jurisdictions, pre-computed indexes, Ed25519-signed.",
21
21
  "licenses": [
22
22
  {
@@ -25,11 +25,11 @@
25
25
  }
26
26
  }
27
27
  ],
28
- "purl": "pkg:npm/%40blamejs/exceptd-skills@0.11.0",
28
+ "purl": "pkg:npm/%40blamejs/exceptd-skills@0.11.1",
29
29
  "externalReferences": [
30
30
  {
31
31
  "type": "distribution",
32
- "url": "https://www.npmjs.com/package/@blamejs/exceptd-skills/v/0.11.0"
32
+ "url": "https://www.npmjs.com/package/@blamejs/exceptd-skills/v/0.11.1"
33
33
  },
34
34
  {
35
35
  "type": "vcs",