@blamejs/exceptd-skills 0.14.14 → 0.14.16

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,24 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.14.16 — 2026-05-27
4
+
5
+ `collect` is dramatically faster on large repositories. The directory-walking collectors (`secrets`, `crypto-codebase`, `containers`, `citation-hygiene`) called `realpathSync` on every file for symlink-cycle detection and stat'd each file before reading it; the walk is now a single shared implementation that resolves real paths only for directories and symlinks and reads files without a pre-stat. On a 5000-file repository `collect secrets` drops from ~1.1s to ~0.18s (and `collect containers` from ~0.7s to ~0.02s), with identical results.
6
+
7
+ Cheap verbs start faster. The CVE catalog (~2.6 MB) was parsed at module load on every invocation; verbs that never analyze (`brief`, `plan`, `look`, `ask`, `lint`, `discover`) no longer pay that cost — the catalog is parsed lazily on the first run that needs it (a corrupt catalog still blocks a `run` cleanly).
8
+
9
+ `ask` routes a few more questions correctly: a CI/OIDC question (e.g. "my CI runner leaked an OIDC token") routes to `cicd-pipeline-compromise` instead of the supply-chain playbooks, and an "AI command and control" question routes to `ai-api`. Common two-letter English filler words ("do", "is", "to", …) no longer produce a spurious low-confidence match for nonsense questions.
10
+
11
+ Removed an always-empty `recipes` field from the CVE cross-reference result. Recipes are use-case curated, never CVE-keyed, so a per-CVE recipe lookup could never populate.
12
+
13
+ ## 0.14.15 — 2026-05-27
14
+
15
+ Emitted CSAF 2.0 and SARIF 2.1.0 documents now pass strict schema/profile validation:
16
+ - Every CSAF vulnerability carries `notes` (CVE-keyed entries previously omitted it, failing the security-advisory profile's mandatory test).
17
+ - A clean run's `csaf_informational_advisory` no longer carries a `/vulnerabilities` array or a `/product_tree` (both are wrong for the informational profile) and now includes the required external `/document/references` entry.
18
+ - `tracking.version` equals the last `revision_history` number and uses the same versioning scheme as it (previously the version was the playbook semver while the revision number was the integer `1` — two violations: a version/revision mismatch and mixed versioning schemes).
19
+ - SARIF results with `kind: "informational"` (framework-gap findings) now use `level: "none"` instead of `"note"`; the SARIF spec requires `level: "none"` whenever `kind` is not `"fail"`, so strict validators and GitHub code scanning previously rejected those results.
20
+ - SARIF `artifactLocation.uri` values from a submission-supplied evidence location are normalized to forward slashes. A Windows operator passing a native backslash path previously produced URIs that violate the SARIF URI-reference requirement (the collector-derived locations were already normalized; submission-threaded ones were not).
21
+
3
22
  ## 0.14.14 — 2026-05-27
4
23
 
5
24
  Attestation durability and verification:
package/bin/exceptd.js CHANGED
@@ -8186,6 +8186,18 @@ function cmdAsk(runner, args, runOpts, pretty) {
8186
8186
  "cred theft": ["cred-stores", "secrets"],
8187
8187
  "credential exfil": ["cred-stores", "llm-tool-use-exfil"],
8188
8188
  "developer laptop": ["cred-stores", "hardening"],
8189
+ // CI/CD + OIDC vocabulary → the dedicated cicd-pipeline-compromise playbook
8190
+ // (the supply-chain playbooks otherwise out-rank it on shared tokens).
8191
+ "oidc": ["cicd-pipeline-compromise", "ci", "pipeline", "runner", "signing"],
8192
+ "cicd": ["cicd-pipeline-compromise", "ci", "pipeline"],
8193
+ "ci/cd": ["cicd-pipeline-compromise", "pipeline"],
8194
+ "runner": ["cicd-pipeline-compromise", "ci"],
8195
+ "pipeline": ["cicd-pipeline-compromise"],
8196
+ // C2-over-AI-API → the dedicated ai-api playbook (llm-tool-use-exfil
8197
+ // otherwise wins on a long C2 sentence).
8198
+ "c2": ["ai-api", "ai-c2"],
8199
+ "command and control": ["ai-api", "ai-c2"],
8200
+ "command-and-control": ["ai-api", "ai-c2"],
8189
8201
  };
8190
8202
 
8191
8203
  // Audit 3 C.1: stopwords filtered after synonym expansion so common
@@ -8202,6 +8214,11 @@ function cmdAsk(runner, args, runOpts, pretty) {
8202
8214
  "than", "then", "them", "some", "more", "most", "very", "much", "such",
8203
8215
  "been", "were", "want", "well", "back", "good", "make", "made", "take",
8204
8216
  "took", "give", "gave", "find", "found", "know", "knew", "told", "ago",
8217
+ // 2-char English fillers (the length>=2 token filter otherwise lets these
8218
+ // substring-hit a haystack — "do" matched ai-api). Deliberately excludes
8219
+ // security-meaningful 2-char tokens (ai, ml, ci, c2, k8s).
8220
+ "do", "is", "my", "it", "me", "to", "of", "on", "or", "an", "as", "at",
8221
+ "be", "by", "we", "up", "so", "no", "if", "in",
8205
8222
  ]);
8206
8223
 
8207
8224
  // Tokenize question (length >= 2, lowercase) + expand via synonyms.
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "schema_version": "1.1.0",
3
- "generated_at": "2026-05-27T23:08:52.304Z",
3
+ "generated_at": "2026-05-28T00:40:30.346Z",
4
4
  "generator": "scripts/build-indexes.js",
5
5
  "source_count": 54,
6
6
  "source_hashes": {
7
- "manifest.json": "008292f92fc956004d340bf314748a28ab03a6f14c88ddf7f5deabdab8b14b4d",
7
+ "manifest.json": "53aaca262d4df7bf007660d881cd042cec47f11bb792f3ab77bed752b875373b",
8
8
  "data/atlas-ttps.json": "d24bc02859d40ccf1615db75cca68c077585904e41e0d8f6de448121e9b1abb0",
9
9
  "data/attack-techniques.json": "fa193f0d2d248176a8beddb641e9fe56ba4faa9e15dc253ff876dbf0c5d58a77",
10
10
  "data/cve-catalog.json": "3d451dda7ac0c7d57a4075ae4bafd3148c6184b35dc1bc59d8b81d1f2641e430",
@@ -30,7 +30,7 @@
30
30
  const fs = require("node:fs");
31
31
  const path = require("node:path");
32
32
 
33
- const { codeExcludeSet, isLinkedWorktreeDir, buildEvidenceLocations, lineFromOffset } = require("./scan-excludes");
33
+ const { codeExcludeSet, walkTree, buildEvidenceLocations, lineFromOffset } = require("./scan-excludes");
34
34
 
35
35
  const COLLECTOR_ID = "citation-hygiene";
36
36
 
@@ -89,43 +89,15 @@ function isIllustrativePath(rel) {
89
89
  return false;
90
90
  }
91
91
 
92
- function walkTree(root, opts = {}) {
93
- const maxDepth = opts.maxDepth ?? DEFAULT_MAX_DEPTH;
94
- const excludes = opts.excludes ?? EXCLUDES;
95
- const out = [];
96
- const seen = new Set();
97
-
98
- function walk(dir, depth) {
99
- if (depth > maxDepth) return;
100
- let entries;
101
- try { entries = fs.readdirSync(dir, { withFileTypes: true }); }
102
- catch { return; }
103
- for (const entry of entries) {
104
- if (excludes.has(entry.name)) continue;
105
- const full = path.join(dir, entry.name);
106
- let real;
107
- try { real = fs.realpathSync(full); } catch { continue; }
108
- if (seen.has(real)) continue;
109
- seen.add(real);
110
- if (entry.isDirectory()) {
111
- // Skip detached git worktrees (agent scratch copies) — descending
112
- // into them rescans unrelated repo state.
113
- if (isLinkedWorktreeDir(full)) continue;
114
- walk(full, depth + 1);
115
- } else if (entry.isFile()) {
116
- out.push({ full, rel: path.relative(root, full), name: entry.name });
117
- }
118
- }
119
- }
120
- walk(root, 0);
121
- return out;
122
- }
123
-
124
92
  function readSafe(full) {
125
93
  try {
126
- const s = fs.statSync(full);
127
- if (s.size > MAX_FILE_BYTES) return null;
128
- return fs.readFileSync(full, "utf8");
94
+ // Read raw bytes, enforce the 2 MB cap on the buffer length, then decode.
95
+ // Replaces a statSync-before-read with a single read; the cap is
96
+ // byte-based, so Buffer.length is the correct measure and an oversized
97
+ // file is rejected before any UTF-8 decode.
98
+ const raw = fs.readFileSync(full);
99
+ if (raw.length > MAX_FILE_BYTES) return null;
100
+ return raw.toString("utf8");
129
101
  } catch { return null; }
130
102
  }
131
103
 
@@ -288,7 +260,7 @@ function collect({ cwd = process.cwd() } = {}) {
288
260
 
289
261
  let files;
290
262
  try {
291
- files = walkTree(root);
263
+ files = walkTree(root, { maxDepth: DEFAULT_MAX_DEPTH, excludes: EXCLUDES });
292
264
  } catch (e) {
293
265
  errors.push({ kind: "walk_failed", reason: e.message });
294
266
  files = [];
@@ -20,7 +20,7 @@
20
20
 
21
21
  const fs = require("node:fs");
22
22
  const path = require("node:path");
23
- const { codeExcludeSet, isLinkedWorktreeDir, buildEvidenceLocations } = require("./scan-excludes");
23
+ const { codeExcludeSet, walkTree, buildEvidenceLocations } = require("./scan-excludes");
24
24
 
25
25
  const COLLECTOR_ID = "containers";
26
26
 
@@ -37,36 +37,6 @@ const COMPOSE_NAMES = new Set([
37
37
  ]);
38
38
  const COMPOSE_PREFIX = "docker-compose."; // docker-compose.override.yml etc.
39
39
 
40
- function walkTree(root, opts = {}) {
41
- const maxDepth = opts.maxDepth ?? DEFAULT_MAX_DEPTH;
42
- const excludes = opts.excludes ?? DEFAULT_EXCLUDES;
43
- const out = [];
44
- const seen = new Set();
45
- function walk(dir, depth) {
46
- if (depth > maxDepth) return;
47
- let entries;
48
- try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
49
- for (const entry of entries) {
50
- if (excludes.has(entry.name)) continue;
51
- const full = path.join(dir, entry.name);
52
- let real;
53
- try { real = fs.realpathSync(full); } catch { continue; }
54
- if (seen.has(real)) continue;
55
- seen.add(real);
56
- if (entry.isDirectory()) {
57
- // Skip linked git worktrees (their `.git` is a gitdir pointer
58
- // file) — e.g. agent-created repo copies under
59
- // `.claude/worktrees/<id>/`. Walking them rescans the same
60
- // Dockerfiles / compose / k8s manifests as the host tree.
61
- if (isLinkedWorktreeDir(full)) continue;
62
- walk(full, depth + 1);
63
- } else if (entry.isFile()) out.push({ full, rel: path.relative(root, full), name: entry.name });
64
- }
65
- }
66
- walk(root, 0);
67
- return out;
68
- }
69
-
70
40
  function classify(file) {
71
41
  const name = file.name;
72
42
  const ext = path.extname(name).toLowerCase();
@@ -85,9 +55,13 @@ function classify(file) {
85
55
 
86
56
  function readSafe(full, max = 512 * 1024) {
87
57
  try {
88
- const s = fs.statSync(full);
89
- if (s.size > max) return null;
90
- return fs.readFileSync(full, "utf8");
58
+ // Read raw bytes, enforce the cap on the buffer length, then decode.
59
+ // Replaces a statSync-before-read with a single read; the cap is
60
+ // byte-based, so Buffer.length is the correct measure and an oversized
61
+ // file is rejected before any UTF-8 decode.
62
+ const raw = fs.readFileSync(full);
63
+ if (raw.length > max) return null;
64
+ return raw.toString("utf8");
91
65
  } catch { return null; }
92
66
  }
93
67
 
@@ -324,7 +298,7 @@ function collect({ cwd = process.cwd(), env = process.env, args = {} } = {}) {
324
298
  const root = path.resolve(cwd);
325
299
 
326
300
  let files;
327
- try { files = walkTree(root); }
301
+ try { files = walkTree(root, { maxDepth: DEFAULT_MAX_DEPTH, excludes: DEFAULT_EXCLUDES }); }
328
302
  catch (e) { errors.push({ kind: "walk_failed", reason: e.message }); files = []; }
329
303
 
330
304
  const dockerfiles = [];
@@ -16,7 +16,7 @@
16
16
 
17
17
  const fs = require("node:fs");
18
18
  const path = require("node:path");
19
- const { codeExcludeSet, isLinkedWorktreeDir, buildEvidenceLocations, lineFromOffset } = require("./scan-excludes");
19
+ const { codeExcludeSet, walkTree, buildEvidenceLocations, lineFromOffset } = require("./scan-excludes");
20
20
 
21
21
  const COLLECTOR_ID = "crypto-codebase";
22
22
 
@@ -67,45 +67,15 @@ function isTestPath(rel) {
67
67
  return false;
68
68
  }
69
69
 
70
- function walkTree(root, opts = {}) {
71
- const maxDepth = opts.maxDepth ?? DEFAULT_MAX_DEPTH;
72
- const excludes = opts.excludes ?? DEFAULT_EXCLUDES;
73
- const out = [];
74
- const seen = new Set();
75
-
76
- function walk(dir, depth) {
77
- if (depth > maxDepth) return;
78
- let entries;
79
- try { entries = fs.readdirSync(dir, { withFileTypes: true }); }
80
- catch { return; }
81
- for (const entry of entries) {
82
- if (excludes.has(entry.name)) continue;
83
- const full = path.join(dir, entry.name);
84
- let real;
85
- try { real = fs.realpathSync(full); } catch { continue; }
86
- if (seen.has(real)) continue;
87
- seen.add(real);
88
- if (entry.isDirectory()) {
89
- // Never descend into a linked git worktree (its `.git` is a
90
- // gitdir pointer file). Agent tooling stamps full repo copies
91
- // under `.claude/worktrees/<id>/`; walking them rescans the same
92
- // source as the host tree and multiplies every hit.
93
- if (isLinkedWorktreeDir(full)) continue;
94
- walk(full, depth + 1);
95
- } else if (entry.isFile()) {
96
- out.push({ full, rel: path.relative(root, full), name: entry.name });
97
- }
98
- }
99
- }
100
- walk(root, 0);
101
- return out;
102
- }
103
-
104
70
  function readSafe(full) {
105
71
  try {
106
- const s = fs.statSync(full);
107
- if (s.size > MAX_FILE_BYTES) return null;
108
- return fs.readFileSync(full, "utf8");
72
+ // Read raw bytes, enforce the 1 MB cap on the buffer length, then decode.
73
+ // Replaces a statSync-before-read on the hot path with a single read; the
74
+ // cap is byte-based, so Buffer.length is the correct measure and an
75
+ // oversized file is rejected before any UTF-8 decode.
76
+ const raw = fs.readFileSync(full);
77
+ if (raw.length > MAX_FILE_BYTES) return null;
78
+ return raw.toString("utf8");
109
79
  } catch { return null; }
110
80
  }
111
81
 
@@ -246,7 +216,7 @@ function collect({ cwd = process.cwd(), env = process.env, args = {} } = {}) {
246
216
 
247
217
  let files;
248
218
  try {
249
- files = walkTree(root);
219
+ files = walkTree(root, { maxDepth: DEFAULT_MAX_DEPTH, excludes: DEFAULT_EXCLUDES });
250
220
  } catch (e) {
251
221
  errors.push({ kind: "walk_failed", reason: e.message });
252
222
  files = [];
Binary file
@@ -18,7 +18,7 @@
18
18
 
19
19
  const fs = require("node:fs");
20
20
  const path = require("node:path");
21
- const { codeExcludeSet, isLinkedWorktreeDir, buildEvidenceLocations, lineFromOffset } = require("./scan-excludes");
21
+ const { codeExcludeSet, walkTree, buildEvidenceLocations, lineFromOffset } = require("./scan-excludes");
22
22
 
23
23
  const COLLECTOR_ID = "secrets";
24
24
 
@@ -119,40 +119,6 @@ const TEXT_EXTENSIONS = new Set([
119
119
  const TEXT_EXACT = new Set(["Dockerfile", "Makefile", "Procfile", ".env", ".envrc"]);
120
120
  const MAX_FILE_BYTES = 1024 * 1024; // 1 MB per file content scan
121
121
 
122
- function walkTree(root, opts = {}) {
123
- const maxDepth = opts.maxDepth ?? DEFAULT_MAX_DEPTH;
124
- const excludes = opts.excludes ?? DEFAULT_EXCLUDES;
125
- const out = [];
126
- const seen = new Set();
127
-
128
- function walk(dir, depth) {
129
- if (depth > maxDepth) return;
130
- let entries;
131
- try { entries = fs.readdirSync(dir, { withFileTypes: true }); }
132
- catch { return; }
133
- for (const entry of entries) {
134
- if (excludes.has(entry.name)) continue;
135
- const full = path.join(dir, entry.name);
136
- let real;
137
- try { real = fs.realpathSync(full); } catch { continue; }
138
- if (seen.has(real)) continue;
139
- seen.add(real);
140
- if (entry.isDirectory()) {
141
- // Skip linked git worktrees (their `.git` is a gitdir pointer
142
- // file). Agent tooling stamps full repo copies under
143
- // `.claude/worktrees/<id>/`; descending into them rescans the
144
- // same files and inflates secret-carrier hit counts.
145
- if (isLinkedWorktreeDir(full)) continue;
146
- walk(full, depth + 1);
147
- } else if (entry.isFile()) {
148
- out.push({ full, rel: path.relative(root, full), name: entry.name });
149
- }
150
- }
151
- }
152
- walk(root, 0);
153
- return out;
154
- }
155
-
156
122
  function classify(file) {
157
123
  const name = file.name;
158
124
  const ext = path.extname(name).toLowerCase();
@@ -200,9 +166,15 @@ function redactMatch(literal) {
200
166
  function scanContent(full, rel) {
201
167
  let buf;
202
168
  try {
203
- const s = fs.statSync(full);
204
- if (s.size > MAX_FILE_BYTES) return { skipped: "file_too_large", bytes: s.size, hits: [] };
205
- buf = fs.readFileSync(full, "utf8");
169
+ // Read raw bytes first, then enforce the 1 MB cap on the buffer length.
170
+ // The previous statSync-before-read doubled the per-file syscall count on
171
+ // the hot path; reading the buffer and measuring it is byte-accurate (the
172
+ // cap is a byte limit) without the extra stat. Reading as a Buffer rather
173
+ // than decoding to a string up front means an oversized file is rejected
174
+ // before any UTF-8 decode work.
175
+ const raw = fs.readFileSync(full);
176
+ if (raw.length > MAX_FILE_BYTES) return { skipped: "file_too_large", bytes: raw.length, hits: [] };
177
+ buf = raw.toString("utf8");
206
178
  } catch (e) {
207
179
  return { skipped: "read_error", reason: e.message, hits: [] };
208
180
  }
@@ -236,7 +208,7 @@ function collect({ cwd = process.cwd(), env = process.env, args = {} } = {}) {
236
208
 
237
209
  let files;
238
210
  try {
239
- files = walkTree(root);
211
+ files = walkTree(root, { maxDepth: DEFAULT_MAX_DEPTH, excludes: DEFAULT_EXCLUDES });
240
212
  } catch (e) {
241
213
  errors.push({ kind: "walk_failed", reason: e.message });
242
214
  files = [];
@@ -145,15 +145,14 @@ function byCve(cveId, opts) {
145
145
  }
146
146
 
147
147
  const xref = loadIndex('xref.json');
148
- const recipes = loadIndex('recipes.json');
149
148
  const theaterFp = loadIndex('theater-fingerprints.json');
150
149
  const gaps = loadCatalog('framework-control-gaps.json');
151
150
  const lessons = loadCatalog('zeroday-lessons.json');
152
151
 
153
152
  const skills = (xref[cveId] || xref.cves?.[cveId] || []).slice();
154
- const matchingRecipes = entries(recipes).filter(([, r]) =>
155
- Array.isArray(r.triggered_by) && r.triggered_by.includes(cveId)
156
- ).map(([id]) => id);
153
+ // (Recipes are use-case curated, not CVE-triggered — recipes.json has no
154
+ // `triggered_by`/CVE keying, so a per-CVE recipe lookup was always empty.
155
+ // The dead `recipes:[]` field is no longer emitted.)
157
156
  const theater = entries(theaterFp).filter(([, t]) =>
158
157
  Array.isArray(t.cve_refs) && t.cve_refs.includes(cveId)
159
158
  ).map(([id, t]) => ({ id, distinguisher: t.distinguisher || t.test }));
@@ -177,7 +176,6 @@ function byCve(cveId, opts) {
177
176
  skills,
178
177
  framework_gaps,
179
178
  theater_tests: theater,
180
- recipes: matchingRecipes,
181
179
  zeroday_lessons: lessons_learned
182
180
  };
183
181
  }
@@ -236,19 +234,6 @@ function byFramework(frameworkId, scenario) {
236
234
  return { framework: frameworkId, scenario: scenario || null, framework_meta: fwMeta, gaps: matching, gap_count: matching.length };
237
235
  }
238
236
 
239
- /**
240
- * Given a detected signal shape, return recipes whose triggered_by matches
241
- * any of the signals. Used by the analyze phase to chain into multi-step
242
- * workflows the agent should walk through next.
243
- */
244
- function recipesFor(signals) {
245
- const recipes = loadIndex('recipes.json');
246
- const sigSet = new Set(signals);
247
- return entries(recipes)
248
- .filter(([, r]) => Array.isArray(r.triggered_by) && r.triggered_by.some(t => sigSet.has(t)))
249
- .map(([id, r]) => ({ id, name: r.name, skills: r.skills, steps: r.steps }));
250
- }
251
-
252
237
  /**
253
238
  * Theater-fingerprint lookup — given a finding shape, return the specific test
254
239
  * that distinguishes paper compliance from actual security (AGENTS.md Hard
@@ -297,7 +282,6 @@ module.exports = {
297
282
  byTtp,
298
283
  bySkill,
299
284
  byFramework,
300
- recipesFor,
301
285
  theaterTestsFor,
302
286
  globalFrameworkContext,
303
287
  clearCache,
@@ -60,26 +60,40 @@ const scoring = require('./scoring');
60
60
  // signal regardless of submission shape.
61
61
  let xref;
62
62
  let _xrefLoadError = null;
63
+ let _xrefProbed = false;
63
64
  try {
64
65
  xref = require('./cross-ref-api');
65
- // Probe-load the catalog so any parse error is observable BEFORE the
66
- // first real analyze() call. Without this, a corrupt catalog would
67
- // only surface on the first byCve invocation, which could be
68
- // mid-pipeline (after preflight/govern/direct phases have already
69
- // emitted artifacts).
70
- try { xref.byCve('__exceptd-probe__'); } catch {}
71
- if (typeof xref.getLoadErrors === 'function') {
72
- const errs = xref.getLoadErrors();
73
- if (errs && errs.length) {
74
- _xrefLoadError = `${errs.length} catalog/index load error(s): ${errs.map(e => `${e.file}: ${e.error}`).join('; ')}`;
75
- }
76
- }
77
66
  } catch (e) {
78
67
  _xrefLoadError = (e && e.message) ? String(e.message) : String(e);
79
68
  xref = {
80
69
  byCve: () => ({ found: false, _error: _xrefLoadError }),
81
70
  _error: _xrefLoadError,
82
71
  };
72
+ _xrefProbed = true; // require itself failed; nothing left to probe
73
+ }
74
+
75
+ // Probe the catalog (parse it, surface any load error) LAZILY on first need
76
+ // rather than at module load. The probe parses the ~2.6MB CVE catalog (~8.5ms);
77
+ // doing it eagerly charged that to every cheap verb (brief/plan/look/ask/lint/
78
+ // discover) that never analyzes. run() calls this before the analyze path, so
79
+ // a corrupt catalog still surfaces as blocked_by:'catalog_corrupt' before
80
+ // analyze — just not on verbs that don't touch the catalog. Memoized: probes
81
+ // at most once per process.
82
+ function getXrefLoadError() {
83
+ if (_xrefProbed) return _xrefLoadError;
84
+ _xrefProbed = true;
85
+ try {
86
+ try { xref.byCve('__exceptd-probe__'); } catch { /* force the lazy catalog load */ }
87
+ if (typeof xref.getLoadErrors === 'function') {
88
+ const errs = xref.getLoadErrors();
89
+ if (errs && errs.length) {
90
+ _xrefLoadError = `${errs.length} catalog/index load error(s): ${errs.map(e => `${e.file}: ${e.error}`).join('; ')}`;
91
+ }
92
+ }
93
+ } catch (e) {
94
+ _xrefLoadError = (e && e.message) ? String(e.message) : String(e);
95
+ }
96
+ return _xrefLoadError;
83
97
  }
84
98
 
85
99
  const ROOT = path.join(__dirname, '..');
@@ -2059,10 +2073,15 @@ function sarifLocationsForIndicator(playbook, indicator) {
2059
2073
  if (ev && ev.length) {
2060
2074
  const locs = [];
2061
2075
  for (const e of ev) {
2076
+ // SARIF artifactLocation.uri is a URI reference (RFC 3986) — the path
2077
+ // separator must be `/`. Operator/agent-supplied evidence on Windows
2078
+ // arrives with backslashes; normalize them so the emitted SARIF is
2079
+ // valid (the collectors already normalize via buildEvidenceLocations,
2080
+ // but submission-threaded locations bypass that path).
2062
2081
  if (typeof e === "string" && e.trim()) {
2063
- locs.push({ physicalLocation: { artifactLocation: { uri: e.trim() } } });
2082
+ locs.push({ physicalLocation: { artifactLocation: { uri: e.trim().replace(/\\/g, "/") } } });
2064
2083
  } else if (e && typeof e === "object" && typeof e.uri === "string" && e.uri.trim()) {
2065
- const pl = { artifactLocation: { uri: e.uri.trim() } };
2084
+ const pl = { artifactLocation: { uri: e.uri.trim().replace(/\\/g, "/") } };
2066
2085
  if (Number.isInteger(e.startLine) && e.startLine > 0) {
2067
2086
  pl.region = { startLine: e.startLine, ...(Number.isInteger(e.endLine) && e.endLine >= e.startLine ? { endLine: e.endLine } : {}) };
2068
2087
  }
@@ -2381,6 +2400,14 @@ function buildEvidenceBundle(format, playbook, analyze, validate, agentSignals,
2381
2400
  }
2382
2401
  }] : [];
2383
2402
  const base = {
2403
+ // CSAF Profile 4 (security_advisory) mandatory test 6.1.27.5 requires
2404
+ // every /vulnerabilities[] item to carry `notes`. Without this the
2405
+ // CVE-keyed entries failed strict validation (the indicator pseudo-CVE
2406
+ // entries already carried notes; real-CVE entries did not).
2407
+ notes: [{
2408
+ category: 'description',
2409
+ text: `${c.cve_id}: RWEP ${c.rwep}${c.active_exploitation ? `, active_exploitation=${c.active_exploitation}` : ''}${c.cisa_kev ? ', CISA KEV' : ''}.`,
2410
+ }],
2384
2411
  scores,
2385
2412
  threats: c.active_exploitation === 'confirmed' ? [{ category: 'exploit_status', details: `Active exploitation confirmed${c.cisa_kev ? ' (CISA KEV)' : ''}.` }] : [],
2386
2413
  remediations,
@@ -2526,6 +2553,13 @@ function buildEvidenceBundle(format, playbook, analyze, validate, agentSignals,
2526
2553
  publisher: publisherBlock,
2527
2554
  title: `exceptd finding: ${playbook.domain.name} (${analyze.matched_cves.length} CVE(s), ${indicatorHits.length} indicator hit(s), ${(analyze.framework_gap_mapping || []).length} framework gap(s))`,
2528
2555
  notes: [...namespaceFallbackNote, ...gapNotes],
2556
+ // Profile 3 (csaf_informational_advisory) mandatory test 6.1.27.2
2557
+ // requires /document/references with at least one `external` item. A
2558
+ // security_advisory carries its references inside the vulnerabilities,
2559
+ // so add this only for the informational (clean-run) profile.
2560
+ ...(csafCategory === 'csaf_informational_advisory' ? {
2561
+ references: [{ category: 'external', summary: `exceptd playbook: ${playbook._meta.id}`, url: `https://exceptd.com/playbooks/${playbook._meta.id}` }],
2562
+ } : {}),
2529
2563
  ...(csafDistribution ? { distribution: csafDistribution } : {}),
2530
2564
  tracking: {
2531
2565
  // F2/F9: CSAF tracking.id binds to the run's session_id (threaded
@@ -2545,35 +2579,42 @@ function buildEvidenceBundle(format, playbook, analyze, validate, agentSignals,
2545
2579
  },
2546
2580
  initial_release_date: now,
2547
2581
  current_release_date: now,
2548
- revision_history: [{ number: '1', date: now, summary: 'Initial finding emission' }]
2582
+ // CSAF 6.1.30 requires homogeneous versioning (all version_t use the
2583
+ // same scheme) and 6.1.16 requires tracking.version === the last
2584
+ // revision_history number. tracking.version is the playbook semver,
2585
+ // so the single revision entry must carry that same semver (not the
2586
+ // integer "1", which mixed schemes AND mismatched the version).
2587
+ revision_history: [{ number: playbook._meta.version, date: now, summary: 'Initial finding emission' }]
2549
2588
  }
2550
2589
  },
2551
- product_tree: (function () {
2552
- // Synthesize a 3-level branches tree (vendor product → version)
2553
- // from catalog data. CSAF §3.1.5.1 makes branches[] strongly
2554
- // recommended for csaf_security_advisory documents because NVD /
2555
- // ENISA / Red Hat dashboards render the affected-product list off
2556
- // the branches tree, not full_product_names[]. The pre-fix tree
2557
- // emitted only the synthetic exceptd-target product and operators
2558
- // browsing the rendered advisory saw no real-world vendor surface.
2559
- const { branches } = buildCsafBranches(analyze.matched_cves || [], runOpts);
2560
- const tree = { full_product_names: fullProductNames };
2561
- if (branches.length > 0) tree.branches = branches;
2562
- return tree;
2563
- })(),
2564
- vulnerabilities: (function () {
2565
- // v0.12.27: deterministic mode sorts vulnerabilities[] by their
2566
- // primary identifier (cve_id for CVE entries, ids[0].text otherwise)
2567
- // ascending. Default mode preserves insertion order so existing
2568
- // operators see byte-identical output to pre-v0.12.27.
2569
- const all = [...cveVulns, ...indicatorVulns];
2570
- if (runOpts && runOpts.bundleDeterministic === true) {
2571
- const keyOf = (v) => (typeof v.cve === 'string' && v.cve)
2572
- || (Array.isArray(v.ids) && v.ids[0] && typeof v.ids[0].text === 'string' ? v.ids[0].text : '');
2573
- return all.slice().sort((a, b) => keyOf(a).localeCompare(keyOf(b)));
2574
- }
2575
- return all;
2576
- })(),
2590
+ // CSAF Profile 3 (informational_advisory) forbids /vulnerabilities
2591
+ // (mandatory test 6.1.27.3) and an informational advisory carrying a
2592
+ // /product_tree is misleading (§4.3 a reader must assume every named
2593
+ // product is affected). Emit both ONLY for the security_advisory profile;
2594
+ // a clean run's informational advisory carries neither.
2595
+ ...(csafCategory === 'csaf_security_advisory' ? {
2596
+ product_tree: (function () {
2597
+ // Synthesize a 3-level branches tree (vendor product → version)
2598
+ // from catalog data. CSAF §3.1.5.1 makes branches[] strongly
2599
+ // recommended because NVD / ENISA / Red Hat dashboards render the
2600
+ // affected-product list off the branches tree, not full_product_names[].
2601
+ const { branches } = buildCsafBranches(analyze.matched_cves || [], runOpts);
2602
+ const tree = { full_product_names: fullProductNames };
2603
+ if (branches.length > 0) tree.branches = branches;
2604
+ return tree;
2605
+ })(),
2606
+ vulnerabilities: (function () {
2607
+ // v0.12.27: deterministic mode sorts vulnerabilities[] by their
2608
+ // primary identifier ascending; default mode preserves insertion order.
2609
+ const all = [...cveVulns, ...indicatorVulns];
2610
+ if (runOpts && runOpts.bundleDeterministic === true) {
2611
+ const keyOf = (v) => (typeof v.cve === 'string' && v.cve)
2612
+ || (Array.isArray(v.ids) && v.ids[0] && typeof v.ids[0].text === 'string' ? v.ids[0].text : '');
2613
+ return all.slice().sort((a, b) => keyOf(a).localeCompare(keyOf(b)));
2614
+ }
2615
+ return all;
2616
+ })(),
2617
+ } : {}),
2577
2618
  exceptd_extension: {
2578
2619
  classification: analyze._detect_classification,
2579
2620
  rwep: analyze.rwep,
@@ -2652,9 +2693,12 @@ function buildEvidenceBundle(format, playbook, analyze, validate, agentSignals,
2652
2693
  const gapResults = (analyze.framework_gap_mapping || []).map((g, idx) => ({
2653
2694
  ruleId: `${rulePrefix}framework-gap-${idx}`,
2654
2695
  // Framework gaps are control-design observations, not vulnerabilities —
2655
- // SARIF §3.27.9 `kind: informational` routes them appropriately.
2696
+ // SARIF §3.27.9 `kind: informational` routes them appropriately. That
2697
+ // same clause requires: when kind !== 'fail', a present `level` SHALL be
2698
+ // 'none'. Pairing 'informational' with 'note' is a schema violation that
2699
+ // strict validators / GitHub code-scanning reject — use 'none'.
2656
2700
  kind: 'informational',
2657
- level: 'note',
2701
+ level: 'none',
2658
2702
  message: { text: `${g.framework}: ${g.claimed_control} — ${g.actual_gap}${g.required_control ? '. Required: ' + g.required_control : ''}` },
2659
2703
  properties: stripNulls({ kind: 'framework_gap', framework: g.framework, control: g.claimed_control }),
2660
2704
  }));
@@ -3092,13 +3136,16 @@ function autoDetectPreconditions(submission, playbook) {
3092
3136
  }
3093
3137
 
3094
3138
  function run(playbookId, directiveId, agentSubmission = {}, runOpts = {}) {
3095
- // Catalog corruption surfaced at module-load blocks runs cleanly.
3096
- if (_xrefLoadError) {
3139
+ // Catalog corruption blocks runs cleanly. Probed lazily here (before the
3140
+ // analyze path) rather than at module load, so cheap verbs don't pay the
3141
+ // catalog-parse cost.
3142
+ const xrefErr = getXrefLoadError();
3143
+ if (xrefErr) {
3097
3144
  return {
3098
3145
  ok: false,
3099
3146
  blocked_by: 'catalog_corrupt',
3100
- error: _xrefLoadError,
3101
- reason: 'cve-catalog.json or an index could not be parsed at module load. Run `npm run build-indexes` to regenerate, or restore the file from git.'
3147
+ error: xrefErr,
3148
+ reason: 'cve-catalog.json or an index could not be parsed. Run `npm run build-indexes` to regenerate, or restore the file from git.'
3102
3149
  };
3103
3150
  }
3104
3151
 
package/manifest.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "exceptd-security",
3
- "version": "0.14.14",
3
+ "version": "0.14.16",
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-27T22:43:04.125Z",
56
+ "signed_at": "2026-05-28T00:39:05.548Z",
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-27T22:43:04.127Z",
126
+ "signed_at": "2026-05-28T00:39:05.549Z",
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-27T22:43:04.127Z",
199
+ "signed_at": "2026-05-28T00:39:05.550Z",
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-27T22:43:04.127Z"
251
+ "signed_at": "2026-05-28T00:39:05.550Z"
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-27T22:43:04.128Z"
282
+ "signed_at": "2026-05-28T00:39:05.551Z"
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-27T22:43:04.128Z"
311
+ "signed_at": "2026-05-28T00:39:05.551Z"
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-27T22:43:04.129Z",
348
+ "signed_at": "2026-05-28T00:39:05.552Z",
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-27T22:43:04.129Z",
408
+ "signed_at": "2026-05-28T00:39:05.552Z",
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-27T22:43:04.129Z",
443
+ "signed_at": "2026-05-28T00:39:05.552Z",
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-27T22:43:04.130Z",
477
+ "signed_at": "2026-05-28T00:39:05.553Z",
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-27T22:43:04.130Z"
516
+ "signed_at": "2026-05-28T00:39:05.553Z"
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-27T22:43:04.131Z",
543
+ "signed_at": "2026-05-28T00:39:05.553Z",
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-27T22:43:04.131Z",
607
+ "signed_at": "2026-05-28T00:39:05.554Z",
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-27T22:43:04.131Z"
655
+ "signed_at": "2026-05-28T00:39:05.554Z"
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-27T22:43:04.132Z",
692
+ "signed_at": "2026-05-28T00:39:05.554Z",
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-27T22:43:04.132Z"
727
+ "signed_at": "2026-05-28T00:39:05.555Z"
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-27T22:43:04.132Z"
799
+ "signed_at": "2026-05-28T00:39:05.555Z"
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-27T22:43:04.133Z"
859
+ "signed_at": "2026-05-28T00:39:05.556Z"
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-27T22:43:04.133Z"
934
+ "signed_at": "2026-05-28T00:39:05.556Z"
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-27T22:43:04.133Z"
1013
+ "signed_at": "2026-05-28T00:39:05.556Z"
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-27T22:43:04.133Z"
1070
+ "signed_at": "2026-05-28T00:39:05.557Z"
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-27T22:43:04.134Z"
1137
+ "signed_at": "2026-05-28T00:39:05.557Z"
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-27T22:43:04.134Z"
1193
+ "signed_at": "2026-05-28T00:39:05.557Z"
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-27T22:43:04.135Z"
1245
+ "signed_at": "2026-05-28T00:39:05.558Z"
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-27T22:43:04.135Z"
1295
+ "signed_at": "2026-05-28T00:39:05.558Z"
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-27T22:43:04.135Z",
1369
+ "signed_at": "2026-05-28T00:39:05.558Z",
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-27T22:43:04.136Z"
1422
+ "signed_at": "2026-05-28T00:39:05.559Z"
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-27T22:43:04.136Z"
1482
+ "signed_at": "2026-05-28T00:39:05.559Z"
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-27T22:43:04.137Z"
1563
+ "signed_at": "2026-05-28T00:39:05.560Z"
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-27T22:43:04.137Z"
1632
+ "signed_at": "2026-05-28T00:39:05.560Z"
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-27T22:43:04.137Z"
1697
+ "signed_at": "2026-05-28T00:39:05.561Z"
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-27T22:43:04.138Z"
1783
+ "signed_at": "2026-05-28T00:39:05.561Z"
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-27T22:43:04.138Z",
1852
+ "signed_at": "2026-05-28T00:39:05.561Z",
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-27T22:43:04.138Z"
1938
+ "signed_at": "2026-05-28T00:39:05.562Z"
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-27T22:43:04.139Z",
2000
+ "signed_at": "2026-05-28T00:39:05.562Z",
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-27T22:43:04.139Z"
2074
+ "signed_at": "2026-05-28T00:39:05.562Z"
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-27T22:43:04.139Z"
2136
+ "signed_at": "2026-05-28T00:39:05.563Z"
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-27T22:43:04.140Z"
2216
+ "signed_at": "2026-05-28T00:39:05.563Z"
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-27T22:43:04.140Z"
2269
+ "signed_at": "2026-05-28T00:39:05.563Z"
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-27T22:43:04.141Z"
2337
+ "signed_at": "2026-05-28T00:39:05.564Z"
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-27T22:43:04.141Z",
2417
+ "signed_at": "2026-05-28T00:39:05.564Z",
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-27T22:43:04.141Z",
2511
+ "signed_at": "2026-05-28T00:39:05.565Z",
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": "VaHQUp8N+B8LWN2cE5Ma/r5QUuCeeXd1qQZRVdxMsZuywrBbCgET5iiP8miDXVAVw6I/bKVxUeBQzPDzbgTCBQ=="
2529
+ "signature_base64": "5XgJWZief+wIcS9RkP9YY046RNPg0KgTo1qMKdaohFusNzSR56iLCWhVtjahNauI3aJEhf2lIzbAWYwB4NNJDg=="
2530
2530
  }
2531
2531
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/exceptd-skills",
3
- "version": "0.14.14",
3
+ "version": "0.14.16",
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:09ce22ed-cf4c-428a-b4ce-4ed39f08cf46",
4
+ "serialNumber": "urn:uuid:dd65cbaf-accd-4874-9598-b38d493f40ea",
5
5
  "version": 1,
6
6
  "metadata": {
7
- "timestamp": "2031-03-19T23:38:21.000Z",
7
+ "timestamp": "2143-09-16T03:46:55.000Z",
8
8
  "tools": [
9
9
  {
10
10
  "vendor": "blamejs",
11
11
  "name": "scripts/refresh-sbom.js",
12
- "version": "0.14.14"
12
+ "version": "0.14.16"
13
13
  }
14
14
  ],
15
15
  "component": {
16
- "bom-ref": "pkg:npm/@blamejs/exceptd-skills@0.14.14",
16
+ "bom-ref": "pkg:npm/@blamejs/exceptd-skills@0.14.16",
17
17
  "type": "application",
18
18
  "name": "@blamejs/exceptd-skills",
19
- "version": "0.14.14",
19
+ "version": "0.14.16",
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.14",
28
+ "purl": "pkg:npm/%40blamejs/exceptd-skills@0.14.16",
29
29
  "hashes": [
30
30
  {
31
31
  "alg": "SHA-256",
32
- "content": "c662ffd7c9591378303810e656ef236bc95461441b0e100888fe51590f04a81e"
32
+ "content": "2629cbd949ba4f156d636a66378b5ee16452c0a66c5e80c4108f1e00a339ce94"
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.14"
38
+ "url": "https://www.npmjs.com/package/@blamejs/exceptd-skills/v/0.14.16"
39
39
  },
40
40
  {
41
41
  "type": "vcs",
@@ -116,11 +116,11 @@
116
116
  "hashes": [
117
117
  {
118
118
  "alg": "SHA-256",
119
- "content": "35200404f3ec807af037296532675b3b89d4edfde5be7906f8f51de79e64b9c1"
119
+ "content": "ba2a7fc91305512c7999cc4b182f380abab7f8b0c8cb85860b47d50752fc92ff"
120
120
  },
121
121
  {
122
122
  "alg": "SHA3-512",
123
- "content": "791909b843163e1295f359e24b1928c2f592f33a99795a1cb0fc73c8282715b78eb4696e30b48a61f63cb5651da945fd11b1707c91a53cea80723fab7553bcec"
123
+ "content": "223a551cbffa38877a96d4f99154f40760251bdd0743078ff5f9d08b242ea693db45b918a40ef8d2b47ec38343af3821404469e28d1bae0625873f7b6115d161"
124
124
  }
125
125
  ]
126
126
  },
@@ -281,11 +281,11 @@
281
281
  "hashes": [
282
282
  {
283
283
  "alg": "SHA-256",
284
- "content": "447e6ba93e05120de060cca6636c8419c465034744d93606bbe6c7175be1a844"
284
+ "content": "2c367e9db7eef475f30986ae46c79751bc5ab240cbad256968d3784afc9371ed"
285
285
  },
286
286
  {
287
287
  "alg": "SHA3-512",
288
- "content": "23f28a5eefab97d5ad447033826279f34424a728123a2a2c6e551fea601504f06fd9b2860b718e515a7e31bfa1a9cb349bffb926a91c63682753c0c979826db6"
288
+ "content": "deed7361c53828191123c2d2261987b823fc69b1b830329ce9c20ea065b5f96fbd955b0640da830dd56a5e7c9ddafcb62a02889e3a05c544a3b0c1cd15c9ecd7"
289
289
  }
290
290
  ]
291
291
  },
@@ -941,11 +941,11 @@
941
941
  "hashes": [
942
942
  {
943
943
  "alg": "SHA-256",
944
- "content": "a06fe534a30436f45d9dfd6c48c31a0a23e69bb1af485e099780421d74fe5070"
944
+ "content": "bdb569b022a878bce913771058c93982dfb928eb12a8ecb5ef7b91eff5745f79"
945
945
  },
946
946
  {
947
947
  "alg": "SHA3-512",
948
- "content": "7013e0fb0a969d472e88d589b354c08b2455a6b20e104a13d3251fa1d75b2d79bf61eb0d625fcf4a11f6d5d5322ada3a969a8c45341a7f684a8062306f881683"
948
+ "content": "1a3c169c1e8306034f67c73561d3ffc141bf5320bd15515ea34d2a5edd40f93e78733c6c7be48a3f2c2d69d7af046ba0767a237a4b397812b1e763f457637a7e"
949
949
  }
950
950
  ]
951
951
  },
@@ -956,11 +956,11 @@
956
956
  "hashes": [
957
957
  {
958
958
  "alg": "SHA-256",
959
- "content": "bbf451e8828b34b6dfad052a80bb41d6d41c823d0896b7b0af88adc1bc3d0363"
959
+ "content": "93be4afb6c08035210b4c67a223ff8dba68ad031d8cc11c9191bc4dc60f9184f"
960
960
  },
961
961
  {
962
962
  "alg": "SHA3-512",
963
- "content": "3cc1ae78ab1fd3995140adf5daad3a276c5c191e37f12183ea4cd1d05faed9b3f52bf7cc028dad36d5bfc347b081ff69f7311b91a70f9f5f235e13297e84846a"
963
+ "content": "2d1f0b4b959cb78746e47eeffb71af1f323fdb3c521f60d341b4878feffcc458527ea48b9cda545302e0d41427d1c286524d45239195d4d299a912aca4ddf695"
964
964
  }
965
965
  ]
966
966
  },
@@ -986,11 +986,11 @@
986
986
  "hashes": [
987
987
  {
988
988
  "alg": "SHA-256",
989
- "content": "745a5d013aa95ae317cf522c14a7121167903a2f2c402f9893d4b11bfce32aa0"
989
+ "content": "fbf7e5ded16143dea47d9fb1862a8b2367f32429e2c51a7d01e66846d264f211"
990
990
  },
991
991
  {
992
992
  "alg": "SHA3-512",
993
- "content": "a74c353e98c4e52c87c7040adfcb940462d848cc3980c72201c9f4d5cd03fb7ad0a6aa6c594985bcc689eed1e68602ee91dbcabc5e9d97a3819f9e409f7e4690"
993
+ "content": "c588a0e877b19974033ef5c3bc98dcb4723497b0916979fa31dda2faf2b74d99d8fedfda3a911e60ce50bf4b0eaa0b9df794e9b088cfdb04e258c7c8c1ff3727"
994
994
  }
995
995
  ]
996
996
  },
@@ -1106,11 +1106,11 @@
1106
1106
  "hashes": [
1107
1107
  {
1108
1108
  "alg": "SHA-256",
1109
- "content": "ee834a396114c8fb409442dc704be8abe3b39f3174dd5c54367790dd4380d8c6"
1109
+ "content": "a39b8a03b89093ad02faa3d106bb65f6a42e959297add8ee4d72c1f0254aaad9"
1110
1110
  },
1111
1111
  {
1112
1112
  "alg": "SHA3-512",
1113
- "content": "4634de496b75b163d26f652bd86b3cfd7434179b99f8b07db6029bd0eb7214ca482474c99647ae776ee8edfb1802b78a4e6cf734beef74f8c5a0d038049937ad"
1113
+ "content": "9229f5a214596e323a6c9a6984acf77e281414723558652677334d1d6d95f5f30250d110c6089425deb487dcbd3dcd29b66422dfd76444f79fbcc87bcb68a3dc"
1114
1114
  }
1115
1115
  ]
1116
1116
  },
@@ -1121,11 +1121,11 @@
1121
1121
  "hashes": [
1122
1122
  {
1123
1123
  "alg": "SHA-256",
1124
- "content": "8154e051408a6b58f328865973c6e57666d89b8a507ebee919bb78ed68310885"
1124
+ "content": "3fb88752e34869fec0d16bafcff3cff0f8b6391303b708b2a49269243292bd30"
1125
1125
  },
1126
1126
  {
1127
1127
  "alg": "SHA3-512",
1128
- "content": "0cd887333d4f82b2a7cb6bd71753b9db64b1776b84a2b490bf0b66e262d52f667e29ae603afb14a291f0bd39905469775a6143b495493cd01518caf59196bef2"
1128
+ "content": "ee158088d12cb2f4dc8de6509dbfb113602b18e191ca31350787795b373bce346985649c10ff2a6d7c61b828bca5a2ffa2555403ac682ebeeb946ef3164382ca"
1129
1129
  }
1130
1130
  ]
1131
1131
  },
@@ -1136,11 +1136,11 @@
1136
1136
  "hashes": [
1137
1137
  {
1138
1138
  "alg": "SHA-256",
1139
- "content": "430530120fad69dfa7d61925a205657eb403e21c74e1453421004f6a5e7cf165"
1139
+ "content": "30bf5cc91adfcdec053c1680aa4d54dff4d8c74d0f2b93b03cc4f7f5a76a47d8"
1140
1140
  },
1141
1141
  {
1142
1142
  "alg": "SHA3-512",
1143
- "content": "6b91946a3c383a992170afa1f0c770701cef97c5cc9ddeb9c181e9684516c50741928f0887e4431c4f26b32f47d3cf45e602318d15df4da2d4fe957c77960e45"
1143
+ "content": "7ad94c3de7a88a6d1e1c4671235c3aece4c911e21c7b36e42514920814dd5191110e49eec3bd2f41fd329f5fd67832b101d40cbc031dbd274fb52b8d33446df0"
1144
1144
  }
1145
1145
  ]
1146
1146
  },
@@ -1316,11 +1316,11 @@
1316
1316
  "hashes": [
1317
1317
  {
1318
1318
  "alg": "SHA-256",
1319
- "content": "9c32c19d0d16a52b1d2d9e44f5f74f27b0c68f7869373b63d1b29c9ed43c36a8"
1319
+ "content": "529c572a1e22087e97f3f78c611bac9110519e39fedda70809b16b784b66d830"
1320
1320
  },
1321
1321
  {
1322
1322
  "alg": "SHA3-512",
1323
- "content": "d711a870f6b4a91b78efd2d883df0c1df8cd82547aedd3affce2c7ab4a16832c67a6c40b9dc478dc55cd932cde1105de42231f560f7580ba881fecd3f488945c"
1323
+ "content": "d2f7fdee66ffd8c0fca45832654568d9fa90abc6ba4d5e6bedf513c472f3941fd6858d5af4962e6e13e7498ed89603d549ea45a2d41449bbb9b073c0e60fcb63"
1324
1324
  }
1325
1325
  ]
1326
1326
  },
@@ -1751,11 +1751,11 @@
1751
1751
  "hashes": [
1752
1752
  {
1753
1753
  "alg": "SHA-256",
1754
- "content": "008292f92fc956004d340bf314748a28ab03a6f14c88ddf7f5deabdab8b14b4d"
1754
+ "content": "53aaca262d4df7bf007660d881cd042cec47f11bb792f3ab77bed752b875373b"
1755
1755
  },
1756
1756
  {
1757
1757
  "alg": "SHA3-512",
1758
- "content": "99f7d34bc0516df27402740184eb99a961e3d8db7929187df1bab453f0a9356029753246d8efcf003bda0ccd57caa29a51c04c8e4fe2f20a6e385f743dc6f9cc"
1758
+ "content": "69fa66c1de4f25b5f42850b498c51c476c05795d0ab1dd59b026e16f7bff455411462f0a6b66dc01de4cf079b6f9939a69aca3c4e4dcbc2e49e182e702fb60d5"
1759
1759
  }
1760
1760
  ]
1761
1761
  },