@blamejs/exceptd-skills 0.12.21 → 0.12.22
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 +57 -24
- package/bin/exceptd.js +364 -62
- package/data/_indexes/_meta.json +3 -3
- package/data/playbooks/runtime.json +2 -0
- package/lib/playbook-runner.js +165 -32
- package/lib/refresh-network.js +9 -4
- package/lib/validate-cve-catalog.js +2 -2
- package/lib/verify.js +36 -5
- package/manifest.json +41 -41
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
- package/scripts/verify-shipped-tarball.js +6 -2
- package/skills/threat-model-currency/skill.md +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.12.22 — 2026-05-15
|
|
4
|
+
|
|
5
|
+
**Patch: trust-chain attestation sidecar redesign, CSAF spec-compliance fixes, CLI flag scoping, concurrency exit-code surface.**
|
|
6
|
+
|
|
7
|
+
### Trust chain
|
|
8
|
+
|
|
9
|
+
- **`.sig` sidecar shape reduced to signed-bytes only.** The previous shape carried `signed_at`, `signs_path`, and `signs_sha256` alongside the Ed25519 signature — but those fields were NOT covered by the signature (the signature signs the attestation file bytes, not the sidecar). An attacker who captured any valid sidecar could rewrite `signed_at` to lie about freshness or `signs_path` to point at a sibling attestation in the same session directory, and the signature still verified. Sidecar now carries `{algorithm, signature_base64, note}` (signed) or `{algorithm, signed: false, note}` (unsigned) only. Operators reading freshness use filesystem mtime; the attestation file's own `captured_at` field is signed.
|
|
10
|
+
- **`cmdReattest --force-replay` persists the override as a signed `replay-<isoZ>.json`** in the session directory alongside `attestation.json`. The previous shape emitted the override metadata only to stdout, so the audit trail vanished when the shell closed. `attest verify <session-id>` surfaces both the original attestation and any replay records so an auditor sees the full chain.
|
|
11
|
+
- **Sidecar verifier enforces `algorithm === 'Ed25519'` strictly.** Both `verifyAttestationSidecar` and `cmdAttest verify` previously fell through to `crypto.verify` for any non-`"unsigned"` algorithm value. A `null`, `"RSA-PSS"`, array, or omitted-field sidecar now surfaces `tamper_class: 'algorithm-unsupported'` and exits 6. Matches the strict gate already in place at `verifyManifestSignature`.
|
|
12
|
+
- **`hasReadableStdin` Windows fallback tightened to strict `=== false`** to close the wrapped-test-harness hang regression. The helper now requires `process.stdin.isTTY === false` (not falsy) on Windows when fstat reports size 0 on a non-FIFO non-socket non-character descriptor. POSIX pipes/FIFOs/sockets remain trusted via the `isFIFO()`/`isSocket()`/`isCharacterDevice()` probes added in v0.12.21.
|
|
13
|
+
- **`keys/EXPECTED_FINGERPRINT` pin loaders strip UTF-8 BOM.** Four sites (`bin/exceptd.js`, `lib/verify.js`, `lib/refresh-network.js`, `scripts/verify-shipped-tarball.js`) now share a single `loadExpectedFingerprintFirstLine` helper that strips a leading `U+FEFF` before splitting on newlines. A pin file saved via Notepad with `files.encoding: utf8bom` previously broke every verify path; the helper closes that DoS-by-encoding-roundtrip class.
|
|
14
|
+
- **`sanitizeOperatorText` (library entry point) NFC-normalizes and rejects Unicode `\p{C}`** (Cc/Cf/Cs/Co/Cn). The CLI-level guard added in v0.12.21 only fired on operator-supplied `--operator` input; library callers of `buildEvidenceBundle` bypassed the sanitization. The helper now uniformly returns null for inputs containing bidi-control / zero-width / surrogate / private-use / unassigned characters or empty-after-strip, and caps at 256 codepoints (not 256 UTF-16 code units, so astral-plane characters don't smuggle past).
|
|
15
|
+
|
|
16
|
+
### Bundles (CSAF / SARIF / OpenVEX)
|
|
17
|
+
|
|
18
|
+
- **CSAF `cvss_v3` block emitted only for `CVSS:3.0` / `CVSS:3.1` vectors.** Catalog entries carrying `CVSS:2.0/` or `CVSS:4.0/` vectors previously produced a `cvss_v3.version` of `'2.0'` / `'4.0'`, which violates the CSAF 2.0 schema enum `["3.0", "3.1"]`. Strict validators (BSI CSAF Validator) rejected the bundle. The block is now omitted for non-v3 vectors and a `bundle_cvss_v3_version_unsupported` runtime warning surfaces in `analyze.runtime_errors[]` so operators see the gap.
|
|
19
|
+
- **CSAF `vulnerabilities[].ids[]` routes `RUSTSEC-*` to `system_name: 'RUSTSEC'`**. Previously RUSTSEC advisories fell through to `system_name: 'OSV'` — mis-attributing the authority. Unknown prefixes (any advisory id not in the GHSA / MAL / OSV / SNYK / RUSTSEC set) now emit `system_name: 'exceptd-unknown'` so downstream tooling sees the authority wasn't recognized.
|
|
20
|
+
- **Non-string `cve_id` no longer emits literal `"null"` text.** Catalog entries whose `cve_id` is `null` / `undefined` / non-string are now omitted from `vulnerabilities[]` entirely, with a `bundle_cve_id_missing` runtime warning. Strict validators no longer see ghost vulnerabilities keyed on `text: "null"`.
|
|
21
|
+
|
|
22
|
+
### CLI
|
|
23
|
+
|
|
24
|
+
- **`--csaf-status` and `--publisher-namespace` refused on info-only verbs**. The flags were previously validated then silently dropped when invoked against `brief`, `list`, `attest`, `discover`, `doctor`, `lint`, etc. — same UX-trap class as the v0.12.21 `--ack` fix. The flags now refuse with a structured error pointing at the verb set that actually consumes them (`run`, `ci`, `run-all`, `ai-run`). Error messages also use the actual invoked verb as the prefix instead of a hardcoded `"run:"`.
|
|
25
|
+
- **`cmdRunMulti` consent gate now per-playbook**. The single-playbook `cmdRun` correctly gates `operator_consent` persistence on `classification === 'detected'`, but `cmdRunMulti` was persisting consent unconditionally across every iteration regardless of the iteration's own classification. Per-playbook consent gating now mirrors the single-run shape; mixed-classification `run-all --ack` runs persist consent only into the detected-playbook attestations.
|
|
26
|
+
- **UTF-16BE `readJsonFile` no longer leaks uninitialized buffer bytes.** The decoder used `Buffer.allocUnsafe` (uninitialized heap memory) and silently skipped the trailing byte on odd-length payloads — the decoded string then included whatever bytes happened to be on the heap at allocation time. Now uses `Buffer.alloc` (zero-initialized) and refuses odd-length payloads with a clear truncation error.
|
|
27
|
+
- **`run` and `ci` help text documents `--csaf-status` and `--publisher-namespace`**.
|
|
28
|
+
|
|
29
|
+
### Concurrency
|
|
30
|
+
|
|
31
|
+
- **`persistAttestation` lock contention exits 8 (`LOCK_CONTENTION`)** distinct from generic exit 1. The v0.12.21 entry claimed callers could distinguish lock-busy from hard failure via the `lock_contention: true` field, but `emit()`'s auto-mapping collapsed the exit code to 1. The function now sets `process.exitCode = 8` before returning, with `exit_code: 8` echoed in the result body. Exit-code table in `run --help` documents the code.
|
|
32
|
+
- **`acquireLock` reclaims same-PID stale lockfiles** older than 30 seconds. The previous PID-liveness probe skipped reclaim when the lockfile's recorded PID matched the current process's PID — but a same-process leak across multiple `run()` invocations left the lockfile orphaned indefinitely. The mtime-staleness check now allows reclaim while preserving legitimate reentrancy on fresh same-PID lockfiles.
|
|
33
|
+
|
|
34
|
+
### Test quality
|
|
35
|
+
|
|
36
|
+
- **5 exit-code assertions tightened from `notEqual(r.status, 0)` to exact-value `assert.equal(r.status, 1)`** across the CSAF and CLI-flag regression suites. Closes the same coincidence-passing-tests regression the v0.12.21 entry's tightening pass left half-done.
|
|
37
|
+
- **CVE-curation tests no longer mutate `data/cve-catalog.json`** in the repo root. Three tests previously injected synthetic `CVE-9999-*` drafts into the live catalog with a `finally{}` restore — a Ctrl-C between mutation and restoration leaked state into the repo. The refresh tests now use the existing `--catalog <path>` flag against a tempdir copy; the validate test uses the in-process module API directly.
|
|
38
|
+
- **Three e2e expect.json files** (`14-framework-jurisdiction-gap`, `16-containers-root-user`, `19-crypto-rsa-2048-eol`) now assert `phases.close.jurisdiction_notifications[0].jurisdiction` is populated. Field-presence-without-content was the previous shape.
|
|
39
|
+
|
|
40
|
+
### Catalog + skill content
|
|
41
|
+
|
|
42
|
+
- **`data/playbooks/runtime.json domain.cve_refs[]`** completes the Dirty-Frag family by adding `CVE-2026-43284` and `CVE-2026-43500` (already referenced by `kernel.json` and `hardening.json`).
|
|
43
|
+
- **`skills/threat-model-currency/skill.md`** inline `last_threat_review` date aligned to frontmatter (`2026-05-14`).
|
|
44
|
+
|
|
45
|
+
Test count: 941 → 992 (989 pass + 3 skipped). Predeploy gates: 14/14. Skills: 38/38 signed; manifest envelope signed.
|
|
46
|
+
|
|
3
47
|
## 0.12.21 — 2026-05-14
|
|
4
48
|
|
|
5
49
|
**Patch: Fragnesia (CVE-2026-46300) catalog + skill integration; trust-chain bypass closures; engine FP-gate extension; CSAF + SARIF + OpenVEX correctness; CLI fuzz; Hard Rule #5 global-first coverage; predeploy regression fix.**
|
|
@@ -8,7 +52,7 @@
|
|
|
8
52
|
|
|
9
53
|
`CVE-2026-46300` (Fragnesia) added — a Linux kernel local privilege escalation disclosed 2026-05-13 by William Bowling / V12 security team. CVSS 7.8 / AV:L. The flaw is in the kernel XFRM ESP-in-TCP path: `skb_try_coalesce()` fails to propagate `SKBFL_SHARED_FRAG` when transferring paged fragments between socket buffers. An unprivileged user can deterministically rewrite read-only page-cache pages without modifying on-disk bytes — no race condition required. A public proof-of-concept demonstrates root shell via `/usr/bin/su`. Mitigation: blacklist or unload `esp4`, `esp6`, `rxrpc` kernel modules (the same set already documented for CVE-2026-31431); AlmaLinux + CloudLinux ship patched kernels in testing; live-patch is available via Canonical Livepatch, kpatch, kGraft, and CloudLinux KernelCare. RWEP today: 20 (will jump to 45 on CISA KEV listing).
|
|
10
54
|
|
|
11
|
-
The `kernel`, `runtime`, and `hardening` playbooks now reference Fragnesia in `domain.cve_refs[]`.
|
|
55
|
+
The `kernel`, `runtime`, and `hardening` playbooks now reference Fragnesia in `domain.cve_refs[]`. Six skills carry cross-references: `kernel-lpe-triage`, `exploit-scoring`, `compliance-theater`, `framework-gap-analysis`, `zeroday-gap-learn`, `threat-model-currency`. `data/zeroday-lessons.json` adds three new control requirements that codify the lesson: page-cache integrity verification (file-integrity tools hashing on-disk bytes miss this class), bug-family mitigation persistence (operators who blacklisted modules for the parent bug remain mitigated for the sequel), and scanner paper-compliance test (a "patched" vulnerability-scanner report based on kernel-package version misses the module-unload mitigation surface).
|
|
12
56
|
|
|
13
57
|
### Trust chain
|
|
14
58
|
|
|
@@ -68,10 +112,9 @@ The `kernel`, `runtime`, and `hardening` playbooks now reference Fragnesia in `d
|
|
|
68
112
|
|
|
69
113
|
### Tests
|
|
70
114
|
|
|
71
|
-
- New
|
|
72
|
-
-
|
|
73
|
-
- `
|
|
74
|
-
- `tests/operator-bugs.test.js` — `#87 doctor --fix is registered` rewritten as a non-mutating `--help` probe; the previous shape staged a dummy `.keys/private.pem` in the real repo root, replicating the v0.12.4 incident anti-pattern.
|
|
115
|
+
- New regression coverage for every closure above.
|
|
116
|
+
- Coincidence-passing-test cleanup: exit-code assertions tightened from `notEqual(r.status, 0)` to exact-value `assert.equal(r.status, <code>)`; classification assertions pinned to expected enum values.
|
|
117
|
+
- `#87 doctor --fix is registered` rewritten as a non-mutating `--help` probe; the previous shape staged a dummy `.keys/private.pem` in the real repo root, replicating the v0.12.4 incident anti-pattern.
|
|
75
118
|
|
|
76
119
|
### Skill content
|
|
77
120
|
|
|
@@ -82,21 +125,11 @@ The `kernel`, `runtime`, and `hardening` playbooks now reference Fragnesia in `d
|
|
|
82
125
|
|
|
83
126
|
UK CAF + AU Essential 8 / ISM entries added to the framework-control-gap declarations across 10 playbooks (`kernel`, `mcp`, `ai-api`, `crypto`, `sbom`, `runtime`, `cred-stores`, `secrets`, `containers`, `hardening`). NIS2 Art. 21 + DORA Art. 9 added to `hardening` and `containers`. Each entry follows the existing schema shape; the gold-standard templates from `framework`, `crypto-codebase`, and `library-author` remain the reference.
|
|
84
127
|
|
|
85
|
-
###
|
|
86
|
-
|
|
87
|
-
A scrub across 19 shipped source files (`bin/`, `lib/`, `scripts/`) removed 101 internal-vocabulary references (`(audit X PN-N)`, `// Audit Y PN-N: ...`, `v0.12.X (audit Z PN-N):`). The remaining behavior-framing comments describe the change itself; the surrounding context (version pin, WHY) is preserved where it carries operator value.
|
|
88
|
-
|
|
89
|
-
### Operator action required
|
|
128
|
+
### Source comments
|
|
90
129
|
|
|
91
|
-
|
|
92
|
-
```
|
|
93
|
-
gh secret set NPM_TOKEN --env npm-publish --body "<paste npm automation token>"
|
|
94
|
-
gh secret list --env npm-publish # verify
|
|
95
|
-
gh secret delete NPM_TOKEN --org blamejs # AFTER verifying the env-scoped one is picked up
|
|
96
|
-
```
|
|
97
|
-
The repo-side wiring (`release.yml` declares `environment: npm-publish` on the publish job; the env has the `tag: v*.*.*` deploy filter) is already in place.
|
|
130
|
+
Cleanup pass across `bin/`, `lib/`, `scripts/` — comments now describe behavior, not work-stream.
|
|
98
131
|
|
|
99
|
-
Test count: 840 →
|
|
132
|
+
Test count: 840 → 941 (938 pass + 3 skipped). Predeploy gates: 14/14. Skills: 38/38 signed; manifest envelope signed.
|
|
100
133
|
|
|
101
134
|
## 0.12.20 — 2026-05-14
|
|
102
135
|
|
|
@@ -110,7 +143,7 @@ The v0.12.19 engine fix blocks `detection_classification: 'detected'` agent over
|
|
|
110
143
|
- `16-containers-root-user`: attest `dockerfile-curl-pipe-bash` (3 checks; `dockerfile-runs-as-root` was already attested)
|
|
111
144
|
- `19-crypto-rsa-2048-eol`: attest `openssl-pre-3-5` + `ml-dsa-slh-dsa-absent` (3 + 3)
|
|
112
145
|
|
|
113
|
-
|
|
146
|
+
v0.12.20 ships the v0.12.19 trust-chain + engine + bundle + concurrency closures plus the scenario updates.
|
|
114
147
|
|
|
115
148
|
## 0.12.19 — 2026-05-14
|
|
116
149
|
|
|
@@ -553,7 +586,7 @@ Test count: 492 → 573 (+81 across engine, sign/verify, refresh-external, prefe
|
|
|
553
586
|
|
|
554
587
|
## 0.12.11 — 2026-05-13
|
|
555
588
|
|
|
556
|
-
**Patch: OSV source hardening, indicator regex widening, CWE/framework-gap reconciliation
|
|
589
|
+
**Patch: OSV source hardening, indicator regex widening, CWE/framework-gap reconciliation.**
|
|
557
590
|
|
|
558
591
|
### OSV source hardening
|
|
559
592
|
|
|
@@ -628,7 +661,7 @@ Test count: 441 → 459 (+18: OSV source tests + matching test references for Ha
|
|
|
628
661
|
|
|
629
662
|
## 0.12.9 — 2026-05-13
|
|
630
663
|
|
|
631
|
-
**Patch:
|
|
664
|
+
**Patch: Hard Rule #15 diff-coverage gate flips blocking, sbom evidence-correlation fix, CVE catalog freshness corrections, recovery of two CLI fixes lost across an interrupted refactor.**
|
|
632
665
|
|
|
633
666
|
### Hard Rule #15 — diff-coverage gate is now blocking
|
|
634
667
|
|
|
@@ -680,7 +713,7 @@ Five entries reconciled against authoritative public sources as of 2026-05-13:
|
|
|
680
713
|
|
|
681
714
|
Each correction carries an inline `*_correction_note` field with the source URL and the rationale for downstream auditors. Two new CVEs surfaced by the freshness sweep (CVE-2026-42208 LiteLLM SQLi on KEV; CVE-2026-39884 mcp-server-kubernetes argument injection) are deferred to a follow-up patch — each warrants its own Hard Rule #14 primary-source IoC review.
|
|
682
715
|
|
|
683
|
-
### v0.12.8
|
|
716
|
+
### Two v0.12.8 CLI fixes recovered
|
|
684
717
|
|
|
685
718
|
Two claims in the v0.12.8 CHANGELOG were not actually on disk in the squash commit, lost during the v0.12.8 recovery flow:
|
|
686
719
|
|
|
@@ -711,7 +744,7 @@ Test count: 418 → 439. Predeploy gates: 15/15 (gate 15 now blocking). Skills:
|
|
|
711
744
|
|
|
712
745
|
## 0.12.8 — 2026-05-13
|
|
713
746
|
|
|
714
|
-
**Patch:
|
|
747
|
+
**Patch: CLI surface fixes, catalog completeness, test infrastructure hardening, AGENTS.md Hard Rule #15.**
|
|
715
748
|
|
|
716
749
|
### Hard Rule #15 — Test coverage on every diff
|
|
717
750
|
|
|
@@ -808,7 +841,7 @@ mcp playbook bumped 1.2.0 → 1.3.0. threat_currency_score stays at 98. `last_th
|
|
|
808
841
|
|
|
809
842
|
## 0.12.6 — 2026-05-13
|
|
810
843
|
|
|
811
|
-
**Patch: primary-source IoC
|
|
844
|
+
**Patch: primary-source IoC review across the catalog — five CVEs reviewed line-level against published exploit source. AGENTS.md Hard Rule #14 added.**
|
|
812
845
|
|
|
813
846
|
Five research agents dispatched in parallel to cross-reference our IoC list for each catalogued CVE against published exploit source / vendor advisories / researcher writeups. Roughly 60 IoCs added, one major CVSS correction, two CVEs gained an `iocs` block where they previously had `null`.
|
|
814
847
|
|