@blamejs/exceptd-skills 0.17.0 → 0.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.18.1 — 2026-06-13
4
+
5
+ A correctness pass across the playbook engine's condition evaluator, the release gates, and the CLI.
6
+
7
+ Conditions that reference a hyphenated signal or indicator id — the catalog's naming convention (`no-security-md`, `kver-in-affected-range`, and many more) — now evaluate correctly. The condition mini-language's token pattern excluded hyphens, so every such escalation criterion, feeds-into chain, and remediation precondition silently evaluated to false; they now fire as authored. `finding.severity >= 'high'` compares by the severity ladder (low < medium < high < critical) instead of lexicographically, so a critical finding chains downstream where it previously did not. An operator-submitted signal can no longer override an engine-computed value (rwep, finding, analyze) in an escalation or feeds-into condition. `contains` is accepted as a synonym for `includes`, and a condition the evaluator cannot parse now surfaces a `condition_unparsed` runtime error instead of silently disabling its rule.
8
+
9
+ `exceptd ci --required ""` / `--scope ""` no longer fall through to cwd auto-detection and emit a green pass for an unrequested playbook set — an empty selector is refused. `exceptd ci --required` with no value gives a clean usage error instead of an internal-error report. `exceptd report --json` (no format) emits JSON instead of failing, and `--json` now produces machine-readable output for the executive, technical, and compliance formats, not only csaf. A `--cwd` passed to a verb that does not consume it (run, ci) is refused rather than silently ignored.
10
+
11
+ The release gates are hardened: the test-count gate no longer counts `test(` occurrences inside block or trailing comments (commenting a test out now registers as a removal); the SBOM currency gate fails when the aggregate bundle digest is absent rather than silently skipping it; the diff-coverage analyzer reads the full `module.exports` object across nested members; and an informational predeploy gate killed by a signal (OOM) is treated as a failure.
12
+
13
+ ## 0.18.0 — 2026-06-13
14
+
15
+ The minimum supported Node.js is raised to 24.16.0 (`engines.node` is now `>=24.16.0`), the current Node 24 LTS patch. This makes the runtime's accumulated security fixes across the 24.x line — V8 and OpenSSL patches — the supported baseline rather than an older floor. CI, the release workflow, the daily-refresh and currency workflows, and the Docker reproduction harness are all pinned to 24.16.0 (the Docker base image is re-pinned by digest), and a `.nvmrc` is added so `nvm use` selects the supported version automatically. Installs on Node 22 will surface an `engines` warning; upgrade to Node 24 LTS.
16
+
3
17
  ## 0.17.0 — 2026-06-13
4
18
 
5
19
  Twenty newly-listed CISA KEV vulnerabilities are now curated into the catalog (439 → 459 CVEs). Each carries verified CVSS and vector, CWE classification, confirmed active-exploitation status, affected versions, framework-control-gap mapping, ATT&CK (and where applicable ATLAS) references, RWEP scoring, behavioral indicators of compromise, and a matching zero-day lesson — every fact sourced from NVD, the CISA KEV catalog, and the vendor advisory.
package/bin/exceptd.js CHANGED
@@ -1316,6 +1316,7 @@ function dispatchPlaybook(cmd, argv) {
1316
1316
  "against", "since", "bundle-epoch", "attestation-root", "format",
1317
1317
  "cwd", // exceptd collect <pb> --cwd <path>
1318
1318
  "limit", // exceptd attest list --limit <n>
1319
+ "required", // exceptd ci --required <pb,pb> — value-less form must refuse, not `true.split(",")`
1319
1320
  ]);
1320
1321
  const verbAllowlist = flagsFor(cmd);
1321
1322
  const allowlistSet = new Set(verbAllowlist);
@@ -1767,6 +1768,10 @@ function dispatchPlaybook(cmd, argv) {
1767
1768
  "max-rwep": ["ci"],
1768
1769
  "diff-from-latest": ["run"],
1769
1770
  "upstream-check": ["run"],
1771
+ // --cwd is only consumed by collect/discover (which scan a directory). On
1772
+ // run/ci/etc. it was silently accepted-and-ignored, so `run secrets --cwd
1773
+ // /target` returned a falsely-clean result for a directory never inspected.
1774
+ "cwd": ["collect", "discover"],
1770
1775
  };
1771
1776
  for (const [flag, relevantVerbs] of Object.entries(SINGLE_VERB_PASSTHROUGH)) {
1772
1777
  // A value-less boolean flag parses as `true`; a value-bearing one as its
@@ -8704,11 +8709,12 @@ function cmdCi(runner, args, runOpts, pretty) {
8704
8709
  );
8705
8710
  }
8706
8711
  ids = positional;
8707
- } else if (args.required) {
8712
+ } else if (args.required !== undefined) {
8713
+ // Gate on PRESENCE, not truthiness: an empty `--required ""` is falsy and
8714
+ // previously fell through to the cwd auto-detect branch, emitting a green
8715
+ // PASS for an unrequested, auto-detected playbook set — a false green.
8708
8716
  // Refuse `--required + --all` / `--required + --scope` as ambiguous
8709
- // (matches the positional-args refusal at top of cmdCi). Pre-fix
8710
- // the runner silently picked --required and dropped --all/--scope,
8711
- // which is operator error — surface it loudly.
8717
+ // (matches the positional-args refusal at top of cmdCi).
8712
8718
  const conflictingFlags = [];
8713
8719
  if (args.all) conflictingFlags.push('--all');
8714
8720
  if (args.scope) conflictingFlags.push('--scope');
@@ -8719,8 +8725,15 @@ function cmdCi(runner, args, runOpts, pretty) {
8719
8725
  pretty,
8720
8726
  );
8721
8727
  }
8722
- const requestedRaw = Array.isArray(args.required) ? args.required.join(",") : args.required;
8728
+ const requestedRaw = Array.isArray(args.required) ? args.required.join(",") : String(args.required);
8723
8729
  const requested = requestedRaw.split(",").map(s => s.trim()).filter(Boolean);
8730
+ if (requested.length === 0) {
8731
+ return emitError(
8732
+ `ci --required: empty playbook list. Pass at least one playbook id (e.g. --required secrets,containers), or use --all / --scope <type>.`,
8733
+ null,
8734
+ pretty,
8735
+ );
8736
+ }
8724
8737
  const all = runner.listPlaybooks();
8725
8738
  const unknown = requested.filter(r => !all.includes(r));
8726
8739
  if (unknown.length > 0) {
@@ -8732,7 +8745,10 @@ function cmdCi(runner, args, runOpts, pretty) {
8732
8745
  ids = runner.listPlaybooks().filter(id =>
8733
8746
  includeJudgementShaped || !POLICY_SKIPPED_PLAYBOOKS.has(id)
8734
8747
  );
8735
- } else if (scope) {
8748
+ } else if (scope !== undefined) {
8749
+ // Presence, not truthiness: an empty `--scope ""` must reach
8750
+ // filterPlaybooksByScope (which rejects it with the accepted-set message),
8751
+ // not silently fall through to the cwd auto-detect branch below.
8736
8752
  const includeJudgementShaped = args["include-judgement-shaped"] === true;
8737
8753
  try { ids = filterPlaybooksByScope(runner, scope, { includeJudgementShaped }); }
8738
8754
  catch (e) { return emitError(`ci: ${e.message}`, { provided_scope: scope }, pretty); }
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "schema_version": "1.1.0",
3
- "generated_at": "2026-06-13T17:35:41.356Z",
3
+ "generated_at": "2026-06-13T20:39:55.957Z",
4
4
  "generator": "scripts/build-indexes.js",
5
5
  "source_count": 64,
6
6
  "source_hashes": {
7
- "manifest.json": "096c0f1edc68448d21c8556dea01e3461d0efa24e0ea9ebe59554a92ca8a3946",
7
+ "manifest.json": "76d31e7050f332fc608e48f3f4c286c8d9dfe309d2fc98ef7d5110e3e4351aac",
8
8
  "README.md": "e7b854e7db9a364a1b368b5084b4f0c2a8282f0459ce39800ac1d1dabdc06074",
9
9
  "data/atlas-ttps.json": "5bc59e23d6c2defa54168de161a0825299b9cc4a49c6b26df2dae70b4f42eedf",
10
10
  "data/attack-techniques.json": "53c6f248760eecb11a0354f74ab467a5814e95075a686b9b3bf18c34e2f7435e",
@@ -1402,7 +1402,7 @@ function analyze(playbookId, directiveId, detectResult, agentSignals = {}, runOp
1402
1402
  findingShape = {};
1403
1403
  }
1404
1404
  for (const ec of an.escalation_criteria || []) {
1405
- if (evalCondition(ec.condition, { rwep: adjustedRwep, blast_radius_score: blastRadiusScore, theater_verdict: theaterVerdict, analyze: result, finding: findingShape, ...agentSignals, ...evalCtxRoot }, playbook)) {
1405
+ if (evalCondition(ec.condition, { ...agentSignals, ...evalCtxRoot, rwep: adjustedRwep, blast_radius_score: blastRadiusScore, theater_verdict: theaterVerdict, analyze: result, finding: findingShape }, playbook)) {
1406
1406
  escalations.push({ condition: ec.condition, action: ec.action, target_playbook: ec.target_playbook || null });
1407
1407
  }
1408
1408
  }
@@ -1920,12 +1920,16 @@ function close(playbookId, directiveId, analyzeResult, validateResult, agentSign
1920
1920
  // feeds_into chaining — full analyze result is exposed so conditions can
1921
1921
  // reference `analyze.compliance_theater_check.verdict` etc.
1922
1922
  const feedsCtx = {
1923
+ // Operator-submitted signals come FIRST so the engine-computed reserved
1924
+ // keys below always win — otherwise a submitted signals.rwep / finding /
1925
+ // analyze would override the engine value the feeds_into condition tests
1926
+ // and could suppress a legitimate downstream chain.
1927
+ ...agentSignals,
1923
1928
  rwep: analyzeResult.rwep?.adjusted,
1924
1929
  theater_score: analyzeResult.compliance_theater_check?.verdict === 'theater' ? 0 : 100,
1925
1930
  analyze: analyzeResult,
1926
1931
  validate: validateResult,
1927
1932
  finding: analyzeFindingShape(analyzeResult),
1928
- ...agentSignals,
1929
1933
  // Surface evalCondition regex failures from the feeds_into chain into
1930
1934
  // the same accumulator. Without this the regex failure happens but
1931
1935
  // analyze.runtime_errors[] never sees it.
@@ -3869,8 +3873,11 @@ function evalCondition(expr, ctx, playbook) {
3869
3873
  const andParts = splitAtTopLevel(expr, 'AND');
3870
3874
  if (andParts.length > 1) return andParts.every(s => evalCondition(s, ctx, playbook));
3871
3875
 
3872
- // "rwep >= 90"
3873
- let m = expr.match(/^(\w+(?:\.\w+)*)\s*(>=|<=|==|=|<|>|!=)\s*(['"]?)([^'"]+)\3$/);
3876
+ // "rwep >= 90". The LHS/RHS path tokens admit hyphens because signal and
3877
+ // indicator IDs are canonically hyphenated across the catalog (e.g.
3878
+ // `no-security-md`, `kver-in-affected-range`); a `\w`-only token silently
3879
+ // failed to match every hyphenated condition and fell through to `false`.
3880
+ let m = expr.match(/^([A-Za-z_][\w-]*(?:\.[A-Za-z_][\w-]*)*)\s*(>=|<=|==|=|<|>|!=)\s*(['"]?)([^'"]+)\3$/);
3874
3881
  if (m) {
3875
3882
  const [, lhs, op, quote, rhsRaw] = m;
3876
3883
  const lv = resolvePath(ctx, lhs);
@@ -3880,32 +3887,42 @@ function evalCondition(expr, ctx, playbook) {
3880
3887
  } else if (rv === 'true') rv = true;
3881
3888
  else if (rv === 'false') rv = false;
3882
3889
  else if (!isNaN(parseFloat(rv)) && /^-?\d+(\.\d+)?$/.test(rv.trim())) rv = parseFloat(rv);
3883
- else if (/^[a-z_][\w.]*$/i.test(rv.trim())) {
3890
+ else if (/^[a-z_][\w.-]*$/i.test(rv.trim())) {
3884
3891
  // Unquoted identifier — treat as a context path. Falls through to the
3885
3892
  // raw string if resolution returns undefined (matches the prior behavior
3886
3893
  // for literals like `theater` that aren't quoted).
3887
3894
  const resolved = resolvePath(ctx, rv.trim());
3888
3895
  if (resolved !== undefined && resolved !== null) rv = resolved;
3889
3896
  }
3897
+ // Severity is an ordinal word ladder (low < medium < high < critical), not
3898
+ // a lexically-ordered string. When both sides are severity words, compare
3899
+ // by rank so `severity >= 'high'` includes 'critical' (raw string `>=`
3900
+ // would exclude it: 'critical' < 'high' lexicographically).
3901
+ const SEV = { low: 0, medium: 1, high: 2, critical: 3 };
3902
+ const lr = SEV[String(lv).toLowerCase()], rr = SEV[String(rv).toLowerCase()];
3903
+ const a = (lr !== undefined && rr !== undefined) ? lr : lv;
3904
+ const b = (lr !== undefined && rr !== undefined) ? rr : rv;
3890
3905
  switch (op) {
3891
3906
  case '==': case '=': return lv == rv;
3892
3907
  case '!=': return lv != rv;
3893
- case '>=': return lv >= rv;
3894
- case '<=': return lv <= rv;
3895
- case '>': return lv > rv;
3896
- case '<': return lv < rv;
3908
+ case '>=': return a >= b;
3909
+ case '<=': return a <= b;
3910
+ case '>': return a > b;
3911
+ case '<': return a < b;
3897
3912
  }
3898
3913
  }
3899
3914
 
3900
- // "scope.targets includes named_remote"
3901
- m = expr.match(/^(\w+(?:\.\w+)*)\s+includes\s+(\w+)$/);
3915
+ // "scope.targets includes named_remote" — `contains` is accepted as a
3916
+ // synonym for `includes` (the catalog uses both); hyphenated paths + members
3917
+ // are admitted for the same reason as the comparison branch above.
3918
+ m = expr.match(/^([A-Za-z_][\w-]*(?:\.[A-Za-z_][\w-]*)*)\s+(?:includes|contains)\s+([\w-]+)$/);
3902
3919
  if (m) {
3903
3920
  const arr = resolvePath(ctx, m[1]);
3904
3921
  return Array.isArray(arr) && arr.includes(m[2]);
3905
3922
  }
3906
3923
 
3907
3924
  // "matched_cve.vector matches /regex/"
3908
- m = expr.match(/^(\w+(?:\.\w+)*)\s+matches\s+\/(.+)\/$/);
3925
+ m = expr.match(/^([A-Za-z_][\w-]*(?:\.[A-Za-z_][\w-]*)*)\s+matches\s+\/(.+)\/$/);
3909
3926
  if (m) {
3910
3927
  const val = resolvePath(ctx, m[1]);
3911
3928
  if (typeof val !== 'string') return false;
@@ -3939,7 +3956,21 @@ function evalCondition(expr, ctx, playbook) {
3939
3956
  }
3940
3957
  }
3941
3958
 
3959
+ // A condition the mini-language can't parse is a DEAD condition — it returns
3960
+ // false for every input, silently disabling whatever escalation / feeds_into
3961
+ // chain / precondition it gates. Surface it as a runtime_error so a dead
3962
+ // condition is observable instead of an invisible `false` (this is how an
3963
+ // authoring typo or an unimplemented operator gets caught at runtime).
3942
3964
  if (process.env.EXCEPTD_DEBUG) console.warn(`[runner] unknown condition: ${expr}`);
3965
+ {
3966
+ const target = (ctx && Array.isArray(ctx._runErrors)) ? ctx._runErrors
3967
+ : (playbook && Array.isArray(playbook._runErrors)) ? playbook._runErrors
3968
+ : null;
3969
+ if (target) {
3970
+ pushRunError(target, { kind: 'condition_unparsed', condition: String(expr).slice(0, 200) },
3971
+ { dedupeKey: x => x.condition || '' });
3972
+ }
3973
+ }
3943
3974
  return false;
3944
3975
  }
3945
3976
 
@@ -4129,7 +4160,9 @@ function computeClockStart(eventName, agentSignals, runOpts = {}, engineClassifi
4129
4160
 
4130
4161
  function expressionKey(expr) {
4131
4162
  // For agentSignals precondition lookups — strip operators/values to leave key.
4132
- const m = expr.match(/^(\w+(?:\.\w+)*)/);
4163
+ // Admits hyphens so a hyphenated precondition id (the catalog norm) resolves
4164
+ // to the full key instead of being truncated at the first hyphen.
4165
+ const m = expr.match(/^([A-Za-z_][\w-]*(?:\.[A-Za-z_][\w-]*)*)/);
4133
4166
  return m ? m[1] : expr;
4134
4167
  }
4135
4168
 
package/manifest.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "exceptd-security",
3
- "version": "0.17.0",
3
+ "version": "0.18.1",
4
4
  "description": "AI security skills grounded in mid-2026 threat reality, not stale framework documentation",
5
5
  "homepage": "https://exceptd.com",
6
6
  "license": "Apache-2.0",
@@ -53,7 +53,7 @@
53
53
  ],
54
54
  "last_threat_review": "2026-05-15",
55
55
  "signature": "0H+JfyUVmo/pVFEi5rLENATHjlukPVUqnOWmNPEH77wm8svKGK0aNJ46k6QU5GdHb8c9X9pVJKiuhON6AxDjDw==",
56
- "signed_at": "2026-06-13T17:11:21.932Z",
56
+ "signed_at": "2026-06-13T20:32:56.044Z",
57
57
  "cwe_refs": [
58
58
  "CWE-125",
59
59
  "CWE-362",
@@ -123,7 +123,7 @@
123
123
  ],
124
124
  "last_threat_review": "2026-06-10",
125
125
  "signature": "WfqxU2x/feWgatYrCeO9PD3dIY0FAfs4nkVdQRac4fg5BDQPG/vhzhvYVN6Mg11oITACghbPOtZXvS37sACsCA==",
126
- "signed_at": "2026-06-13T17:11:21.933Z",
126
+ "signed_at": "2026-06-13T20:32:56.046Z",
127
127
  "cwe_refs": [
128
128
  "CWE-1039",
129
129
  "CWE-1426",
@@ -196,7 +196,7 @@
196
196
  ],
197
197
  "last_threat_review": "2026-06-10",
198
198
  "signature": "9bNOW4MzmUlFgkXH5tIZkq8YpzhMM+r8YVcNEM39kdEZwfRNG4Bj+Dgr7v4Qgp9D4q7hGNFpK0ywX+dPd3VWAg==",
199
- "signed_at": "2026-06-13T17:11:21.934Z",
199
+ "signed_at": "2026-06-13T20:32:56.047Z",
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-06-10",
250
250
  "signature": "5A08+nAu5jwNQ1+vGbFgFAJA/kYD3oiaevhCxT4/2FeW++F070Pm4MD1+oXdv8R2DbtYr+7yXnCGM7UbUPR2AA==",
251
- "signed_at": "2026-06-13T17:11:21.934Z"
251
+ "signed_at": "2026-06-13T20:32:56.047Z"
252
252
  },
253
253
  {
254
254
  "name": "compliance-theater",
@@ -279,7 +279,7 @@
279
279
  ],
280
280
  "last_threat_review": "2026-06-10",
281
281
  "signature": "RCgbI0sMNTKAscXUtrVU25u7OuE64uQvO5JKsB8vIJFWguvT6nZyD7euKuIxIzYhLTyHBiD7Rhp70btlK+JkCg==",
282
- "signed_at": "2026-06-13T17:11:21.935Z"
282
+ "signed_at": "2026-06-13T20:32:56.048Z"
283
283
  },
284
284
  {
285
285
  "name": "exploit-scoring",
@@ -308,7 +308,7 @@
308
308
  ],
309
309
  "last_threat_review": "2026-06-10",
310
310
  "signature": "JJrs+B22/hYxvdYs+abmoire5PbQXPb2uVYyjInZ3UbT8gEjs/5SyfGKx7z86jbtvB3ItApQVAx1AuseIgdyAQ==",
311
- "signed_at": "2026-06-13T17:11:21.935Z"
311
+ "signed_at": "2026-06-13T20:32:56.048Z"
312
312
  },
313
313
  {
314
314
  "name": "rag-pipeline-security",
@@ -345,7 +345,7 @@
345
345
  ],
346
346
  "last_threat_review": "2026-06-10",
347
347
  "signature": "Z6WbTqG98bQVRd6+kXDb6xIEe0ZryAeGxVSuKKxdK1PoZqew7NJcMsa6sJVSq+0r7KMsFwfTEdKorp6UDdhpCA==",
348
- "signed_at": "2026-06-13T17:11:21.936Z",
348
+ "signed_at": "2026-06-13T20:32:56.049Z",
349
349
  "cwe_refs": [
350
350
  "CWE-1395",
351
351
  "CWE-1426"
@@ -405,7 +405,7 @@
405
405
  ],
406
406
  "last_threat_review": "2026-06-10",
407
407
  "signature": "DLKcrgon+MZrqmivlFLBH4vlzZbJSW1531RsYDm9/KdW2CgcUUHwNMSzAfU1mAsFwFkAHjy/a8e1FbS9msTvDA==",
408
- "signed_at": "2026-06-13T17:11:21.936Z",
408
+ "signed_at": "2026-06-13T20:32:56.049Z",
409
409
  "cwe_refs": [
410
410
  "CWE-918"
411
411
  ],
@@ -443,7 +443,7 @@
443
443
  "framework_gaps": [],
444
444
  "last_threat_review": "2026-06-10",
445
445
  "signature": "eWCOD+mR1c6B8vKDBot8y8UkQrKPCIEBHU3JRQET9xZnYGx8hrOyeRUElcKqCO5aG/2LISs9Atg1H+ehWDNbCw==",
446
- "signed_at": "2026-06-13T17:11:21.936Z",
446
+ "signed_at": "2026-06-13T20:32:56.049Z",
447
447
  "cwe_refs": [
448
448
  "CWE-1188"
449
449
  ],
@@ -477,7 +477,7 @@
477
477
  "framework_gaps": [],
478
478
  "last_threat_review": "2026-06-10",
479
479
  "signature": "kZca725/+BFr1mpxL29+cGzjqsiYhChWScf7VjU6vCTBZrmCps6W9gYYjf4lVORXaM4DAg3UVBOXyhpTst0rAw==",
480
- "signed_at": "2026-06-13T17:11:21.937Z",
480
+ "signed_at": "2026-06-13T20:32:56.050Z",
481
481
  "forward_watch": [
482
482
  "New AI attack classes as ATLAS v6 publishes",
483
483
  "Post-quantum adversary capability timeline",
@@ -516,7 +516,7 @@
516
516
  "framework_gaps": [],
517
517
  "last_threat_review": "2026-05-01",
518
518
  "signature": "b5miTiY0cnxETd2btxorfZBdJKt/fLnQx20sGYUb9zEqGqtm0LMLpghkW68j4/9k48KNyuGMtNWiKTSnodUGBw==",
519
- "signed_at": "2026-06-13T17:11:21.937Z"
519
+ "signed_at": "2026-06-13T20:32:56.050Z"
520
520
  },
521
521
  {
522
522
  "name": "zeroday-gap-learn",
@@ -543,7 +543,7 @@
543
543
  "framework_gaps": [],
544
544
  "last_threat_review": "2026-06-10",
545
545
  "signature": "NVBY4I5a+v2/nUeCYcy3LLVKk3Tg9gmz+sCKyvnf8t2fXMNSIGtoy+P8K4Gfos1rGzRRMaU01V7L4kh/IQ8UBQ==",
546
- "signed_at": "2026-06-13T17:11:21.938Z",
546
+ "signed_at": "2026-06-13T20:32:56.051Z",
547
547
  "forward_watch": [
548
548
  "New CISA KEV entries",
549
549
  "New ATLAS TTP additions in each ATLAS release",
@@ -607,7 +607,7 @@
607
607
  ],
608
608
  "last_threat_review": "2026-06-10",
609
609
  "signature": "U54OxIjPT+W9yafjpeFgY7klXMltLYBuSCtu/AAlXxOIPehJrbf/vbGgnBn58nqOd7RjoKjmKz9WQs4DMqWcDw==",
610
- "signed_at": "2026-06-13T17:11:21.938Z",
610
+ "signed_at": "2026-06-13T20:32:56.051Z",
611
611
  "cwe_refs": [
612
612
  "CWE-327"
613
613
  ],
@@ -655,7 +655,7 @@
655
655
  ],
656
656
  "last_threat_review": "2026-06-10",
657
657
  "signature": "QsJMwvnUJXHwoBPJ4BiUx3FO9giXfos/sMbx5tyO83AJ2JxdVskeC6g/WDfO+rVkWNXRgWi4h1eSYz6T8/tpAg==",
658
- "signed_at": "2026-06-13T17:11:21.939Z"
658
+ "signed_at": "2026-06-13T20:32:56.051Z"
659
659
  },
660
660
  {
661
661
  "name": "security-maturity-tiers",
@@ -692,7 +692,7 @@
692
692
  ],
693
693
  "last_threat_review": "2026-05-01",
694
694
  "signature": "8KbLVVPI1QgYa+HAnEYvKf1cXnxQ3zsncqTGrAOrBP4rwUeZsxJm8KyFCwI0XytnZG35PZ830BFlG/okPW3sBQ==",
695
- "signed_at": "2026-06-13T17:11:21.939Z",
695
+ "signed_at": "2026-06-13T20:32:56.052Z",
696
696
  "cwe_refs": [
697
697
  "CWE-1188"
698
698
  ]
@@ -727,7 +727,7 @@
727
727
  "framework_gaps": [],
728
728
  "last_threat_review": "2026-05-11",
729
729
  "signature": "iJWevUBurLvt2v8X+Ch2eHmZkPWpKeAtIpxTIP4MwbUHyco3igDeBywJCyaR2vURYRx8LkzzIMM8DxQM4LAXBQ==",
730
- "signed_at": "2026-06-13T17:11:21.939Z"
730
+ "signed_at": "2026-06-13T20:32:56.052Z"
731
731
  },
732
732
  {
733
733
  "name": "attack-surface-pentest",
@@ -799,7 +799,7 @@
799
799
  "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"
800
800
  ],
801
801
  "signature": "ZnFi+Rk4CWrjFEVU4hC6Q66UbDmndNL/ICLWQNA1TQYFGSTnng+nboM4tfi6h0yMbbDj7dOXYqqeHNK+M5ZiBA==",
802
- "signed_at": "2026-06-13T17:11:21.940Z"
802
+ "signed_at": "2026-06-13T20:32:56.052Z"
803
803
  },
804
804
  {
805
805
  "name": "fuzz-testing-strategy",
@@ -859,7 +859,7 @@
859
859
  "OSS-Fuzz-Gen / AI-assisted harness generation becoming the default expectation for OSS maintainers"
860
860
  ],
861
861
  "signature": "45ICz7vcnmHYE1+RIDv8wQjScKj6p8hqJQ5m6I0tDdKVnGdMXCYIgZnYqFJgHBDXYAXlGx7D7UxhFz6WOCDqAQ==",
862
- "signed_at": "2026-06-13T17:11:21.940Z"
862
+ "signed_at": "2026-06-13T20:32:56.053Z"
863
863
  },
864
864
  {
865
865
  "name": "dlp-gap-analysis",
@@ -934,7 +934,7 @@
934
934
  "Quebec Law 25, India DPDPA, KSA PDPL enforcement actions naming AI-tool prompt data as in-scope personal information"
935
935
  ],
936
936
  "signature": "k6A7QcFEzgGXYWftt/elhQe7kU4F67c0ol3a8DZfj1XW4UrH99SLhPHr6ih3R3EYCl/Ey/zXt/x3a9fDT3RmAw==",
937
- "signed_at": "2026-06-13T17:11:21.940Z"
937
+ "signed_at": "2026-06-13T20:32:56.053Z"
938
938
  },
939
939
  {
940
940
  "name": "supply-chain-integrity",
@@ -1013,7 +1013,7 @@
1013
1013
  "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"
1014
1014
  ],
1015
1015
  "signature": "5shKqNcLC8Obt0EdvKnck8OwWIuRMgZuuZd12pvWqJIltsYMyTrw2M+lgbQfqg3x2KO6qsJljGAhTew5jM58DQ==",
1016
- "signed_at": "2026-06-13T17:11:21.941Z"
1016
+ "signed_at": "2026-06-13T20:32:56.054Z"
1017
1017
  },
1018
1018
  {
1019
1019
  "name": "defensive-countermeasure-mapping",
@@ -1070,7 +1070,7 @@
1070
1070
  ],
1071
1071
  "last_threat_review": "2026-06-10",
1072
1072
  "signature": "lfa5Wgep26mK3o2ZSe5qN2vVxg+xRRURRpmnqP4JdQvvecW3T2+PxHqjT48DegXJHRXJ6DPzyIElFc7no86TAg==",
1073
- "signed_at": "2026-06-13T17:11:21.941Z"
1073
+ "signed_at": "2026-06-13T20:32:56.054Z"
1074
1074
  },
1075
1075
  {
1076
1076
  "name": "identity-assurance",
@@ -1137,7 +1137,7 @@
1137
1137
  "d3fend_refs": [],
1138
1138
  "last_threat_review": "2026-05-11",
1139
1139
  "signature": "UV3458QXSkEpenzrOmdlTTfPHUD4hNyKMDHoeZDq/kiFb4mAG0ghQGTTgI9Ru8cJbSmYM1++m9N5TFIJ6JJPBg==",
1140
- "signed_at": "2026-06-13T17:11:21.942Z"
1140
+ "signed_at": "2026-06-13T20:32:56.054Z"
1141
1141
  },
1142
1142
  {
1143
1143
  "name": "ot-ics-security",
@@ -1193,7 +1193,7 @@
1193
1193
  "d3fend_refs": [],
1194
1194
  "last_threat_review": "2026-06-10",
1195
1195
  "signature": "afJ00EDXgARQJrbRqN6xe1+E/kbHF7O2fxQFVvrLLXdrKfK86RK/oJU1AwpxX6mclX9f+5ivy6FvFIuRMA4dBQ==",
1196
- "signed_at": "2026-06-13T17:11:21.942Z"
1196
+ "signed_at": "2026-06-13T20:32:56.055Z"
1197
1197
  },
1198
1198
  {
1199
1199
  "name": "coordinated-vuln-disclosure",
@@ -1245,7 +1245,7 @@
1245
1245
  "NYDFS 23 NYCRR 500 amendments potentially adding explicit CVD program requirements"
1246
1246
  ],
1247
1247
  "signature": "VzivMW82mRrG9+07ZX8sj4j9O9YjE/m07m1UHgSwEgex+FLaVPvIj3y1Vk6qeFOvDURmtVaztWlpSlk7iSn0DA==",
1248
- "signed_at": "2026-06-13T17:11:21.943Z"
1248
+ "signed_at": "2026-06-13T20:32:56.055Z"
1249
1249
  },
1250
1250
  {
1251
1251
  "name": "threat-modeling-methodology",
@@ -1295,7 +1295,7 @@
1295
1295
  "PASTA v2 updates incorporating AI/ML application threats"
1296
1296
  ],
1297
1297
  "signature": "Fk5LzzeS/bH5SQJNySgZqSdeQf1udwdRs3FVLwig5kuqyyKkuIJcF4HW+xHF/1e66HR+gjgRUoqWZGsv58lLAg==",
1298
- "signed_at": "2026-06-13T17:11:21.943Z"
1298
+ "signed_at": "2026-06-13T20:32:56.055Z"
1299
1299
  },
1300
1300
  {
1301
1301
  "name": "webapp-security",
@@ -1369,7 +1369,7 @@
1369
1369
  "d3fend_refs": [],
1370
1370
  "last_threat_review": "2026-06-10",
1371
1371
  "signature": "1skHJsPPOIXLQdU5MGHirP/Q07aCYTirtDmfWDO7ZNuto58NlCttW5ZjCpyh37vASlMljIZx0/R+PIl2PfwTBw==",
1372
- "signed_at": "2026-06-13T17:11:21.943Z",
1372
+ "signed_at": "2026-06-13T20:32:56.055Z",
1373
1373
  "forward_watch": [
1374
1374
  "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"
1375
1375
  ]
@@ -1422,7 +1422,7 @@
1422
1422
  "d3fend_refs": [],
1423
1423
  "last_threat_review": "2026-05-15",
1424
1424
  "signature": "SBB7c3wNYfIdkyOp4g4nW0WP7xS+YokMzg32aaeJdbf14LTGQRzQUvSqb2TCj2HFUSHESOyKT1JpkAfyHLSQBQ==",
1425
- "signed_at": "2026-06-13T17:11:21.944Z"
1425
+ "signed_at": "2026-06-13T20:32:56.056Z"
1426
1426
  },
1427
1427
  {
1428
1428
  "name": "sector-healthcare",
@@ -1482,7 +1482,7 @@
1482
1482
  "d3fend_refs": [],
1483
1483
  "last_threat_review": "2026-06-10",
1484
1484
  "signature": "yyAGG3nUC6/XWQGRF2ufzILfiJitMFczqWNY83lZShCgYSxfFCul0UsICClOc7Xjw76052uydm8kLH9gIzhsAg==",
1485
- "signed_at": "2026-06-13T17:11:21.945Z"
1485
+ "signed_at": "2026-06-13T20:32:56.057Z"
1486
1486
  },
1487
1487
  {
1488
1488
  "name": "sector-financial",
@@ -1563,7 +1563,7 @@
1563
1563
  "TIBER-EU framework v2.0 alignment with DORA TLPT RTS (JC 2024/40); cross-recognition with CBEST and iCAST"
1564
1564
  ],
1565
1565
  "signature": "/+df91Qhl1TIxEAqzgqB7+YJ6mneX6Dif+h6RaCbxOLGE6KRTFst1Q/Fu5SStt8Zx5JI62bkQ2BBrNOKfUlPBg==",
1566
- "signed_at": "2026-06-13T17:11:21.945Z"
1566
+ "signed_at": "2026-06-13T20:32:56.057Z"
1567
1567
  },
1568
1568
  {
1569
1569
  "name": "sector-federal-government",
@@ -1632,7 +1632,7 @@
1632
1632
  "Australia PSPF 2024 revision and ISM quarterly updates — track for Essential Eight Maturity Level requirements for federal entities"
1633
1633
  ],
1634
1634
  "signature": "k6MAPS8HJivu0DNwC1azPvyKeig2xLGz/hcqYd8T+s5GvIypPGfkeYRRKmdqMgHUe24f7bQeRZ7AxcQ9Bi6SCg==",
1635
- "signed_at": "2026-06-13T17:11:21.945Z"
1635
+ "signed_at": "2026-06-13T20:32:56.057Z"
1636
1636
  },
1637
1637
  {
1638
1638
  "name": "sector-energy",
@@ -1697,7 +1697,7 @@
1697
1697
  "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"
1698
1698
  ],
1699
1699
  "signature": "IYKXn009jfhpKdQKiGB4xb7VgxsJXa6PZawoguP42khU6S/TTemp/YsK1YQCUyabE+2biElL/PP1Hi8zCkSSBw==",
1700
- "signed_at": "2026-06-13T17:11:21.946Z"
1700
+ "signed_at": "2026-06-13T20:32:56.058Z"
1701
1701
  },
1702
1702
  {
1703
1703
  "name": "sector-telecom",
@@ -1783,7 +1783,7 @@
1783
1783
  "O-RAN SFG / WG11 security specifications"
1784
1784
  ],
1785
1785
  "signature": "NAtyzfLPXlUuB78Snb9nWmbZalC1CNlIYN9rYhdEmtB/xQGC6vVnThgrEAHlm7v/jMCFuknvEpUHKdscUnUADw==",
1786
- "signed_at": "2026-06-13T17:11:21.946Z"
1786
+ "signed_at": "2026-06-13T20:32:56.058Z"
1787
1787
  },
1788
1788
  {
1789
1789
  "name": "api-security",
@@ -1852,7 +1852,7 @@
1852
1852
  "d3fend_refs": [],
1853
1853
  "last_threat_review": "2026-06-10",
1854
1854
  "signature": "HUjhPGpZeZeVi1nzBIsEAYh18x5ltlIXnt1yQ5YKz+jC2Dvxw7vV/Clx1/1gDBLAuBvRW9wcNtDr4c0RxhfGDQ==",
1855
- "signed_at": "2026-06-13T17:11:21.947Z",
1855
+ "signed_at": "2026-06-13T20:32:56.058Z",
1856
1856
  "forward_watch": [
1857
1857
  "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",
1858
1858
  "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",
@@ -1938,7 +1938,7 @@
1938
1938
  "CISA KEV additions for cloud-control-plane CVEs (IMDSv1 abuses, federation token mishandling, cross-tenant boundary failures); CISA Cybersecurity Advisories for cross-cloud advisories"
1939
1939
  ],
1940
1940
  "signature": "xa1kr/92/0fBds7LVtRy9aCM2tQRWAfNSFoC6ygbOU7QjamUo8Wt2h2n9W+nZT34STN3woYhXqtfmm8NSjXdBQ==",
1941
- "signed_at": "2026-06-13T17:11:21.947Z"
1941
+ "signed_at": "2026-06-13T20:32:56.059Z"
1942
1942
  },
1943
1943
  {
1944
1944
  "name": "container-runtime-security",
@@ -2000,7 +2000,7 @@
2000
2000
  "d3fend_refs": [],
2001
2001
  "last_threat_review": "2026-06-10",
2002
2002
  "signature": "60hyXAiYfY9lI+EpHG3iFFxa+oF5n71c0tTMa6iXFLNjBJKPevTsdNgQlQsBEnmRJ+dWtOjb/GNBW1KiBx4XAQ==",
2003
- "signed_at": "2026-06-13T17:11:21.948Z",
2003
+ "signed_at": "2026-06-13T20:32:56.059Z",
2004
2004
  "forward_watch": [
2005
2005
  "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"
2006
2006
  ]
@@ -2074,7 +2074,7 @@
2074
2074
  "MITRE ATLAS v2026.05 (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"
2075
2075
  ],
2076
2076
  "signature": "IEECw5lO6ZdpkE/j7Hv6/3Jg2vj9fpXlozY+h85Hrn9QYOJuI0gfBjHoBEchJ2GUTmPwUKoiBoI72PBAQw9RCQ==",
2077
- "signed_at": "2026-06-13T17:11:21.948Z"
2077
+ "signed_at": "2026-06-13T20:32:56.060Z"
2078
2078
  },
2079
2079
  {
2080
2080
  "name": "incident-response-playbook",
@@ -2136,7 +2136,7 @@
2136
2136
  "NYDFS 23 NYCRR 500.17 amendments tightening ransom-payment 24h disclosure operationalization"
2137
2137
  ],
2138
2138
  "signature": "pN1Qa5NRyNJ6h0pmbbqpy3xWdkMANDMUbBst5SKR5fmmqMFopmyrzwIGagiiNgqxjjCunj9J9Wbzs1sL48sMCQ==",
2139
- "signed_at": "2026-06-13T17:11:21.949Z"
2139
+ "signed_at": "2026-06-13T20:32:56.060Z"
2140
2140
  },
2141
2141
  {
2142
2142
  "name": "ransomware-response",
@@ -2216,7 +2216,7 @@
2216
2216
  ],
2217
2217
  "last_threat_review": "2026-06-10",
2218
2218
  "signature": "sXPA1lvWvZpf5sHxZXRkO0Kcs6gnXiYq0mWA53y1HeBwQGJ+viSPMt5IMobkjd5qLFYchg0CpW/nBUIvUE6/DA==",
2219
- "signed_at": "2026-06-13T17:11:21.949Z"
2219
+ "signed_at": "2026-06-13T20:32:56.061Z"
2220
2220
  },
2221
2221
  {
2222
2222
  "name": "email-security-anti-phishing",
@@ -2269,7 +2269,7 @@
2269
2269
  "d3fend_refs": [],
2270
2270
  "last_threat_review": "2026-05-18",
2271
2271
  "signature": "FVBn4ex2qPIo9SHMVJ6tntoz4tVwjbIq3m6wDjjZyv2JODlS+90GBYCOkNamxxkmw/6de6SMs0YHQiF/xjo/DQ==",
2272
- "signed_at": "2026-06-13T17:11:21.950Z"
2272
+ "signed_at": "2026-06-13T20:32:56.061Z"
2273
2273
  },
2274
2274
  {
2275
2275
  "name": "age-gates-child-safety",
@@ -2337,7 +2337,7 @@
2337
2337
  "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"
2338
2338
  ],
2339
2339
  "signature": "7Wdii9uwYmDrzQohoWRWDa5A4aDRKOmdLPq5Jsql86O0Gm7pQANnj9tX+cHpQcQ/Ui8VLuNVP8UFdKxxHeDnBg==",
2340
- "signed_at": "2026-06-13T17:11:21.950Z"
2340
+ "signed_at": "2026-06-13T20:32:56.061Z"
2341
2341
  },
2342
2342
  {
2343
2343
  "name": "cloud-iam-incident",
@@ -2417,7 +2417,7 @@
2417
2417
  ],
2418
2418
  "last_threat_review": "2026-05-15",
2419
2419
  "signature": "r9ii4nb3HJELdtKCGF5qy9PHOiot3GC24yfxfGAKlLENHkdRvRkvvL99eV/6RXyfUaMyrnc2Te8tPQcNu5bsDg==",
2420
- "signed_at": "2026-06-13T17:11:21.951Z",
2420
+ "signed_at": "2026-06-13T20:32:56.062Z",
2421
2421
  "forward_watch": [
2422
2422
  "AWS IAM Identity Center session-policy refresh and step-up-on-admin enforcement (anticipated 2026-H2 release)",
2423
2423
  "GCP Workload Identity Federation principal-set attribute mapping tightening (post-2026 Q3 Federation hardening guide)",
@@ -2511,7 +2511,7 @@
2511
2511
  ],
2512
2512
  "last_threat_review": "2026-05-15",
2513
2513
  "signature": "9mfDtMApMAg9V/lmwpniNxo/6gNZoOEoYDfyFvyWvKrPMtc7H9F8uz06FVoARe/J49saAKTVXOurNE1D/KtpCQ==",
2514
- "signed_at": "2026-06-13T17:11:21.951Z",
2514
+ "signed_at": "2026-06-13T20:32:56.062Z",
2515
2515
  "forward_watch": [
2516
2516
  "Entra ID conditional access evolution post-Midnight Blizzard — Microsoft's 2025-2026 commitments on legacy-tenant MFA enforcement and OAuth-app consent gating",
2517
2517
  "Okta IPSIE (Interoperability Profile for Secure Identity in the Enterprise) OpenID Foundation working-group output and adoption timeline",
@@ -2583,7 +2583,7 @@
2583
2583
  "d3fend_refs": [],
2584
2584
  "last_threat_review": "2026-06-02",
2585
2585
  "signature": "dOFgANMtHm64uSFRjMfeA0fWZS9ISwMTt59I2CbjaF1PgTtbtb8yg/f+3pC4eXcXIET0KPGxRRmENycfIz3rAw==",
2586
- "signed_at": "2026-06-13T17:11:21.952Z"
2586
+ "signed_at": "2026-06-13T20:32:56.062Z"
2587
2587
  },
2588
2588
  {
2589
2589
  "name": "mail-server-hardening",
@@ -2642,7 +2642,7 @@
2642
2642
  "d3fend_refs": [],
2643
2643
  "last_threat_review": "2026-06-02",
2644
2644
  "signature": "5x8RGzKo/T2IbYnFkYMZs16ThD9a5FEdrKHsnwvtMuGxdyyfZsKNIJVWw2I8O0um0UOXwwkl17Z3zhch26ymCQ==",
2645
- "signed_at": "2026-06-13T17:11:21.952Z"
2645
+ "signed_at": "2026-06-13T20:32:56.062Z"
2646
2646
  },
2647
2647
  {
2648
2648
  "name": "network-trust",
@@ -2698,7 +2698,7 @@
2698
2698
  "d3fend_refs": [],
2699
2699
  "last_threat_review": "2026-06-02",
2700
2700
  "signature": "C/RdmnGjXKreKiGkwJfUESIxUwQzisGJ9A7KBdE9cBRBHBYHkAcScV89L+Z3kOG032Qax6LeIQai7Lr3nrQBCA==",
2701
- "signed_at": "2026-06-13T17:11:21.952Z"
2701
+ "signed_at": "2026-06-13T20:32:56.063Z"
2702
2702
  },
2703
2703
  {
2704
2704
  "name": "audit-log-integrity",
@@ -2753,7 +2753,7 @@
2753
2753
  "d3fend_refs": [],
2754
2754
  "last_threat_review": "2026-06-02",
2755
2755
  "signature": "LvUAq+rXCA8PgNy9lTKOmFqa8T9EJg4JEH0axXvEAf9MIHqbR51ergUkFMLaTuvFuApwsPu0r1C30QgScZbiAw==",
2756
- "signed_at": "2026-06-13T17:11:21.953Z"
2756
+ "signed_at": "2026-06-13T20:32:56.063Z"
2757
2757
  },
2758
2758
  {
2759
2759
  "name": "self-update-integrity",
@@ -2807,7 +2807,7 @@
2807
2807
  "d3fend_refs": [],
2808
2808
  "last_threat_review": "2026-06-02",
2809
2809
  "signature": "BOQZ8G9LdyG/tfpz6rCU4THz5Dq2HGJkUV7uCqSTCMrqnXH6seNXKM41FWawYo0s2VUlXtRgaO7ETp5qgxw+CQ==",
2810
- "signed_at": "2026-06-13T17:11:21.953Z"
2810
+ "signed_at": "2026-06-13T20:32:56.063Z"
2811
2811
  },
2812
2812
  {
2813
2813
  "name": "multitenancy-isolation",
@@ -2865,7 +2865,7 @@
2865
2865
  "d3fend_refs": [],
2866
2866
  "last_threat_review": "2026-06-02",
2867
2867
  "signature": "bncN0VE4ns0lxtPe48xDR33lerw54BPVkSwX7XlrLQcMHTrV94lmjO8z4ZP4Ko61n+KpuP2SNU8v9JT0bGMmAA==",
2868
- "signed_at": "2026-06-13T17:11:21.953Z"
2868
+ "signed_at": "2026-06-13T20:32:56.063Z"
2869
2869
  },
2870
2870
  {
2871
2871
  "name": "decompression-dos",
@@ -2923,7 +2923,7 @@
2923
2923
  "d3fend_refs": [],
2924
2924
  "last_threat_review": "2026-06-02",
2925
2925
  "signature": "03i0ZO5ScHIOjGvDKUEsws6m+20fnlJrEhSBtMU7PJzQutrPpmW9gULE0wbayTD5H9URh5nSKZuuVjvjVhZNCg==",
2926
- "signed_at": "2026-06-13T17:11:21.953Z"
2926
+ "signed_at": "2026-06-13T20:32:56.064Z"
2927
2927
  },
2928
2928
  {
2929
2929
  "name": "log-injection-telemetry",
@@ -2978,7 +2978,7 @@
2978
2978
  "d3fend_refs": [],
2979
2979
  "last_threat_review": "2026-06-02",
2980
2980
  "signature": "twTmA2l+Am2k2PCzB2DNxDQwoYlGopJp/3QgQwBsP7h2LF+uNudisqfuOVV/bYnWfrUhuYZUWbsR3mhzbJCUBw==",
2981
- "signed_at": "2026-06-13T17:11:21.954Z"
2981
+ "signed_at": "2026-06-13T20:32:56.064Z"
2982
2982
  },
2983
2983
  {
2984
2984
  "name": "privacy-consent-ops",
@@ -3033,11 +3033,11 @@
3033
3033
  "d3fend_refs": [],
3034
3034
  "last_threat_review": "2026-06-02",
3035
3035
  "signature": "0sfKSVrIf555c9bDGkXB96fUClN52fMAMYoQsNQ9eUUSzDiWUwdou+q0gYt43nObtj3m9Yb9X6q3oCXDVMgUDg==",
3036
- "signed_at": "2026-06-13T17:11:21.954Z"
3036
+ "signed_at": "2026-06-13T20:32:56.064Z"
3037
3037
  }
3038
3038
  ],
3039
3039
  "manifest_signature": {
3040
3040
  "algorithm": "Ed25519",
3041
- "signature_base64": "+mMtPuzhLEQTT0v7GfPGnLhhEr2QTH19cDXYqpb9TJPotBPW1EYc08Zh8vy5+l5/ofZBVwfN++z8eou/oomgCA=="
3041
+ "signature_base64": "Jjbs014T8Wttzxj8RHg18ND3ASK9YudsKwKFtJRBwaFBcG1gcxDJz6vbyHNwkcLA2fND58IRE2wkGPPgBCwABw=="
3042
3042
  }
3043
3043
  }
@@ -114,7 +114,10 @@ async function main() {
114
114
  runCurrency();
115
115
  break;
116
116
  case 'report':
117
- await runReport(args[0] || 'technical');
117
+ // Resolve the format from the first NON-FLAG positional so `report --json`
118
+ // (no format given) defaults to technical instead of treating "--json" as
119
+ // an invalid format and exiting 1.
120
+ await runReport(args.find((a) => typeof a === 'string' && !a.startsWith('--')) || 'technical');
118
121
  break;
119
122
  case 'watch':
120
123
  await runWatch();
@@ -581,6 +584,25 @@ async function runReport(format) {
581
584
  const plan = dispatch(scanResult.findings);
582
585
  const { currency_report } = currencyCheck();
583
586
 
587
+ // The help advertises `--json for machine-readable output`. Previously only
588
+ // `report csaf` emitted JSON; executive/technical/compliance silently
589
+ // rendered Markdown even with --json. Honor the flag for those formats too.
590
+ if (args.includes('--json')) {
591
+ process.stdout.write(JSON.stringify({
592
+ ok: true,
593
+ verb: 'report',
594
+ format,
595
+ generated_at: new Date().toISOString(),
596
+ summary: scanResult.summary,
597
+ priority_actions: plan.plan
598
+ .filter((p) => p.priority <= 2)
599
+ .map((p) => ({ severity: p.finding_severity, skill: p.skill_name, action_required: p.action_required })),
600
+ skill_currency: currency_report,
601
+ host: scanResult.host,
602
+ }, null, 2) + '\n');
603
+ return;
604
+ }
605
+
584
606
  // Bug #48: header now self-describes the report flavor so a piped-to-file
585
607
  // report carries its provenance internally. Previously only stderr
586
608
  // (`[orchestrator] Generating <X> report`) distinguished the three.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/exceptd-skills",
3
- "version": "0.17.0",
3
+ "version": "0.18.1",
4
4
  "description": "AI security skills grounded in mid-2026 threat reality, not stale framework documentation. 51 skills, 11 catalogs (459 CVEs / 181 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",
@@ -43,7 +43,7 @@
43
43
  "license": "Apache-2.0",
44
44
  "author": "blamejs contributors",
45
45
  "engines": {
46
- "node": ">=22.11.0"
46
+ "node": ">=24.16.0"
47
47
  },
48
48
  "bin": {
49
49
  "exceptd": "bin/exceptd.js"
package/sbom.cdx.json CHANGED
@@ -1,22 +1,22 @@
1
1
  {
2
2
  "bomFormat": "CycloneDX",
3
3
  "specVersion": "1.6",
4
- "serialNumber": "urn:uuid:a48e92a1-ec59-4f7f-beed-28a5c2480b31",
4
+ "serialNumber": "urn:uuid:763b8882-5bfd-43d3-98a3-7d5bce35cfda",
5
5
  "version": 1,
6
6
  "metadata": {
7
- "timestamp": "2113-06-27T18:51:13.000Z",
7
+ "timestamp": "2088-11-09T11:37:38.000Z",
8
8
  "tools": [
9
9
  {
10
10
  "vendor": "blamejs",
11
11
  "name": "scripts/refresh-sbom.js",
12
- "version": "0.17.0"
12
+ "version": "0.18.1"
13
13
  }
14
14
  ],
15
15
  "component": {
16
- "bom-ref": "pkg:npm/@blamejs/exceptd-skills@0.17.0",
16
+ "bom-ref": "pkg:npm/@blamejs/exceptd-skills@0.18.1",
17
17
  "type": "application",
18
18
  "name": "@blamejs/exceptd-skills",
19
- "version": "0.17.0",
19
+ "version": "0.18.1",
20
20
  "description": "AI security skills grounded in mid-2026 threat reality, not stale framework documentation. 51 skills, 11 catalogs (459 CVEs / 181 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.17.0",
28
+ "purl": "pkg:npm/%40blamejs/exceptd-skills@0.18.1",
29
29
  "hashes": [
30
30
  {
31
31
  "alg": "SHA-256",
32
- "content": "35329d2060894d8caddf3e0339f194480b5c3f19410dbd1bf85b908741f2fbf2"
32
+ "content": "b6d798a95882195d629316ed20d3a16ccf6c4f3d244afdb7a73c4f81047dad02"
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.17.0"
38
+ "url": "https://www.npmjs.com/package/@blamejs/exceptd-skills/v/0.18.1"
39
39
  },
40
40
  {
41
41
  "type": "vcs",
@@ -116,11 +116,11 @@
116
116
  "hashes": [
117
117
  {
118
118
  "alg": "SHA-256",
119
- "content": "bf46918f437356885dcfad9739d101d5578221e3eca873991645126d441697b8"
119
+ "content": "238b86fe051241489de918c24646d341e1efc25fb165532c88719277cbc1d908"
120
120
  },
121
121
  {
122
122
  "alg": "SHA3-512",
123
- "content": "5b39aa1b3b81e8cec7d5555fc16111e46baf41e2f345c8ca19329e78af65d320b9b10d5e6224453ea39f0a0d0960b2162598478817780dc6f4d0a4dcfe297357"
123
+ "content": "8bc3c909b8bd6d5b140a0735c6abe35e32e191b273d1862816ec2878b36359d34d3d951c2c046b98a42f3cad9d2a1577f280ad5b5b13b2f80ba6d4586b208391"
124
124
  }
125
125
  ]
126
126
  },
@@ -281,11 +281,11 @@
281
281
  "hashes": [
282
282
  {
283
283
  "alg": "SHA-256",
284
- "content": "17553af5707a5bf163f76eb28c62bca472d7d9cb74c5d2fb5eb7bf18ab63bde8"
284
+ "content": "379a16da41f9d9ff66c4e1d19453d6a92d71da5f4464f94440d5185e1e14b4de"
285
285
  },
286
286
  {
287
287
  "alg": "SHA3-512",
288
- "content": "05a98e18e6e13d94ee77b95bcb5e22288b5780c1279d89549e9683fce3661165c2e9578ac08ef0732543fb51f59e9588fe6a0141a498a5e91e9afa07766f21f0"
288
+ "content": "803a6475512aea6fb1eb2f64a20ae3122398cee7408a00a0ca309ed8d41d6d70fb442603bfbdfa43b043b198c3662f2e141f73ae7729f47e4ed37750111d6dbd"
289
289
  }
290
290
  ]
291
291
  },
@@ -1466,11 +1466,11 @@
1466
1466
  "hashes": [
1467
1467
  {
1468
1468
  "alg": "SHA-256",
1469
- "content": "59179357f3ebc1cae07ca63f6e53441e1f5ce001491b0a71c03b00270403a7c7"
1469
+ "content": "0941607a18c500307bc7eb21f799f1ed752aa7ce74fca0e32807e6e1fe2845f2"
1470
1470
  },
1471
1471
  {
1472
1472
  "alg": "SHA3-512",
1473
- "content": "b2f8c4ebda7ada3125f8b6cb56fedf7b6072606130cba59c29679df22161162774d7a43e9c3a1b8860ada5320f18ee9d771cd20b48a324c4f2218afd42b0a03d"
1473
+ "content": "8f7ddf0df4be4d5480c30769ddd1d443ab53bc49d0d8b6e2322e42fed52ad5b541e40f1d632c5e25112979a6fca93e67f9ce831a8e1d35570f062c66867f1410"
1474
1474
  }
1475
1475
  ]
1476
1476
  },
@@ -1901,11 +1901,11 @@
1901
1901
  "hashes": [
1902
1902
  {
1903
1903
  "alg": "SHA-256",
1904
- "content": "096c0f1edc68448d21c8556dea01e3461d0efa24e0ea9ebe59554a92ca8a3946"
1904
+ "content": "76d31e7050f332fc608e48f3f4c286c8d9dfe309d2fc98ef7d5110e3e4351aac"
1905
1905
  },
1906
1906
  {
1907
1907
  "alg": "SHA3-512",
1908
- "content": "1eda539b22b0baabfc79b681ffa9f4e36242d02f511c79017292b139552867aff7c44bb2e8e73a17dbd157d446e55dd2588adacf5dc3c1f1215cb7e6f1f85717"
1908
+ "content": "37d22dbc0e4d2af548f889b2b8835f399f5a433b849c8005658591c4a853a1abe5121f8b96710033bfe30acb9703749ce9d248da665922ec5480099954438645"
1909
1909
  }
1910
1910
  ]
1911
1911
  },
@@ -1961,11 +1961,11 @@
1961
1961
  "hashes": [
1962
1962
  {
1963
1963
  "alg": "SHA-256",
1964
- "content": "79ff6e5e1360099debfa00034ccf903f0b10f58dab982af180aa79ea69ee6dbd"
1964
+ "content": "19673efb614ff862387b4d862a412851f05c8e0488f5bb4129b7798349f17502"
1965
1965
  },
1966
1966
  {
1967
1967
  "alg": "SHA3-512",
1968
- "content": "e57b550de41c42538aa0fc995bbea05f15b5a6c634fda4ec3fbba490ff520269525596f9d2cc941c4656651fae14ee4433f8b88cb4142578ea8218e2e365c47f"
1968
+ "content": "424d8c62e8e43f4b7a22e6c3963179eaaa431f50ec2b6af12176e85331ec9b0bd651f7a535ea0fbc6d3910e22ea7140a7e90208c1d379e08788ac6b85157d221"
1969
1969
  }
1970
1970
  ]
1971
1971
  },
@@ -2396,11 +2396,11 @@
2396
2396
  "hashes": [
2397
2397
  {
2398
2398
  "alg": "SHA-256",
2399
- "content": "3fcc2abf934ee1b70674856ba937833830d45346b4f146d613e500b9a9b4d163"
2399
+ "content": "6d8c8b1f1a412d7b0e357377359c1992cc67eb0d73d28d7f56c89347a09e2003"
2400
2400
  },
2401
2401
  {
2402
2402
  "alg": "SHA3-512",
2403
- "content": "1cc3b832434aa188fc7136feadb286c68d528e1e35406572bbaaf29a1b21be90ca59f3a8ea96d7173e7365e6c7b9248fdb45dcb6c56bbeec93ede95341ca5720"
2403
+ "content": "8dfec6c80f9bd0bdc3a942b901d593895f9d7ee58a49b8aa1d21a48d36a73be3842efa4a965b74d6c3f72d62c4dd8c56afb134a9c1e269904f2bd087721ea8dc"
2404
2404
  }
2405
2405
  ]
2406
2406
  },
@@ -2411,11 +2411,11 @@
2411
2411
  "hashes": [
2412
2412
  {
2413
2413
  "alg": "SHA-256",
2414
- "content": "8748d74589b7cad24ca8fb7cdb8a425b16830ac1709c08e0294bb65bea218cc6"
2414
+ "content": "2ca90dd0f7df6c21a97fa8e7bc52b6256a76eaa1cd61e4b60bcc6c5ab7cb84b0"
2415
2415
  },
2416
2416
  {
2417
2417
  "alg": "SHA3-512",
2418
- "content": "aa40992693b3a2c4d901801729858c97ec877ff249d50569cf470a2050a60ea36651456dc9b2797ca930c3faff69fb5fd1d4f28a2296bc485741287f7a804272"
2418
+ "content": "0f33949765c4493404886832a9753eee05b01841564f7a917ce502504661fd8127a76fdb6c89d3fa09a68cc378a91785ff3298137d2664d941a1c89435f9d604"
2419
2419
  }
2420
2420
  ]
2421
2421
  },
@@ -2441,11 +2441,11 @@
2441
2441
  "hashes": [
2442
2442
  {
2443
2443
  "alg": "SHA-256",
2444
- "content": "906735e02f452962cc221e129a9d7d08cb645f0b79b48168fea62038c35f6b71"
2444
+ "content": "25662547fd3f91f61554605a62fa9fc56f8c23a5a675e01178320c0f7ba77582"
2445
2445
  },
2446
2446
  {
2447
2447
  "alg": "SHA3-512",
2448
- "content": "09590d90712ecec967caf198f33f0ea62b24152dcfda51b1bd0422e64add9cf2f97a5873310d11d654846b83dd92c9c82ca014973ca51888d14607e2b3092e0f"
2448
+ "content": "b4c14dd1859c76dd35125761c57d45aee28f3cf33891269b9a7876c8bbef0e4090d8f929b2013927d2e668c5e1a8bff1276ccc0a293ab7d6926a3fece1dfba3c"
2449
2449
  }
2450
2450
  ]
2451
2451
  },
@@ -2486,11 +2486,11 @@
2486
2486
  "hashes": [
2487
2487
  {
2488
2488
  "alg": "SHA-256",
2489
- "content": "3e37c0d93dfc1ff3c6fd5e90bb65417f3ebcabd8435925c4292b4ca86bdd11c2"
2489
+ "content": "8dbdaf22258d895c0a7d4d006bbcb6eaa40830eeedfc399851267aa4eba60f57"
2490
2490
  },
2491
2491
  {
2492
2492
  "alg": "SHA3-512",
2493
- "content": "8c1be019521e12c483cb5e71ee94e6601497c514b09080c2560ca942bb2979f0b08928866213b13fc882105f6aa6e33901fb7edb8cc7dad9a1f03d8bd8a45ce6"
2493
+ "content": "4456979314318624a16393f94a1f4e68f371e522b95a4a001a33cb64373c32ddc3fdb86f1b8f211de5bc3fce27ac2f259a4e98504404242af2a57aaac33310ec"
2494
2494
  }
2495
2495
  ]
2496
2496
  },
@@ -331,12 +331,22 @@ function checkSbomCurrency(root) {
331
331
  // pass already tied each recorded hash to live bytes).
332
332
  const compHashes = (sbom.metadata && sbom.metadata.component && sbom.metadata.component.hashes) || [];
333
333
  const recorded = (compHashes.find((h) => h && h.alg === "SHA-256") || {}).content;
334
- if (recorded && fileComps.length) {
335
- const recomputed = bundleDigest(fileComps);
336
- if (recomputed !== recorded) {
334
+ if (fileComps.length) {
335
+ // A missing aggregate digest is NOT benign: it is the bundle-as-a-whole
336
+ // verification anchor. An absent one must fail the gate (symmetric with
337
+ // the per-file SHA3-512 hard-error that defeats downgrade/strip attacks) —
338
+ // previously an absent digest silently skipped the comparison entirely.
339
+ if (!recorded) {
337
340
  errors.push(
338
- `SBOM bundle digest mismatch: metadata.component.hashes SHA-256 ${String(recorded).slice(0, 12)}… != recomputed ${recomputed.slice(0, 12)}… from file: components — run \`npm run refresh-sbom\``
341
+ "SBOM metadata.component.hashes lacks a SHA-256 bundle digest — the aggregate verification anchor is missing; run `npm run refresh-sbom`"
339
342
  );
343
+ } else {
344
+ const recomputed = bundleDigest(fileComps);
345
+ if (recomputed !== recorded) {
346
+ errors.push(
347
+ `SBOM bundle digest mismatch: metadata.component.hashes SHA-256 ${String(recorded).slice(0, 12)}… != recomputed ${recomputed.slice(0, 12)}… from file: components — run \`npm run refresh-sbom\``
348
+ );
349
+ }
340
350
  }
341
351
  }
342
352
  } catch (e) {
@@ -56,11 +56,17 @@ function listTestFiles(dir) {
56
56
  }
57
57
 
58
58
  function countTests(filePath) {
59
- const text = fs.readFileSync(filePath, 'utf8');
60
- const lines = text.split('\n');
59
+ let text = fs.readFileSync(filePath, 'utf8');
60
+ // Strip block comments first so a `/* test('x'); */`-disabled test is not
61
+ // counted — the most common "temporarily disable" action, which previously
62
+ // defeated this gate's entire purpose (it only stripped whole-line `//`).
63
+ text = text.replace(/\/\*[\s\S]*?\*\//g, '');
61
64
  let count = 0;
62
- for (const line of lines) {
63
- const stripped = line.replace(/^\s*\/\/.*$/, '').trim();
65
+ for (const rawLine of text.split('\n')) {
66
+ // Drop a trailing line comment too (`test('x'); // disabled`). Best-effort:
67
+ // a `//` inside a string literal would over-strip, which under-counts — the
68
+ // SAFE direction for a no-silent-shrinkage gate.
69
+ const stripped = rawLine.replace(/\/\/.*$/, '').trim();
64
70
  if (!stripped) continue;
65
71
  if (/(?<![A-Za-z0-9_$.])test(?:\.only|\.skip)?\s*\(/.test(stripped)) count++;
66
72
  }
@@ -309,11 +309,34 @@ function extractLibExports(content) {
309
309
  const stripped = content
310
310
  .replace(/\/\*[\s\S]*?\*\//g, "")
311
311
  .replace(/^\s*\/\/.*$/gm, "");
312
- const m = stripped.match(/module\.exports\s*=\s*\{([^}]+)\}/);
313
- if (m) {
314
- for (const tok of m[1].split(",")) {
315
- const id = tok.split(":")[0].trim();
316
- if (/^[a-zA-Z_$][\w$]*$/.test(id)) out.add(id);
312
+ // Capture the `module.exports = { ... }` body with brace-balancing so a
313
+ // nested object/array member (e.g. `{ CONFIG: { a: 1 }, x, y }`) does not
314
+ // truncate the export list at the first inner `}` and hide later exports —
315
+ // which would let an uncovered new export ship green (the gate's blind spot).
316
+ const objStart = stripped.search(/module\.exports\s*=\s*\{/);
317
+ if (objStart !== -1) {
318
+ const openIdx = stripped.indexOf("{", objStart);
319
+ let depth = 0, end = -1;
320
+ for (let i = openIdx; i < stripped.length; i++) {
321
+ const ch = stripped[i];
322
+ if (ch === "{") depth++;
323
+ else if (ch === "}") { depth--; if (depth === 0) { end = i; break; } }
324
+ }
325
+ if (end !== -1) {
326
+ const body = stripped.slice(openIdx + 1, end);
327
+ let d = 0, cur = "";
328
+ const members = [];
329
+ for (const ch of body) {
330
+ if (ch === "{" || ch === "[") d++;
331
+ else if (ch === "}" || ch === "]") d--;
332
+ if (ch === "," && d === 0) { members.push(cur); cur = ""; }
333
+ else cur += ch;
334
+ }
335
+ members.push(cur);
336
+ for (const tok of members) {
337
+ const id = tok.split(":")[0].trim();
338
+ if (/^[a-zA-Z_$][\w$]*$/.test(id)) out.add(id);
339
+ }
317
340
  }
318
341
  }
319
342
  const re = /module\.exports\.([a-zA-Z_$][\w$]*)\s*=/g;
@@ -308,11 +308,17 @@ function runGate(gate) {
308
308
  const ceil = typeof gate.informationalMaxExitCode === "number"
309
309
  ? gate.informationalMaxExitCode
310
310
  : Infinity;
311
- if (r.status !== null && r.status > ceil) {
311
+ // A signal kill (spawnSync returns status:null with r.signal set — e.g. a
312
+ // 137 OOM kill) is a crash, not an informational soft-signal. Without this,
313
+ // an OOM-killed informational gate fell through to "informational" and the
314
+ // release proceeded as if the gate had merely produced advisory output.
315
+ if (r.signal || (r.status !== null && r.status > ceil)) {
312
316
  return {
313
317
  status: "failed",
314
318
  exitCode: r.status,
315
- message: `informational gate crashed (exit ${r.status} > informationalMaxExitCode=${ceil})`,
319
+ message: r.signal
320
+ ? `informational gate killed by signal ${r.signal} (treated as a crash)`
321
+ : `informational gate crashed (exit ${r.status} > informationalMaxExitCode=${ceil})`,
316
322
  durationMs,
317
323
  warnCount,
318
324
  };