@blamejs/exceptd-skills 0.12.15 → 0.12.18
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 +136 -0
- package/bin/exceptd.js +395 -20
- package/data/_indexes/_meta.json +3 -3
- package/data/cve-catalog.json +1 -1
- package/data/playbooks/ai-api.json +27 -5
- package/data/playbooks/containers.json +34 -7
- package/data/playbooks/cred-stores.json +21 -4
- package/data/playbooks/crypto-codebase.json +29 -14
- package/data/playbooks/crypto.json +13 -3
- package/data/playbooks/framework.json +15 -3
- package/data/playbooks/hardening.json +24 -5
- package/data/playbooks/kernel.json +13 -3
- package/data/playbooks/library-author.json +21 -10
- package/data/playbooks/mcp.json +16 -4
- package/data/playbooks/runtime.json +20 -4
- package/data/playbooks/sbom.json +18 -5
- package/data/playbooks/secrets.json +33 -6
- package/lib/auto-discovery.js +70 -32
- package/lib/cve-curation.js +15 -9
- package/lib/prefetch.js +30 -8
- package/lib/refresh-network.js +40 -0
- package/lib/schemas/playbook.schema.json +7 -1
- package/lib/scoring.js +171 -11
- package/lib/sign.js +163 -2
- package/lib/validate-playbooks.js +46 -0
- package/lib/verify.js +149 -2
- package/manifest-snapshot.json +1 -1
- package/manifest-snapshot.sha256 +1 -1
- package/manifest.json +45 -40
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
- package/scripts/verify-shipped-tarball.js +35 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,141 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.12.18 — 2026-05-14
|
|
4
|
+
|
|
5
|
+
**Patch: e2e FP-check attestations for v0.12.17 indicator backfill. v0.12.17 publish payload.**
|
|
6
|
+
|
|
7
|
+
The v0.12.17 tag exists on git but never reached npm — release.yml's validate gate's `npm run test:e2e` failed 4/20 scenarios because v0.12.17's indicator FP-check backfill (audit K) caused the runner to downgrade hits to inconclusive without operator attestation, dropping RWEP scores below the e2e expect thresholds.
|
|
8
|
+
|
|
9
|
+
v0.12.18 ships the v0.12.17 fix payload plus FP-check attestations on the affected e2e scenarios:
|
|
10
|
+
|
|
11
|
+
- `12-crypto-codebase-md5-eol`: attest FP checks for `weak-hash-import` + `no-ml-kem-implementation`
|
|
12
|
+
- `15-cred-stores-aws-static`: attest FP checks for `aws-static-key-present`
|
|
13
|
+
- `16-containers-root-user`: attest FP checks for `dockerfile-runs-as-root`; lower `adjusted` threshold from 15 → 10 (only `dockerfile-from-latest` carries an `rwep_inputs` entry on the containers playbook; the FP-attested `dockerfile-runs-as-root` fires but doesn't drive RWEP)
|
|
14
|
+
- `20-ai-api-openai-dotfile`: attest FP checks for `cleartext-api-key-in-dotfile` + `long-lived-aws-keys`
|
|
15
|
+
|
|
16
|
+
Attestation shape per the E1 contract (v0.12.12): `signal_overrides: { '<indicator>__fp_checks': { '0': true, '1': true, ... } }` — each entry means "I've verified that this FP scenario does NOT apply; this is a real hit."
|
|
17
|
+
|
|
18
|
+
## 0.12.17 — 2026-05-14
|
|
19
|
+
|
|
20
|
+
**Patch: remaining deferred P1/P2 items from the v0.12.15 audit pile — manifest signing, Windows ACL, indicator FP backfill, schema promotion.**
|
|
21
|
+
|
|
22
|
+
### Manifest signing (audit I P1-4)
|
|
23
|
+
|
|
24
|
+
The previous trust chain signed each skill body individually but the manifest itself was just an unsigned index. A coordinated attacker who could rewrite `manifest.json` + `manifest-snapshot.json` + `manifest-snapshot.sha256` passed every gate (snapshot is checked locally, the sha256 also computed locally).
|
|
25
|
+
|
|
26
|
+
Now: `manifest.json` carries a top-level `manifest_signature` field (Ed25519 over canonical sort-keys representation with the signature field excluded and `normalize()`-applied bytes). `lib/sign.js sign-all` and `lib/sign.js sign-skill` both re-sign the manifest after per-skill work; `lib/verify.js loadManifestValidated()` verifies the manifest signature before iterating skills. Tampered manifest entries (path swap, signature substitution) now fail the manifest-level check. Missing `manifest_signature` field emits a warning but doesn't block (backward-compat for legacy tarballs in the wild).
|
|
27
|
+
|
|
28
|
+
Canonical-form contract documented in both `lib/sign.js` and `lib/verify.js` headers — future shape changes to manifest.json must respect the invariants (sort top-level keys, exclude `manifest_signature`, normalize line endings).
|
|
29
|
+
|
|
30
|
+
### Windows ACL on `.keys/private.pem` (audit I P1-3)
|
|
31
|
+
|
|
32
|
+
`lib/sign.js` previously wrote the private key with `{ mode: 0o600 }`. On POSIX this restricts read access to the owner. On Windows the `mode` argument maps to read/write attributes only, not POSIX permissions; ACLs inherited from the parent directory. A multi-user maintainer workstation or shared CI runner therefore allowed any process under the same desktop user to read the key. Now: on `win32`, `lib/sign.js` calls `icacls /inheritance:r /grant:r ${USERNAME}:F` after writing the private key, narrowing the ACL to the current user. The same restriction is applied via `restrictWindowsAcl(targetPath)` from `scripts/bootstrap.js` when bootstrap creates the keypair. Falls back to a stderr warning if `icacls` is unavailable; doesn't fail key generation.
|
|
33
|
+
|
|
34
|
+
### Indicator FP-check backfill (audit K)
|
|
35
|
+
|
|
36
|
+
36 deterministic indicators across 11 playbooks now carry `false_positive_checks_required[]` entries (the gold-standard pattern from `library-author.gha-workflow-script-injection-sink` in v0.12.13). Per-playbook coverage:
|
|
37
|
+
|
|
38
|
+
- `ai-api` — 4 indicators (cleartext-api-key-in-dotfile, long-lived-aws-keys, gcp-service-account-json, kubeconfig-with-static-token)
|
|
39
|
+
- `containers` — 4 (dockerfile-runs-as-root, dockerfile-curl-pipe-bash, compose-cap-add-sys-admin, compose-host-network)
|
|
40
|
+
- `cred-stores` — 3 (aws-static-key-present, docker-cleartext-auth, credentials-file-bad-perms)
|
|
41
|
+
- `crypto-codebase` — 3 (weak-hash-import, weak-cipher-mode, tls-old-protocol)
|
|
42
|
+
- `crypto` — 2 (ml-dsa-slh-dsa-absent, openssl-pre-3-5)
|
|
43
|
+
- `framework` — 3 (exception-missing-expiry-or-owner, jurisdiction-without-framework, compound-theater)
|
|
44
|
+
- `hardening` — 4 (kptr-restrict-disabled, yama-ptrace-permissive, kaslr-disabled-at-boot, mitigations-off)
|
|
45
|
+
- `kernel` — 2 (unpriv-userns-enabled, unpriv-bpf-allowed)
|
|
46
|
+
- `mcp` — 3 (mcp-response-ansi-escape, mcp-response-unicode-tag-smuggling, mcp-server-running-as-root)
|
|
47
|
+
- `runtime` — 3 (duplicate-uid-zero, world-writable-in-trusted-path, orphan-privileged-process)
|
|
48
|
+
- `sbom` — 3 (lockfile-no-integrity, kev-listed-match, windsurf-vulnerable-version)
|
|
49
|
+
- `secrets` — 5 (aws-secret-access-key, slack-bot-or-user-token, stripe-secret-key, openai-api-key, anthropic-api-key)
|
|
50
|
+
|
|
51
|
+
Each entry is a 1-line check an AI assistant or operator must satisfy before the indicator's `hit` verdict can drive `classification: detected`. The runner downgrades a hit with unsatisfied FP checks to `inconclusive` (v0.12.12 E1 contract). Backfill complements the playbook-level `false_positive_profile[]` documentation by binding FP checks per-indicator at the schema layer.
|
|
52
|
+
|
|
53
|
+
### Schema promotion (audit K + audit M)
|
|
54
|
+
|
|
55
|
+
`lib/schemas/playbook.schema.json` indicator object now formally declares `false_positive_checks_required[]` and `cve_ref` as optional fields (was unschema'd; produced WARN noise on every validate run). The `cve_ref` field has been load-bearing since v0.12.14 F3 (drives `analyze.matched_cves[]` correlation); the schema declaration just catches up. After the schema promotion + the v0.12.16 enum-drift normalisation, `validate-playbooks` runs 13/13 PASS with zero warnings (was 12/13 PASS + 1 WARN in v0.12.16, 8/13 PASS + 5 WARN in v0.12.15).
|
|
56
|
+
|
|
57
|
+
### Operator-facing surfaces
|
|
58
|
+
|
|
59
|
+
- **`--diff-from-latest` result surfaced in `run` human renderer (audit L F11)**. Operators running with `--diff-from-latest` and no `--json` previously got no visibility on drift; now: `> drift vs prior: unchanged (same evidence_hash as session <prior_id>)` or `> drift vs prior: DRIFTED — evidence_hash differs from session <prior_id>` is added near the classification line. No line when there's no prior attestation for the playbook (don't clutter).
|
|
60
|
+
- **`ai-run` stdin acceptance contract documented in `--help`**. The streaming + no-stream paths both consume "first parseable evidence event wins on stdin; subsequent evidence events ignored; non-evidence chatter silently ignored; invalid JSON exits 1." Was implicit behavior; now explicit so AI agents calling `exceptd ai-run` know the contract.
|
|
61
|
+
|
|
62
|
+
### Auto-discovery hygiene (audit M P3-O)
|
|
63
|
+
|
|
64
|
+
`lib/auto-discovery.js discoverNewKev` previously hardcoded `severity: 'high'` on every KEV-discovered diff. Now uses `deriveKevSeverity(kevEntry)` — returns `'critical'` when `knownRansomwareCampaignUse === 'Known'` OR `dueDate` is within 7 days; otherwise `'high'`. Downstream PR-body categorization can now route ransomware-use + imminent-due-date KEVs differently.
|
|
65
|
+
|
|
66
|
+
### Tests
|
|
67
|
+
|
|
68
|
+
- 20 new regression tests in `tests/audit-i-l-m-fixes.test.js` covering: Windows ACL helper export, manifest canonical-bytes determinism (stale signature + key-order invariants), sign + verify round trip, live manifest carries valid signature, tampered signature/skill detection, backward-compat `missing` status, `--diff-from-latest` renderer string/branch assertions, ai-run help-text content, `deriveKevSeverity` matrix, `discoverNewKev` propagation against synthetic KEV feed.
|
|
69
|
+
|
|
70
|
+
Test count: 740 → 760 (+20 + 1 reworked). Predeploy gates: 14/14. Skills: 38/38 signed; manifest itself signed.
|
|
71
|
+
|
|
72
|
+
## 0.12.16 — 2026-05-14
|
|
73
|
+
|
|
74
|
+
**Patch: highest-impact P1 security findings from the v0.12.15 audit pile.**
|
|
75
|
+
|
|
76
|
+
### Sign/verify trust chain (audit I)
|
|
77
|
+
|
|
78
|
+
- **CRLF/BOM bypass on the shipped-tarball verify gate closed.** `scripts/verify-shipped-tarball.js` previously read raw on-disk bytes and called `crypto.verify` directly — bypassing the CRLF/BOM normalization that `lib/sign.js` + `lib/verify.js` apply on both sides of the byte-stability contract. The gate's whole purpose is to catch the v0.11.x signature regression class; without the same normalization, it would itself report 0/38 on any tree where line-ending normalization touched the source between sign and pack (a Windows contributor with `core.autocrlf=true`, or any tool like Prettier in the CI pipeline). The `normalizeSkillBytes` helper is now mirrored in this fourth normalize() implementation.
|
|
79
|
+
- **`keys/EXPECTED_FINGERPRINT` pin now consulted at every public-key load site.** Previously only `lib/verify.js` + `scripts/verify-shipped-tarball.js` checked the pin. `lib/refresh-network.js` and `bin/exceptd.js attest verify` both loaded `keys/public.pem` and trusted it without the cross-check. A coordinated attacker who tampered with `keys/public.pem` on the operator's host (e.g. via a prior compromised refresh) passed every check because the local↔tarball fingerprints matched each other. Now the pin is the external trust anchor at all four load sites. Honors `KEYS_ROTATED=1` env to allow legitimate rotation without re-bootstrap; missing pin file degrades to warn-and-continue.
|
|
80
|
+
|
|
81
|
+
### CI workflow security (audit N)
|
|
82
|
+
|
|
83
|
+
- **`atlas-currency.yml` script-injection sink closed (CWE-1395).** `${{ steps.currency.outputs.report }}` was interpolated directly into a github-script template literal; the `report` value is unescaped output of `node orchestrator/index.js currency`. A skill author who landed a string containing a backtick followed by `${process.exit(0)}` (or worse, an exfil to a webhook with `${process.env.GITHUB_TOKEN}`) got arbitrary JS execution inside the github-script runtime with the workflow's token. Now routed via `env.REPORT_TEXT` and read inside the script body as `process.env.REPORT_TEXT`.
|
|
84
|
+
- **`refresh.yml` shell-injection from `workflow_dispatch` input closed (CWE-78).** `${{ inputs.source }}` was interpolated directly into a bash `run:` block. An operator passing `kev; rm -rf /; #` got shell injection inside the runner. Now routed via `env.SOURCE_INPUT` and validated against `^[a-z,]+$` (the documented `kev,epss,nvd,rfc,pins` allowlist shape) before passing to the CLI.
|
|
85
|
+
- `actions/checkout` SHA comments aligned across `ci.yml`/`release.yml`/`scorecard.yml` (no SHA change; comment-only).
|
|
86
|
+
- `secret-scan` job declares explicit `permissions: contents: read` (survives a future repo visibility flip).
|
|
87
|
+
- `gitleaks` resolver now has a hardcoded fallback version + non-fatal failure path so a GitHub API HTML-error response doesn't block every CI run.
|
|
88
|
+
- New `tests/workflows-security.test.js` enforces: no `${{ steps.*.outputs.* }}` inside github-script template literals; no `${{ inputs.* }}` inside bash `run:` blocks; every third-party action is SHA-pinned; every workflow declares `permissions:`.
|
|
89
|
+
|
|
90
|
+
### CLI hardening (audit L)
|
|
91
|
+
|
|
92
|
+
- **`--block-on-jurisdiction-clock` now honored on `cmdRun`.** Previously the flag was registered + documented but only `cmdCi` consumed it; `run --block-on-jurisdiction-clock` exited 0 even when an NIS2 24h clock had started. Now both verbs exit 5 (`CLOCK_STARTED`) when any notification action has a non-null `clock_started_at` and an unacked operator consent.
|
|
93
|
+
- **`cmdIngest` auto-detects piped stdin.** Mirrors the `cmdRun` shape — `echo '{...}' | exceptd ingest` now works without an explicit `--evidence -`.
|
|
94
|
+
- **`--vex` validates document shape before applying.** Previously any malformed JSON (SARIF, SBOM, CSAF advisory by mistake) resulted in a silent empty filter; now CycloneDX (`vulnerabilities[]` or `bomFormat: 'CycloneDX'`) or OpenVEX (`statements[]` + `@context` on openvex.dev) shape required before the filter is consumed.
|
|
95
|
+
- **`cmdReattest` verifies the `.sig` sidecar** before consuming the prior attestation. A tampered attestation is no longer silently consumed for the drift verdict. `--force-replay` available for legitimate ack-of-divergence.
|
|
96
|
+
- **`--operator <name>` validated**: rejects ASCII control chars + newlines; caps length at 256; rejects all-whitespace. Closes the "multi-line operator forgery" surface in CSAF / attest export rendering.
|
|
97
|
+
- **`--diff-from-latest` result surfaced in human renderer**: operators running with `--diff-from-latest` and no `--json` now see a `> drift vs prior: <status>` line.
|
|
98
|
+
- **Cross-playbook jurisdiction clock rollup** in `cmdRunMulti` / `cmdCi`: deduped by `(jurisdiction, regulation, obligation, window_hours)`, `triggered_by_playbooks[]` lists contributors. Operators running 13 playbooks no longer draft 8 separate NIS2 24h notifications.
|
|
99
|
+
- `--block-on-jurisdiction-clock` exit code split from `FAIL` (exit 2) → `CLOCK_STARTED` (exit 5). CI gates can distinguish "detected" from "clock fired".
|
|
100
|
+
- `cmdReattest --since` validated as parseable ISO-8601.
|
|
101
|
+
|
|
102
|
+
### Scoring math hardening (audit J)
|
|
103
|
+
|
|
104
|
+
- `scoreCustom` now treats `active_exploitation: 'unknown'` as `0.25 × weight` (was 0) — aligning with `playbook-runner._activeExploitationLadder` semantics so catalog-side and runtime-side scoring agree.
|
|
105
|
+
- New `deriveRwepFromFactors(factors)` helper exported; detects whether `rwep_factors` is in Shape A (boolean inputs to `scoreCustom`) or Shape B (numeric weighted contributions) and produces a consistent score. Documents the dual-semantics so the rename can land cleanly in v0.13.0.
|
|
106
|
+
- `validateFactors` NaN/Infinity diagnostics now use `Number.isFinite` with dedicated messages (was misleading "expected number, got number (null)").
|
|
107
|
+
- `validateFactors` flags unknown factor keys ("unknown factor: X (ignored)").
|
|
108
|
+
- `scoreCustom(factors, {collectWarnings: true})` returns `_rwep_raw_unclamped` so operators see deduction magnitude even when the floor clamp absorbs negative weights.
|
|
109
|
+
- `compare()` "broadly aligned" band tightened from ±20 to ±10. The Copy Fail RWEP-vs-CVSS divergence (delta 12) now correctly surfaces as "significantly higher than CVSS equivalent."
|
|
110
|
+
- `Math.floor(20/2)` arithmetic replaced with `RWEP_WEIGHTS.active_exploitation * 0.5` (no behavior change today; closes a future odd-weight asymmetry).
|
|
111
|
+
|
|
112
|
+
### Curation + auto-discovery + prefetch (audit M)
|
|
113
|
+
|
|
114
|
+
- **Hidden second scoring path in `lib/cve-curation.js` closed.** The apply path previously derived `rwep_score` via `Object.values(rwep_factors).reduce(sum, 0)` — bypassing `scoring.js` entirely. Replaced with `deriveRwepFromFactors()`.
|
|
115
|
+
- **Auto-discovery RWEP divergence closed.** `lib/auto-discovery.js` previously stored `rwep_factors` with null values for poc_available/ai_*/reboot_required while calling `scoreCustom` with `true` defaults; stored factors and stored score were inconsistent and `scoring.validate()` always flagged it. New `buildScoringInputs(kev, nvd)` is the single source of truth.
|
|
116
|
+
- **`lib/prefetch.js` GITHUB_TOKEN now reaches the request.** The auth lookup keyed off source name `"github"` but the registered source is `"pins"` — anonymous rate-limit applied even when `GITHUB_TOKEN` was set. Fixed.
|
|
117
|
+
- **`lib/prefetch.js` docs corrected**: header comment + `printHelp()` no longer reference non-existent source names `ietf` and `github`.
|
|
118
|
+
- **`readCached` no longer returns stale data as fresh** when `fetched_at` is missing/corrupt (the `NaN > maxAgeMs === false` short-circuit was treating undefined-age entries as eternally-fresh).
|
|
119
|
+
|
|
120
|
+
### Playbook quality (audit K)
|
|
121
|
+
|
|
122
|
+
- **Mutex reciprocity validator** in `lib/validate-playbooks.js`: walks every `_meta.mutex` entry, emits WARNING per asymmetric edge. Reciprocity backfilled across 7 mutex relationships (secrets↔library-author, kernel↔hardening, containers↔library-author, etc.).
|
|
123
|
+
- **`containers → sbom` feeds_into edge** added (container-image-layer SBOM matching against KEV-listed CVEs is a primary v0.12.x use case but wasn't declared).
|
|
124
|
+
- **Domain CVE refs backfilled** where threat_context cited CVEs without referencing them: `runtime.cve_refs += CVE-2026-31431`, `ai-api.cve_refs += CVE-2026-30615`. `containers` threat_context's stale `CVE-2024-21626` (not in catalog) stripped.
|
|
125
|
+
- **ATLAS refs backfilled**: `cred-stores.atlas_refs += AML.T0055` (Unsecured Credentials), `containers.atlas_refs += AML.T0010` (ML Supply Chain).
|
|
126
|
+
- **Artifact type enum drift normalized**: 19 occurrences across crypto-codebase / crypto / library-author / mcp / sbom of `"file_path"` and `"log_pattern"` rewritten to the schema enum (`"file"` / `"log"`).
|
|
127
|
+
- **Indicator type enum drift normalized**: 3 occurrences in `library-author` of `"api_response"` rewritten to `"api_call_sequence"`.
|
|
128
|
+
- **FP-check backfill** on library-author indicators (publish-workflow-action-refs-mutable + tag-protection-absent) — gold-standard pattern from `gha-workflow-script-injection-sink` extended to two more high-confidence indicators.
|
|
129
|
+
|
|
130
|
+
### Repository
|
|
131
|
+
|
|
132
|
+
- `data/cve-catalog.json` synthetic test-pollution entry (`CVE-9999-99999`) removed (left by a test run that used the real catalog path).
|
|
133
|
+
- 29 new RWEP vector regression tests in `tests/scoring-vectors.test.js`.
|
|
134
|
+
- 8 new workflow-security regression tests in `tests/workflows-security.test.js`.
|
|
135
|
+
- `validate-playbooks.js` now reports 12/13 PASS + 1 WARN (was 8 PASS + 5 WARN before normalization).
|
|
136
|
+
|
|
137
|
+
Test count: 701 → 738 (+37: 29 scoring vectors + 8 workflow-security). Predeploy gates: 14/14. Skills: 38/38 signed and verified.
|
|
138
|
+
|
|
3
139
|
## 0.12.15 — 2026-05-14
|
|
4
140
|
|
|
5
141
|
**Patch: e2e RWEP factor-scaling fix + audit-surfaced silent-disable regressions. v0.12.14 publish payload.**
|