@blamejs/exceptd-skills 0.11.2 → 0.11.4

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,65 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.11.4 — 2026-05-12
4
+
5
+ **Patch: high-impact #71 fix + items 72-77.**
6
+
7
+ ### Critical fix
8
+
9
+ - **#71 detect didn't accept indicator-result synonyms.** Operators submitting flat-shape evidence with `observation.result: "no_hit"` (the standard vocabulary for years of CI/security tooling) hit the runner's strict `hit|miss|inconclusive` set, falsed every comparison, and ended up with `classification: "inconclusive"` regardless of evidence. This silently broke the new flat-shape submission UX that v0.11.0/v0.11.3 was built around. Same evidence in the legacy `signal_overrides` shape produced the correct `not_detected` verdict.
10
+
11
+ Fix: a `canonicalize()` step in both `normalizeSubmission` and `detect()` maps `no_hit`/`no-hit`/`clean`/`clear`/`not_hit`/`ok`/`pass`/`negative`/`false` → `miss`; `hit`/`detected`/`positive`/`true` → `hit`; `inconclusive`/`unknown`/`unverified`/`null` → `inconclusive`. Operator vocabulary is now normalized to the engine's canonical 3-value set at submission boundary.
12
+
13
+ - **#77 CSAF/OpenVEX bundles auto-fixed.** Downstream of #71: now that detect actually processes signal_overrides correctly, the per-CVE statements in `bundle.vulnerabilities` / `statements` populate when there are matched_cves.
14
+
15
+ ### Bugs
16
+
17
+ - **#72 ci --format silently ignored.** `exceptd ci --scope code --format summary` and the bare command emitted byte-identical full bundles (~350 KB). CI gates couldn't get a compact verdict without piping through jq. Now ci honors `--format summary|markdown|csaf-2.0|sarif|openvex` with the same shortcuts as `run --format`. Summary is a single-line JSON with `session_id + playbooks_run + verdict + counts`.
18
+ - **#73 `indicators_evaluated` type changed silently.** v0.11.3 introduced it as an integer count; downstream consumers iterating `for i in detect.indicators_evaluated` crashed. Restored to an array of `{signal_id, outcome, confidence}`. Added `indicators_evaluated_count` as a peer field for callers wanting the integer.
19
+ - **#76 `ci --format garbage` silent empty stdout.** Invalid format values now return `{ok:false, error, verb:"ci"}` JSON to stderr with exit 2, matching the unified error shape.
20
+
21
+ ### Not addressed in this patch
22
+
23
+ - **#74 default-output flip still incomplete.** `emit()` indents JSON when stdout is a TTY (improvement over compact), but `brief`/`run`/`attest list`/`lint` still emit JSON, not a custom human form. The richer data on `brief`/`run` doesn't have a natural compact human view. Indented-JSON-on-TTY ships as the v0.11.x answer; a true human renderer per verb is deferred. `discover`/`doctor`/`ask`/`refresh` continue with their custom renderers.
24
+ - **#75 preflight-blocked exit 0 for warn-level.** `on_fail: halt` preconditions correctly exit 1; `on_fail: warn` preconditions correctly exit 0 with `preflight_issues` populated. The operator wants warn-level to also fail CI — that's a `--strict-preconditions` flag, deferred to v0.11.5. Today: use `exceptd ci` for CI gates (correctly maps detected/escalate to exit 2); `run` is for single-investigation invocations where warn-level info is appropriate.
25
+
26
+ ### Already shipped (cross-referenced)
27
+
28
+ - #78 `doctor --fix` (v0.11.2).
29
+
30
+ ## 0.11.3 — 2026-05-12
31
+
32
+ **Patch: operator-reported item #71 + full feature audit findings.**
33
+
34
+ A full audit across v0.10.0 → v0.11.2 features (64 surface elements: bug fixes, new verbs, flags, output formats, integration paths) confirmed 62/64 work as documented; this release fixes the 2 real gaps the audit found plus closes operator-reported #71.
35
+
36
+ ### Bugs
37
+
38
+ - **#71 lint accepted half-shape submissions the runner couldn't drive detect with.** Operators submitting flat-shape evidence with `observations: { "<artifact-id>": { captured, value } }` (no `indicator + result` inline) passed lint with zero warnings, then got `detect.classification: "inconclusive"` from the runner because nothing drove indicator decisions. The flat-shape migration was half-complete: validator accepted the new shape; runner couldn't consume it.
39
+
40
+ Fixes:
41
+ - **Lint** now warns `observation_lacks_indicator_result` per captured artifact that lacks `indicator + result` AND no `verdict.classification` is supplied, plus an `info` saying "detect will be inconclusive". Operators see the gap before paying the run cost.
42
+ - **`normalizeSubmission`** previously bailed when the submission already had any nested key (`signals`, `artifacts`, `signal_overrides`) — including when the CLI itself had injected `signals._bundle_formats` for `--format` support. Now shape detection prioritizes `observations` / `verdict` and merges any pre-existing nested keys into the normalized output.
43
+ - **`detect` output** surfaces `observations_received`, `signals_received`, `indicators_evaluated`, `classification_override_applied`, and `submission_shape_seen` so operators can see exactly what the runner consumed from their submission. Pre-0.11.3 an inconclusive verdict was opaque.
44
+
45
+ - **`attest export --format csaf` was a no-op.** The `--format` flag is registered as a multi-flag (returning an array), but the export subverb compared `format === "csaf"` directly against the array, falsing every time. Operators always got the plain redacted-JSON export regardless of the flag. Now unwrapped + normalizes `csaf-2.0` → `csaf` so both shortcuts hit the CSAF envelope path.
46
+
47
+ ### Audit pass — verified working as documented
48
+
49
+ Smoke-tested 64 features across v0.10.0–v0.11.2. The full list:
50
+
51
+ - **Bug regressions:** skill not-found JSON, unknown-command JSON, prefetch --quiet summary, validate-cves --offline, --mode validation, --session-key hex validation, framework-gap NIST normalization, default-stdin on pipe, --json-stdout-only stderr silence, mutex lockfile released after run, session-id collision refusal, --operator persistence, --ack persistence, --diff-from-latest, reattest --latest.
52
+ - **Verbs:** brief (incl. --all / --phase), discover, doctor (all four sub-checks), ask (incl. synonym routing), lint (catches missing artifacts), ci (incl. --scope code alignment with discover), watch, verify-attestation alias, run-all alias, attest list/show/verify/export/diff/diff --against.
53
+ - **Run flags:** --evidence, --evidence-dir, --vex, --explain, --signal-list, --format summary/markdown/sarif/openvex (--format csaf fixed here), --diff-from-latest, --ci, --force-overwrite.
54
+ - **Attestation root:** EXCEPTD_HOME respected, --attestation-root respected, legacy + new root both scanned by `findSessionDir`.
55
+ - **Catalog tooling:** validate-cves --since filter, refresh --no-network / --indexes-only routing, report csaf envelope.
56
+ - **Flat submission shape:** verdict.classification propagates, observation + indicator + result drives detect, smart precondition auto-detect resolves cwd_readable / host.platform / agent_has_command.
57
+ - **First-run welcome.**
58
+
59
+ ### Audit pass — known false positives
60
+
61
+ - **`exceptd watch`** prints `"[orchestrator] Starting event watcher..."` not `"Listening"` — works correctly; my test string was wrong.
62
+
3
63
  ## 0.11.2 — 2026-05-12
4
64
 
5
65
  **Patch: operator-reported items 58-70 from real CLI use.**
package/bin/exceptd.js CHANGED
@@ -866,6 +866,37 @@ function cmdLint(runner, args, runOpts, pretty) {
866
866
  issues.push({ severity: "warn", kind: "unknown_observation_key", key: k });
867
867
  }
868
868
 
869
+ // #71 (v0.11.3): when a submission is flat-shape with all captured artifacts
870
+ // but no indicator+result inline and no verdict.classification — detect()
871
+ // will return "inconclusive" because nothing drives the indicator decisions.
872
+ // Lint must surface this so operators don't ship a half-shape evidence file
873
+ // that passes lint but produces an inconclusive run.
874
+ if (flat) {
875
+ const observationsWithoutIndicator = Object.entries(flat).filter(([k, v]) => {
876
+ if (!knownArtifacts.has(k)) return false; // unknown keys flagged elsewhere
877
+ if (typeof v !== "object" || v === null) return false;
878
+ const captured = v.captured !== false;
879
+ return captured && !(v.indicator && v.result);
880
+ });
881
+ const verdictClass = submission.verdict?.classification;
882
+ const verdictWillDrive = verdictClass === "clean" || verdictClass === "not_detected" || verdictClass === "detected" || verdictClass === "inconclusive";
883
+ if (observationsWithoutIndicator.length > 0 && !verdictWillDrive && Object.keys(submission.signal_overrides || {}).length === 0) {
884
+ for (const [k] of observationsWithoutIndicator) {
885
+ issues.push({
886
+ severity: "warn",
887
+ kind: "observation_lacks_indicator_result",
888
+ observation_key: k,
889
+ hint: `Artifact "${k}" captured without "indicator" + "result" fields. detect will return 'inconclusive' for this indicator. Either add { "indicator": "<id>", "result": "hit"|"miss"|"inconclusive" } per observation, OR supply verdict.classification at the submission root to drive the overall verdict.`,
890
+ });
891
+ }
892
+ issues.push({
893
+ severity: "info",
894
+ kind: "detect_will_be_inconclusive",
895
+ hint: `Flat submission shape with ${observationsWithoutIndicator.length} captured artifact(s) but no indicator+result inline and no verdict.classification. detect() will return 'inconclusive'. Run \`exceptd run ${playbookId} --signal-list\` to see the indicator IDs the playbook recognizes.`,
896
+ });
897
+ }
898
+ }
899
+
869
900
  const ok = issues.every(i => i.severity !== "error");
870
901
  emit({
871
902
  verb: "lint",
@@ -1944,7 +1975,12 @@ function cmdAttest(runner, args, runOpts, pretty) {
1944
1975
  // remediation choice, residual risk acceptance, signature. Auditors get
1945
1976
  // what they need (the verdict + proof of process) without leaking raw
1946
1977
  // captured data (which may contain PII / secret shapes).
1947
- const format = args.format || "json";
1978
+ //
1979
+ // v0.11.3: --format is registered as multi in parseArgs, so args.format
1980
+ // is an array when present. Unwrap for direct comparison.
1981
+ let formatRaw = args.format || "json";
1982
+ if (Array.isArray(formatRaw)) formatRaw = formatRaw[0];
1983
+ const format = formatRaw === "csaf-2.0" ? "csaf" : formatRaw;
1948
1984
  const redacted = attestations.map(a => ({
1949
1985
  session_id: a.session_id,
1950
1986
  playbook_id: a.playbook_id,
@@ -3030,25 +3066,53 @@ function cmdCi(runner, args, runOpts, pretty) {
3030
3066
  const rwepValues = results.map(r => r.phases?.analyze?.rwep?.adjusted ?? 0);
3031
3067
  const maxRwepObserved = rwepValues.length ? Math.max(...rwepValues) : 0;
3032
3068
 
3033
- emit({
3034
- verb: "ci",
3035
- session_id: sessionId,
3036
- playbooks_run: ids,
3037
- summary: {
3038
- total: results.length,
3039
- detected: results.filter(r => r.phases?.detect?.classification === "detected").length,
3040
- inconclusive: results.filter(r => r.phases?.detect?.classification === "inconclusive").length,
3041
- not_detected: results.filter(r => ["not_detected", "clean"].includes(r.phases?.detect?.classification)).length,
3042
- blocked: results.filter(r => r && r.ok === false).length,
3043
- max_rwep_observed: maxRwepObserved,
3044
- jurisdiction_clocks_started: results
3045
- .flatMap(r => r.phases?.close?.notification_actions || [])
3046
- .filter(n => n && n.clock_started_at != null).length,
3047
- verdict: fail ? "FAIL" : "PASS",
3048
- fail_reasons: failReasons,
3049
- },
3050
- results,
3051
- }, pretty);
3069
+ const summary = {
3070
+ total: results.length,
3071
+ detected: results.filter(r => r.phases?.detect?.classification === "detected").length,
3072
+ inconclusive: results.filter(r => r.phases?.detect?.classification === "inconclusive").length,
3073
+ not_detected: results.filter(r => ["not_detected", "clean"].includes(r.phases?.detect?.classification)).length,
3074
+ blocked: results.filter(r => r && r.ok === false).length,
3075
+ max_rwep_observed: maxRwepObserved,
3076
+ jurisdiction_clocks_started: results
3077
+ .flatMap(r => r.phases?.close?.notification_actions || [])
3078
+ .filter(n => n && n.clock_started_at != null).length,
3079
+ verdict: fail ? "FAIL" : "PASS",
3080
+ fail_reasons: failReasons,
3081
+ };
3082
+
3083
+ // v0.11.4 (#72): ci --format <fmt> previously emitted the full bundle
3084
+ // regardless of flag. Now honors the same shortcuts as `run --format`:
3085
+ // summary → one-line JSON of session + verdict + counts
3086
+ // markdown → operator-readable digest
3087
+ // csaf → CSAF 2.0 envelope wrapping every result
3088
+ // sarif → SARIF 2.1.0 with results from every playbook
3089
+ // openvex → OpenVEX statements derived from every playbook's matched_cves
3090
+ let formatRaw = args.format;
3091
+ if (Array.isArray(formatRaw)) formatRaw = formatRaw[0];
3092
+ const fmt = formatRaw === "csaf-2.0" ? "csaf" : formatRaw;
3093
+ if (fmt === "summary") {
3094
+ emit({ verb: "ci", session_id: sessionId, playbooks_run: ids, summary }, pretty);
3095
+ } else if (fmt === "markdown") {
3096
+ const lines = [`# exceptd ci summary`, `session-id: ${sessionId}`, `verdict: **${summary.verdict}**`, ``];
3097
+ lines.push(`**Playbooks run:** ${summary.total} (${summary.detected} detected, ${summary.inconclusive} inconclusive, ${summary.not_detected} clean, ${summary.blocked} blocked)`);
3098
+ lines.push(`**Max RWEP observed:** ${summary.max_rwep_observed}`);
3099
+ lines.push(`**Jurisdiction clocks started:** ${summary.jurisdiction_clocks_started}`);
3100
+ if (summary.fail_reasons.length) {
3101
+ lines.push(``, `## Fail reasons`);
3102
+ for (const r of summary.fail_reasons) lines.push(`- ${r}`);
3103
+ }
3104
+ process.stdout.write(lines.join("\n") + "\n");
3105
+ } else if (fmt === "csaf" || fmt === "sarif" || fmt === "openvex") {
3106
+ // Aggregate the per-run bundles_by_format if present.
3107
+ const bundles = results.map(r => r.phases?.close?.evidence_package?.bundles_by_format?.[fmt === "csaf" ? "csaf-2.0" : fmt]).filter(Boolean);
3108
+ emit({ verb: "ci", session_id: sessionId, format: fmt, bundles_count: bundles.length, bundles }, pretty);
3109
+ } else if (fmt && fmt !== "json") {
3110
+ // v0.11.4 (#76): garbage format rejected with structured error, not silent empty stdout.
3111
+ process.stderr.write(JSON.stringify({ ok: false, error: `ci: --format "${fmt}" not in accepted set ["summary","markdown","csaf-2.0","sarif","openvex","json"].`, verb: "ci" }) + "\n");
3112
+ process.exit(2);
3113
+ } else {
3114
+ emit({ verb: "ci", session_id: sessionId, playbooks_run: ids, summary, results }, pretty);
3115
+ }
3052
3116
  if (fail) {
3053
3117
  process.stderr.write(`[exceptd ci] FAIL: ${failReasons.join("; ")}\n`);
3054
3118
  process.exit(2);
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "schema_version": "1.1.0",
3
- "generated_at": "2026-05-12T15:32:44.669Z",
3
+ "generated_at": "2026-05-12T16:00:45.631Z",
4
4
  "generator": "scripts/build-indexes.js",
5
5
  "source_count": 49,
6
6
  "source_hashes": {
7
- "manifest.json": "13b2aa2d552d684a06d9865d94e7233438ba95129914b4fd89e15968cf544ff5",
7
+ "manifest.json": "6935c71303fe299a6a52e1776d571e30808cae14ddfadc48b27d9ac2443c423a",
8
8
  "data/atlas-ttps.json": "1500b5830dab070c4252496964a8c0948e1052a656e2c7c6e1efaf0350645e13",
9
9
  "data/cve-catalog.json": "a81d3e4b491b27ccc084596b063a6108ff10c9eb01d7776922fc393980b534fe",
10
10
  "data/cwe-catalog.json": "c3367d469b4b3d31e4c56397dd7a8305a0be338ecd85afa27804c0c9ce12157b",
@@ -304,8 +304,23 @@ function detect(playbookId, directiveId, agentSubmission = {}) {
304
304
  const artifacts = agentSubmission.artifacts || {};
305
305
  const overrides = agentSubmission.signal_overrides || {};
306
306
 
307
+ // v0.11.4 (#71): canonicalize the indicator result vocabulary. Operators
308
+ // submit shapes like "no_hit" / "clean" / "ok" / false from years of
309
+ // CI/security tooling convention; the engine internally uses
310
+ // hit | miss | inconclusive. Without canonicalization every flat-shape
311
+ // observation with result:"no_hit" silently fell through to inconclusive
312
+ // and broke per-indicator detection. Canonicalization happens here so
313
+ // both detect() and normalizeSubmission consumers see the same outcomes.
314
+ const canonicalize = (v) => {
315
+ if (v === true || v === 'hit' || v === 'detected' || v === 'positive') return 'hit';
316
+ if (v === false || v === 'miss' || v === 'no_hit' || v === 'no-hit' || v === 'clean' || v === 'clear' || v === 'not_hit' || v === 'ok' || v === 'pass' || v === 'negative') return 'miss';
317
+ if (v === 'inconclusive' || v === 'unknown' || v === 'unverified' || v === null) return 'inconclusive';
318
+ return null; // truly unknown — fall through
319
+ };
320
+
307
321
  const indicatorResults = (det.indicators || []).map(ind => {
308
- const override = overrides[ind.id];
322
+ const rawOverride = overrides[ind.id];
323
+ const override = canonicalize(rawOverride);
309
324
  let verdict;
310
325
  if (override === 'hit' || override === 'miss' || override === 'inconclusive') {
311
326
  verdict = override;
@@ -361,7 +376,19 @@ function detect(playbookId, directiveId, agentSubmission = {}) {
361
376
  indicators: indicatorResults,
362
377
  false_positive_checks_required: fpChecksRequired,
363
378
  classification,
364
- minimum_signal_basis: det.minimum_signal?.[classification === 'detected' ? 'detected' : classification === 'not_detected' ? 'not_detected' : 'inconclusive']
379
+ minimum_signal_basis: det.minimum_signal?.[classification === 'detected' ? 'detected' : classification === 'not_detected' ? 'not_detected' : 'inconclusive'],
380
+ // v0.11.3 #71: surface what detect actually consumed. Operators reading
381
+ // the detect output now see whether their flat-shape observations + the
382
+ // signal_overrides + the classification override all reached the runner.
383
+ observations_received: Object.keys(agentSubmission.artifacts || {}),
384
+ signals_received: Object.keys(agentSubmission.signal_overrides || {}),
385
+ // v0.11.4 (#73): downstream consumers iterating `indicators_evaluated`
386
+ // expect an array, not a count. Restore as array; provide
387
+ // `indicators_evaluated_count` for callers wanting the integer.
388
+ indicators_evaluated: indicatorResults.map(i => ({ signal_id: i.id, outcome: i.verdict, confidence: i.confidence })),
389
+ indicators_evaluated_count: indicatorResults.length,
390
+ classification_override_applied: validOverrides.has(override) ? (override === 'clean' ? 'not_detected' : override) : null,
391
+ submission_shape_seen: agentSubmission._original_shape || (agentSubmission.artifacts ? 'nested (v0.10.x)' : 'empty')
365
392
  };
366
393
  }
367
394
 
@@ -884,13 +911,42 @@ function buildEvidenceBundle(format, playbook, analyze, validate, agentSignals)
884
911
  */
885
912
  function normalizeSubmission(submission, playbook) {
886
913
  if (!submission || typeof submission !== "object") return submission || {};
887
- if (submission.artifacts || submission.signal_overrides || submission.signals) return submission;
888
- if (!submission.observations && !submission.verdict) return submission;
889
914
 
890
- const out = { artifacts: {}, signal_overrides: {}, signals: {}, precondition_checks: {} };
915
+ // v0.11.3 #71 fix: the CLI may inject `signals._bundle_formats` before
916
+ // calling normalize (for --format <fmt> support). Pre-0.11.3 normalize
917
+ // detected the injected `signals` key and bailed, leaving the flat
918
+ // `observations` / `verdict` untranslated and breaking detect. The shape
919
+ // detector now treats `observations` or `verdict` as authoritative for
920
+ // "this is flat" — even when nested keys also exist — and merges any
921
+ // pre-existing nested keys into the normalized result.
922
+ const hasFlat = submission.observations || submission.verdict;
923
+
924
+ if (!hasFlat) {
925
+ // Truly already-nested. Mark shape and return.
926
+ if (!submission._original_shape) submission._original_shape = 'nested (v0.10.x)';
927
+ return submission;
928
+ }
929
+
930
+ const out = {
931
+ artifacts: { ...(submission.artifacts || {}) },
932
+ signal_overrides: { ...(submission.signal_overrides || {}) },
933
+ signals: { ...(submission.signals || {}) },
934
+ precondition_checks: { ...(submission.precondition_checks || {}) },
935
+ _original_shape: 'flat (v0.11.0)',
936
+ };
891
937
  const knownPreconditions = new Set((playbook?._meta?.preconditions || []).map(p => p.id));
892
938
  const knownArtifacts = new Set((playbook?.phases?.look?.artifacts || []).map(a => a.id));
893
939
 
940
+ // v0.11.4 (#71): canonicalize indicator outcome strings here too so the
941
+ // signal_overrides object handed to detect() carries the runner's expected
942
+ // hit|miss|inconclusive vocabulary regardless of what the operator typed.
943
+ const canonicalizeOutcome = (v) => {
944
+ if (v === true || v === 'hit' || v === 'detected' || v === 'positive') return 'hit';
945
+ if (v === false || v === 'miss' || v === 'no_hit' || v === 'no-hit' || v === 'clean' || v === 'clear' || v === 'not_hit' || v === 'ok' || v === 'pass' || v === 'negative') return 'miss';
946
+ if (v === 'inconclusive' || v === 'unknown' || v === 'unverified' || v === null) return 'inconclusive';
947
+ return v; // leave unrecognized values for detect() to decide
948
+ };
949
+
894
950
  for (const [key, val] of Object.entries(submission.observations || {})) {
895
951
  if (knownPreconditions.has(key)) {
896
952
  out.precondition_checks[key] = val === "ok" || val === true || val === "true";
@@ -899,7 +955,9 @@ function normalizeSubmission(submission, playbook) {
899
955
  if (typeof val === "object" && val !== null) {
900
956
  const aid = knownArtifacts.has(key) ? key : (val.artifact || key);
901
957
  out.artifacts[aid] = { value: val.value, captured: val.captured !== false };
902
- if (val.indicator && val.result) out.signal_overrides[val.indicator] = val.result;
958
+ if (val.indicator && val.result !== undefined) {
959
+ out.signal_overrides[val.indicator] = canonicalizeOutcome(val.result);
960
+ }
903
961
  }
904
962
  }
905
963
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "_comment": "Auto-generated by scripts/refresh-manifest-snapshot.js — do not hand-edit. Public skill surface used by check-manifest-snapshot.js to detect breaking removals.",
3
- "_generated_at": "2026-05-12T15:31:43.757Z",
3
+ "_generated_at": "2026-05-12T15:59:29.819Z",
4
4
  "atlas_version": "5.1.0",
5
5
  "skill_count": 38,
6
6
  "skills": [
package/manifest.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "exceptd-security",
3
- "version": "0.11.2",
3
+ "version": "0.11.4",
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",
@@ -52,7 +52,7 @@
52
52
  ],
53
53
  "last_threat_review": "2026-05-01",
54
54
  "signature": "WprHkO1KOjQtCBj6/EJghBTNyNKJhn7O2HDbAQZPi5jn4flwHpSrtP8LC15a4Unoh+xiIIgGhvTHZIQFHGMpBQ==",
55
- "signed_at": "2026-05-12T15:31:43.313Z",
55
+ "signed_at": "2026-05-12T15:59:29.388Z",
56
56
  "cwe_refs": [
57
57
  "CWE-125",
58
58
  "CWE-362",
@@ -116,7 +116,7 @@
116
116
  ],
117
117
  "last_threat_review": "2026-05-01",
118
118
  "signature": "fg20bOXGRkPUdLmegeXpTM4hnzl/ArgcVc88rItZN5DdsnFnzPgUU1PwCI82zooyj2GfxJHYjxNkq5qd2zNPBg==",
119
- "signed_at": "2026-05-12T15:31:43.315Z",
119
+ "signed_at": "2026-05-12T15:59:29.390Z",
120
120
  "cwe_refs": [
121
121
  "CWE-1039",
122
122
  "CWE-1426",
@@ -179,7 +179,7 @@
179
179
  ],
180
180
  "last_threat_review": "2026-05-01",
181
181
  "signature": "6JuSzkSSFzFHEZ3ANzqjtIbKPOkwJeKhQ+8WAPB4+dTRvDSeg46n3D88XfGaNd2z7pmg/i8p9ZoImQcHFS4BCg==",
182
- "signed_at": "2026-05-12T15:31:43.315Z",
182
+ "signed_at": "2026-05-12T15:59:29.390Z",
183
183
  "cwe_refs": [
184
184
  "CWE-22",
185
185
  "CWE-345",
@@ -225,7 +225,7 @@
225
225
  "framework_gaps": [],
226
226
  "last_threat_review": "2026-05-01",
227
227
  "signature": "PYSw9abiYfW+y7IkY8udJG5LSds2a4rMimlw3rrdD0zE3vunEeV/y7oTmDD4o83OqHSCKNzF/7vMhvd/noqICQ==",
228
- "signed_at": "2026-05-12T15:31:43.316Z"
228
+ "signed_at": "2026-05-12T15:59:29.391Z"
229
229
  },
230
230
  {
231
231
  "name": "compliance-theater",
@@ -256,7 +256,7 @@
256
256
  ],
257
257
  "last_threat_review": "2026-05-01",
258
258
  "signature": "BMFmmJYP3HsHIjUqnhw8E3MiMGZJsI/eDq51we+nxUicZ8nFUQT9DhmRntAqOs6BUnsfiQNNLc/rrsNh8yg1CQ==",
259
- "signed_at": "2026-05-12T15:31:43.317Z"
259
+ "signed_at": "2026-05-12T15:59:29.392Z"
260
260
  },
261
261
  {
262
262
  "name": "exploit-scoring",
@@ -285,7 +285,7 @@
285
285
  ],
286
286
  "last_threat_review": "2026-05-01",
287
287
  "signature": "VGPyDwy5BRlpn1lZthhPB6ytb4ZcU2j0KtCZbaMkyLdMugQJtK2yEuwrsDH4yEtAhTB6/A4B3eSygJckum49Ag==",
288
- "signed_at": "2026-05-12T15:31:43.317Z"
288
+ "signed_at": "2026-05-12T15:59:29.392Z"
289
289
  },
290
290
  {
291
291
  "name": "rag-pipeline-security",
@@ -322,7 +322,7 @@
322
322
  ],
323
323
  "last_threat_review": "2026-05-01",
324
324
  "signature": "XkFGpsNnXBVslkQ48usEu9l1LjPiV2ppW+M4B63zXFBP2Puh52qYCffEPjUHYhoO5bjgTM7yCbK8XF/Dzk5wBw==",
325
- "signed_at": "2026-05-12T15:31:43.317Z",
325
+ "signed_at": "2026-05-12T15:59:29.392Z",
326
326
  "cwe_refs": [
327
327
  "CWE-1395",
328
328
  "CWE-1426"
@@ -379,7 +379,7 @@
379
379
  ],
380
380
  "last_threat_review": "2026-05-01",
381
381
  "signature": "1Xqy7Kxxy6GpTvuYJPdllPzVDRFxb7N6AuxKuoaO4v91CiZLmiXt0sTIWImKJ3p9Eup6rJNDdsY71dolFhHNBA==",
382
- "signed_at": "2026-05-12T15:31:43.317Z",
382
+ "signed_at": "2026-05-12T15:59:29.392Z",
383
383
  "d3fend_refs": [
384
384
  "D3-CA",
385
385
  "D3-CSPP",
@@ -414,7 +414,7 @@
414
414
  "framework_gaps": [],
415
415
  "last_threat_review": "2026-05-01",
416
416
  "signature": "QNLOmAL54S/Cmk4cdO4L2BCGkqZ/FgY4UBsKWtg/EEW+YXF5ev+a8XsUT8q5veuUa2VYcYna7rD1iAnE+2PDBA==",
417
- "signed_at": "2026-05-12T15:31:43.318Z",
417
+ "signed_at": "2026-05-12T15:59:29.393Z",
418
418
  "cwe_refs": [
419
419
  "CWE-1188"
420
420
  ]
@@ -442,7 +442,7 @@
442
442
  "framework_gaps": [],
443
443
  "last_threat_review": "2026-05-01",
444
444
  "signature": "aFHq4cSl3CKchnVITxx+BrAEWD33WtFFJoQtwAug5g9R3/3ABtjaXYGVQaZcdcG1AIZkMoGSPywgLQWDY7ZDCw==",
445
- "signed_at": "2026-05-12T15:31:43.318Z"
445
+ "signed_at": "2026-05-12T15:59:29.393Z"
446
446
  },
447
447
  {
448
448
  "name": "global-grc",
@@ -474,7 +474,7 @@
474
474
  "framework_gaps": [],
475
475
  "last_threat_review": "2026-05-01",
476
476
  "signature": "viCTUWdy6euvd2KTAo6sLvarK/FZkDtYGocxBt0H+fY94kLQGW8K5cSpqIWdUF5NUytSHBCiG4YcSze8P9Z/BQ==",
477
- "signed_at": "2026-05-12T15:31:43.318Z"
477
+ "signed_at": "2026-05-12T15:59:29.394Z"
478
478
  },
479
479
  {
480
480
  "name": "zeroday-gap-learn",
@@ -501,7 +501,7 @@
501
501
  "framework_gaps": [],
502
502
  "last_threat_review": "2026-05-01",
503
503
  "signature": "6PkUaHQi3Hxuqq/Jp4GYckvfqVEofmeT87NUH0T+pwyjlc+xZkoqNPn65f7ldciEPL86JIPi3/dDTKQbIFFBCw==",
504
- "signed_at": "2026-05-12T15:31:43.319Z"
504
+ "signed_at": "2026-05-12T15:59:29.394Z"
505
505
  },
506
506
  {
507
507
  "name": "pqc-first",
@@ -553,7 +553,7 @@
553
553
  ],
554
554
  "last_threat_review": "2026-05-01",
555
555
  "signature": "ZenFTEzWx+DzrSXlNXhbZ70vOdJSXfrnKkAwqMlBf5nlDf38V1/hG4XCKj43snQXWr4mVJOX6ilqFLTYNIjnBw==",
556
- "signed_at": "2026-05-12T15:31:43.319Z",
556
+ "signed_at": "2026-05-12T15:59:29.394Z",
557
557
  "cwe_refs": [
558
558
  "CWE-327"
559
559
  ],
@@ -600,7 +600,7 @@
600
600
  ],
601
601
  "last_threat_review": "2026-05-01",
602
602
  "signature": "ih0vpd2v2zS31JSJv7SnABoya8JlJdrXZXx4rBnrsV3Assj+dbjAP0pQ1HMT/5RX8yTTswRQsg0bJV3qmbJ3Bw==",
603
- "signed_at": "2026-05-12T15:31:43.320Z"
603
+ "signed_at": "2026-05-12T15:59:29.395Z"
604
604
  },
605
605
  {
606
606
  "name": "security-maturity-tiers",
@@ -637,7 +637,7 @@
637
637
  ],
638
638
  "last_threat_review": "2026-05-01",
639
639
  "signature": "Lv8dHiwIqUbNsywCCB/+pYWGF+MHCvxVn1IAvR7Cnif5fy0sICv0N4SVsSb621qAAkHNshpfxqwuhbuQnE1TBA==",
640
- "signed_at": "2026-05-12T15:31:43.320Z",
640
+ "signed_at": "2026-05-12T15:59:29.395Z",
641
641
  "cwe_refs": [
642
642
  "CWE-1188"
643
643
  ]
@@ -672,7 +672,7 @@
672
672
  "framework_gaps": [],
673
673
  "last_threat_review": "2026-05-11",
674
674
  "signature": "BS+wrL28HHYhBpe+v84VLoq9KPBXu6alfG968katfGIoLNYQueaHP931bRmlkrjfeb6qbDf067GWdPEh7nroAw==",
675
- "signed_at": "2026-05-12T15:31:43.320Z"
675
+ "signed_at": "2026-05-12T15:59:29.395Z"
676
676
  },
677
677
  {
678
678
  "name": "attack-surface-pentest",
@@ -743,7 +743,7 @@
743
743
  "PTES revision incorporating AI-surface enumeration"
744
744
  ],
745
745
  "signature": "vLhIYT/CC3IzxMRa+UPeqGSZTvthuwUeTMGNFMm37+TaEk0TtfwPrPyrBJLHw4W6Wt7+pufjHs46X3nTgzoRAg==",
746
- "signed_at": "2026-05-12T15:31:43.320Z"
746
+ "signed_at": "2026-05-12T15:59:29.396Z"
747
747
  },
748
748
  {
749
749
  "name": "fuzz-testing-strategy",
@@ -803,7 +803,7 @@
803
803
  "OSS-Fuzz-Gen / AI-assisted harness generation becoming the default expectation for OSS maintainers"
804
804
  ],
805
805
  "signature": "TOcQLy/427cuf0Lw90J7A0oIeuhUmf9NXb6tOUS5K3SazCKTJujPgYSVAPZOYf1zZrRAY/aq0iqELd5cLyk5DA==",
806
- "signed_at": "2026-05-12T15:31:43.321Z"
806
+ "signed_at": "2026-05-12T15:59:29.396Z"
807
807
  },
808
808
  {
809
809
  "name": "dlp-gap-analysis",
@@ -878,7 +878,7 @@
878
878
  "Quebec Law 25, India DPDPA, KSA PDPL enforcement actions naming AI-tool prompt data as in-scope personal information"
879
879
  ],
880
880
  "signature": "u4IN7escQa5V+OgdtaJXLdvhmNiGZsdmGOvebTLZ30WoImT+WiksvaqSa0POGdbr6HzFkALe2RrZEH9Tr0U6Dg==",
881
- "signed_at": "2026-05-12T15:31:43.321Z"
881
+ "signed_at": "2026-05-12T15:59:29.396Z"
882
882
  },
883
883
  {
884
884
  "name": "supply-chain-integrity",
@@ -955,7 +955,7 @@
955
955
  "OpenSSF model-signing — emerging Sigstore-based signing standard for ML model weights; track for production adoption"
956
956
  ],
957
957
  "signature": "eTGQJ3gnG24WggfwuFNNIFOWV/ttPxTa3pvx9OH28m5KDS1a4ZmOR7K8y01wk/su8bH0ClYYRfoBfKQOtRswAg==",
958
- "signed_at": "2026-05-12T15:31:43.321Z"
958
+ "signed_at": "2026-05-12T15:59:29.396Z"
959
959
  },
960
960
  {
961
961
  "name": "defensive-countermeasure-mapping",
@@ -1012,7 +1012,7 @@
1012
1012
  ],
1013
1013
  "last_threat_review": "2026-05-11",
1014
1014
  "signature": "q7gFLPoqf/8bqATR6gt/nj0EoyUOlfzi+bZ0bT3pC9KW7O6M/ji9fT+AXSGNp6PKd+70ACb3mkMGmWgjLpQXCg==",
1015
- "signed_at": "2026-05-12T15:31:43.322Z"
1015
+ "signed_at": "2026-05-12T15:59:29.397Z"
1016
1016
  },
1017
1017
  {
1018
1018
  "name": "identity-assurance",
@@ -1079,7 +1079,7 @@
1079
1079
  "d3fend_refs": [],
1080
1080
  "last_threat_review": "2026-05-11",
1081
1081
  "signature": "pX8rhrrzuyG3iRrPORLqTZAjzGdWK/bKPUGJG5WHSZcv4LB0kQXOit4sHG0exdXxI6HY8jyX67QY4r5vEHHACw==",
1082
- "signed_at": "2026-05-12T15:31:43.322Z"
1082
+ "signed_at": "2026-05-12T15:59:29.397Z"
1083
1083
  },
1084
1084
  {
1085
1085
  "name": "ot-ics-security",
@@ -1135,7 +1135,7 @@
1135
1135
  "d3fend_refs": [],
1136
1136
  "last_threat_review": "2026-05-11",
1137
1137
  "signature": "ypb8kNZQRdyu5mWeveB7sjCjNKXS1yXvjDJv88muzwhOs/a4Fu/Gb532js5NKyy+eCw/emrphpTZaL8R9a2lBA==",
1138
- "signed_at": "2026-05-12T15:31:43.322Z"
1138
+ "signed_at": "2026-05-12T15:59:29.397Z"
1139
1139
  },
1140
1140
  {
1141
1141
  "name": "coordinated-vuln-disclosure",
@@ -1187,7 +1187,7 @@
1187
1187
  "NYDFS 23 NYCRR 500 amendments potentially adding explicit CVD program requirements"
1188
1188
  ],
1189
1189
  "signature": "346Lt+277ycRNsyAOGwLSONi4awgxKy3hP9G+BWjwaa8ySmTeqbYsbyyhtxjeohk9bV2SF+Hl2q4JdSvc/2qCQ==",
1190
- "signed_at": "2026-05-12T15:31:43.322Z"
1190
+ "signed_at": "2026-05-12T15:59:29.398Z"
1191
1191
  },
1192
1192
  {
1193
1193
  "name": "threat-modeling-methodology",
@@ -1237,7 +1237,7 @@
1237
1237
  "PASTA v2 updates incorporating AI/ML application threats"
1238
1238
  ],
1239
1239
  "signature": "ewTvG5vu3ngFHyXgBur5vSKDFQsOZx0x79djGMricl7LCvQf5//OG6LZKXa+AOuEq58prRS+HgzrFA1DiTfeCQ==",
1240
- "signed_at": "2026-05-12T15:31:43.323Z"
1240
+ "signed_at": "2026-05-12T15:59:29.398Z"
1241
1241
  },
1242
1242
  {
1243
1243
  "name": "webapp-security",
@@ -1311,7 +1311,7 @@
1311
1311
  "d3fend_refs": [],
1312
1312
  "last_threat_review": "2026-05-11",
1313
1313
  "signature": "ZHjbKu0Em92Kimr2esL1g93mf9TmcsChBhVEMWf/lFrjeLcg8nyHEIcDstIZ3FWYgc6MQNHnc3Rup3Xp/Za1Cw==",
1314
- "signed_at": "2026-05-12T15:31:43.323Z"
1314
+ "signed_at": "2026-05-12T15:59:29.398Z"
1315
1315
  },
1316
1316
  {
1317
1317
  "name": "ai-risk-management",
@@ -1361,7 +1361,7 @@
1361
1361
  "d3fend_refs": [],
1362
1362
  "last_threat_review": "2026-05-11",
1363
1363
  "signature": "1KRxjCbAX0Rs5NTOioi1w/f1SOzDQrtRoXjTDtzEwJ+d1QzFf9cqmBlp0uXmGpL0bzEaHWIctjigSychmoL2Dw==",
1364
- "signed_at": "2026-05-12T15:31:43.323Z"
1364
+ "signed_at": "2026-05-12T15:59:29.399Z"
1365
1365
  },
1366
1366
  {
1367
1367
  "name": "sector-healthcare",
@@ -1421,7 +1421,7 @@
1421
1421
  "d3fend_refs": [],
1422
1422
  "last_threat_review": "2026-05-11",
1423
1423
  "signature": "eiajFh7w7d4g+/crGalTtw9Qsu0deVsdHkdthZSy595ifGmgu0zaFD8usKThbPhOdUCCclTYkZYz5GalQmkhCw==",
1424
- "signed_at": "2026-05-12T15:31:43.324Z"
1424
+ "signed_at": "2026-05-12T15:59:29.399Z"
1425
1425
  },
1426
1426
  {
1427
1427
  "name": "sector-financial",
@@ -1502,7 +1502,7 @@
1502
1502
  "TIBER-EU framework v2.0 alignment with DORA TLPT RTS (JC 2024/40); cross-recognition with CBEST and iCAST"
1503
1503
  ],
1504
1504
  "signature": "iSZR/fYESQVyjkcqj+O+yzU0BQfaELH5s7WizzUTWvDPDTD2ZyOnZTT1r/Zfx2l4mbPmVeFGWdYnnVFTk/i3Aw==",
1505
- "signed_at": "2026-05-12T15:31:43.324Z"
1505
+ "signed_at": "2026-05-12T15:59:29.399Z"
1506
1506
  },
1507
1507
  {
1508
1508
  "name": "sector-federal-government",
@@ -1571,7 +1571,7 @@
1571
1571
  "Australia PSPF 2024 revision and ISM quarterly updates — track for Essential Eight Maturity Level requirements for federal entities"
1572
1572
  ],
1573
1573
  "signature": "Wjdo5YXEL8XeNZkaEueG1DOUoyalstNPzQkxD/cwP5iMrJWg/Ly+sC0Oluuqm3aU7d63z55PrbGQCJD0XVZqBg==",
1574
- "signed_at": "2026-05-12T15:31:43.324Z"
1574
+ "signed_at": "2026-05-12T15:59:29.400Z"
1575
1575
  },
1576
1576
  {
1577
1577
  "name": "sector-energy",
@@ -1636,7 +1636,7 @@
1636
1636
  "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"
1637
1637
  ],
1638
1638
  "signature": "c/l7dOHe0Zj6Ag3abUaEie6o0f8M4rhY5aPI9/wG4z6FDue9PzCVw8vUGoITFgg89g97lMfy2C3CE2PegQoFCw==",
1639
- "signed_at": "2026-05-12T15:31:43.325Z"
1639
+ "signed_at": "2026-05-12T15:59:29.400Z"
1640
1640
  },
1641
1641
  {
1642
1642
  "name": "api-security",
@@ -1705,7 +1705,7 @@
1705
1705
  "d3fend_refs": [],
1706
1706
  "last_threat_review": "2026-05-11",
1707
1707
  "signature": "9FgcJvYeo07QxQ+mnVRQk4jYLDMO/AVSXMs8cueO2f/qMOTQmrhBMVhj5ze7hzvXpGkp7EK/3Q1XKqde61JMAg==",
1708
- "signed_at": "2026-05-12T15:31:43.325Z"
1708
+ "signed_at": "2026-05-12T15:59:29.400Z"
1709
1709
  },
1710
1710
  {
1711
1711
  "name": "cloud-security",
@@ -1786,7 +1786,7 @@
1786
1786
  "CISA KEV additions for cloud-control-plane CVEs (IMDSv1 abuses, federation token mishandling, cross-tenant boundary failures); CISA Cybersecurity Advisories for cross-cloud advisories"
1787
1787
  ],
1788
1788
  "signature": "xRA0XZf7VPtuBtbsm41bay9yBLphw/hlL3YxIUrpko5g9ldM3oJe9o1qSwzIj/wSnQSI29qqPpNsnlks+HEOCA==",
1789
- "signed_at": "2026-05-12T15:31:43.325Z"
1789
+ "signed_at": "2026-05-12T15:59:29.401Z"
1790
1790
  },
1791
1791
  {
1792
1792
  "name": "container-runtime-security",
@@ -1848,7 +1848,7 @@
1848
1848
  "d3fend_refs": [],
1849
1849
  "last_threat_review": "2026-05-11",
1850
1850
  "signature": "GcU50DStuN1gU/Evm/sFRgeieQbqffVp12rgbGnasRX89Q7kM4ltFXB+bgCXHIvICzYb78hPIifWQb9UVupWBQ==",
1851
- "signed_at": "2026-05-12T15:31:43.326Z"
1851
+ "signed_at": "2026-05-12T15:59:29.401Z"
1852
1852
  },
1853
1853
  {
1854
1854
  "name": "mlops-security",
@@ -1919,7 +1919,7 @@
1919
1919
  "MITRE ATLAS v5.2 — track AML.T0010 sub-technique expansion and any new MLOps-pipeline-specific TTPs"
1920
1920
  ],
1921
1921
  "signature": "onIazpFoL1t4PMNRsoF06ggnl7BzCKjt0x+ZmVfWfyt1V06DgllsrbN3AAz4+g4jW2Sc71q0vIFKfwEUWpGVAQ==",
1922
- "signed_at": "2026-05-12T15:31:43.326Z"
1922
+ "signed_at": "2026-05-12T15:59:29.401Z"
1923
1923
  },
1924
1924
  {
1925
1925
  "name": "incident-response-playbook",
@@ -1981,7 +1981,7 @@
1981
1981
  "NYDFS 23 NYCRR 500.17 amendments tightening ransom-payment 24h disclosure operationalization"
1982
1982
  ],
1983
1983
  "signature": "P0Yv4CtqbnBNP6nSIxQUYYHL7T7ci+iE7iE2UXVfnMPeWVdKG2nvRePjBXc3JZTLima1Txn/I5ocDNhLTIeUAQ==",
1984
- "signed_at": "2026-05-12T15:31:43.326Z"
1984
+ "signed_at": "2026-05-12T15:59:29.402Z"
1985
1985
  },
1986
1986
  {
1987
1987
  "name": "email-security-anti-phishing",
@@ -2034,7 +2034,7 @@
2034
2034
  "d3fend_refs": [],
2035
2035
  "last_threat_review": "2026-05-11",
2036
2036
  "signature": "2pv81lLRbazpHqundCANb3YiLB4lkVsYctIDvI8rxSvHxhPS9jYXqmAoB5APSdDuOaew6XqpfZOehQUj9WmyBw==",
2037
- "signed_at": "2026-05-12T15:31:43.327Z"
2037
+ "signed_at": "2026-05-12T15:59:29.402Z"
2038
2038
  },
2039
2039
  {
2040
2040
  "name": "age-gates-child-safety",
@@ -2102,7 +2102,7 @@
2102
2102
  "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"
2103
2103
  ],
2104
2104
  "signature": "BJ/YYnGVXeSBaR9oWAVrcNX7Wz+kE8R4CghX6+XEI/qY89fyrkKNNwo2veqqf49wffJhHVJ1wTp8ZDECjNp+Dw==",
2105
- "signed_at": "2026-05-12T15:31:43.327Z"
2105
+ "signed_at": "2026-05-12T15:59:29.402Z"
2106
2106
  }
2107
2107
  ]
2108
2108
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/exceptd-skills",
3
- "version": "0.11.2",
3
+ "version": "0.11.4",
4
4
  "description": "AI security skills grounded in mid-2026 threat reality, not stale framework documentation. 38 skills, 10 catalogs, 34 jurisdictions, pre-computed indexes, Ed25519-signed.",
5
5
  "keywords": [
6
6
  "ai-security",
package/sbom.cdx.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "bomFormat": "CycloneDX",
3
3
  "specVersion": "1.6",
4
- "serialNumber": "urn:uuid:e0933f0c-38fd-4206-8754-c27d585831ad",
4
+ "serialNumber": "urn:uuid:057eee67-86e4-4750-80f8-8dfdf5b5c9d3",
5
5
  "version": 1,
6
6
  "metadata": {
7
- "timestamp": "2026-05-12T15:31:44.204Z",
7
+ "timestamp": "2026-05-12T15:59:30.308Z",
8
8
  "tools": [
9
9
  {
10
10
  "name": "hand-written",
@@ -13,10 +13,10 @@
13
13
  }
14
14
  ],
15
15
  "component": {
16
- "bom-ref": "pkg:npm/@blamejs/exceptd-skills@0.11.2",
16
+ "bom-ref": "pkg:npm/@blamejs/exceptd-skills@0.11.4",
17
17
  "type": "application",
18
18
  "name": "@blamejs/exceptd-skills",
19
- "version": "0.11.2",
19
+ "version": "0.11.4",
20
20
  "description": "AI security skills grounded in mid-2026 threat reality, not stale framework documentation. 38 skills, 10 catalogs, 34 jurisdictions, pre-computed indexes, Ed25519-signed.",
21
21
  "licenses": [
22
22
  {
@@ -25,11 +25,11 @@
25
25
  }
26
26
  }
27
27
  ],
28
- "purl": "pkg:npm/%40blamejs/exceptd-skills@0.11.2",
28
+ "purl": "pkg:npm/%40blamejs/exceptd-skills@0.11.4",
29
29
  "externalReferences": [
30
30
  {
31
31
  "type": "distribution",
32
- "url": "https://www.npmjs.com/package/@blamejs/exceptd-skills/v/0.11.2"
32
+ "url": "https://www.npmjs.com/package/@blamejs/exceptd-skills/v/0.11.4"
33
33
  },
34
34
  {
35
35
  "type": "vcs",