@blamejs/exceptd-skills 0.14.12 → 0.14.14
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 +25 -0
- package/README.md +38 -8
- package/bin/exceptd.js +166 -70
- package/data/_indexes/_meta.json +2 -2
- package/lib/collectors/cicd-pipeline-compromise.js +6 -1
- package/lib/collectors/containers.js +9 -1
- package/lib/collectors/library-author.js +6 -1
- package/lib/playbook-runner.js +87 -11
- package/manifest.json +44 -44
- package/package.json +1 -1
- package/sbom.cdx.json +24 -24
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.14.14 — 2026-05-27
|
|
4
|
+
|
|
5
|
+
Attestation durability and verification:
|
|
6
|
+
- Attestations are now written atomically. The body and its Ed25519 `.sig` sidecar are written to fsync'd temporary files and placed together (the body via an atomic create that still detects a session-id collision, the sidecar alongside it), so a crash or out-of-space mid-write can no longer leave a truncated `attestation.json` or a body without its signature. A failed write also leaves no partial file at the slot.
|
|
7
|
+
- `attest verify` now flags a deleted `.sig` sidecar as tampering (exit 6) when a signature was expected — i.e. when a signing key is present or a sibling attestation in the same session is signed — instead of accepting it as a benign "unsigned" attestation (exit 0). This makes the default `attest verify` agree with `reattest`, which already refused. A genuinely unsigned attestation on a keyless host stays benign.
|
|
8
|
+
- A `run` now blocks (`blocked_by: "mutex"`) when a live concurrent process holds the run lock, rather than proceeding without the lock after losing the acquire race. Same-process reentrancy and filesystem quirks are unaffected.
|
|
9
|
+
|
|
10
|
+
## 0.14.13 — 2026-05-27
|
|
11
|
+
|
|
12
|
+
Security: a collector scanning a hostile repository no longer hangs on a crafted file. Three workflow/Dockerfile/manifest scanners (`library-author`, `cicd-pipeline-compromise`, `containers`) had a regex that backtracked catastrophically on a long whitespace line — a single planted file could wedge the scan for minutes. The regexes are fixed and a per-line length cap bounds any future regression.
|
|
13
|
+
|
|
14
|
+
Deeply-nested evidence is now rejected with an actionable message instead of crashing with an opaque "internal error". The submission canonicalizer (which runs on every `run` to compute the evidence hash) recursed without bound; it now refuses a submission nested beyond 200 levels.
|
|
15
|
+
|
|
16
|
+
`run --strict-preconditions` now fails (exit 1) when a `skip_phase` precondition is false. Previously such a run skipped the detect phase and exited 0, so a CI gate relying on the flag silently passed despite the detection never running.
|
|
17
|
+
|
|
18
|
+
Detection no longer silently loses or buries a result:
|
|
19
|
+
- A `signal_overrides` value that isn't a recognized result (e.g. `"maybe"`, a number) now surfaces a `signal_override_unrecognized` runtime error instead of being dropped as if the signal were never supplied.
|
|
20
|
+
- A `not_detected` / `clean` classification override is refused when it would bury a deterministic indicator hit (a deterministic hit is too strong to downgrade to "nothing found"); the run stays inconclusive with an explanatory error. Probabilistic hits remain overridable for the legitimate "I confirmed these are benign" workflow. A refused override is no longer reported as applied.
|
|
21
|
+
|
|
22
|
+
`run --all` / `run-all` now exits 7 (session-id collision) when a reused `--session-id` collides across the batch, matching the single-run behavior — previously a batch that persisted nothing exited 0 and reported success.
|
|
23
|
+
|
|
24
|
+
`watch --help` prints usage and exits instead of starting the blocking daemon and hanging the terminal; `collect --help` now prints its synopsis. The `--help` synopsis for the spawned verbs (`watch`, `watchlist`, `report`, `scan`, `dispatch`, `currency`, `validate-cves`, `validate-rfcs`) is filled in.
|
|
25
|
+
|
|
26
|
+
README corrects the `watch` / `watchlist` documentation (the one-shot aggregator with `--alerts` / `--org-scan` is `watchlist`; `watch` is the long-running daemon) and the `refresh --prefetch` description (it warms the cache by fetching, the opposite of the report-only `--no-network`).
|
|
27
|
+
|
|
3
28
|
## 0.14.12 — 2026-05-27
|
|
4
29
|
|
|
5
30
|
Structured-bundle accuracy:
|
package/README.md
CHANGED
|
@@ -116,7 +116,7 @@ npx @blamejs/exceptd-skills path
|
|
|
116
116
|
That prints the absolute path of the installed package. Point your AI assistant at:
|
|
117
117
|
|
|
118
118
|
- `<path>/AGENTS.md` — canonical project rules + ground truth for every skill
|
|
119
|
-
- `<path>/data/_indexes/summary-cards.json` — 100-word abstract per skill (
|
|
119
|
+
- `<path>/data/_indexes/summary-cards.json` — 100-word abstract per skill (~95 KB)
|
|
120
120
|
- `<path>/data/_indexes/recipes.json` — curated multi-skill chains for common use cases
|
|
121
121
|
|
|
122
122
|
No clone, no signing keys, no Node 24 required for assistants that read directly from disk. If your assistant needs a local copy as a regular checkout, use `npx degit blamejs/exceptd-skills my-skills` instead.
|
|
@@ -156,9 +156,9 @@ Fresh-disclosure workflow (v0.12.0): the nightly auto-PR job pulls KEV / NVD / E
|
|
|
156
156
|
|
|
157
157
|
Primary-source advisory polling: `exceptd refresh --check-advisories` polls 15 vendor and coordinated-disclosure feeds — 8 advisory/coordinated-disclosure venues (Qualys TRU, Red Hat RHSA, Ubuntu USN, Zero Day Initiative, kernel.org commits, oss-security mailing list, JFrog SecOps, CISA current advisories), 4 vendor security research blogs added in v0.13.14 (Microsoft Security Blog, Sysdig, Trail of Bits, Embrace the Red), and 3 additions in v0.13.17 (BleepingComputer security, The Hacker News, and a GitLab activity-feed tracker for the Nightmare-Eclipse researcher handle that anchors NEW-CTRL-073). Combined coverage publishes CVE IDs at T+0 to T+1 — typically 3–14 days ahead of NVD enrichment. The command is report-only: it returns a structured `diffs[]` listing each newly-seen CVE ID with its source attributions and advisory URLs, but does not mutate the catalog. v0.13.17 also adds a complementary detection method (NEW-CTRL-074 / `lib/cve-regression-watcher.js`): the watcher cross-checks poller diffs for historical-CVE references (year ≤ currentYear − 2) and surfaces candidate silent-regression cases — the class anchored by MiniPlasma (a 2026 PoC drop that re-broke CVE-2020-17103 without any new ID being assigned). Operators triage the output and route promising IDs through `exceptd refresh --advisory <CVE-ID> --apply`. Pairs naturally with the daily scheduled remote agent below.
|
|
158
158
|
|
|
159
|
-
CVE-class alert surfacing: `exceptd
|
|
159
|
+
CVE-class alert surfacing: `exceptd watchlist --alerts` matches the live `cve-catalog.json` against five operational patterns (`kernel_lpe_with_poc`, `supply_chain_family`, `ai_discovered_kev`, `active_exploitation_unpatched`, `recent_poc_no_kev_yet`) and returns the matches sorted critical-severity-first, then by RWEP. Use as a fast operational triage on a refreshed catalog without scanning every entry by hand.
|
|
160
160
|
|
|
161
|
-
GitHub repo-pattern monitoring: `exceptd
|
|
161
|
+
GitHub repo-pattern monitoring: `exceptd watchlist --org-scan --org <login>` probes GitHub Search for repositories matching known threat-actor naming patterns ("A Gift From TeamPCP", "Shai-Hulud", "TeamPCP") scoped to one org. Custom patterns via repeatable `--pattern <s>`. Implements the canonical detection for the Shai-Hulud / TeamPCP supply-chain framework class — the attacker uses GitHub itself as the exfil channel. Set `GITHUB_TOKEN` for private-repo coverage and rate-limit headroom; public-repo search works without auth.
|
|
162
162
|
|
|
163
163
|
AI-assistant config-file audit: `exceptd doctor --ai-config` walks `~/.claude`, `~/.cursor`, `~/.codeium`, `~/.aider`, and `~/.continue`, flagging sensitive files (`settings.json`, `mcp.json`, `*.mcp_config.json`, `api_key*`, `*.token`, `*.credentials`) not at mode 0600 on POSIX. On Windows the mode bits aren't load-bearing; each finding is surfaced with an info-level "manual ACL review" note. Catches the AI-config-credential-exfil class that the Shai-Hulud framework targets. Opt-in — does not run as part of the default no-flag `doctor` pass.
|
|
164
164
|
|
|
@@ -329,6 +329,21 @@ exceptd doctor One-shot health check.
|
|
|
329
329
|
note for each sensitive file on Windows.
|
|
330
330
|
Opt-in; not part of the default doctor
|
|
331
331
|
pass.
|
|
332
|
+
--fix Auto-remediate signing gaps: regenerate
|
|
333
|
+
the local Ed25519 private key when
|
|
334
|
+
keys/public.pem exists but .keys/private.pem
|
|
335
|
+
is absent. No-op when the key is present.
|
|
336
|
+
--registry-check Probe the npm registry for the latest
|
|
337
|
+
published version + days-since-publish.
|
|
338
|
+
Off by default; --air-gap suppresses it.
|
|
339
|
+
--collectors Enumerate the per-playbook collector layer:
|
|
340
|
+
which playbooks ship a collector, which are
|
|
341
|
+
policy-skipped, and which are unwired.
|
|
342
|
+
--shipped-tarball Run the pack + extract + verify round-trip
|
|
343
|
+
against the tarball operators receive, not
|
|
344
|
+
just the source tree.
|
|
345
|
+
--exit-codes Print the canonical exit-code table as
|
|
346
|
+
JSON for CI / scripting consumers.
|
|
332
347
|
|
|
333
348
|
exceptd ci One-shot CI gate. Exit codes: 0 PASS,
|
|
334
349
|
1 framework error, 2 detected/escalate
|
|
@@ -382,8 +397,12 @@ exceptd refresh Refresh upstream catalogs + indexes.
|
|
|
382
397
|
Replaces prefetch + refresh + build-indexes.
|
|
383
398
|
--apply Write diffs back + rebuild indexes.
|
|
384
399
|
--from-cache [<dir>] Read from prefetch cache.
|
|
385
|
-
--prefetch
|
|
386
|
-
|
|
400
|
+
--prefetch Warm the offline cache by fetching every
|
|
401
|
+
upstream artifact now (network required).
|
|
402
|
+
Run on a connected host, then point
|
|
403
|
+
--from-cache at the result on the air-gap.
|
|
404
|
+
--no-network Report-only dry-run: list what would be
|
|
405
|
+
fetched without touching the network.
|
|
387
406
|
--network (v0.11.14) Fetch latest signed catalog
|
|
388
407
|
snapshot from npm tarball, verify against
|
|
389
408
|
local public.pem, swap data/ in place.
|
|
@@ -417,9 +436,10 @@ Packages dataset (`MAL-*` keys). New IDs land as drafts that the catalog
|
|
|
417
436
|
validator treats as warnings, not errors — editorial review (framework
|
|
418
437
|
gaps, IoCs, ATLAS/ATT&CK refs) is still required.
|
|
419
438
|
|
|
420
|
-
exceptd
|
|
439
|
+
exceptd watchlist Default mode: aggregate every skill's
|
|
421
440
|
forward_watch entries (upcoming standards,
|
|
422
|
-
RFC publications, new TTPs to monitor)
|
|
441
|
+
RFC publications, new TTPs to monitor) in
|
|
442
|
+
one shot.
|
|
423
443
|
`--by-skill` inverts the grouping.
|
|
424
444
|
--alerts Switch to CVE-catalog pattern alerts.
|
|
425
445
|
Five patterns ship:
|
|
@@ -448,6 +468,15 @@ exceptd watch Default mode: aggregate every skill's
|
|
|
448
468
|
limit; without it, public-repo search
|
|
449
469
|
only.
|
|
450
470
|
|
|
471
|
+
exceptd watch Long-running forward-watch daemon. Blocks
|
|
472
|
+
and listens for KEV additions, ATLAS
|
|
473
|
+
updates, CVE drops, and framework
|
|
474
|
+
amendments, with scheduled currency /
|
|
475
|
+
validation checks. Ctrl-C (or SIGTERM /
|
|
476
|
+
SIGHUP / SIGBREAK) to stop. For one-shot
|
|
477
|
+
aggregation, pattern alerts, or org-scan,
|
|
478
|
+
use `exceptd watchlist`.
|
|
479
|
+
|
|
451
480
|
exceptd skill <name> Show context for one skill.
|
|
452
481
|
exceptd framework-gap <FW> <ref> One framework + one CVE/scenario, JSON
|
|
453
482
|
or human. (Operates outside the seven-
|
|
@@ -455,7 +484,8 @@ exceptd framework-gap <FW> <ref> One framework + one CVE/scenario, JSON
|
|
|
455
484
|
exceptd path Absolute path to the installed package.
|
|
456
485
|
exceptd version Package version.
|
|
457
486
|
exceptd help This help.
|
|
458
|
-
exceptd <verb> --help
|
|
487
|
+
exceptd <verb> --help Most verbs print per-verb usage with flag
|
|
488
|
+
descriptions.
|
|
459
489
|
```
|
|
460
490
|
|
|
461
491
|
### Legacy v0.10.x verbs
|
package/bin/exceptd.js
CHANGED
|
@@ -761,6 +761,16 @@ function main() {
|
|
|
761
761
|
"framework-gap-analysis": "exceptd framework-gap <framework> <cve-or-scenario> One-framework gap analysis.",
|
|
762
762
|
cve: "exceptd cve <CVE-ID> [--json] [--air-gap|--no-network] Resolve a CVE: published/rejected/disputed/fabricated/nonexistent (catalog -> cache -> NVD). Exit 2 when the citation won't stand up (rejected/fabricated/nonexistent/withdrawn).",
|
|
763
763
|
rfc: "exceptd rfc <number> [--check \"<title>\"] [--json] [--air-gap] Resolve an RFC number -> title + status (local index, offline). Exit 2 when nonexistent or --check title MISMATCH.",
|
|
764
|
+
// watch MUST be here: without the interception `watch --help` falls through
|
|
765
|
+
// to spawning the blocking daemon, hanging the operator's terminal.
|
|
766
|
+
watch: "exceptd watch Long-running forward-watch daemon (blocks; Ctrl-C to stop). For a one-shot aggregator use `exceptd watchlist`.",
|
|
767
|
+
watchlist: "exceptd watchlist [--alerts] [--org-scan --org <login>] [--by-skill] [--json] One-shot forward-watch aggregator across skills.",
|
|
768
|
+
report: "exceptd report [executive] [--json] Structured posture report.",
|
|
769
|
+
scan: "exceptd scan [--json] [legacy] Working-directory CVE/KEV scan (orchestrator). See `exceptd discover`.",
|
|
770
|
+
dispatch: "exceptd dispatch [--json] [legacy] Scan + route findings to skills (orchestrator). See `exceptd discover`.",
|
|
771
|
+
currency: "exceptd currency [--json] [legacy] Skill threat-currency report. See `exceptd doctor --currency`.",
|
|
772
|
+
"validate-cves": "exceptd validate-cves [--offline|--air-gap] [--json] Validate the CVE catalog against upstream (offline-first).",
|
|
773
|
+
"validate-rfcs": "exceptd validate-rfcs [--offline|--air-gap] [--json] Validate the RFC index against upstream (offline-first).",
|
|
764
774
|
};
|
|
765
775
|
if ((effectiveRest.includes("--help") || effectiveRest.includes("-h")) && SPAWN_HELP_USAGE[effectiveCmd]) {
|
|
766
776
|
process.stdout.write(SPAWN_HELP_USAGE[effectiveCmd] + "\n Full reference: exceptd help\n");
|
|
@@ -2314,6 +2324,20 @@ Exit codes:
|
|
|
2314
2324
|
Output: verb, session_id, playbooks_run, summary{total, detected,
|
|
2315
2325
|
max_rwep_observed, jurisdiction_clocks_started, verdict, fail_reasons[]},
|
|
2316
2326
|
results[].`,
|
|
2327
|
+
collect: `collect <playbook> [--cwd <dir>] [--resolve] [--air-gap] [--json]
|
|
2328
|
+
|
|
2329
|
+
Scan the working directory (or --cwd <dir>) and emit an evidence submission
|
|
2330
|
+
for <playbook>, ready to pipe into \`run\`:
|
|
2331
|
+
|
|
2332
|
+
exceptd collect <playbook> | exceptd run <playbook> --evidence -
|
|
2333
|
+
|
|
2334
|
+
Flags:
|
|
2335
|
+
--cwd <dir> Scan <dir> instead of the current directory.
|
|
2336
|
+
--resolve (citation-hygiene) resolve uncatalogued CVE/RFC
|
|
2337
|
+
citations found during the scan.
|
|
2338
|
+
--air-gap Do not touch the network during collection.
|
|
2339
|
+
--json Raw JSON (default when piped; collect output is the
|
|
2340
|
+
submission, not a human digest).`,
|
|
2317
2341
|
brief: `brief [playbook] — unified info doc (v0.11.0).
|
|
2318
2342
|
|
|
2319
2343
|
Collapses the three info-only phases plan + govern + direct + look into a
|
|
@@ -3553,8 +3577,13 @@ function cmdRun(runner, args, runOpts, pretty) {
|
|
|
3553
3577
|
// behavior where warn-level issues stay informational. CI gates wanting
|
|
3554
3578
|
// "fail on any unverified precondition" pass this flag.
|
|
3555
3579
|
if (args["strict-preconditions"] && result && Array.isArray(result.preflight_issues)) {
|
|
3580
|
+
// precondition_skip MUST be included: a false skip_phase precondition
|
|
3581
|
+
// means detect never ran, so a CI gate relying on --strict-preconditions
|
|
3582
|
+
// ("any precondition_check returning false fails the run", per --help) would
|
|
3583
|
+
// otherwise silently pass (verdict:skipped, exit 0) — the exact gap the
|
|
3584
|
+
// flag exists to close.
|
|
3556
3585
|
const warnIssues = result.preflight_issues.filter(i =>
|
|
3557
|
-
i.kind === "precondition_unverified" || i.kind === "precondition_warn"
|
|
3586
|
+
i.kind === "precondition_unverified" || i.kind === "precondition_warn" || i.kind === "precondition_skip"
|
|
3558
3587
|
);
|
|
3559
3588
|
if (warnIssues.length > 0) {
|
|
3560
3589
|
// v0.12.12: surface the contract violation in the emitted body so
|
|
@@ -4303,9 +4332,19 @@ function cmdRunMulti(runner, ids, args, runOpts, pretty, meta) {
|
|
|
4303
4332
|
// remediation without parsing the body.
|
|
4304
4333
|
const anyLockBusy = results.some(r => r.attestation_persist && r.attestation_persist.lock_contention === true);
|
|
4305
4334
|
const anyStorageExhausted = results.some(r => r.attestation_persist && r.attestation_persist.storage_exhausted === true);
|
|
4335
|
+
// A persist failure that is neither lock-contention nor storage-exhaustion is
|
|
4336
|
+
// a session-id collision (the single-run path exits 7 for the same
|
|
4337
|
+
// condition). Pre-fix a batch where every attestation refused to overwrite
|
|
4338
|
+
// exited 0, so a re-run with a reused --session-id silently persisted nothing
|
|
4339
|
+
// while reporting success. Surface it with the same code as the single-run
|
|
4340
|
+
// path so a CI gate sees it.
|
|
4341
|
+
const anySessionCollision = results.some(r =>
|
|
4342
|
+
r.attestation_persist && r.attestation_persist.ok === false
|
|
4343
|
+
&& !r.attestation_persist.lock_contention && !r.attestation_persist.storage_exhausted);
|
|
4306
4344
|
const anyBlocked = results.some(r => r.ok === false);
|
|
4307
4345
|
if (anyLockBusy) { process.exitCode = EXIT_CODES.LOCK_CONTENTION; return; }
|
|
4308
4346
|
if (anyStorageExhausted) { process.exitCode = EXIT_CODES.STORAGE_EXHAUSTED; return; }
|
|
4347
|
+
if (anySessionCollision) { process.exitCode = EXIT_CODES.SESSION_ID_COLLISION; return; }
|
|
4309
4348
|
if (anyBlocked) { process.exitCode = EXIT_CODES.GENERIC_FAILURE; return; }
|
|
4310
4349
|
}
|
|
4311
4350
|
|
|
@@ -4525,23 +4564,68 @@ function persistAttestation(args) {
|
|
|
4525
4564
|
prior_evidence_hash: priorEvidenceHash,
|
|
4526
4565
|
prior_captured_at: priorCapturedAt,
|
|
4527
4566
|
};
|
|
4528
|
-
// Atomic
|
|
4529
|
-
//
|
|
4530
|
-
//
|
|
4531
|
-
//
|
|
4567
|
+
// Atomic write: the body and its .sig are written to fsync'd tmp files,
|
|
4568
|
+
// then placed with linkSync (create) / rename (force-overwrite) so a
|
|
4569
|
+
// crash mid-write can never leave a TRUNCATED attestation.json, and the
|
|
4570
|
+
// body never appears partially written. linkSync preserves the O_EXCL
|
|
4571
|
+
// collision guarantee the old "wx" flag gave: it throws EEXIST when the
|
|
4572
|
+
// slot is taken (one winner + one EEXIST loser on concurrent same-
|
|
4573
|
+
// session-id runs), and the placed file has the full content instantly.
|
|
4532
4574
|
//
|
|
4533
|
-
// v0.12.38
|
|
4534
|
-
//
|
|
4535
|
-
//
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4575
|
+
// v0.12.38: mode 0o600 + Windows ACL hardening — attestations carry the
|
|
4576
|
+
// operator's evidence, jurisdiction obligations, and consent records and
|
|
4577
|
+
// must not be world-readable on multi-tenant hosts.
|
|
4578
|
+
const crypto = require("crypto");
|
|
4579
|
+
const jsonStr = JSON.stringify(attestation, null, 2);
|
|
4580
|
+
const sigPath = filePath + ".sig";
|
|
4581
|
+
const suffix = `.${process.pid}.${crypto.randomBytes(6).toString("hex")}.tmp`;
|
|
4582
|
+
const jsonTmp = filePath + suffix;
|
|
4583
|
+
const sigTmp = sigPath + suffix;
|
|
4584
|
+
const writeFsync = (p, data) => {
|
|
4585
|
+
const fd = fs.openSync(p, "w", 0o600);
|
|
4586
|
+
try { fs.writeFileSync(fd, data); fs.fsyncSync(fd); }
|
|
4587
|
+
finally { fs.closeSync(fd); }
|
|
4588
|
+
};
|
|
4589
|
+
// Sidecar is computed over the SAME normalized bytes that will land, so
|
|
4590
|
+
// the sig always matches the placed body.
|
|
4591
|
+
const sidecarBytes = computeSidecarBytes(normalizeAttestationBytes(jsonStr));
|
|
4592
|
+
writeFsync(jsonTmp, jsonStr);
|
|
4593
|
+
writeFsync(sigTmp, sidecarBytes);
|
|
4594
|
+
try {
|
|
4595
|
+
if (flag === "wx") {
|
|
4596
|
+
// Atomic create + collision detection.
|
|
4597
|
+
try {
|
|
4598
|
+
fs.linkSync(jsonTmp, filePath);
|
|
4599
|
+
} catch (linkErr) {
|
|
4600
|
+
if (linkErr.code === "EEXIST") throw linkErr; // collision — outer handler decides
|
|
4601
|
+
// Filesystems without hard-link support (EPERM/EXDEV/ENOSYS): fall
|
|
4602
|
+
// back to an existsSync collision check + atomic rename. Narrow
|
|
4603
|
+
// TOCTOU window, only on such filesystems.
|
|
4604
|
+
if (fs.existsSync(filePath)) { const e = new Error("EEXIST"); e.code = "EEXIST"; throw e; }
|
|
4605
|
+
fs.renameSync(jsonTmp, filePath);
|
|
4606
|
+
}
|
|
4607
|
+
// Slot won — place the sidecar (sigPath is fresh on a create).
|
|
4608
|
+
fs.renameSync(sigTmp, sigPath);
|
|
4609
|
+
try { fs.unlinkSync(jsonTmp); } catch { /* hard-link path leaves a second name */ }
|
|
4610
|
+
} else {
|
|
4611
|
+
// Force-overwrite, under the persist lock: atomic replace of both.
|
|
4612
|
+
// Both tmps are fully written + fsync'd, so the new body and its
|
|
4613
|
+
// matching new sidecar are placed back-to-back.
|
|
4614
|
+
fs.renameSync(jsonTmp, filePath);
|
|
4615
|
+
fs.renameSync(sigTmp, sigPath);
|
|
4616
|
+
}
|
|
4617
|
+
} catch (placeErr) {
|
|
4618
|
+
// Clean up tmps on any placement failure (incl. EEXIST collision) so
|
|
4619
|
+
// a failed/refused write never leaves orphan tmp files at the slot.
|
|
4620
|
+
try { fs.unlinkSync(jsonTmp); } catch { /* may already be linked/renamed */ }
|
|
4621
|
+
try { fs.unlinkSync(sigTmp); } catch { /* may already be renamed */ }
|
|
4622
|
+
throw placeErr;
|
|
4623
|
+
}
|
|
4540
4624
|
try {
|
|
4541
4625
|
const { restrictWindowsAcl } = require(path.join(PKG_ROOT, "lib", "sign.js"));
|
|
4542
4626
|
restrictWindowsAcl(filePath);
|
|
4627
|
+
restrictWindowsAcl(sigPath);
|
|
4543
4628
|
} catch { /* sign.js not loadable in some test paths — best-effort */ }
|
|
4544
|
-
maybeSignAttestation(filePath);
|
|
4545
4629
|
};
|
|
4546
4630
|
|
|
4547
4631
|
try {
|
|
@@ -4714,40 +4798,18 @@ function normalizeAttestationBytes(input) {
|
|
|
4714
4798
|
return s.replace(/\r\n/g, "\n");
|
|
4715
4799
|
}
|
|
4716
4800
|
|
|
4717
|
-
|
|
4801
|
+
// Compute the `.sig` sidecar bytes for an attestation's (already-normalized)
|
|
4802
|
+
// content. Pure — does NOT write any file; the persist path writes the
|
|
4803
|
+
// returned string to a tmp and atomically renames it into place alongside the
|
|
4804
|
+
// attestation body, so the body never lands without its sidecar bytes ready.
|
|
4805
|
+
// Emits the one-time-per-process unsigned warning.
|
|
4806
|
+
function computeSidecarBytes(contentNormalized) {
|
|
4718
4807
|
const crypto = require("crypto");
|
|
4719
|
-
|
|
4720
|
-
//
|
|
4721
|
-
//
|
|
4722
|
-
// against PKG_ROOT/keys/public.pem; if we sign with cwd/.keys/private.pem
|
|
4723
|
-
// (e.g. the maintainer's repo-local keypair) the resulting `.sig` will
|
|
4724
|
-
// verify INVALID and report a false tamper signal on every freshly-written
|
|
4725
|
-
// attestation. PKG_ROOT-only resolution is the right answer; the original
|
|
4726
|
-
// smoke report's "doctor finds key, run does not" gap is fixed in `doctor`
|
|
4727
|
-
// (reporting only PKG_ROOT now), not by making `run` follow a cwd key the
|
|
4728
|
-
// verifier doesn't trust.
|
|
4808
|
+
// v0.12.9: keep the sign key aligned with the VERIFY key. `attest verify`
|
|
4809
|
+
// checks signatures against PKG_ROOT/keys/public.pem; signing with a
|
|
4810
|
+
// cwd-local key would verify INVALID. PKG_ROOT-only resolution is correct.
|
|
4729
4811
|
const privKeyPath = path.join(PKG_ROOT, ".keys", "private.pem");
|
|
4730
|
-
// Normalize attestation bytes before sign — strip leading UTF-8 BOM +
|
|
4731
|
-
// collapse CRLF to LF. Mirrors lib/sign.js / lib/verify.js /
|
|
4732
|
-
// lib/refresh-network.js / scripts/verify-shipped-tarball.js. The
|
|
4733
|
-
// attestation file lives on disk under .exceptd/ and can pick up CRLF
|
|
4734
|
-
// through git-attribute / editor round-trips on Windows; without
|
|
4735
|
-
// normalization the sign/verify pair diverges on the same logical content.
|
|
4736
|
-
// The byte-stability contract spans five sites; tests/normalize-contract
|
|
4737
|
-
// .test.js enforces byte-identical output across all of them.
|
|
4738
|
-
const rawContent = fs.readFileSync(filePath, "utf8");
|
|
4739
|
-
const content = normalizeAttestationBytes(rawContent);
|
|
4740
4812
|
// One-time-per-process unsigned warning so cron jobs don't spam stderr.
|
|
4741
|
-
// Operators who set `.keys/private.pem` get tamper-evident attestations;
|
|
4742
|
-
// operators without the keypair get a single nudge per session telling them
|
|
4743
|
-
// exactly how to enable signing.
|
|
4744
|
-
//
|
|
4745
|
-
// Consumer installs (`npm install -g`) land PKG_ROOT under
|
|
4746
|
-
// node_modules/ where the operator typically can't write to
|
|
4747
|
-
// .keys/. For those installs the multi-line nudge prescribes a
|
|
4748
|
-
// remediation (doctor --fix) the operator doesn't own — surface a
|
|
4749
|
-
// single-line marker so the unsigned attestation isn't silent, but
|
|
4750
|
-
// skip the call-to-action that doesn't apply.
|
|
4751
4813
|
if (!fs.existsSync(privKeyPath) && !process.env.EXCEPTD_UNSIGNED_WARNED) {
|
|
4752
4814
|
const pkgRootSegments = PKG_ROOT.split(/[\\/]/);
|
|
4753
4815
|
const isConsumerInstall =
|
|
@@ -4768,37 +4830,45 @@ function maybeSignAttestation(filePath) {
|
|
|
4768
4830
|
try {
|
|
4769
4831
|
if (fs.existsSync(privKeyPath)) {
|
|
4770
4832
|
const privateKey = fs.readFileSync(privKeyPath, "utf8");
|
|
4771
|
-
const sig = crypto.sign(null, Buffer.from(
|
|
4833
|
+
const sig = crypto.sign(null, Buffer.from(contentNormalized, "utf8"), {
|
|
4772
4834
|
key: privateKey,
|
|
4773
4835
|
dsaEncoding: "ieee-p1363",
|
|
4774
4836
|
});
|
|
4775
|
-
// The
|
|
4776
|
-
//
|
|
4777
|
-
|
|
4778
|
-
// directory can mutate them without invalidating the signature. The
|
|
4779
|
-
// sidecar therefore carries only the algorithm tag, the Ed25519
|
|
4780
|
-
// signature payload, and an explanatory note — no `signed_at`,
|
|
4781
|
-
// `signs_path`, or `signs_sha256`. Operators reading freshness use
|
|
4782
|
-
// filesystem mtime; the attestation file's `captured_at` field is
|
|
4783
|
-
// what's signed.
|
|
4784
|
-
fs.writeFileSync(sigPath, JSON.stringify({
|
|
4837
|
+
// The Ed25519 signature covers ONLY the attestation file bytes; no
|
|
4838
|
+
// replay-rewritable metadata travels in the sidecar.
|
|
4839
|
+
return JSON.stringify({
|
|
4785
4840
|
algorithm: "Ed25519",
|
|
4786
4841
|
signature_base64: sig.toString("base64"),
|
|
4787
4842
|
note: "Ed25519 signature covers the attestation file bytes only. Use filesystem mtime for freshness; use the attestation's `captured_at` for the signed timestamp.",
|
|
4788
|
-
}, null, 2)
|
|
4789
|
-
// Mirror the v0.12.38 attestation.json hardening: 0o600 on POSIX +
|
|
4790
|
-
// icacls inheritance strip on win32. The sidecar carries the
|
|
4791
|
-
// signature payload; multi-tenant hosts shouldn't leak it.
|
|
4792
|
-
try { require("./../lib/sign.js").restrictWindowsAcl(sigPath); } catch { /* best-effort */ }
|
|
4793
|
-
} else {
|
|
4794
|
-
fs.writeFileSync(sigPath, JSON.stringify({
|
|
4795
|
-
algorithm: "unsigned",
|
|
4796
|
-
signed: false,
|
|
4797
|
-
note: "No private key at .keys/private.pem — attestation is hash-stable but unsigned. Run `exceptd doctor --fix` to enable signing.",
|
|
4798
|
-
}, null, 2), { mode: 0o600 });
|
|
4799
|
-
try { require("./../lib/sign.js").restrictWindowsAcl(sigPath); } catch { /* best-effort */ }
|
|
4843
|
+
}, null, 2);
|
|
4800
4844
|
}
|
|
4801
|
-
|
|
4845
|
+
return JSON.stringify({
|
|
4846
|
+
algorithm: "unsigned",
|
|
4847
|
+
signed: false,
|
|
4848
|
+
note: "No private key at .keys/private.pem — attestation is hash-stable but unsigned. Run `exceptd doctor --fix` to enable signing.",
|
|
4849
|
+
}, null, 2);
|
|
4850
|
+
} catch {
|
|
4851
|
+
// Signing failure must not block the run — fall back to an unsigned marker
|
|
4852
|
+
// so the sidecar always exists alongside the body.
|
|
4853
|
+
return JSON.stringify({
|
|
4854
|
+
algorithm: "unsigned",
|
|
4855
|
+
signed: false,
|
|
4856
|
+
note: "Signing failed at write time; attestation is hash-stable but unsigned.",
|
|
4857
|
+
}, null, 2);
|
|
4858
|
+
}
|
|
4859
|
+
}
|
|
4860
|
+
|
|
4861
|
+
// Sign an already-written file in place by computing + writing its `.sig`
|
|
4862
|
+
// sidecar. The main attestation-persist path writes the sidecar atomically
|
|
4863
|
+
// alongside the body (via computeSidecarBytes); this helper serves the
|
|
4864
|
+
// replay-record path, which writes a uniquely-named file and so needs no
|
|
4865
|
+
// atomic-collision handling. Best-effort: a sign-time failure leaves the
|
|
4866
|
+
// record unsigned (still a valid audit entry) rather than aborting.
|
|
4867
|
+
function maybeSignAttestation(filePath) {
|
|
4868
|
+
const content = normalizeAttestationBytes(fs.readFileSync(filePath, "utf8"));
|
|
4869
|
+
const sidecar = computeSidecarBytes(content);
|
|
4870
|
+
fs.writeFileSync(filePath + ".sig", sidecar, { mode: 0o600 });
|
|
4871
|
+
try { require(path.join(PKG_ROOT, "lib", "sign.js")).restrictWindowsAcl(filePath + ".sig"); } catch { /* best-effort */ }
|
|
4802
4872
|
}
|
|
4803
4873
|
|
|
4804
4874
|
/**
|
|
@@ -5754,6 +5824,22 @@ function cmdAttest(runner, args, runOpts, pretty) {
|
|
|
5754
5824
|
// tampered attestation.json and overwrote .sig with the unsigned stub).
|
|
5755
5825
|
const privKeyPath = path.join(PKG_ROOT, ".keys", "private.pem");
|
|
5756
5826
|
const hasPrivKey = fs.existsSync(privKeyPath);
|
|
5827
|
+
// Does any sidecar in this session dir carry a real Ed25519 signature? If
|
|
5828
|
+
// so, a sibling attestation with NO sidecar is suspicious (a sig was
|
|
5829
|
+
// expected). Combined with hasPrivKey, this lets default `attest verify`
|
|
5830
|
+
// treat a deleted sidecar as tamper — agreeing with `reattest`, which
|
|
5831
|
+
// already refuses. The keyless case (no key, all-unsigned peers) stays
|
|
5832
|
+
// benign so keyless CI is unaffected.
|
|
5833
|
+
let anyPeerEd25519Signed = false;
|
|
5834
|
+
try {
|
|
5835
|
+
for (const sf of fs.readdirSync(dir)) {
|
|
5836
|
+
if (!sf.endsWith(".sig")) continue;
|
|
5837
|
+
try {
|
|
5838
|
+
const sd = JSON.parse(fs.readFileSync(path.join(dir, sf), "utf8"));
|
|
5839
|
+
if (sd && sd.algorithm === "Ed25519") { anyPeerEd25519Signed = true; break; }
|
|
5840
|
+
} catch { /* skip unparseable sidecar */ }
|
|
5841
|
+
}
|
|
5842
|
+
} catch { /* dir unreadable — fall through */ }
|
|
5757
5843
|
|
|
5758
5844
|
// Sidecar-verify helper shared by both the attestations[] and
|
|
5759
5845
|
// replay-records[] partitions. Centralising the per-file verify
|
|
@@ -5761,7 +5847,16 @@ function cmdAttest(runner, args, runOpts, pretty) {
|
|
|
5761
5847
|
// instead of two parallel branches.
|
|
5762
5848
|
const verifySidecar = (f) => {
|
|
5763
5849
|
const sigPath = path.join(dir, f + ".sig");
|
|
5764
|
-
if (!fs.existsSync(sigPath))
|
|
5850
|
+
if (!fs.existsSync(sigPath)) {
|
|
5851
|
+
// A missing sidecar is benign ONLY when none was ever expected (the
|
|
5852
|
+
// attestation was written on a keyless host and no peer is signed).
|
|
5853
|
+
// When a sig SHOULD exist, an absent one is a deletion-to-evade-tamper
|
|
5854
|
+
// signal — flag it so default verify matches reattest's refusal.
|
|
5855
|
+
if (hasPrivKey || anyPeerEd25519Signed) {
|
|
5856
|
+
return { file: f, signed: false, verified: false, reason: "no .sig sidecar, but one was expected (signing key present or a signed peer attestation exists) — sidecar deletion suspected", tamper_class: "sidecar-missing" };
|
|
5857
|
+
}
|
|
5858
|
+
return { file: f, signed: false, verified: false, reason: "no .sig sidecar" };
|
|
5859
|
+
}
|
|
5765
5860
|
let sigDoc;
|
|
5766
5861
|
try { sigDoc = JSON.parse(fs.readFileSync(sigPath, "utf8")); }
|
|
5767
5862
|
catch (e) {
|
|
@@ -5837,7 +5932,8 @@ function cmdAttest(runner, args, runOpts, pretty) {
|
|
|
5837
5932
|
(r.signed && !r.verified)
|
|
5838
5933
|
|| r.tamper_class === "sidecar-corrupt"
|
|
5839
5934
|
|| r.tamper_class === "unsigned-substitution"
|
|
5840
|
-
|| r.tamper_class === "algorithm-unsupported"
|
|
5935
|
+
|| r.tamper_class === "algorithm-unsupported"
|
|
5936
|
+
|| r.tamper_class === "sidecar-missing";
|
|
5841
5937
|
const attTampered = attResults.some(tamperPredicate);
|
|
5842
5938
|
const replayTampered = replayResults.some(tamperPredicate);
|
|
5843
5939
|
|
package/data/_indexes/_meta.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema_version": "1.1.0",
|
|
3
|
-
"generated_at": "2026-05-
|
|
3
|
+
"generated_at": "2026-05-27T23:08:52.304Z",
|
|
4
4
|
"generator": "scripts/build-indexes.js",
|
|
5
5
|
"source_count": 54,
|
|
6
6
|
"source_hashes": {
|
|
7
|
-
"manifest.json": "
|
|
7
|
+
"manifest.json": "008292f92fc956004d340bf314748a28ab03a6f14c88ddf7f5deabdab8b14b4d",
|
|
8
8
|
"data/atlas-ttps.json": "d24bc02859d40ccf1615db75cca68c077585904e41e0d8f6de448121e9b1abb0",
|
|
9
9
|
"data/attack-techniques.json": "fa193f0d2d248176a8beddb641e9fe56ba4faa9e15dc253ff876dbf0c5d58a77",
|
|
10
10
|
"data/cve-catalog.json": "3d451dda7ac0c7d57a4075ae4bafd3148c6184b35dc1bc59d8b81d1f2641e430",
|
|
@@ -180,7 +180,12 @@ function scanWorkflow(content, rel) {
|
|
|
180
180
|
// composite actions (`uses: ./`).
|
|
181
181
|
const lines2 = content.split(/\r?\n/);
|
|
182
182
|
for (let i = 0; i < lines2.length; i++) {
|
|
183
|
-
|
|
183
|
+
// A real `uses:` line is never multiple KB. Skip overlong lines so a
|
|
184
|
+
// crafted whitespace run can't drive regex backtracking.
|
|
185
|
+
if (lines2[i].length > 4096) continue;
|
|
186
|
+
// `^[ \t]*(?:-[ \t]*)?` anchors the indentation once, then an optional
|
|
187
|
+
// `- ` list marker — no overlapping `\s*` runs that backtrack.
|
|
188
|
+
const m = lines2[i].match(/^[ \t]*(?:-[ \t]*)?uses:\s*['"]?([^'"\s#]+)['"]?/);
|
|
184
189
|
if (!m) continue;
|
|
185
190
|
const refStr = m[1];
|
|
186
191
|
if (refStr.startsWith("./") || refStr.startsWith("docker://")) continue;
|
|
@@ -222,6 +222,9 @@ function scanCompose(content, rel) {
|
|
|
222
222
|
|
|
223
223
|
for (let i = 0; i < lines.length; i++) {
|
|
224
224
|
const line = lines[i];
|
|
225
|
+
// A real compose line is never multiple KB. Skip overlong lines so a
|
|
226
|
+
// crafted whitespace run can't drive regex backtracking.
|
|
227
|
+
if (line.length > 4096) continue;
|
|
225
228
|
if (/^\s*#/.test(line)) continue;
|
|
226
229
|
if (/^\s*privileged:\s*true\b/i.test(line)) hits["compose-privileged"].push({ file: rel, line: i + 1, snippet: line.trim() });
|
|
227
230
|
// compose-host-network: per playbook, fires on any of
|
|
@@ -268,6 +271,9 @@ function scanK8s(content, rel) {
|
|
|
268
271
|
|
|
269
272
|
for (let i = 0; i < lines.length; i++) {
|
|
270
273
|
const line = lines[i];
|
|
274
|
+
// A real manifest line is never multiple KB. Skip overlong lines so a
|
|
275
|
+
// crafted whitespace run can't drive regex backtracking.
|
|
276
|
+
if (line.length > 4096) continue;
|
|
271
277
|
if (/^\s*#/.test(line)) continue;
|
|
272
278
|
if (/^\s*privileged:\s*true\b/i.test(line)) hits["k8s-privileged"].push({ file: rel, line: i + 1, snippet: line.trim() });
|
|
273
279
|
if (/^\s*(hostNetwork|hostPID|hostIPC):\s*true\b/.test(line)) hits["k8s-host-namespaces"].push({ file: rel, line: i + 1, snippet: line.trim() });
|
|
@@ -287,7 +293,9 @@ function scanK8s(content, rel) {
|
|
|
287
293
|
}
|
|
288
294
|
// image: ...:latest OR image: ... (no tag, defaults to latest)
|
|
289
295
|
// Allow optional leading `-` from a YAML list item: `- image: ...`.
|
|
290
|
-
|
|
296
|
+
// `^[ \t]*(?:-[ \t]*)?` anchors the indentation once, then an optional
|
|
297
|
+
// `- ` list marker — no overlapping `\s*` runs that backtrack.
|
|
298
|
+
const imageMatch = line.match(/^[ \t]*(?:-[ \t]*)?image:\s*['"]?([^'"@\s]+)(?:@[^'"]+)?['"]?\s*$/);
|
|
291
299
|
if (imageMatch) {
|
|
292
300
|
const ref = imageMatch[1];
|
|
293
301
|
const tagMatch = ref.match(/:([^/]+)$/);
|
|
@@ -157,8 +157,13 @@ function scanPublishWorkflow(content, rel) {
|
|
|
157
157
|
const lines = content.split(/\r?\n/);
|
|
158
158
|
for (let i = 0; i < lines.length; i++) {
|
|
159
159
|
const line = lines[i];
|
|
160
|
+
// A real `uses:` line is never multiple KB. Skip overlong lines so a
|
|
161
|
+
// crafted whitespace run can't drive regex backtracking.
|
|
162
|
+
if (line.length > 4096) continue;
|
|
160
163
|
// Allow optional leading `-` from a YAML list item: `- uses: ...`.
|
|
161
|
-
|
|
164
|
+
// `^[ \t]*(?:-[ \t]*)?` anchors the indentation once, then an optional
|
|
165
|
+
// `- ` list marker — no overlapping `\s*` runs that backtrack.
|
|
166
|
+
const m = line.match(/^[ \t]*(?:-[ \t]*)?uses:\s*['"]?([^'"\s]+)['"]?\s*$/);
|
|
162
167
|
if (!m) continue;
|
|
163
168
|
const ref = m[1];
|
|
164
169
|
if (ref.startsWith("./") || ref.startsWith("./.github/")) continue; // local
|
package/lib/playbook-runner.js
CHANGED
|
@@ -691,15 +691,26 @@ function detect(playbookId, directiveId, agentSubmission = {}, runOpts = {}) {
|
|
|
691
691
|
}
|
|
692
692
|
}
|
|
693
693
|
} else {
|
|
694
|
-
//
|
|
694
|
+
// An override WAS supplied for this indicator but its value didn't
|
|
695
|
+
// canonicalize to hit/miss/inconclusive (e.g. "maybe", "present", a
|
|
696
|
+
// number). Pre-fix this was silently dropped — the operator believed
|
|
697
|
+
// they'd asserted a result but the signal vanished, yielding a false
|
|
698
|
+
// not_detected. Surface it as a runtime_error (mirrors the
|
|
699
|
+
// classification_override_invalid signal) so the drop is visible.
|
|
700
|
+
if (rawOverride !== undefined && runOpts && Array.isArray(runOpts._runErrors)) {
|
|
701
|
+
pushRunError(runOpts._runErrors, {
|
|
702
|
+
kind: 'signal_override_unrecognized',
|
|
703
|
+
indicator_id: ind.id,
|
|
704
|
+
supplied_value: (typeof rawOverride === 'object') ? JSON.stringify(rawOverride).slice(0, 80) : String(rawOverride).slice(0, 80),
|
|
705
|
+
message: `signal_overrides["${ind.id}"] value was not recognized (expected hit/miss/inconclusive or a boolean); the signal was ignored.`,
|
|
706
|
+
}, { dedupeKey: e => e.indicator_id || '' });
|
|
707
|
+
}
|
|
708
|
+
// Without a usable override, treat any captured artifact as evidence
|
|
695
709
|
// the indicator could be evaluated. Mark inconclusive if any artifact
|
|
696
710
|
// was captured (engine doesn't pattern-match raw artifact content; the
|
|
697
711
|
// host AI is responsible for that). With NO captured artifacts, this is
|
|
698
712
|
// a clean empty submission — emit 'miss' so the run can reach
|
|
699
713
|
// classification:'not_detected' rather than getting stuck inconclusive.
|
|
700
|
-
// A clean empty run with no captured artifacts must emit 'miss' so
|
|
701
|
-
// classification can reach 'not_detected'; otherwise theater_verdict
|
|
702
|
-
// stays 'pending_agent_run' indefinitely.
|
|
703
714
|
const anyCaptured = Object.values(artifacts).some(a => a && a.captured);
|
|
704
715
|
verdict = anyCaptured ? 'inconclusive' : 'miss';
|
|
705
716
|
}
|
|
@@ -765,12 +776,16 @@ function detect(playbookId, directiveId, agentSubmission = {}, runOpts = {}) {
|
|
|
765
776
|
const anyFpDowngrade = indicatorResults.some(r => Array.isArray(r.fp_checks_unsatisfied) && r.fp_checks_unsatisfied.length > 0);
|
|
766
777
|
|
|
767
778
|
let classification;
|
|
779
|
+
// Track whether the override was actually honored, so we don't report a
|
|
780
|
+
// refused override as "applied" (L6).
|
|
781
|
+
let overrideEffective = !!override;
|
|
768
782
|
if (override) {
|
|
769
783
|
classification = override === 'clean' ? 'not_detected' : override;
|
|
770
784
|
if (anyFpDowngrade) {
|
|
771
785
|
const substituted = 'inconclusive';
|
|
772
786
|
const attempted = override; // record what the operator submitted, not the mapped form
|
|
773
787
|
classification = substituted;
|
|
788
|
+
overrideEffective = false;
|
|
774
789
|
if (runOpts && Array.isArray(runOpts._runErrors)) {
|
|
775
790
|
pushRunError(runOpts._runErrors, {
|
|
776
791
|
kind: 'classification_override_blocked',
|
|
@@ -782,6 +797,24 @@ function detect(playbookId, directiveId, agentSubmission = {}, runOpts = {}) {
|
|
|
782
797
|
.map(r => ({ id: r.id, fp_checks_unsatisfied_count: r.fp_checks_unsatisfied.length })),
|
|
783
798
|
}, { dedupeKey: e => String(e.attempted) });
|
|
784
799
|
}
|
|
800
|
+
} else if (classification === 'not_detected' && hasDeterministicHit) {
|
|
801
|
+
// A not_detected/clean override must not silently bury a DETERMINISTIC
|
|
802
|
+
// hit. A deterministic indicator firing is high-signal evidence; hiding
|
|
803
|
+
// it as not_detected is a strictly worse false-negative than leaving the
|
|
804
|
+
// run inconclusive. (Probabilistic hits remain overridable — that's the
|
|
805
|
+
// legitimate "I confirmed these are benign" workflow.) Substitute
|
|
806
|
+
// inconclusive and surface why.
|
|
807
|
+
classification = 'inconclusive';
|
|
808
|
+
overrideEffective = false;
|
|
809
|
+
if (runOpts && Array.isArray(runOpts._runErrors)) {
|
|
810
|
+
pushRunError(runOpts._runErrors, {
|
|
811
|
+
kind: 'classification_override_masks_deterministic_hit',
|
|
812
|
+
attempted: override,
|
|
813
|
+
substituted: 'inconclusive',
|
|
814
|
+
reason: 'A not_detected/clean classification override was refused because one or more deterministic indicators fired. A deterministic hit cannot be downgraded to not_detected.',
|
|
815
|
+
deterministic_hit_indicators: hits.filter(r => r.deterministic).map(r => r.id),
|
|
816
|
+
}, { dedupeKey: e => String(e.attempted) });
|
|
817
|
+
}
|
|
785
818
|
}
|
|
786
819
|
} else if (hasDeterministicHit || hasHighConfHit) {
|
|
787
820
|
classification = 'detected';
|
|
@@ -818,7 +851,11 @@ function detect(playbookId, directiveId, agentSubmission = {}, runOpts = {}) {
|
|
|
818
851
|
from_observation: agentSubmission._signal_origins?.[i.id] || null,
|
|
819
852
|
})),
|
|
820
853
|
indicators_evaluated_count: indicatorResults.length,
|
|
821
|
-
|
|
854
|
+
// Only report the override as applied when it was actually honored — a
|
|
855
|
+
// refused override (FP-downgrade or deterministic-hit masking) left
|
|
856
|
+
// `classification` as inconclusive, so reporting the attempted value here
|
|
857
|
+
// would contradict the verdict.
|
|
858
|
+
classification_override_applied: (override && overrideEffective) ? (override === 'clean' ? 'not_detected' : override) : null,
|
|
822
859
|
submission_shape_seen: agentSubmission._original_shape || (agentSubmission.artifacts ? 'nested (v0.10.x)' : 'empty'),
|
|
823
860
|
// Pass through any flat-shape observation collisions detected at
|
|
824
861
|
// normalize time so analyze() can publish them under
|
|
@@ -3148,10 +3185,38 @@ function run(playbookId, directiveId, agentSubmission = {}, runOpts = {}) {
|
|
|
3148
3185
|
};
|
|
3149
3186
|
}
|
|
3150
3187
|
|
|
3188
|
+
// Cross-process mutex lock for this run. Use the diagnostic variant so a
|
|
3189
|
+
// CONFIRMED live foreign holder BLOCKS the run. The bare acquireLock returned
|
|
3190
|
+
// null on a lost race (the TOCTOU window between preflight's check and the
|
|
3191
|
+
// acquire) and the run then proceeded UNLOCKED, defeating the mutex. Block
|
|
3192
|
+
// only on `held_by_live_pid` — same-PID reentrancy and FS quirks still
|
|
3193
|
+
// proceed best-effort (lockPath null), so no legitimate nested/edge case
|
|
3194
|
+
// regresses. Released in the finally block.
|
|
3195
|
+
const lockResult = acquireLockDiagnostic(playbookId);
|
|
3196
|
+
// Block ONLY on a confirmed live foreign holder with a real numeric pid.
|
|
3197
|
+
// acquireLockDiagnostic also returns reason:'held_by_live_pid' with
|
|
3198
|
+
// holder_pid:null for a MALFORMED/truncated lockfile (it can't parse a pid to
|
|
3199
|
+
// probe). Blocking on that would let one corrupted lockfile left by a crash
|
|
3200
|
+
// deny every future run forever. A null-pid (malformed) lock instead falls
|
|
3201
|
+
// through to proceed best-effort (lockPath null) — matching how the preflight
|
|
3202
|
+
// mutex check treats unparsable lockfiles as stale.
|
|
3203
|
+
if (!lockResult.ok && lockResult.reason === 'held_by_live_pid'
|
|
3204
|
+
&& Number.isInteger(lockResult.holder_pid) && lockResult.holder_pid > 0) {
|
|
3205
|
+
return {
|
|
3206
|
+
ok: false,
|
|
3207
|
+
playbook_id: playbookId,
|
|
3208
|
+
directive_id: directiveId,
|
|
3209
|
+
verdict: 'blocked',
|
|
3210
|
+
summary_line: `${playbookId}: blocked — a concurrent run holds the mutex (pid ${lockResult.holder_pid})`.slice(0, 240),
|
|
3211
|
+
phase: 'preflight',
|
|
3212
|
+
blocked_by: 'mutex',
|
|
3213
|
+
reason: `A concurrent run of "${playbookId}" holds the run lock (live pid ${lockResult.holder_pid}). Retry after it completes.`,
|
|
3214
|
+
remediation: 'Wait for the in-flight run to finish, then retry.',
|
|
3215
|
+
evidence_completeness: 'not-evaluated'
|
|
3216
|
+
};
|
|
3217
|
+
}
|
|
3218
|
+
const lockPath = lockResult.ok ? lockResult.path : null;
|
|
3151
3219
|
_activeRuns.add(playbookId);
|
|
3152
|
-
// Cross-process mutex lock for this run. preflight verified no other lock
|
|
3153
|
-
// exists; we acquire ours and release in the finally block.
|
|
3154
|
-
const lockPath = acquireLock(playbookId);
|
|
3155
3220
|
// Parse the playbook once at run() entry and thread the parsed object
|
|
3156
3221
|
// through each phase via runOpts._playbookCache. Each phase otherwise
|
|
3157
3222
|
// calls loadPlaybook() independently; for a single run that's seven
|
|
@@ -3377,11 +3442,22 @@ function run(playbookId, directiveId, agentSubmission = {}, runOpts = {}) {
|
|
|
3377
3442
|
* through directly. Avoids JSON.stringify's replacer indirection because
|
|
3378
3443
|
* a top-level array would otherwise miss the canonicalization recursion.
|
|
3379
3444
|
*/
|
|
3380
|
-
|
|
3445
|
+
const CANONICAL_MAX_DEPTH = 200;
|
|
3446
|
+
function canonicalStringify(v, _depth = 0) {
|
|
3381
3447
|
if (v === null || typeof v !== 'object') return JSON.stringify(v);
|
|
3382
|
-
|
|
3448
|
+
// Bounded recursion: adversarial (or accidental) deeply-nested evidence
|
|
3449
|
+
// would otherwise overflow the stack with an opaque "internal error". This
|
|
3450
|
+
// runs on every run() (evidence_hash + session_id derivation), so the guard
|
|
3451
|
+
// turns a crash into an actionable rejection. 200 levels is far beyond any
|
|
3452
|
+
// legitimate submission.
|
|
3453
|
+
if (_depth > CANONICAL_MAX_DEPTH) {
|
|
3454
|
+
const e = new Error(`evidence nesting exceeds the maximum depth of ${CANONICAL_MAX_DEPTH} — flatten the submission`);
|
|
3455
|
+
e.code = 'EVIDENCE_TOO_DEEP';
|
|
3456
|
+
throw e;
|
|
3457
|
+
}
|
|
3458
|
+
if (Array.isArray(v)) return '[' + v.map(x => canonicalStringify(x, _depth + 1)).join(',') + ']';
|
|
3383
3459
|
const keys = Object.keys(v).sort();
|
|
3384
|
-
return '{' + keys.map(k => JSON.stringify(k) + ':' + canonicalStringify(v[k])).join(',') + '}';
|
|
3460
|
+
return '{' + keys.map(k => JSON.stringify(k) + ':' + canonicalStringify(v[k], _depth + 1)).join(',') + '}';
|
|
3385
3461
|
}
|
|
3386
3462
|
|
|
3387
3463
|
/**
|
package/manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "exceptd-security",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.14",
|
|
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",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
],
|
|
54
54
|
"last_threat_review": "2026-05-15",
|
|
55
55
|
"signature": "lXhZgoIrrVloO3XaTvo/43AxZn4mwErstd7DR0O/oVhD3AOGODM4HqrageYEou9WKOdMEGP5mJNTjJsXdP5NDA==",
|
|
56
|
-
"signed_at": "2026-05-
|
|
56
|
+
"signed_at": "2026-05-27T22:43:04.125Z",
|
|
57
57
|
"cwe_refs": [
|
|
58
58
|
"CWE-125",
|
|
59
59
|
"CWE-362",
|
|
@@ -123,7 +123,7 @@
|
|
|
123
123
|
],
|
|
124
124
|
"last_threat_review": "2026-05-17",
|
|
125
125
|
"signature": "ztSKk/zFMFbT12qRcEeBKpydBn7fTT86KxMmor0DTCoKQWk5fJ0fSInfP1XMSB6rFk4/SuSjKVxQRMKVJ5a+Cg==",
|
|
126
|
-
"signed_at": "2026-05-
|
|
126
|
+
"signed_at": "2026-05-27T22:43:04.127Z",
|
|
127
127
|
"cwe_refs": [
|
|
128
128
|
"CWE-1039",
|
|
129
129
|
"CWE-1426",
|
|
@@ -196,7 +196,7 @@
|
|
|
196
196
|
],
|
|
197
197
|
"last_threat_review": "2026-05-17",
|
|
198
198
|
"signature": "K6QdPHNK5c4K5QFjrW0QsUhjp71D7SOisSoulwPNSvKRdi2rY+yg0kdckijBMkLMsVPyUvcC9giu93mKJ1OZDg==",
|
|
199
|
-
"signed_at": "2026-05-
|
|
199
|
+
"signed_at": "2026-05-27T22:43:04.127Z",
|
|
200
200
|
"cwe_refs": [
|
|
201
201
|
"CWE-22",
|
|
202
202
|
"CWE-345",
|
|
@@ -248,7 +248,7 @@
|
|
|
248
248
|
"framework_gaps": [],
|
|
249
249
|
"last_threat_review": "2026-05-22",
|
|
250
250
|
"signature": "Qd3SBWmUAaaT++e1Ry2wBIz/dCBmNBMl0+4Rb0etvJLES0fIBEAkU1mTbgNZnT5XOg9J5twdUpymWtmKnDDQCQ==",
|
|
251
|
-
"signed_at": "2026-05-
|
|
251
|
+
"signed_at": "2026-05-27T22:43:04.127Z"
|
|
252
252
|
},
|
|
253
253
|
{
|
|
254
254
|
"name": "compliance-theater",
|
|
@@ -279,7 +279,7 @@
|
|
|
279
279
|
],
|
|
280
280
|
"last_threat_review": "2026-05-22",
|
|
281
281
|
"signature": "F2Shxae0ua0gPtvwzTRVzzHaIgJcFDRT3/akLUAZ4aaMQhkleKkcTaTpkjp+pTVEdPfLeLGNCeAOMs+whVYOBg==",
|
|
282
|
-
"signed_at": "2026-05-
|
|
282
|
+
"signed_at": "2026-05-27T22:43:04.128Z"
|
|
283
283
|
},
|
|
284
284
|
{
|
|
285
285
|
"name": "exploit-scoring",
|
|
@@ -308,7 +308,7 @@
|
|
|
308
308
|
],
|
|
309
309
|
"last_threat_review": "2026-05-18",
|
|
310
310
|
"signature": "NA1hoQycvQhSUoG5rwlXX0mOVmGxoXRVezkELGEA2nZOdGis4gXkHT3O6Sfw7zxE4JuMrsCb65TEeOWk9WEPDg==",
|
|
311
|
-
"signed_at": "2026-05-
|
|
311
|
+
"signed_at": "2026-05-27T22:43:04.128Z"
|
|
312
312
|
},
|
|
313
313
|
{
|
|
314
314
|
"name": "rag-pipeline-security",
|
|
@@ -345,7 +345,7 @@
|
|
|
345
345
|
],
|
|
346
346
|
"last_threat_review": "2026-05-22",
|
|
347
347
|
"signature": "W3pS8lnaCP96TQzsJpG5d5yv5IwgaQyS4Z2Ctcz5BOJf6LbajSIgeDgTZ4f4Bhr5m4E7KsgWGjZS4x7Fwd33BQ==",
|
|
348
|
-
"signed_at": "2026-05-
|
|
348
|
+
"signed_at": "2026-05-27T22:43:04.129Z",
|
|
349
349
|
"cwe_refs": [
|
|
350
350
|
"CWE-1395",
|
|
351
351
|
"CWE-1426"
|
|
@@ -405,7 +405,7 @@
|
|
|
405
405
|
],
|
|
406
406
|
"last_threat_review": "2026-05-17",
|
|
407
407
|
"signature": "/WDGygh1Ck4yWlBWDGtEUVCqKB8d+UaJXoAoBXujtt+GAl8JbMNpaN1TvI0WkEltQ9dTxaAzSn20/eVDqv8iDQ==",
|
|
408
|
-
"signed_at": "2026-05-
|
|
408
|
+
"signed_at": "2026-05-27T22:43:04.129Z",
|
|
409
409
|
"d3fend_refs": [
|
|
410
410
|
"D3-CA",
|
|
411
411
|
"D3-CSPP",
|
|
@@ -440,7 +440,7 @@
|
|
|
440
440
|
"framework_gaps": [],
|
|
441
441
|
"last_threat_review": "2026-05-22",
|
|
442
442
|
"signature": "za1NKBpy9LC91F/ESO/qhUfmvVr8GNItQOjR5OJLeHm+2dQ9HHiFWQK2eo53V/n/0uhubuggURA3yS6kJuWwBg==",
|
|
443
|
-
"signed_at": "2026-05-
|
|
443
|
+
"signed_at": "2026-05-27T22:43:04.129Z",
|
|
444
444
|
"cwe_refs": [
|
|
445
445
|
"CWE-1188"
|
|
446
446
|
],
|
|
@@ -474,7 +474,7 @@
|
|
|
474
474
|
"framework_gaps": [],
|
|
475
475
|
"last_threat_review": "2026-05-18",
|
|
476
476
|
"signature": "xiHAhhdufm9hCKU8PLiPE0MX65ej2F4OZwtlWLGLCiie9/km+Kiqbt192LcMvr94v83C98pb9wIaqFsFWft6AQ==",
|
|
477
|
-
"signed_at": "2026-05-
|
|
477
|
+
"signed_at": "2026-05-27T22:43:04.130Z",
|
|
478
478
|
"forward_watch": [
|
|
479
479
|
"New AI attack classes as ATLAS v6 publishes",
|
|
480
480
|
"Post-quantum adversary capability timeline",
|
|
@@ -513,7 +513,7 @@
|
|
|
513
513
|
"framework_gaps": [],
|
|
514
514
|
"last_threat_review": "2026-05-01",
|
|
515
515
|
"signature": "oYsSk35N2Uzq7MRofACykylcVwkgPhI4luWZ14vmQT+gUKLyZiKVOUJbe1+7lGl6BYPRN0sUDQ0f7S5Eu5w2Ag==",
|
|
516
|
-
"signed_at": "2026-05-
|
|
516
|
+
"signed_at": "2026-05-27T22:43:04.130Z"
|
|
517
517
|
},
|
|
518
518
|
{
|
|
519
519
|
"name": "zeroday-gap-learn",
|
|
@@ -540,7 +540,7 @@
|
|
|
540
540
|
"framework_gaps": [],
|
|
541
541
|
"last_threat_review": "2026-05-18",
|
|
542
542
|
"signature": "igRqYyU1unRFH40BsPyAR62SPrk8QZv8dPGb8S9O9EvLCNOZAzm3t+HdT/NKqzWHwrpomOzkkkyLfYI/0qTUDA==",
|
|
543
|
-
"signed_at": "2026-05-
|
|
543
|
+
"signed_at": "2026-05-27T22:43:04.131Z",
|
|
544
544
|
"forward_watch": [
|
|
545
545
|
"New CISA KEV entries",
|
|
546
546
|
"New ATLAS TTP additions in each ATLAS release",
|
|
@@ -604,7 +604,7 @@
|
|
|
604
604
|
],
|
|
605
605
|
"last_threat_review": "2026-05-22",
|
|
606
606
|
"signature": "i/17u4kJiSpcZAz7LnTyRePFugQOstQ1P4kVoe0oGf4E2/j8oIN9U9DccjUn/YHZhKWIJ2AILG/DMhvMrr3bBg==",
|
|
607
|
-
"signed_at": "2026-05-
|
|
607
|
+
"signed_at": "2026-05-27T22:43:04.131Z",
|
|
608
608
|
"cwe_refs": [
|
|
609
609
|
"CWE-327"
|
|
610
610
|
],
|
|
@@ -652,7 +652,7 @@
|
|
|
652
652
|
],
|
|
653
653
|
"last_threat_review": "2026-05-22",
|
|
654
654
|
"signature": "QuOVaQ4E2Sl39TClbhZ7HA9XrYAyRrDL44HY3RTE7aWLue0hV2cxaBt40ALGmHS++631QGFDlZTLZI77Tr6nAA==",
|
|
655
|
-
"signed_at": "2026-05-
|
|
655
|
+
"signed_at": "2026-05-27T22:43:04.131Z"
|
|
656
656
|
},
|
|
657
657
|
{
|
|
658
658
|
"name": "security-maturity-tiers",
|
|
@@ -689,7 +689,7 @@
|
|
|
689
689
|
],
|
|
690
690
|
"last_threat_review": "2026-05-01",
|
|
691
691
|
"signature": "8Px1s2lDj10/Q6erwEQlXgUHM1+OTruUR8qAHPX7Oo3k/l69N6P9sm0PsafS9wDFtj9l5C/OiLiFgzMlMt6vBw==",
|
|
692
|
-
"signed_at": "2026-05-
|
|
692
|
+
"signed_at": "2026-05-27T22:43:04.132Z",
|
|
693
693
|
"cwe_refs": [
|
|
694
694
|
"CWE-1188"
|
|
695
695
|
]
|
|
@@ -724,7 +724,7 @@
|
|
|
724
724
|
"framework_gaps": [],
|
|
725
725
|
"last_threat_review": "2026-05-11",
|
|
726
726
|
"signature": "urRcataVWg6/utyEkSiOWoNxTL8sABRjPR7ShyDfZGnAozFph/yDktSoaPVxQDXwu9EfJE+qhUW5OYR/yJECBQ==",
|
|
727
|
-
"signed_at": "2026-05-
|
|
727
|
+
"signed_at": "2026-05-27T22:43:04.132Z"
|
|
728
728
|
},
|
|
729
729
|
{
|
|
730
730
|
"name": "attack-surface-pentest",
|
|
@@ -796,7 +796,7 @@
|
|
|
796
796
|
"Pwn2Own Berlin 2026 (disclosed 2026-05-14, embargo ends 2026-08-12) — Microsoft Edge 4-bug sandbox escape by Orange Tsai (DEVCORE); forward-watch only (browser sandbox, out of current playbook scope); track Microsoft Edge security advisory and KEV add"
|
|
797
797
|
],
|
|
798
798
|
"signature": "C7lv65/Ecm8JJgSKxrX5lxx0YFzKWtrIQSKp+vy50I5e8945s1JmifGUUrnQwRQhq/Pkv7EmfiH5XSO8h75bDg==",
|
|
799
|
-
"signed_at": "2026-05-
|
|
799
|
+
"signed_at": "2026-05-27T22:43:04.132Z"
|
|
800
800
|
},
|
|
801
801
|
{
|
|
802
802
|
"name": "fuzz-testing-strategy",
|
|
@@ -856,7 +856,7 @@
|
|
|
856
856
|
"OSS-Fuzz-Gen / AI-assisted harness generation becoming the default expectation for OSS maintainers"
|
|
857
857
|
],
|
|
858
858
|
"signature": "Z7ypCUnXx8JpLtgxxB6RHNi39w74AmrGY1N4ofAGCXhkuM2EaFVm1AU0dvl9UQ1bVLfHKEDGqMO/TwlIY7RABg==",
|
|
859
|
-
"signed_at": "2026-05-
|
|
859
|
+
"signed_at": "2026-05-27T22:43:04.133Z"
|
|
860
860
|
},
|
|
861
861
|
{
|
|
862
862
|
"name": "dlp-gap-analysis",
|
|
@@ -931,7 +931,7 @@
|
|
|
931
931
|
"Quebec Law 25, India DPDPA, KSA PDPL enforcement actions naming AI-tool prompt data as in-scope personal information"
|
|
932
932
|
],
|
|
933
933
|
"signature": "IgEnpHOhCftAyfUNdKsjbrd169T9pJkk/rRM2ZEna+H18y7p5x48+1kME2sJMZjJuyAdQFBJi8PJXZFwLGI+DQ==",
|
|
934
|
-
"signed_at": "2026-05-
|
|
934
|
+
"signed_at": "2026-05-27T22:43:04.133Z"
|
|
935
935
|
},
|
|
936
936
|
{
|
|
937
937
|
"name": "supply-chain-integrity",
|
|
@@ -1010,7 +1010,7 @@
|
|
|
1010
1010
|
"Pwn2Own Berlin 2026 (disclosed 2026-05-14, embargo ends 2026-08-12) — NVIDIA Megatron Bridge path traversal by haehae; AI training-stack file-system trust boundary; track patch and SBOM-attestation impact"
|
|
1011
1011
|
],
|
|
1012
1012
|
"signature": "pcLrM98A3vUSZRjwNAk0aZ9umvOwB41XCLLsCOy/IebB2F/06oIrGUKkMHtHwm4pTVPShMMcKdZQQ3jz30FnCg==",
|
|
1013
|
-
"signed_at": "2026-05-
|
|
1013
|
+
"signed_at": "2026-05-27T22:43:04.133Z"
|
|
1014
1014
|
},
|
|
1015
1015
|
{
|
|
1016
1016
|
"name": "defensive-countermeasure-mapping",
|
|
@@ -1067,7 +1067,7 @@
|
|
|
1067
1067
|
],
|
|
1068
1068
|
"last_threat_review": "2026-05-11",
|
|
1069
1069
|
"signature": "G5q5elh7Q7eu2xcwTVQJGDTGfvZR0OGQaLSLJPb2wjzCHFF8PWuZfCHZdjjqisiRzRWPyLlzgfHeMJqOdy7cBw==",
|
|
1070
|
-
"signed_at": "2026-05-
|
|
1070
|
+
"signed_at": "2026-05-27T22:43:04.133Z"
|
|
1071
1071
|
},
|
|
1072
1072
|
{
|
|
1073
1073
|
"name": "identity-assurance",
|
|
@@ -1134,7 +1134,7 @@
|
|
|
1134
1134
|
"d3fend_refs": [],
|
|
1135
1135
|
"last_threat_review": "2026-05-11",
|
|
1136
1136
|
"signature": "Wv5hGMeHjlaQK1zwicVCA7AvdKgJBgvcjdpGM9Ywahh9tagAKhbkOjybowDQZzu7OZ3bDkbh6pBYc1Sdwr6NAA==",
|
|
1137
|
-
"signed_at": "2026-05-
|
|
1137
|
+
"signed_at": "2026-05-27T22:43:04.134Z"
|
|
1138
1138
|
},
|
|
1139
1139
|
{
|
|
1140
1140
|
"name": "ot-ics-security",
|
|
@@ -1190,7 +1190,7 @@
|
|
|
1190
1190
|
"d3fend_refs": [],
|
|
1191
1191
|
"last_threat_review": "2026-05-11",
|
|
1192
1192
|
"signature": "8t5qKHd3yWi57dvG36YQkLN/X9bQWqtEiYjay4IfSmqhJpM/xXPaQVKNGz3wscrO8OLKUZ0OaX7Mj5kzpgBKBQ==",
|
|
1193
|
-
"signed_at": "2026-05-
|
|
1193
|
+
"signed_at": "2026-05-27T22:43:04.134Z"
|
|
1194
1194
|
},
|
|
1195
1195
|
{
|
|
1196
1196
|
"name": "coordinated-vuln-disclosure",
|
|
@@ -1242,7 +1242,7 @@
|
|
|
1242
1242
|
"NYDFS 23 NYCRR 500 amendments potentially adding explicit CVD program requirements"
|
|
1243
1243
|
],
|
|
1244
1244
|
"signature": "GDGt4UPqBa04PjlpSmpyihGzd3OgfBN7jaAK5tfwp+LRSs3ygKOdbeivUCCHNagTY1hE6hG2Ou40ADfBFuXeAg==",
|
|
1245
|
-
"signed_at": "2026-05-
|
|
1245
|
+
"signed_at": "2026-05-27T22:43:04.135Z"
|
|
1246
1246
|
},
|
|
1247
1247
|
{
|
|
1248
1248
|
"name": "threat-modeling-methodology",
|
|
@@ -1292,7 +1292,7 @@
|
|
|
1292
1292
|
"PASTA v2 updates incorporating AI/ML application threats"
|
|
1293
1293
|
],
|
|
1294
1294
|
"signature": "rFBpOQEJUPpl+v88Lw/WqVJRhTl80vy0VbPAbzQj3Q0suJRRrJg368I9uKu5LXIBKFDvKxnGIcIzbGg9NUtaCA==",
|
|
1295
|
-
"signed_at": "2026-05-
|
|
1295
|
+
"signed_at": "2026-05-27T22:43:04.135Z"
|
|
1296
1296
|
},
|
|
1297
1297
|
{
|
|
1298
1298
|
"name": "webapp-security",
|
|
@@ -1366,7 +1366,7 @@
|
|
|
1366
1366
|
"d3fend_refs": [],
|
|
1367
1367
|
"last_threat_review": "2026-05-11",
|
|
1368
1368
|
"signature": "ux85YI4t2mVHOyt744Yin1HHy+z11JIFygjKfFfQOBBl5QVV3A267jeIy7utix85irMcpZm/T3yx/ooqiK2tBA==",
|
|
1369
|
-
"signed_at": "2026-05-
|
|
1369
|
+
"signed_at": "2026-05-27T22:43:04.135Z",
|
|
1370
1370
|
"forward_watch": [
|
|
1371
1371
|
"NGINX Rift CVE-2026-42945 (disclosed 2026-05-13, source depthfirst) — KEV-watch predicted CISA KEV listing by 2026-05-29; AI-assisted discovery angle; track for active-exploitation confirmation and patch advisory affecting front-door web app deployments"
|
|
1372
1372
|
]
|
|
@@ -1419,7 +1419,7 @@
|
|
|
1419
1419
|
"d3fend_refs": [],
|
|
1420
1420
|
"last_threat_review": "2026-05-15",
|
|
1421
1421
|
"signature": "IIXnkZ5ZNqFwOto5KfytADTLLZLoyXNZACD1ORZ40P1HUAQxe6u2uyXFzzsfuob4Uy06jNkRGr2FFgCphUH1Cw==",
|
|
1422
|
-
"signed_at": "2026-05-
|
|
1422
|
+
"signed_at": "2026-05-27T22:43:04.136Z"
|
|
1423
1423
|
},
|
|
1424
1424
|
{
|
|
1425
1425
|
"name": "sector-healthcare",
|
|
@@ -1479,7 +1479,7 @@
|
|
|
1479
1479
|
"d3fend_refs": [],
|
|
1480
1480
|
"last_threat_review": "2026-05-11",
|
|
1481
1481
|
"signature": "AhF9KF8ZBlDteciV+F8IBSmFVYCvQOn44GmD4rZjgLoPxfIv/QE1/vSkK32zyqDKtHWkLSXExbkkPkxA/V6dDw==",
|
|
1482
|
-
"signed_at": "2026-05-
|
|
1482
|
+
"signed_at": "2026-05-27T22:43:04.136Z"
|
|
1483
1483
|
},
|
|
1484
1484
|
{
|
|
1485
1485
|
"name": "sector-financial",
|
|
@@ -1560,7 +1560,7 @@
|
|
|
1560
1560
|
"TIBER-EU framework v2.0 alignment with DORA TLPT RTS (JC 2024/40); cross-recognition with CBEST and iCAST"
|
|
1561
1561
|
],
|
|
1562
1562
|
"signature": "HQgZvb4ReziEz5rNFr8i/O8/rJEZR+iHRROT7m/D2QUqhrcNISPkYXENsUZlG8xapzy/Ik92ehkseyj4hdmhCQ==",
|
|
1563
|
-
"signed_at": "2026-05-
|
|
1563
|
+
"signed_at": "2026-05-27T22:43:04.137Z"
|
|
1564
1564
|
},
|
|
1565
1565
|
{
|
|
1566
1566
|
"name": "sector-federal-government",
|
|
@@ -1629,7 +1629,7 @@
|
|
|
1629
1629
|
"Australia PSPF 2024 revision and ISM quarterly updates — track for Essential Eight Maturity Level requirements for federal entities"
|
|
1630
1630
|
],
|
|
1631
1631
|
"signature": "linxmsXZiOYtcs71sSWgGCrvb8xQfmxmtTY5PRvZJ0/8FgJulo0tQtejzexYG775s7XhjAmGsDP238BQTQ8ADA==",
|
|
1632
|
-
"signed_at": "2026-05-
|
|
1632
|
+
"signed_at": "2026-05-27T22:43:04.137Z"
|
|
1633
1633
|
},
|
|
1634
1634
|
{
|
|
1635
1635
|
"name": "sector-energy",
|
|
@@ -1694,7 +1694,7 @@
|
|
|
1694
1694
|
"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"
|
|
1695
1695
|
],
|
|
1696
1696
|
"signature": "JjBfc0ovta560Clk0x3QGRM5osFJDwcvpy3rT7QEGdCIL827jzE8QCow1C8deXq+4JhY2sA/d7/8IsxikdlkCg==",
|
|
1697
|
-
"signed_at": "2026-05-
|
|
1697
|
+
"signed_at": "2026-05-27T22:43:04.137Z"
|
|
1698
1698
|
},
|
|
1699
1699
|
{
|
|
1700
1700
|
"name": "sector-telecom",
|
|
@@ -1780,7 +1780,7 @@
|
|
|
1780
1780
|
"O-RAN SFG / WG11 security specifications"
|
|
1781
1781
|
],
|
|
1782
1782
|
"signature": "JWVxKFoKrbX4d+Tko1d4OBdwyg25MfFFKn4CT6E/CzH+YwnU3T6Y76uBQIKg3+gIGTvPduqyvQwQQ5FxKDuPBw==",
|
|
1783
|
-
"signed_at": "2026-05-
|
|
1783
|
+
"signed_at": "2026-05-27T22:43:04.138Z"
|
|
1784
1784
|
},
|
|
1785
1785
|
{
|
|
1786
1786
|
"name": "api-security",
|
|
@@ -1849,7 +1849,7 @@
|
|
|
1849
1849
|
"d3fend_refs": [],
|
|
1850
1850
|
"last_threat_review": "2026-05-18",
|
|
1851
1851
|
"signature": "BmCRCestWqr55+fCynEhtAl5NWLT+xLTkpwS0Icp3SaoZOw/ce3Y6TtqjHRSKn4CBJq7YDiLRWxmhO3MStvOAA==",
|
|
1852
|
-
"signed_at": "2026-05-
|
|
1852
|
+
"signed_at": "2026-05-27T22:43:04.138Z",
|
|
1853
1853
|
"forward_watch": [
|
|
1854
1854
|
"NGINX Rift CVE-2026-42945 (disclosed 2026-05-13, source depthfirst) — KEV-watch predicted CISA KEV listing by 2026-05-29; track for active-exploitation confirmation and patch advisory affecting API gateway / reverse-proxy deployments",
|
|
1855
1855
|
"Pwn2Own Berlin 2026 (disclosed 2026-05-14, embargo ends 2026-08-12) — LiteLLM 3-bug SSRF + Code Injection chain by k3vg3n; LLM-proxy API surface; track upstream patch and CVE assignments",
|
|
@@ -1935,7 +1935,7 @@
|
|
|
1935
1935
|
"CISA KEV additions for cloud-control-plane CVEs (IMDSv1 abuses, federation token mishandling, cross-tenant boundary failures); CISA Cybersecurity Advisories for cross-cloud advisories"
|
|
1936
1936
|
],
|
|
1937
1937
|
"signature": "/DV3pmZwrRySrk1OCbyI+0BQESacjupJfUX3eC2NGtXuYOBro0vndIP+z27heFxumnjU3a9sfla7/U9X+pqnDw==",
|
|
1938
|
-
"signed_at": "2026-05-
|
|
1938
|
+
"signed_at": "2026-05-27T22:43:04.138Z"
|
|
1939
1939
|
},
|
|
1940
1940
|
{
|
|
1941
1941
|
"name": "container-runtime-security",
|
|
@@ -1997,7 +1997,7 @@
|
|
|
1997
1997
|
"d3fend_refs": [],
|
|
1998
1998
|
"last_threat_review": "2026-05-15",
|
|
1999
1999
|
"signature": "E2UGSf9ATyYgzBr8uM/0ubOUmDqo1jVA7f9mVxv6LHfWGCNuQNXDyuNou9VAmUCeeXEeUYIi3AFjXkJqpOkxDA==",
|
|
2000
|
-
"signed_at": "2026-05-
|
|
2000
|
+
"signed_at": "2026-05-27T22:43:04.139Z",
|
|
2001
2001
|
"forward_watch": [
|
|
2002
2002
|
"Pwn2Own Berlin 2026 (disclosed 2026-05-14, embargo ends 2026-08-12) — NVIDIA Container Toolkit container escape ($50K award) by chompie / IBM X-Force XOR; high-severity container/hypervisor boundary break; track patch and KEV add post-embargo"
|
|
2003
2003
|
]
|
|
@@ -2071,7 +2071,7 @@
|
|
|
2071
2071
|
"MITRE ATLAS v5.6.0 (released May 2026) shipped the AML.T0010 sub-technique expansion this forecast tracked plus new techniques (\"Publish Poisoned AI Agent Tool\", \"Escape to Host\"); inventory now 16 tactics, 84 techniques, 56 sub-techniques. Forward watch: subsequent ATLAS minor and major releases — track next-cadence updates to agentic-AI TTPs and MLOps-pipeline-specific techniques"
|
|
2072
2072
|
],
|
|
2073
2073
|
"signature": "IL+DlRCDJN/p08iiJCFkasKcoyjcB0uWrJ6ORLjQcS1HrUa5Xt62QxVjYPHzaevlm5y36ZdmfESqsZJmzK3lCg==",
|
|
2074
|
-
"signed_at": "2026-05-
|
|
2074
|
+
"signed_at": "2026-05-27T22:43:04.139Z"
|
|
2075
2075
|
},
|
|
2076
2076
|
{
|
|
2077
2077
|
"name": "incident-response-playbook",
|
|
@@ -2133,7 +2133,7 @@
|
|
|
2133
2133
|
"NYDFS 23 NYCRR 500.17 amendments tightening ransom-payment 24h disclosure operationalization"
|
|
2134
2134
|
],
|
|
2135
2135
|
"signature": "MmjLjlmOMLjhJJ4ZfR8MYlHam+ZB+eSqfh6Nv+DecaG4O5zeo9DBP/iL3cbyDVZxmhnhivgJild2ccYeWTeZAg==",
|
|
2136
|
-
"signed_at": "2026-05-
|
|
2136
|
+
"signed_at": "2026-05-27T22:43:04.139Z"
|
|
2137
2137
|
},
|
|
2138
2138
|
{
|
|
2139
2139
|
"name": "ransomware-response",
|
|
@@ -2213,7 +2213,7 @@
|
|
|
2213
2213
|
],
|
|
2214
2214
|
"last_threat_review": "2026-05-22",
|
|
2215
2215
|
"signature": "ssueL03g9fWlhXpTe+IiY5l7RqQkunN4DTN5QETKE+VOX+qggdjAR8PONxk77ol4xWYmHrM/VcH8CNtXUEvgBA==",
|
|
2216
|
-
"signed_at": "2026-05-
|
|
2216
|
+
"signed_at": "2026-05-27T22:43:04.140Z"
|
|
2217
2217
|
},
|
|
2218
2218
|
{
|
|
2219
2219
|
"name": "email-security-anti-phishing",
|
|
@@ -2266,7 +2266,7 @@
|
|
|
2266
2266
|
"d3fend_refs": [],
|
|
2267
2267
|
"last_threat_review": "2026-05-18",
|
|
2268
2268
|
"signature": "rK+WnuS+9tqEABmwc0jO/PEmxcLjG1/tmUb897HsClQeKzf+TQOlwBE+OsbtuKxpjYNwur62Xxs3TxObkwm8Cw==",
|
|
2269
|
-
"signed_at": "2026-05-
|
|
2269
|
+
"signed_at": "2026-05-27T22:43:04.140Z"
|
|
2270
2270
|
},
|
|
2271
2271
|
{
|
|
2272
2272
|
"name": "age-gates-child-safety",
|
|
@@ -2334,7 +2334,7 @@
|
|
|
2334
2334
|
"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"
|
|
2335
2335
|
],
|
|
2336
2336
|
"signature": "Rgho5TOFUL1txOzcVR0kASCNdovSU4yt99JlGilJlJRyg0A+BdeeQYrZrhPF6Vx2reUAVG0BeHfcZtSbi+cwCg==",
|
|
2337
|
-
"signed_at": "2026-05-
|
|
2337
|
+
"signed_at": "2026-05-27T22:43:04.141Z"
|
|
2338
2338
|
},
|
|
2339
2339
|
{
|
|
2340
2340
|
"name": "cloud-iam-incident",
|
|
@@ -2414,7 +2414,7 @@
|
|
|
2414
2414
|
],
|
|
2415
2415
|
"last_threat_review": "2026-05-15",
|
|
2416
2416
|
"signature": "e/kij7GtKaytROyIj7V5RH+FC9WtmVFzrmG2kIlNDNn29ep/CRNlIQKwXLpzo/81AIf634pmdr1qy/+vwIuUDA==",
|
|
2417
|
-
"signed_at": "2026-05-
|
|
2417
|
+
"signed_at": "2026-05-27T22:43:04.141Z",
|
|
2418
2418
|
"forward_watch": [
|
|
2419
2419
|
"AWS IAM Identity Center session-policy refresh and step-up-on-admin enforcement (anticipated 2026-H2 release)",
|
|
2420
2420
|
"GCP Workload Identity Federation principal-set attribute mapping tightening (post-2026 Q3 Federation hardening guide)",
|
|
@@ -2508,7 +2508,7 @@
|
|
|
2508
2508
|
],
|
|
2509
2509
|
"last_threat_review": "2026-05-15",
|
|
2510
2510
|
"signature": "ew9Kglc9fAZzbn0ZIfGP7WSK/j4eV2VhSvpy+s5bEfNEVYIMa2kZjnGBapgUsyGDLes9H9K2ovjQyX17+GKiBw==",
|
|
2511
|
-
"signed_at": "2026-05-
|
|
2511
|
+
"signed_at": "2026-05-27T22:43:04.141Z",
|
|
2512
2512
|
"forward_watch": [
|
|
2513
2513
|
"Entra ID conditional access evolution post-Midnight Blizzard — Microsoft's 2025-2026 commitments on legacy-tenant MFA enforcement and OAuth-app consent gating",
|
|
2514
2514
|
"Okta IPSIE (Interoperability Profile for Secure Identity in the Enterprise) OpenID Foundation working-group output and adoption timeline",
|
|
@@ -2526,6 +2526,6 @@
|
|
|
2526
2526
|
],
|
|
2527
2527
|
"manifest_signature": {
|
|
2528
2528
|
"algorithm": "Ed25519",
|
|
2529
|
-
"signature_base64": "
|
|
2529
|
+
"signature_base64": "VaHQUp8N+B8LWN2cE5Ma/r5QUuCeeXd1qQZRVdxMsZuywrBbCgET5iiP8miDXVAVw6I/bKVxUeBQzPDzbgTCBQ=="
|
|
2530
2530
|
}
|
|
2531
2531
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blamejs/exceptd-skills",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.14",
|
|
4
4
|
"description": "AI security skills grounded in mid-2026 threat reality, not stale framework documentation. 42 skills, 11 catalogs (406 CVEs / 171 CWEs / 805 ATT&CK + ICS / 170 ATLAS / 468 D3FEND / 8888 RFCs), 35 jurisdictions, 10-class catalog gap detector + budget gate, real XML parser + canonical-form diff + content-pattern regression detection, Ed25519-signed.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-security",
|
package/sbom.cdx.json
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"bomFormat": "CycloneDX",
|
|
3
3
|
"specVersion": "1.6",
|
|
4
|
-
"serialNumber": "urn:uuid:
|
|
4
|
+
"serialNumber": "urn:uuid:09ce22ed-cf4c-428a-b4ce-4ed39f08cf46",
|
|
5
5
|
"version": 1,
|
|
6
6
|
"metadata": {
|
|
7
|
-
"timestamp": "
|
|
7
|
+
"timestamp": "2031-03-19T23:38:21.000Z",
|
|
8
8
|
"tools": [
|
|
9
9
|
{
|
|
10
10
|
"vendor": "blamejs",
|
|
11
11
|
"name": "scripts/refresh-sbom.js",
|
|
12
|
-
"version": "0.14.
|
|
12
|
+
"version": "0.14.14"
|
|
13
13
|
}
|
|
14
14
|
],
|
|
15
15
|
"component": {
|
|
16
|
-
"bom-ref": "pkg:npm/@blamejs/exceptd-skills@0.14.
|
|
16
|
+
"bom-ref": "pkg:npm/@blamejs/exceptd-skills@0.14.14",
|
|
17
17
|
"type": "application",
|
|
18
18
|
"name": "@blamejs/exceptd-skills",
|
|
19
|
-
"version": "0.14.
|
|
19
|
+
"version": "0.14.14",
|
|
20
20
|
"description": "AI security skills grounded in mid-2026 threat reality, not stale framework documentation. 42 skills, 11 catalogs (406 CVEs / 171 CWEs / 805 ATT&CK + ICS / 170 ATLAS / 468 D3FEND / 8888 RFCs), 35 jurisdictions, 10-class catalog gap detector + budget gate, real XML parser + canonical-form diff + content-pattern regression detection, Ed25519-signed.",
|
|
21
21
|
"licenses": [
|
|
22
22
|
{
|
|
@@ -25,17 +25,17 @@
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
],
|
|
28
|
-
"purl": "pkg:npm/%40blamejs/exceptd-skills@0.14.
|
|
28
|
+
"purl": "pkg:npm/%40blamejs/exceptd-skills@0.14.14",
|
|
29
29
|
"hashes": [
|
|
30
30
|
{
|
|
31
31
|
"alg": "SHA-256",
|
|
32
|
-
"content": "
|
|
32
|
+
"content": "c662ffd7c9591378303810e656ef236bc95461441b0e100888fe51590f04a81e"
|
|
33
33
|
}
|
|
34
34
|
],
|
|
35
35
|
"externalReferences": [
|
|
36
36
|
{
|
|
37
37
|
"type": "distribution",
|
|
38
|
-
"url": "https://www.npmjs.com/package/@blamejs/exceptd-skills/v/0.14.
|
|
38
|
+
"url": "https://www.npmjs.com/package/@blamejs/exceptd-skills/v/0.14.14"
|
|
39
39
|
},
|
|
40
40
|
{
|
|
41
41
|
"type": "vcs",
|
|
@@ -116,11 +116,11 @@
|
|
|
116
116
|
"hashes": [
|
|
117
117
|
{
|
|
118
118
|
"alg": "SHA-256",
|
|
119
|
-
"content": "
|
|
119
|
+
"content": "35200404f3ec807af037296532675b3b89d4edfde5be7906f8f51de79e64b9c1"
|
|
120
120
|
},
|
|
121
121
|
{
|
|
122
122
|
"alg": "SHA3-512",
|
|
123
|
-
"content": "
|
|
123
|
+
"content": "791909b843163e1295f359e24b1928c2f592f33a99795a1cb0fc73c8282715b78eb4696e30b48a61f63cb5651da945fd11b1707c91a53cea80723fab7553bcec"
|
|
124
124
|
}
|
|
125
125
|
]
|
|
126
126
|
},
|
|
@@ -176,11 +176,11 @@
|
|
|
176
176
|
"hashes": [
|
|
177
177
|
{
|
|
178
178
|
"alg": "SHA-256",
|
|
179
|
-
"content": "
|
|
179
|
+
"content": "7ec3434f017d0f634d64518178cb0fbe051450e180ae91335f951bfc1901d5ff"
|
|
180
180
|
},
|
|
181
181
|
{
|
|
182
182
|
"alg": "SHA3-512",
|
|
183
|
-
"content": "
|
|
183
|
+
"content": "ec79b55f86aa52f266f6bbcf3f62132bd8a55f853fec892158996deaa495f6776d406efd2945910e8ff61b297d0fb4b3713904d97d3080a8b33fb3e462293fc7"
|
|
184
184
|
}
|
|
185
185
|
]
|
|
186
186
|
},
|
|
@@ -281,11 +281,11 @@
|
|
|
281
281
|
"hashes": [
|
|
282
282
|
{
|
|
283
283
|
"alg": "SHA-256",
|
|
284
|
-
"content": "
|
|
284
|
+
"content": "447e6ba93e05120de060cca6636c8419c465034744d93606bbe6c7175be1a844"
|
|
285
285
|
},
|
|
286
286
|
{
|
|
287
287
|
"alg": "SHA3-512",
|
|
288
|
-
"content": "
|
|
288
|
+
"content": "23f28a5eefab97d5ad447033826279f34424a728123a2a2c6e551fea601504f06fd9b2860b718e515a7e31bfa1a9cb349bffb926a91c63682753c0c979826db6"
|
|
289
289
|
}
|
|
290
290
|
]
|
|
291
291
|
},
|
|
@@ -926,11 +926,11 @@
|
|
|
926
926
|
"hashes": [
|
|
927
927
|
{
|
|
928
928
|
"alg": "SHA-256",
|
|
929
|
-
"content": "
|
|
929
|
+
"content": "e8ab11fa2f22bfac303b39cec8226ee61f9eb638a38bb91d38ed31e5f245e036"
|
|
930
930
|
},
|
|
931
931
|
{
|
|
932
932
|
"alg": "SHA3-512",
|
|
933
|
-
"content": "
|
|
933
|
+
"content": "206ccebd277a7da70a8ad26d9bd6fa2ebf39b7deee02f04de70031dce3a36b1ef7f7c3c8c3af080afa86b00e094dc88000ef42b424cb28a0cc52a7f96ba7db36"
|
|
934
934
|
}
|
|
935
935
|
]
|
|
936
936
|
},
|
|
@@ -956,11 +956,11 @@
|
|
|
956
956
|
"hashes": [
|
|
957
957
|
{
|
|
958
958
|
"alg": "SHA-256",
|
|
959
|
-
"content": "
|
|
959
|
+
"content": "bbf451e8828b34b6dfad052a80bb41d6d41c823d0896b7b0af88adc1bc3d0363"
|
|
960
960
|
},
|
|
961
961
|
{
|
|
962
962
|
"alg": "SHA3-512",
|
|
963
|
-
"content": "
|
|
963
|
+
"content": "3cc1ae78ab1fd3995140adf5daad3a276c5c191e37f12183ea4cd1d05faed9b3f52bf7cc028dad36d5bfc347b081ff69f7311b91a70f9f5f235e13297e84846a"
|
|
964
964
|
}
|
|
965
965
|
]
|
|
966
966
|
},
|
|
@@ -1046,11 +1046,11 @@
|
|
|
1046
1046
|
"hashes": [
|
|
1047
1047
|
{
|
|
1048
1048
|
"alg": "SHA-256",
|
|
1049
|
-
"content": "
|
|
1049
|
+
"content": "adacaf29b1359d15de4974508b06f41da7811aa9602fc56b724ddc01b9211938"
|
|
1050
1050
|
},
|
|
1051
1051
|
{
|
|
1052
1052
|
"alg": "SHA3-512",
|
|
1053
|
-
"content": "
|
|
1053
|
+
"content": "c4d98ecbc6bc8a897a37e0ae7d5883c71816b3665e2c20a313955b3e0917f81657851c61790dc793c28bf85937d07be2623311a54cfa1c5823bcdaf9d9221c2f"
|
|
1054
1054
|
}
|
|
1055
1055
|
]
|
|
1056
1056
|
},
|
|
@@ -1316,11 +1316,11 @@
|
|
|
1316
1316
|
"hashes": [
|
|
1317
1317
|
{
|
|
1318
1318
|
"alg": "SHA-256",
|
|
1319
|
-
"content": "
|
|
1319
|
+
"content": "9c32c19d0d16a52b1d2d9e44f5f74f27b0c68f7869373b63d1b29c9ed43c36a8"
|
|
1320
1320
|
},
|
|
1321
1321
|
{
|
|
1322
1322
|
"alg": "SHA3-512",
|
|
1323
|
-
"content": "
|
|
1323
|
+
"content": "d711a870f6b4a91b78efd2d883df0c1df8cd82547aedd3affce2c7ab4a16832c67a6c40b9dc478dc55cd932cde1105de42231f560f7580ba881fecd3f488945c"
|
|
1324
1324
|
}
|
|
1325
1325
|
]
|
|
1326
1326
|
},
|
|
@@ -1751,11 +1751,11 @@
|
|
|
1751
1751
|
"hashes": [
|
|
1752
1752
|
{
|
|
1753
1753
|
"alg": "SHA-256",
|
|
1754
|
-
"content": "
|
|
1754
|
+
"content": "008292f92fc956004d340bf314748a28ab03a6f14c88ddf7f5deabdab8b14b4d"
|
|
1755
1755
|
},
|
|
1756
1756
|
{
|
|
1757
1757
|
"alg": "SHA3-512",
|
|
1758
|
-
"content": "
|
|
1758
|
+
"content": "99f7d34bc0516df27402740184eb99a961e3d8db7929187df1bab453f0a9356029753246d8efcf003bda0ccd57caa29a51c04c8e4fe2f20a6e385f743dc6f9cc"
|
|
1759
1759
|
}
|
|
1760
1760
|
]
|
|
1761
1761
|
},
|