@blamejs/exceptd-skills 0.13.57 → 0.13.59
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 +35 -0
- package/bin/exceptd.js +271 -28
- package/data/_indexes/_meta.json +2 -2
- package/lib/refresh-external.js +20 -0
- package/manifest.json +44 -44
- package/package.json +1 -1
- package/sbom.cdx.json +16 -16
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.13.59 — 2026-05-22
|
|
4
|
+
|
|
5
|
+
Air-gap mode honored by `--upstream-check` and the `collect` envelope. `doctor` subchecks surface freshness timestamps + walk-cap markers. `--collectors` text matches its JSON.
|
|
6
|
+
|
|
7
|
+
### Bugs
|
|
8
|
+
|
|
9
|
+
- **`run --upstream-check --air-gap` was making the registry call anyway.** The upstream-check helper had no air-gap awareness, and the run path didn't gate the call. The refusal now lives at the central upstream-check dispatch so any future caller inherits it; the result envelope carries `upstream_check.air_gap_blocked: true` and `source: "air-gap"` so consumers see the refusal happened.
|
|
10
|
+
- **`doctor --ai-config` walked unbounded** — 48k+ entries under `~/.claude/` (conversation logs, cache, plugin tarballs) before finishing. The walk now caps at 4 depth + 5000 files and skips known-noisy subdir names (`node_modules`, `.git`, `.cache`, `logs`, `sessions`, `conversations`, `history`, `tmp`, `cache`). When the cap fires, `walk_truncated: true` and `walk_caps: { max_depth, max_files }` surface so operators see the bound.
|
|
11
|
+
- **`doctor --ai-config` text mode still said "manual ACL review noted for any sensitive files found"** on Windows even though the runtime ACL audit lands real findings via `icacls`. The placeholder dated to the original POSIX-only implementation; replaced with a description that matches the actual check.
|
|
12
|
+
- **`doctor --collectors` text mode was a strict subset of JSON** — the count of policy-skipped playbooks was visible but the names were `--json`-only. Text now enumerates the first 5 names + a `+N more` indicator so terminal operators see the same actionable information.
|
|
13
|
+
|
|
14
|
+
### Features
|
|
15
|
+
|
|
16
|
+
- **`doctor --currency`** surfaces `oldest_last_threat_review`, `newest_last_threat_review`, `max_days_since_review`, and `checked_at` so operators can answer "is my skill catalog stale?" without parsing the per-skill report.
|
|
17
|
+
- **`doctor --rfcs`** surfaces `index_last_modified` + `index_age_days` from the RFC index mtime so operators can answer "is the offline RFC catalog fresh?" without running a separate refresh.
|
|
18
|
+
- **`collect` envelope** surfaces `air_gap_mode` at the top of the result so downstream `run --evidence -` and AI consumers see the collection-time mode propagating. Collectors themselves don't currently make network calls; the marker flags the collection context for future collector additions.
|
|
19
|
+
|
|
20
|
+
## 0.13.58 — 2026-05-22
|
|
21
|
+
|
|
22
|
+
Air-gap mode now blocks every refresh source (not just GHSA/OSV). `doctor` help text catches up with runtime flag set, surfaces a fix status when nothing to remediate, and refuses unknown flags. `ask` stops returning false-positive substring matches and learns identity / phishing / SSO / famous-attack vocabulary.
|
|
23
|
+
|
|
24
|
+
### Bugs
|
|
25
|
+
|
|
26
|
+
- **`refresh --air-gap` was a partial guarantee.** GHSA + OSV honored `ctx.airGap` at the source-module level. `kev`, `epss`, `nvd`, `rfc`, and `pins` fell through to their live-network branches when neither `--from-fixture` nor `--from-cache` was wired up — operators got a banner promising no egress while CISA KEV updates were being applied to the catalog. The air-gap guard now lives at the central source-dispatch (`runOne` in `lib/refresh-external.js`) so the guarantee holds uniformly across every source. Sources with a `fixtures.<name>` or `cacheDir` offline path still run normally.
|
|
27
|
+
- **`exceptd doctor --help` advertised 6 flags but the runtime accepted 10.** `--collectors`, `--ai-config`, `--exit-codes`, and `--shipped-tarball` were dispatchable but undiscoverable. The help block now enumerates the full set, with one-paragraph descriptions of what each surface checks. `--exit-codes` is now operator-discoverable as the canonical EXIT_CODES contract dump.
|
|
28
|
+
- **`doctor --cves` breakdown arithmetic** silently dropped `BUG-*` and any non-CVE/non-MAL prefix entries from the named totals. `312 entries (301 CVE + 8 MAL)` is now `312 entries (3 BUG + 301 CVE + 8 MAL)` — exact sum. Output adds a structured `by_prefix` map so the breakdown is data-driven against the live catalog instead of hardcoded against two prefixes (same regression class as the v0.13.6 MAL fix, now generalized).
|
|
29
|
+
- **`doctor --fix` silently no-op'd when nothing was wrong.** Operators couldn't distinguish "we tried and were already healthy" from "we tried and failed silently." The summary now carries `fix_status: "already_present"` + `fix_skipped_reason` on a healthy install. Existing `fix_applied` / `fix_attempted` / `fix_partial` states unchanged.
|
|
30
|
+
- **`doctor` accepted unknown flags as no-ops** instead of refusing. `doctor --singatures` (typo), `doctor --data` (renamed-out flag), `doctor --bogus-flag-xyz` all ran a full default scan and exited 0. Refusal is now structured: ok:false + `unknown_flags[]` with did-you-mean candidates + `known_flags[]` listing every accepted flag. Non-zero exit.
|
|
31
|
+
- **`ask` substring matching produced stopword false positives.** `"the"` substring-hit `"authentication"` in the haystack, so any query containing a stopword inflated scores. The pure-stopword query `"the the the the"` routed confidently to ai-api. Fix: tokenize the haystack with the same splitter as the question and match by whole-token Set membership instead of `String.includes`. Plus a stopword filter applied after synonym expansion to drop common English words that would otherwise dilute scoring.
|
|
32
|
+
|
|
33
|
+
### Features
|
|
34
|
+
|
|
35
|
+
- **`run` / `ai-run` / `ci` envelopes surface `air_gap_mode` at the top.** Stdout-parsing consumers can detect that air-gap was active without descending into `phases.govern`. Mirrors the existing result-hoist pattern (`verdict`, `rwep_score`, `evidence_completeness`, `attestation_path`).
|
|
36
|
+
- **`ask` synonym map grew to cover identity / phishing / SSO / BEC / famous-attack vocabulary.** New keys: `phish*`, `sso`, `oauth`, `saml`, `okta`, `entra`, `bec`, `deepfake`, `left-pad`, `event-stream`, `shai-hulud`, `agentic`, `rogue`, `credential theft`, `developer laptop`. Queries like `"I think we got phished"` now top-route to identity-sso-compromise; `"left-pad style attack"` to library-author; `"credential theft from developer laptop"` to secrets (with cred-stores in top 3).
|
|
37
|
+
|
|
3
38
|
## 0.13.57 — 2026-05-22
|
|
4
39
|
|
|
5
40
|
`attest list` populates the signed field. README + verb help document the ai-run JSONL event grammar. `--block-on-jurisdiction-clock` clarifies pending-vs-started semantics.
|
package/bin/exceptd.js
CHANGED
|
@@ -1989,14 +1989,42 @@ Subchecks:
|
|
|
1989
1989
|
Currently scoped to: regenerate the local Ed25519
|
|
1990
1990
|
private key when keys/public.pem exists but
|
|
1991
1991
|
.keys/private.pem is absent. Does NOT modify any
|
|
1992
|
-
file outside .keys/.
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1992
|
+
file outside .keys/. When the key is already
|
|
1993
|
+
present, --fix is a no-op (surfaces fix_status:
|
|
1994
|
+
"already_present" so callers can distinguish
|
|
1995
|
+
clean-state from broken-state).
|
|
1996
|
+
--collectors Audit the per-playbook collector layer:
|
|
1997
|
+
which playbooks have a collector module under
|
|
1998
|
+
lib/collectors/, which are policy-skipped by
|
|
1999
|
+
design, and which collectors haven't been wired
|
|
2000
|
+
up yet. JSON emits has_collector / policy_skips /
|
|
2001
|
+
without_collector arrays.
|
|
2002
|
+
--ai-config Walk the operator's AI-assistant configuration
|
|
2003
|
+
files (~/.claude/, ~/.cursor/, ~/.codeium/,
|
|
2004
|
+
~/.aider/, ~/.continue/) and surface sensitive
|
|
2005
|
+
content (API keys, tokens, MCP server
|
|
2006
|
+
definitions) plus on Windows the icacls ACL
|
|
2007
|
+
state. Combine with --fix to harden ACLs.
|
|
2008
|
+
Opt-in — never part of the default scan.
|
|
2009
|
+
--exit-codes Dump the canonical EXIT_CODES table as JSON.
|
|
2010
|
+
Useful for CI / scripting consumers that want
|
|
2011
|
+
the documented exit-code contract without parsing
|
|
2012
|
+
help text. Off by default.
|
|
2013
|
+
--shipped-tarball Round-trip the verify-shipped-tarball gate:
|
|
2014
|
+
npm pack → extract to a tempdir → run
|
|
2015
|
+
lib/verify.js against the extracted tree.
|
|
2016
|
+
Surfaces the signature-regression class where
|
|
2017
|
+
source-tree verify passes but the published
|
|
2018
|
+
tarball fails. Off by default.
|
|
2019
|
+
(no flag) --signatures + --currency + --cves + --rfcs +
|
|
2020
|
+
signing-status. --registry-check, --collectors,
|
|
2021
|
+
--ai-config, --exit-codes, --shipped-tarball are
|
|
2022
|
+
opt-in and never run as part of the default scan.
|
|
1996
2023
|
|
|
1997
2024
|
Flags:
|
|
1998
2025
|
--json Emit JSON (default is human-readable text).
|
|
1999
2026
|
--pretty Indented JSON output (implies --json).
|
|
2027
|
+
--air-gap Suppress the --registry-check network probe.
|
|
2000
2028
|
|
|
2001
2029
|
Output: checks{} per subcheck + summary{all_green, issues_count}.`,
|
|
2002
2030
|
"ai-run": `ai-run <playbook> — streaming JSONL contract for AI-driven runs (v0.11.0).
|
|
@@ -2326,7 +2354,23 @@ function cmdCollect(runner, args, runOpts, pretty) {
|
|
|
2326
2354
|
// `exceptd run <playbook> --evidence -` to drive a real verdict.
|
|
2327
2355
|
// Human-rendered version is concise so an interactive operator can
|
|
2328
2356
|
// see what the collector found without parsing the JSON.
|
|
2329
|
-
|
|
2357
|
+
// Audit 3 A.4: surface air_gap_mode on the collect envelope so the
|
|
2358
|
+
// downstream `run --evidence -` sees the mode propagating from the
|
|
2359
|
+
// collection step. Collectors themselves currently make no network
|
|
2360
|
+
// calls — but the flag's intent is to flag the collection context for
|
|
2361
|
+
// any future collector that might.
|
|
2362
|
+
// Also honor _meta.air_gap_mode on the playbook itself — playbooks
|
|
2363
|
+
// like secrets / cred-stores / containers declare air-gap intrinsically
|
|
2364
|
+
// and `run` honors that even without --air-gap. Collect must mirror so
|
|
2365
|
+
// automation downstream sees the same intrinsic mode.
|
|
2366
|
+
let pbMetaAirGap = false;
|
|
2367
|
+
try { pbMetaAirGap = !!(runner.loadPlaybook(playbookId)?._meta?.air_gap_mode); }
|
|
2368
|
+
catch { /* playbook load shouldn't fail here — collector exists — but be defensive */ }
|
|
2369
|
+
const collectAirGap = !!(runOpts.airGap || process.env.EXCEPTD_AIR_GAP === "1" || pbMetaAirGap);
|
|
2370
|
+
// Spread `submission` first, then explicit fields, so a submission key
|
|
2371
|
+
// named `air_gap_mode` (currently always undefined but defensive against
|
|
2372
|
+
// future collector contracts) can't clobber the envelope marker.
|
|
2373
|
+
emit({ verb: "collect", playbook_id: playbookId, ...submission, air_gap_mode: collectAirGap }, pretty, (obj) => {
|
|
2330
2374
|
const lines = [];
|
|
2331
2375
|
const meta = obj.collector_meta || {};
|
|
2332
2376
|
lines.push(`collect: ${obj.playbook_id} (${meta.collector_version || "?"} on ${meta.platform || "?"})`);
|
|
@@ -3146,25 +3190,47 @@ function cmdRun(runner, args, runOpts, pretty) {
|
|
|
3146
3190
|
// Network bounded by an 8s timeout; degrades gracefully when offline.
|
|
3147
3191
|
let upstreamCheck = null;
|
|
3148
3192
|
if (args["upstream-check"]) {
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3193
|
+
// Audit 3 A.6: --air-gap must refuse the registry probe. The
|
|
3194
|
+
// upstream-check helper has no air-gap awareness of its own; the
|
|
3195
|
+
// central refusal lives here so any future caller of --upstream-check
|
|
3196
|
+
// inherits it.
|
|
3197
|
+
if (runOpts.airGap || process.env.EXCEPTD_AIR_GAP === "1") {
|
|
3198
|
+
upstreamCheck = {
|
|
3199
|
+
ok: false,
|
|
3200
|
+
source: "air-gap",
|
|
3201
|
+
air_gap_blocked: true,
|
|
3202
|
+
skipped_reason: "--upstream-check would query the npm registry; refused under --air-gap.",
|
|
3203
|
+
};
|
|
3204
|
+
} else {
|
|
3205
|
+
try {
|
|
3206
|
+
const cliPath = path.join(PKG_ROOT, "lib", "upstream-check-cli.js");
|
|
3207
|
+
const res = spawnSync(process.execPath, [cliPath, "--timeout", "5000"], {
|
|
3208
|
+
encoding: "utf8",
|
|
3209
|
+
cwd: PKG_ROOT,
|
|
3210
|
+
timeout: 8000,
|
|
3211
|
+
});
|
|
3212
|
+
try { upstreamCheck = JSON.parse((res.stdout || "").trim()); } catch { /* fall through */ }
|
|
3213
|
+
if (upstreamCheck && upstreamCheck.behind) {
|
|
3214
|
+
process.stderr.write(`[exceptd run --upstream-check] STALE: local v${upstreamCheck.local_version} < published v${upstreamCheck.latest_version} (published ${upstreamCheck.latest_published_at}, ${upstreamCheck.days_since_latest_publish}d ago). Continuing with local catalog. Run \`npm update -g @blamejs/exceptd-skills\` or \`exceptd refresh --network\` to consume the latest.\n`);
|
|
3215
|
+
}
|
|
3216
|
+
} catch (e) {
|
|
3217
|
+
upstreamCheck = { ok: false, error: e.message, source: "offline" };
|
|
3159
3218
|
}
|
|
3160
|
-
} catch (e) {
|
|
3161
|
-
upstreamCheck = { ok: false, error: e.message, source: "offline" };
|
|
3162
3219
|
}
|
|
3163
3220
|
}
|
|
3164
3221
|
|
|
3165
3222
|
const result = runner.run(playbookId, directiveId, submission, runOpts);
|
|
3166
3223
|
if (result && upstreamCheck) result.upstream_check = upstreamCheck;
|
|
3167
3224
|
|
|
3225
|
+
// Audit 3 A.2: surface air_gap_mode at the top of the envelope so
|
|
3226
|
+
// stdout-parsing consumers can detect that the run honored --air-gap
|
|
3227
|
+
// (or the playbook's _meta.air_gap_mode flag) without descending into
|
|
3228
|
+
// phases.govern. Mirrors the run-result hoist pattern (verdict,
|
|
3229
|
+
// rwep_score, evidence_completeness, attestation_path).
|
|
3230
|
+
if (result) {
|
|
3231
|
+
result.air_gap_mode = !!(pb._meta?.air_gap_mode || runOpts.airGap);
|
|
3232
|
+
}
|
|
3233
|
+
|
|
3168
3234
|
// v0.11.9 (#113/#114): surface --operator and --ack in the run result so
|
|
3169
3235
|
// operators see the attribution + consent state without inspecting the
|
|
3170
3236
|
// attestation file. Pre-0.11.9 these were persisted to disk only.
|
|
@@ -5920,6 +5986,27 @@ function cmdDoctor(runner, args, runOpts, pretty) {
|
|
|
5920
5986
|
const wantJson = !!args.json || !!args.pretty;
|
|
5921
5987
|
const indent = !!args.pretty;
|
|
5922
5988
|
|
|
5989
|
+
// Refuse unknown flags rather than silently running the full default
|
|
5990
|
+
// scan. Pre-fix, typos and renamed-out flags all returned exit 0 with
|
|
5991
|
+
// the operator believing they'd run a targeted check.
|
|
5992
|
+
const KNOWN_DOCTOR_FLAGS = new Set([
|
|
5993
|
+
"json", "pretty", "fix", "air-gap",
|
|
5994
|
+
"signatures", "currency", "cves", "rfcs", "registry-check",
|
|
5995
|
+
"ai-config", "collectors", "exit-codes", "shipped-tarball",
|
|
5996
|
+
// Global flags the parser may inject regardless of verb.
|
|
5997
|
+
"_", "json-stdout-only", "_jsonMode",
|
|
5998
|
+
]);
|
|
5999
|
+
const unknownFlags = Object.keys(args).filter(k => !KNOWN_DOCTOR_FLAGS.has(k));
|
|
6000
|
+
if (unknownFlags.length > 0) {
|
|
6001
|
+
const dym = unknownFlags.map(f => {
|
|
6002
|
+
const candidates = [...KNOWN_DOCTOR_FLAGS].filter(k => typeof k === "string" && k.length >= 2 && (k.includes(f) || f.includes(k) || levenshtein1(f, k) <= 1));
|
|
6003
|
+
return { flag: `--${f}`, did_you_mean: candidates.slice(0, 3).map(c => `--${c}`) };
|
|
6004
|
+
});
|
|
6005
|
+
return emitError(`doctor: unknown flag(s): ${unknownFlags.map(f => `--${f}`).join(", ")}`,
|
|
6006
|
+
{ verb: "doctor", unknown_flags: dym, known_flags: [...KNOWN_DOCTOR_FLAGS].filter(k => k !== "_" && !k.startsWith("_") && k !== "json-stdout-only").sort().map(k => `--${k}`) },
|
|
6007
|
+
pretty);
|
|
6008
|
+
}
|
|
6009
|
+
|
|
5923
6010
|
// `doctor --exit-codes` dumps the canonical exit-code table as JSON so
|
|
5924
6011
|
// operator-facing docs cannot drift from runtime behavior. Short-circuit
|
|
5925
6012
|
// before the regular health checks since the dump is informational.
|
|
@@ -6039,12 +6126,25 @@ function cmdDoctor(runner, args, runOpts, pretty) {
|
|
|
6039
6126
|
const stale = parsed.currency_report.filter(s => s.action_required || s.currency_label !== "current");
|
|
6040
6127
|
const critical = parsed.currency_report.filter(s => s.currency_score !== undefined && s.currency_score < 50);
|
|
6041
6128
|
const ok = stale.length === 0 && !parsed.action_required;
|
|
6129
|
+
// Audit 3 B.7: surface the freshest + stalest last_threat_review
|
|
6130
|
+
// so operators can answer "is my data stale?" without parsing the
|
|
6131
|
+
// full report. Falls back gracefully when the upstream report
|
|
6132
|
+
// omits the per-skill date.
|
|
6133
|
+
const dates = parsed.currency_report
|
|
6134
|
+
.map(s => s.last_threat_review)
|
|
6135
|
+
.filter(d => typeof d === "string" && /^\d{4}-\d{2}-\d{2}$/.test(d))
|
|
6136
|
+
.sort();
|
|
6137
|
+
const minDaysSince = dates.length ? Math.floor((Date.now() - new Date(dates[0]).getTime()) / 86400000) : null;
|
|
6042
6138
|
checks.currency = {
|
|
6043
6139
|
ok,
|
|
6044
6140
|
total_skills: parsed.currency_report.length,
|
|
6045
6141
|
stale_skills: stale.map(s => s.skill),
|
|
6046
6142
|
critical_stale: critical.map(s => s.skill),
|
|
6047
6143
|
critical_count: parsed.critical_count || 0,
|
|
6144
|
+
oldest_last_threat_review: dates[0] || null,
|
|
6145
|
+
newest_last_threat_review: dates[dates.length - 1] || null,
|
|
6146
|
+
max_days_since_review: minDaysSince,
|
|
6147
|
+
checked_at: new Date().toISOString(),
|
|
6048
6148
|
};
|
|
6049
6149
|
if (!ok) issues.push("currency");
|
|
6050
6150
|
} else {
|
|
@@ -6079,22 +6179,37 @@ function cmdDoctor(runner, args, runOpts, pretty) {
|
|
|
6079
6179
|
// MAL-* (malicious package) entries silently dropped from the
|
|
6080
6180
|
// doctor report — operators reading "34 entries" assumed the
|
|
6081
6181
|
// Shai-Hulud / TanStack worm intel had been removed when it was
|
|
6082
|
-
// present all along. Read the catalog and report
|
|
6182
|
+
// present all along. Read the catalog and report all prefix
|
|
6183
|
+
// groups — the previous CVE+MAL-only enumeration silently dropped
|
|
6184
|
+
// any other prefix (BUG-*, SNYK-*, etc.) from the breakdown even
|
|
6185
|
+
// though they were summed into total. Same regression class as
|
|
6186
|
+
// the original CVE-only fix; this generalizes it.
|
|
6083
6187
|
let total = null;
|
|
6084
6188
|
let cve_count = null;
|
|
6085
6189
|
let mal_count = null;
|
|
6190
|
+
let by_prefix = null;
|
|
6086
6191
|
try {
|
|
6087
6192
|
const catalog = require(path.join(PKG_ROOT, "data", "cve-catalog.json"));
|
|
6088
6193
|
const keys = Object.keys(catalog).filter((k) => !k.startsWith("_"));
|
|
6089
6194
|
cve_count = keys.filter((k) => k.startsWith("CVE-")).length;
|
|
6090
6195
|
mal_count = keys.filter((k) => k.startsWith("MAL-")).length;
|
|
6091
6196
|
total = keys.length;
|
|
6197
|
+
// Enumerate every prefix present so future additions (BUG-*,
|
|
6198
|
+
// SNYK-*, GHSA-*, RUSTSEC-*) surface in the breakdown instead
|
|
6199
|
+
// of vanishing into the total - sum-of-named-prefixes gap.
|
|
6200
|
+
by_prefix = {};
|
|
6201
|
+
for (const k of keys) {
|
|
6202
|
+
const m = k.match(/^([A-Z]+)-/);
|
|
6203
|
+
const p = m ? m[1] : "OTHER";
|
|
6204
|
+
by_prefix[p] = (by_prefix[p] || 0) + 1;
|
|
6205
|
+
}
|
|
6092
6206
|
} catch { /* fall through with nulls */ }
|
|
6093
6207
|
checks.cves = {
|
|
6094
6208
|
ok,
|
|
6095
6209
|
total,
|
|
6096
6210
|
cve_count,
|
|
6097
6211
|
mal_count,
|
|
6212
|
+
by_prefix,
|
|
6098
6213
|
drift: driftMatch ? Number(driftMatch[1]) : 0,
|
|
6099
6214
|
...(ok ? {} : { exit_code: res.status, raw: text.slice(0, 500) }),
|
|
6100
6215
|
};
|
|
@@ -6117,10 +6232,23 @@ function cmdDoctor(runner, args, runOpts, pretty) {
|
|
|
6117
6232
|
const rfcRows = (text.match(/^RFC-\d+/gm) || []).length;
|
|
6118
6233
|
const driftMatch = text.match(/drift[:\s]+(\d+)/i);
|
|
6119
6234
|
const ok = res.status === 0;
|
|
6235
|
+
// Audit 3 B.7: surface the RFC catalog file's mtime + age so
|
|
6236
|
+
// operators can answer "is the offline RFC index fresh?" without
|
|
6237
|
+
// running a separate refresh.
|
|
6238
|
+
let rfcMtime = null;
|
|
6239
|
+
let rfcAgeDays = null;
|
|
6240
|
+
try {
|
|
6241
|
+
const rfcPath = path.join(PKG_ROOT, "data", "rfc-index.json");
|
|
6242
|
+
const st = fs.statSync(rfcPath);
|
|
6243
|
+
rfcMtime = st.mtime.toISOString();
|
|
6244
|
+
rfcAgeDays = Math.floor((Date.now() - st.mtimeMs) / 86400000);
|
|
6245
|
+
} catch { /* file may not exist on contributor checkouts */ }
|
|
6120
6246
|
checks.rfcs = {
|
|
6121
6247
|
ok,
|
|
6122
6248
|
total: rfcRows,
|
|
6123
6249
|
drift: driftMatch ? Number(driftMatch[1]) : 0,
|
|
6250
|
+
index_last_modified: rfcMtime,
|
|
6251
|
+
index_age_days: rfcAgeDays,
|
|
6124
6252
|
...(ok ? {} : { exit_code: res.status, raw: text.slice(0, 500) }),
|
|
6125
6253
|
};
|
|
6126
6254
|
if (!ok) issues.push("rfcs");
|
|
@@ -6271,18 +6399,47 @@ function cmdDoctor(runner, args, runOpts, pretty) {
|
|
|
6271
6399
|
const findings = [];
|
|
6272
6400
|
let scannedDirs = 0;
|
|
6273
6401
|
let scannedFiles = 0;
|
|
6274
|
-
|
|
6402
|
+
let walkAborted = false;
|
|
6403
|
+
// Audit 3 B.9: cap depth + file count to bound the walk. Without
|
|
6404
|
+
// these, doctor --ai-config can read 48k+ entries under ~/.claude
|
|
6405
|
+
// (conversation logs, cache dirs, plugin tarballs) before finishing.
|
|
6406
|
+
// The five SENSITIVE_PATTERNS files all live within ~3 levels of an
|
|
6407
|
+
// AI-assistant config root, so 4 is generous. The skipped dirs are
|
|
6408
|
+
// known non-config noise that shouldn't carry credentials.
|
|
6409
|
+
const MAX_DEPTH = 4;
|
|
6410
|
+
const MAX_FILES = 5000;
|
|
6411
|
+
const SKIP_DIR_NAMES = new Set([
|
|
6412
|
+
'node_modules', '.git', '.cache', 'logs', 'log',
|
|
6413
|
+
'sessions', 'session', 'transcripts', 'transcript',
|
|
6414
|
+
'conversations', 'history', 'tmp', 'temp', 'cache',
|
|
6415
|
+
]);
|
|
6416
|
+
function walk(absDir, displayRoot, rel, depth = 0) {
|
|
6417
|
+
if (walkAborted) return;
|
|
6418
|
+
if (depth > MAX_DEPTH) return;
|
|
6419
|
+
if (scannedFiles > MAX_FILES) {
|
|
6420
|
+
walkAborted = true;
|
|
6421
|
+
return;
|
|
6422
|
+
}
|
|
6275
6423
|
if (!fs.existsSync(absDir)) return;
|
|
6276
6424
|
let entries;
|
|
6277
6425
|
try { entries = fs.readdirSync(absDir, { withFileTypes: true }); }
|
|
6278
6426
|
catch { return; }
|
|
6279
6427
|
for (const e of entries) {
|
|
6428
|
+
if (walkAborted) return;
|
|
6429
|
+
if (e.isDirectory() && SKIP_DIR_NAMES.has(e.name.toLowerCase())) continue;
|
|
6280
6430
|
const childAbs = path.join(absDir, e.name);
|
|
6281
6431
|
const childRel = rel ? rel + '/' + e.name : e.name;
|
|
6282
6432
|
if (e.isDirectory()) {
|
|
6283
|
-
walk(childAbs, displayRoot, childRel);
|
|
6433
|
+
walk(childAbs, displayRoot, childRel, depth + 1);
|
|
6284
6434
|
} else if (e.isFile()) {
|
|
6285
6435
|
scannedFiles++;
|
|
6436
|
+
// Check the file cap immediately after the increment so a
|
|
6437
|
+
// single large directory doesn't process tens of thousands of
|
|
6438
|
+
// entries before the next recursive call catches the bound.
|
|
6439
|
+
if (scannedFiles > MAX_FILES) {
|
|
6440
|
+
walkAborted = true;
|
|
6441
|
+
return;
|
|
6442
|
+
}
|
|
6286
6443
|
if (!SENSITIVE_PATTERNS.some((re) => re.test(e.name))) continue;
|
|
6287
6444
|
let st;
|
|
6288
6445
|
try { st = fs.statSync(childAbs); } catch { continue; }
|
|
@@ -6375,6 +6532,8 @@ function cmdDoctor(runner, args, runOpts, pretty) {
|
|
|
6375
6532
|
severity: errorFindings.length > 0 && fixesFailed > 0 ? 'warn' : (errorFindings.length > 0 && !args.fix ? 'warn' : 'info'),
|
|
6376
6533
|
scanned_dirs: scannedDirs,
|
|
6377
6534
|
scanned_files: scannedFiles,
|
|
6535
|
+
walk_truncated: walkAborted,
|
|
6536
|
+
walk_caps: { max_depth: MAX_DEPTH, max_files: MAX_FILES },
|
|
6378
6537
|
directories_inspected: AI_CONFIG_DIRS.map((d) => d.display),
|
|
6379
6538
|
sensitive_patterns: ['settings.json', 'mcp.json', '*.mcp_config.json', 'api_key*', '*.token', '*.credentials'],
|
|
6380
6539
|
findings,
|
|
@@ -6543,6 +6702,23 @@ function cmdDoctor(runner, args, runOpts, pretty) {
|
|
|
6543
6702
|
}
|
|
6544
6703
|
}
|
|
6545
6704
|
|
|
6705
|
+
// Audit 3 B.3: --fix was passed but nothing to fix. Pre-fix this was
|
|
6706
|
+
// silently a no-op — operators couldn't distinguish "we tried and were
|
|
6707
|
+
// already healthy" from "we tried and failed silently." Now surfaces a
|
|
6708
|
+
// structured fix_status so callers reading the envelope can branch on
|
|
6709
|
+
// it.
|
|
6710
|
+
// The ai-config branch tracks its remediations on checks.ai_config
|
|
6711
|
+
// (fix_applied / fix_failed are nested there, not on out.summary), so
|
|
6712
|
+
// include that signal when deciding whether ANY fix actually ran.
|
|
6713
|
+
// Without this, doctor --ai-config --fix applying chmod/icacls would
|
|
6714
|
+
// simultaneously report checks.ai_config.fix_applied > 0 AND
|
|
6715
|
+
// summary.fix_status: "already_present", which is contradictory.
|
|
6716
|
+
const aiConfigFixed = !!(checks.ai_config && ((checks.ai_config.fix_applied || 0) > 0 || (checks.ai_config.fix_failed || 0) > 0));
|
|
6717
|
+
if (args.fix && !out.summary.fix_applied && !out.summary.fix_attempted && !out.summary.fix_partial && !out.summary.fix_decline_reason && !aiConfigFixed) {
|
|
6718
|
+
out.summary.fix_status = "already_present";
|
|
6719
|
+
out.summary.fix_skipped_reason = "Signing key + skill signatures are already valid; nothing to remediate.";
|
|
6720
|
+
}
|
|
6721
|
+
|
|
6546
6722
|
if (wantJson) {
|
|
6547
6723
|
emit(out, indent);
|
|
6548
6724
|
if (!allGreen) process.exitCode = EXIT_CODES.GENERIC_FAILURE;
|
|
@@ -6578,7 +6754,12 @@ function cmdDoctor(runner, args, runOpts, pretty) {
|
|
|
6578
6754
|
mark(checks.cves, c => {
|
|
6579
6755
|
if (!c.ok) return `CVE catalog FAILED (exit=${c.exit_code ?? "?"})`;
|
|
6580
6756
|
const total = c.total ?? "?";
|
|
6581
|
-
|
|
6757
|
+
// Audit 3 B.2: enumerate every prefix so the sum equals total.
|
|
6758
|
+
// Falls back to the legacy CVE + MAL display only if by_prefix isn't
|
|
6759
|
+
// present (older catalog read paths).
|
|
6760
|
+
const breakdown = c.by_prefix
|
|
6761
|
+
? ` (${Object.entries(c.by_prefix).sort().map(([p, n]) => `${n} ${p}`).join(" + ")})`
|
|
6762
|
+
: (c.cve_count != null && c.mal_count != null)
|
|
6582
6763
|
? ` (${c.cve_count} CVE + ${c.mal_count} MAL)`
|
|
6583
6764
|
: "";
|
|
6584
6765
|
return `CVE catalog: ${total} entries${breakdown}, drift ${c.drift ?? 0}`;
|
|
@@ -6644,6 +6825,16 @@ function cmdDoctor(runner, args, runOpts, pretty) {
|
|
|
6644
6825
|
? ` (${c.policy_skips.length} judgement-shaped playbooks intentionally without a collector — see AGENTS.md)`
|
|
6645
6826
|
: "";
|
|
6646
6827
|
lines.push(` ${icon} collector layer: ${c.with_collector ?? "?"}/${c.total_playbooks ?? "?"} playbooks have collectors${skipNote}`);
|
|
6828
|
+
// Enumerate the policy-skipped playbooks in text mode so it carries
|
|
6829
|
+
// the same operator-actionable information as the JSON envelope.
|
|
6830
|
+
// Pre-fix the count was visible but the names appeared only in the
|
|
6831
|
+
// structured output, forcing operators to parse JSON to learn which
|
|
6832
|
+
// playbooks are policy-skipped.
|
|
6833
|
+
if (Array.isArray(c.policy_skips) && c.policy_skips.length > 0) {
|
|
6834
|
+
const shown = c.policy_skips.slice(0, 5).join(", ");
|
|
6835
|
+
const more = c.policy_skips.length > 5 ? `, … +${c.policy_skips.length - 5} more` : "";
|
|
6836
|
+
lines.push(` policy-skipped: ${shown}${more}`);
|
|
6837
|
+
}
|
|
6647
6838
|
if (Array.isArray(c.load_errors) && c.load_errors.length > 0) {
|
|
6648
6839
|
lines.push(` ${c.load_errors.length} collector(s) failed to load:`);
|
|
6649
6840
|
for (const e of c.load_errors.slice(0, 5)) {
|
|
@@ -6660,7 +6851,10 @@ function cmdDoctor(runner, args, runOpts, pretty) {
|
|
|
6660
6851
|
const fileCount = c.scanned_files ?? 0;
|
|
6661
6852
|
lines.push(` ${icon} AI-assistant config audit: scanned ${fileCount} file(s) across ${dirCount} dir(s) of ${(c.directories_inspected || []).length} candidate root(s); ${findings.length} finding(s)`);
|
|
6662
6853
|
if (c.platform === "win32" && findings.length === 0 && fileCount > 0) {
|
|
6663
|
-
lines.push(` (
|
|
6854
|
+
lines.push(` (Windows: ACL inspected via icacls; every sensitive file restricted to the workstation user)`);
|
|
6855
|
+
}
|
|
6856
|
+
if (c.walk_truncated) {
|
|
6857
|
+
lines.push(` (walk truncated at ${c.walk_caps?.max_files || "?"} file(s) / depth ${c.walk_caps?.max_depth || "?"}; rerun under a narrower path if you need exhaustive coverage)`);
|
|
6664
6858
|
}
|
|
6665
6859
|
for (const f of findings.slice(0, 5)) {
|
|
6666
6860
|
const sev = f.severity === "error" ? "[!!]" : f.severity === "warn" ? "[warn]" : "[info]";
|
|
@@ -7243,10 +7437,10 @@ function cmdAsk(runner, args, runOpts, pretty) {
|
|
|
7243
7437
|
// Keeps cmdAsk dependency-free; rich enough to cover the 80% of natural
|
|
7244
7438
|
// queries listed in the operator report.
|
|
7245
7439
|
const SYNONYMS = {
|
|
7246
|
-
"credential": ["secret", "key", "token", "password", "cred"],
|
|
7247
|
-
"credentials": ["secret", "key", "token", "password", "cred"],
|
|
7248
|
-
"api key": ["secret", "credential"],
|
|
7249
|
-
"api keys": ["secret", "credential"],
|
|
7440
|
+
"credential": ["secret", "key", "token", "password", "cred", "secrets"],
|
|
7441
|
+
"credentials": ["secret", "key", "token", "password", "cred", "secrets"],
|
|
7442
|
+
"api key": ["secret", "credential", "secrets"],
|
|
7443
|
+
"api keys": ["secret", "credential", "secrets"],
|
|
7250
7444
|
"supply chain": ["sbom", "dependency", "vendor", "package", "library", "publish"],
|
|
7251
7445
|
"supply-chain": ["sbom", "dependency", "vendor", "package", "library", "publish"],
|
|
7252
7446
|
"npm package": ["sbom", "dependency", "library", "publish"],
|
|
@@ -7262,8 +7456,48 @@ function cmdAsk(runner, args, runOpts, pretty) {
|
|
|
7262
7456
|
"secret": ["credential", "key", "token", "env", "leak"],
|
|
7263
7457
|
"secrets": ["credential", "key", "token", "env", "leak", "repo"],
|
|
7264
7458
|
"config": ["configuration", "settings"],
|
|
7459
|
+
// Audit 3 C.2: missing identity / phishing / SSO / BEC vocabulary.
|
|
7460
|
+
"phish": ["identity-sso-compromise", "idp-incident", "sso", "bec"],
|
|
7461
|
+
"phishing": ["identity-sso-compromise", "idp-incident", "sso", "bec"],
|
|
7462
|
+
"phished": ["identity-sso-compromise", "idp-incident", "sso", "bec"],
|
|
7463
|
+
"sso": ["identity-sso-compromise", "idp-incident", "okta", "azure-ad", "entra"],
|
|
7464
|
+
"oauth": ["identity-sso-compromise", "idp-incident", "openid", "oidc"],
|
|
7465
|
+
"saml": ["identity-sso-compromise", "idp-incident"],
|
|
7466
|
+
"okta": ["identity-sso-compromise", "idp-incident", "sso"],
|
|
7467
|
+
"entra": ["identity-sso-compromise", "idp-incident", "azure-ad", "sso"],
|
|
7468
|
+
"bec": ["identity-sso-compromise", "phish"],
|
|
7469
|
+
"deepfake": ["identity-sso-compromise", "phish", "ai-c2"],
|
|
7470
|
+
// Famous-attack vocabulary maps to library-author / sbom / supply-chain.
|
|
7471
|
+
"left-pad": ["library-author", "sbom", "supply-chain-recovery", "npm"],
|
|
7472
|
+
"left pad": ["library-author", "sbom", "supply-chain-recovery", "npm"],
|
|
7473
|
+
"event-stream": ["library-author", "sbom", "supply-chain-recovery", "npm"],
|
|
7474
|
+
"shai-hulud": ["library-author", "sbom", "supply-chain-recovery", "npm"],
|
|
7475
|
+
"ransomware": ["ransomware", "kernel", "runtime"],
|
|
7476
|
+
"rogue": ["ai-c2", "llm-tool-use-exfil"],
|
|
7477
|
+
"agentic": ["ai-c2", "llm-tool-use-exfil", "mcp"],
|
|
7478
|
+
// Credential theft from a developer laptop is cred-stores territory.
|
|
7479
|
+
"credential theft": ["cred-stores", "secrets"],
|
|
7480
|
+
"cred theft": ["cred-stores", "secrets"],
|
|
7481
|
+
"credential exfil": ["cred-stores", "llm-tool-use-exfil"],
|
|
7482
|
+
"developer laptop": ["cred-stores", "hardening"],
|
|
7265
7483
|
};
|
|
7266
7484
|
|
|
7485
|
+
// Audit 3 C.1: stopwords filtered after synonym expansion so common
|
|
7486
|
+
// English words don't drive false positives via raw substring matching.
|
|
7487
|
+
// "the the the the" used to route to ai-api because "the" substring-hit
|
|
7488
|
+
// "anthropic" in the haystack.
|
|
7489
|
+
const STOPWORDS = new Set([
|
|
7490
|
+
"the", "and", "for", "are", "but", "not", "you", "all", "can", "had",
|
|
7491
|
+
"her", "was", "one", "our", "out", "day", "get", "has", "him", "his",
|
|
7492
|
+
"how", "man", "new", "now", "old", "see", "two", "way", "who", "boy",
|
|
7493
|
+
"did", "its", "let", "put", "say", "she", "too", "use", "any", "got",
|
|
7494
|
+
"from", "this", "that", "with", "have", "they", "what", "your", "when",
|
|
7495
|
+
"which", "would", "could", "should", "there", "their", "about", "into",
|
|
7496
|
+
"than", "then", "them", "some", "more", "most", "very", "much", "such",
|
|
7497
|
+
"been", "were", "want", "well", "back", "good", "make", "made", "take",
|
|
7498
|
+
"took", "give", "gave", "find", "found", "know", "knew", "told", "ago",
|
|
7499
|
+
]);
|
|
7500
|
+
|
|
7267
7501
|
// Tokenize question (length >= 2, lowercase) + expand via synonyms.
|
|
7268
7502
|
const baseTokens = q.split(/\W+/).filter(t => t.length >= 2);
|
|
7269
7503
|
const expanded = new Set(baseTokens);
|
|
@@ -7275,13 +7509,16 @@ function cmdAsk(runner, args, runOpts, pretty) {
|
|
|
7275
7509
|
for (const t of baseTokens) {
|
|
7276
7510
|
if (SYNONYMS[t]) for (const s of SYNONYMS[t]) expanded.add(s);
|
|
7277
7511
|
}
|
|
7512
|
+
// Filter stopwords AFTER synonym expansion (synonym lookup may have
|
|
7513
|
+
// pulled in canonical tokens via a stopword-adjacent multi-word phrase).
|
|
7514
|
+
for (const sw of STOPWORDS) expanded.delete(sw);
|
|
7278
7515
|
const tokens = [...expanded];
|
|
7279
7516
|
|
|
7280
7517
|
const scored = [];
|
|
7281
7518
|
for (const id of ids) {
|
|
7282
7519
|
let pb;
|
|
7283
7520
|
try { pb = runner.loadPlaybook(id); } catch { continue; }
|
|
7284
|
-
const
|
|
7521
|
+
const haystackText = [
|
|
7285
7522
|
pb._meta?.id || id,
|
|
7286
7523
|
pb.domain?.name || "",
|
|
7287
7524
|
pb.domain?.attack_class || "",
|
|
@@ -7297,8 +7534,14 @@ function cmdAsk(runner, args, runOpts, pretty) {
|
|
|
7297
7534
|
pb.phases?.look?.collection_scope?.asset_scope || "",
|
|
7298
7535
|
pb.phases?.look?.collection_scope?.time_window || "",
|
|
7299
7536
|
].join(" ").toLowerCase();
|
|
7537
|
+
// Audit 3 C.1: tokenize the haystack and use Set membership instead
|
|
7538
|
+
// of raw substring matching. The substring approach matched "the" in
|
|
7539
|
+
// "authentication" and "got" inside larger words, allowing stopword
|
|
7540
|
+
// and partial-word false positives. Tokenized membership matches
|
|
7541
|
+
// whole tokens only.
|
|
7542
|
+
const haystackTokens = new Set(haystackText.split(/\W+/).filter(t => t.length >= 2));
|
|
7300
7543
|
let score = 0;
|
|
7301
|
-
for (const t of tokens) if (
|
|
7544
|
+
for (const t of tokens) if (haystackTokens.has(t)) score++;
|
|
7302
7545
|
// ID match counts double — "secrets" should map to the secrets playbook.
|
|
7303
7546
|
if (tokens.some(t => (pb._meta?.id || id) === t)) score += 3;
|
|
7304
7547
|
scored.push({ id: pb._meta?.id || id, score });
|
package/data/_indexes/_meta.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema_version": "1.1.0",
|
|
3
|
-
"generated_at": "2026-05-
|
|
3
|
+
"generated_at": "2026-05-22T23:28:32.508Z",
|
|
4
4
|
"generator": "scripts/build-indexes.js",
|
|
5
5
|
"source_count": 54,
|
|
6
6
|
"source_hashes": {
|
|
7
|
-
"manifest.json": "
|
|
7
|
+
"manifest.json": "020990fd6a13ea3a2a0273e80d0607c1cf0fd8887f03bb48294d2568a22d2789",
|
|
8
8
|
"data/atlas-ttps.json": "d296c1d3e71807c9279b731f047e57796e85137f186586743a8cdad214b408f9",
|
|
9
9
|
"data/attack-techniques.json": "49b6010b317edd219def135171ea8f3b1bbf1e00e9c5a08bf7237215ff54e2c3",
|
|
10
10
|
"data/cve-catalog.json": "a09c83af3f9679a7ea73935726a1ff9de2cab94b4ab6321fc017fc147747d7c3",
|
package/lib/refresh-external.js
CHANGED
|
@@ -1426,6 +1426,22 @@ async function main() {
|
|
|
1426
1426
|
// against each other for tokens. The two modes produce the same report
|
|
1427
1427
|
// structure; only wall-clock differs.
|
|
1428
1428
|
const runOne = async (src) => {
|
|
1429
|
+
// Audit 3 A.1: --air-gap was honored by GHSA/OSV at the source-module
|
|
1430
|
+
// level, but kev/epss/nvd/rfc/pins fell through to their live-network
|
|
1431
|
+
// branches when neither fixtures nor cacheDir was wired up. Refuse
|
|
1432
|
+
// here so the air-gap guarantee holds uniformly across every source.
|
|
1433
|
+
if (ctx.airGap && !ctx.fixtures?.[src.name] && !ctx.cacheDir) {
|
|
1434
|
+
return {
|
|
1435
|
+
src,
|
|
1436
|
+
diff: {
|
|
1437
|
+
status: "unreachable",
|
|
1438
|
+
diffs: [],
|
|
1439
|
+
errors: 0,
|
|
1440
|
+
summary: `air-gap mode: ${src.name} skipped (no fixture or cache configured; would have made a live network call)`,
|
|
1441
|
+
air_gap_blocked: true,
|
|
1442
|
+
},
|
|
1443
|
+
};
|
|
1444
|
+
}
|
|
1429
1445
|
let diff;
|
|
1430
1446
|
try {
|
|
1431
1447
|
diff = await src.fetchDiff(ctx);
|
|
@@ -1457,6 +1473,10 @@ async function main() {
|
|
|
1457
1473
|
diffs: diff.diffs,
|
|
1458
1474
|
applies_to: src.applies_to,
|
|
1459
1475
|
report_only: !!src.report_only,
|
|
1476
|
+
// Audit 3 A.1: thread the central-dispatch air-gap short-circuit
|
|
1477
|
+
// marker through to the persisted report so stdout-parsing consumers
|
|
1478
|
+
// and the regression test can verify the network refusal happened.
|
|
1479
|
+
...(diff.air_gap_blocked ? { air_gap_blocked: true } : {}),
|
|
1460
1480
|
};
|
|
1461
1481
|
if (opts.apply && diff.diffs.length > 0 && !src.report_only) {
|
|
1462
1482
|
const r = await src.applyDiff(ctx, diff.diffs);
|
package/manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "exceptd-security",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.59",
|
|
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-01",
|
|
55
55
|
"signature": "lXhZgoIrrVloO3XaTvo/43AxZn4mwErstd7DR0O/oVhD3AOGODM4HqrageYEou9WKOdMEGP5mJNTjJsXdP5NDA==",
|
|
56
|
-
"signed_at": "2026-05-
|
|
56
|
+
"signed_at": "2026-05-22T23:28:31.011Z",
|
|
57
57
|
"cwe_refs": [
|
|
58
58
|
"CWE-125",
|
|
59
59
|
"CWE-362",
|
|
@@ -117,7 +117,7 @@
|
|
|
117
117
|
],
|
|
118
118
|
"last_threat_review": "2026-05-01",
|
|
119
119
|
"signature": "vSVqu4wBm+d68ujZmM6Rto/HzViCkE0gPUcv/MYE/bjFiqamf/s0On4kTOo1KIveV9cOwYNxiItaGEWlVkRFDg==",
|
|
120
|
-
"signed_at": "2026-05-
|
|
120
|
+
"signed_at": "2026-05-22T23:28:31.013Z",
|
|
121
121
|
"cwe_refs": [
|
|
122
122
|
"CWE-1039",
|
|
123
123
|
"CWE-1426",
|
|
@@ -180,7 +180,7 @@
|
|
|
180
180
|
],
|
|
181
181
|
"last_threat_review": "2026-05-01",
|
|
182
182
|
"signature": "RIgXKvolQjgJdnlrDnVOd90IOY1B7VHHZD/YJQRzouL+wUeOLclPrdK/EgEuFyiu7lR4bi+Pl6aGB9G9tOxYCQ==",
|
|
183
|
-
"signed_at": "2026-05-
|
|
183
|
+
"signed_at": "2026-05-22T23:28:31.013Z",
|
|
184
184
|
"cwe_refs": [
|
|
185
185
|
"CWE-22",
|
|
186
186
|
"CWE-345",
|
|
@@ -226,7 +226,7 @@
|
|
|
226
226
|
"framework_gaps": [],
|
|
227
227
|
"last_threat_review": "2026-05-01",
|
|
228
228
|
"signature": "RYOxeq/o3uTwTWq4H7RcdH2Aclg9UyCERfUH9Frwkzncsowg7LgxpaEDc3swTCv73HMEGbU8wVbXguZ4JxHUCQ==",
|
|
229
|
-
"signed_at": "2026-05-
|
|
229
|
+
"signed_at": "2026-05-22T23:28:31.014Z"
|
|
230
230
|
},
|
|
231
231
|
{
|
|
232
232
|
"name": "compliance-theater",
|
|
@@ -257,7 +257,7 @@
|
|
|
257
257
|
],
|
|
258
258
|
"last_threat_review": "2026-05-01",
|
|
259
259
|
"signature": "DneJCPKCPcoe6nQ82XptqSqNfSRdt1orKaO+o7K36YCciDrzwJb+1BuBLusPDtpcdDaGY0y0e+AqiTYJklhBAQ==",
|
|
260
|
-
"signed_at": "2026-05-
|
|
260
|
+
"signed_at": "2026-05-22T23:28:31.014Z"
|
|
261
261
|
},
|
|
262
262
|
{
|
|
263
263
|
"name": "exploit-scoring",
|
|
@@ -286,7 +286,7 @@
|
|
|
286
286
|
],
|
|
287
287
|
"last_threat_review": "2026-05-01",
|
|
288
288
|
"signature": "NA1hoQycvQhSUoG5rwlXX0mOVmGxoXRVezkELGEA2nZOdGis4gXkHT3O6Sfw7zxE4JuMrsCb65TEeOWk9WEPDg==",
|
|
289
|
-
"signed_at": "2026-05-
|
|
289
|
+
"signed_at": "2026-05-22T23:28:31.015Z"
|
|
290
290
|
},
|
|
291
291
|
{
|
|
292
292
|
"name": "rag-pipeline-security",
|
|
@@ -323,7 +323,7 @@
|
|
|
323
323
|
],
|
|
324
324
|
"last_threat_review": "2026-05-01",
|
|
325
325
|
"signature": "XgrzcA2brPhXrSTxrcLnJec0OpgGYJBoSTUlJ10UdePHffxqb9LTVGnfbmEk1ykQifXREZexui2bG7X/+eFfCQ==",
|
|
326
|
-
"signed_at": "2026-05-
|
|
326
|
+
"signed_at": "2026-05-22T23:28:31.015Z",
|
|
327
327
|
"cwe_refs": [
|
|
328
328
|
"CWE-1395",
|
|
329
329
|
"CWE-1426"
|
|
@@ -380,7 +380,7 @@
|
|
|
380
380
|
],
|
|
381
381
|
"last_threat_review": "2026-05-01",
|
|
382
382
|
"signature": "9+hZlZOqZdeACUmamQk66L5levZhhwnFXuYRhdT6Mce99eQaKT7wNfWq12hXQztkRcVRKaFH+a01zwJQwsRQCA==",
|
|
383
|
-
"signed_at": "2026-05-
|
|
383
|
+
"signed_at": "2026-05-22T23:28:31.016Z",
|
|
384
384
|
"d3fend_refs": [
|
|
385
385
|
"D3-CA",
|
|
386
386
|
"D3-CSPP",
|
|
@@ -415,7 +415,7 @@
|
|
|
415
415
|
"framework_gaps": [],
|
|
416
416
|
"last_threat_review": "2026-05-01",
|
|
417
417
|
"signature": "ciqhVloMWWXEigPZvvwoV2c54tEqsDqsoc+sS/mNTFFJk2H+tz2+XUrgfEPRuYw0FeyNB6/+27pL2NpKHzUqAg==",
|
|
418
|
-
"signed_at": "2026-05-
|
|
418
|
+
"signed_at": "2026-05-22T23:28:31.016Z",
|
|
419
419
|
"cwe_refs": [
|
|
420
420
|
"CWE-1188"
|
|
421
421
|
]
|
|
@@ -443,7 +443,7 @@
|
|
|
443
443
|
"framework_gaps": [],
|
|
444
444
|
"last_threat_review": "2026-05-01",
|
|
445
445
|
"signature": "xiHAhhdufm9hCKU8PLiPE0MX65ej2F4OZwtlWLGLCiie9/km+Kiqbt192LcMvr94v83C98pb9wIaqFsFWft6AQ==",
|
|
446
|
-
"signed_at": "2026-05-
|
|
446
|
+
"signed_at": "2026-05-22T23:28:31.016Z"
|
|
447
447
|
},
|
|
448
448
|
{
|
|
449
449
|
"name": "global-grc",
|
|
@@ -475,7 +475,7 @@
|
|
|
475
475
|
"framework_gaps": [],
|
|
476
476
|
"last_threat_review": "2026-05-01",
|
|
477
477
|
"signature": "oYsSk35N2Uzq7MRofACykylcVwkgPhI4luWZ14vmQT+gUKLyZiKVOUJbe1+7lGl6BYPRN0sUDQ0f7S5Eu5w2Ag==",
|
|
478
|
-
"signed_at": "2026-05-
|
|
478
|
+
"signed_at": "2026-05-22T23:28:31.017Z"
|
|
479
479
|
},
|
|
480
480
|
{
|
|
481
481
|
"name": "zeroday-gap-learn",
|
|
@@ -502,7 +502,7 @@
|
|
|
502
502
|
"framework_gaps": [],
|
|
503
503
|
"last_threat_review": "2026-05-01",
|
|
504
504
|
"signature": "igRqYyU1unRFH40BsPyAR62SPrk8QZv8dPGb8S9O9EvLCNOZAzm3t+HdT/NKqzWHwrpomOzkkkyLfYI/0qTUDA==",
|
|
505
|
-
"signed_at": "2026-05-
|
|
505
|
+
"signed_at": "2026-05-22T23:28:31.017Z"
|
|
506
506
|
},
|
|
507
507
|
{
|
|
508
508
|
"name": "pqc-first",
|
|
@@ -554,7 +554,7 @@
|
|
|
554
554
|
],
|
|
555
555
|
"last_threat_review": "2026-05-01",
|
|
556
556
|
"signature": "vhc3wuQEro/86s1ro2b/KakUXg8QVnySYTBqA7ebzv9oeR2HYO5bvGEJp3oOHWtL37JDqcCAHYadSN/qxIyCCA==",
|
|
557
|
-
"signed_at": "2026-05-
|
|
557
|
+
"signed_at": "2026-05-22T23:28:31.018Z",
|
|
558
558
|
"cwe_refs": [
|
|
559
559
|
"CWE-327"
|
|
560
560
|
],
|
|
@@ -601,7 +601,7 @@
|
|
|
601
601
|
],
|
|
602
602
|
"last_threat_review": "2026-05-01",
|
|
603
603
|
"signature": "MS35nWm8djfJGn4OOoT0JKJ2aO+Dkbb6wOOWJYvNZlRKT3UGA59o2gxg1JOnD20hb/RwxtkmCujhl2tuYSR+AQ==",
|
|
604
|
-
"signed_at": "2026-05-
|
|
604
|
+
"signed_at": "2026-05-22T23:28:31.018Z"
|
|
605
605
|
},
|
|
606
606
|
{
|
|
607
607
|
"name": "security-maturity-tiers",
|
|
@@ -638,7 +638,7 @@
|
|
|
638
638
|
],
|
|
639
639
|
"last_threat_review": "2026-05-01",
|
|
640
640
|
"signature": "8Px1s2lDj10/Q6erwEQlXgUHM1+OTruUR8qAHPX7Oo3k/l69N6P9sm0PsafS9wDFtj9l5C/OiLiFgzMlMt6vBw==",
|
|
641
|
-
"signed_at": "2026-05-
|
|
641
|
+
"signed_at": "2026-05-22T23:28:31.018Z",
|
|
642
642
|
"cwe_refs": [
|
|
643
643
|
"CWE-1188"
|
|
644
644
|
]
|
|
@@ -673,7 +673,7 @@
|
|
|
673
673
|
"framework_gaps": [],
|
|
674
674
|
"last_threat_review": "2026-05-11",
|
|
675
675
|
"signature": "WAu5fRirzSOcnnZsTx2d/JJZwa/LPpXCi+31qATTGLmoNuhyy81k3ooPe9kCM3E0CLMtvTePg9DagYqBninZDQ==",
|
|
676
|
-
"signed_at": "2026-05-
|
|
676
|
+
"signed_at": "2026-05-22T23:28:31.019Z"
|
|
677
677
|
},
|
|
678
678
|
{
|
|
679
679
|
"name": "attack-surface-pentest",
|
|
@@ -744,7 +744,7 @@
|
|
|
744
744
|
"PTES revision incorporating AI-surface enumeration"
|
|
745
745
|
],
|
|
746
746
|
"signature": "7eEwCXFd9pDKUw7yCUbRJSjfzozE44dwwwemCQUPm8JBPztLltibD9bL/RszSbYyCrYJmVb5Drncz2cGe62gCw==",
|
|
747
|
-
"signed_at": "2026-05-
|
|
747
|
+
"signed_at": "2026-05-22T23:28:31.019Z"
|
|
748
748
|
},
|
|
749
749
|
{
|
|
750
750
|
"name": "fuzz-testing-strategy",
|
|
@@ -804,7 +804,7 @@
|
|
|
804
804
|
"OSS-Fuzz-Gen / AI-assisted harness generation becoming the default expectation for OSS maintainers"
|
|
805
805
|
],
|
|
806
806
|
"signature": "Z7ypCUnXx8JpLtgxxB6RHNi39w74AmrGY1N4ofAGCXhkuM2EaFVm1AU0dvl9UQ1bVLfHKEDGqMO/TwlIY7RABg==",
|
|
807
|
-
"signed_at": "2026-05-
|
|
807
|
+
"signed_at": "2026-05-22T23:28:31.019Z"
|
|
808
808
|
},
|
|
809
809
|
{
|
|
810
810
|
"name": "dlp-gap-analysis",
|
|
@@ -879,7 +879,7 @@
|
|
|
879
879
|
"Quebec Law 25, India DPDPA, KSA PDPL enforcement actions naming AI-tool prompt data as in-scope personal information"
|
|
880
880
|
],
|
|
881
881
|
"signature": "fgxG344JGYBWWWwFXZ1IzGipWKP7EyBhrsvsbsb0CCGXfv/MvNHVNI6G0zQddCsWX1JeQbhZT3Vk8v1uJKDTDA==",
|
|
882
|
-
"signed_at": "2026-05-
|
|
882
|
+
"signed_at": "2026-05-22T23:28:31.020Z"
|
|
883
883
|
},
|
|
884
884
|
{
|
|
885
885
|
"name": "supply-chain-integrity",
|
|
@@ -956,7 +956,7 @@
|
|
|
956
956
|
"OpenSSF model-signing — emerging Sigstore-based signing standard for ML model weights; track for production adoption"
|
|
957
957
|
],
|
|
958
958
|
"signature": "pcLrM98A3vUSZRjwNAk0aZ9umvOwB41XCLLsCOy/IebB2F/06oIrGUKkMHtHwm4pTVPShMMcKdZQQ3jz30FnCg==",
|
|
959
|
-
"signed_at": "2026-05-
|
|
959
|
+
"signed_at": "2026-05-22T23:28:31.020Z"
|
|
960
960
|
},
|
|
961
961
|
{
|
|
962
962
|
"name": "defensive-countermeasure-mapping",
|
|
@@ -1013,7 +1013,7 @@
|
|
|
1013
1013
|
],
|
|
1014
1014
|
"last_threat_review": "2026-05-11",
|
|
1015
1015
|
"signature": "gqF8eU3VBrZhO2WnlcqKa7wm1d2mmWtvpbmx0kNCgHojNV+qEt+Ij84RO6bZvaUqhfYPWizWL79Fa4DL0curAQ==",
|
|
1016
|
-
"signed_at": "2026-05-
|
|
1016
|
+
"signed_at": "2026-05-22T23:28:31.020Z"
|
|
1017
1017
|
},
|
|
1018
1018
|
{
|
|
1019
1019
|
"name": "identity-assurance",
|
|
@@ -1080,7 +1080,7 @@
|
|
|
1080
1080
|
"d3fend_refs": [],
|
|
1081
1081
|
"last_threat_review": "2026-05-11",
|
|
1082
1082
|
"signature": "Wv5hGMeHjlaQK1zwicVCA7AvdKgJBgvcjdpGM9Ywahh9tagAKhbkOjybowDQZzu7OZ3bDkbh6pBYc1Sdwr6NAA==",
|
|
1083
|
-
"signed_at": "2026-05-
|
|
1083
|
+
"signed_at": "2026-05-22T23:28:31.021Z"
|
|
1084
1084
|
},
|
|
1085
1085
|
{
|
|
1086
1086
|
"name": "ot-ics-security",
|
|
@@ -1136,7 +1136,7 @@
|
|
|
1136
1136
|
"d3fend_refs": [],
|
|
1137
1137
|
"last_threat_review": "2026-05-11",
|
|
1138
1138
|
"signature": "8t5qKHd3yWi57dvG36YQkLN/X9bQWqtEiYjay4IfSmqhJpM/xXPaQVKNGz3wscrO8OLKUZ0OaX7Mj5kzpgBKBQ==",
|
|
1139
|
-
"signed_at": "2026-05-
|
|
1139
|
+
"signed_at": "2026-05-22T23:28:31.021Z"
|
|
1140
1140
|
},
|
|
1141
1141
|
{
|
|
1142
1142
|
"name": "coordinated-vuln-disclosure",
|
|
@@ -1188,7 +1188,7 @@
|
|
|
1188
1188
|
"NYDFS 23 NYCRR 500 amendments potentially adding explicit CVD program requirements"
|
|
1189
1189
|
],
|
|
1190
1190
|
"signature": "GDGt4UPqBa04PjlpSmpyihGzd3OgfBN7jaAK5tfwp+LRSs3ygKOdbeivUCCHNagTY1hE6hG2Ou40ADfBFuXeAg==",
|
|
1191
|
-
"signed_at": "2026-05-
|
|
1191
|
+
"signed_at": "2026-05-22T23:28:31.021Z"
|
|
1192
1192
|
},
|
|
1193
1193
|
{
|
|
1194
1194
|
"name": "threat-modeling-methodology",
|
|
@@ -1238,7 +1238,7 @@
|
|
|
1238
1238
|
"PASTA v2 updates incorporating AI/ML application threats"
|
|
1239
1239
|
],
|
|
1240
1240
|
"signature": "rFBpOQEJUPpl+v88Lw/WqVJRhTl80vy0VbPAbzQj3Q0suJRRrJg368I9uKu5LXIBKFDvKxnGIcIzbGg9NUtaCA==",
|
|
1241
|
-
"signed_at": "2026-05-
|
|
1241
|
+
"signed_at": "2026-05-22T23:28:31.022Z"
|
|
1242
1242
|
},
|
|
1243
1243
|
{
|
|
1244
1244
|
"name": "webapp-security",
|
|
@@ -1312,7 +1312,7 @@
|
|
|
1312
1312
|
"d3fend_refs": [],
|
|
1313
1313
|
"last_threat_review": "2026-05-11",
|
|
1314
1314
|
"signature": "ux85YI4t2mVHOyt744Yin1HHy+z11JIFygjKfFfQOBBl5QVV3A267jeIy7utix85irMcpZm/T3yx/ooqiK2tBA==",
|
|
1315
|
-
"signed_at": "2026-05-
|
|
1315
|
+
"signed_at": "2026-05-22T23:28:31.022Z"
|
|
1316
1316
|
},
|
|
1317
1317
|
{
|
|
1318
1318
|
"name": "ai-risk-management",
|
|
@@ -1362,7 +1362,7 @@
|
|
|
1362
1362
|
"d3fend_refs": [],
|
|
1363
1363
|
"last_threat_review": "2026-05-11",
|
|
1364
1364
|
"signature": "IIXnkZ5ZNqFwOto5KfytADTLLZLoyXNZACD1ORZ40P1HUAQxe6u2uyXFzzsfuob4Uy06jNkRGr2FFgCphUH1Cw==",
|
|
1365
|
-
"signed_at": "2026-05-
|
|
1365
|
+
"signed_at": "2026-05-22T23:28:31.022Z"
|
|
1366
1366
|
},
|
|
1367
1367
|
{
|
|
1368
1368
|
"name": "sector-healthcare",
|
|
@@ -1422,7 +1422,7 @@
|
|
|
1422
1422
|
"d3fend_refs": [],
|
|
1423
1423
|
"last_threat_review": "2026-05-11",
|
|
1424
1424
|
"signature": "AhF9KF8ZBlDteciV+F8IBSmFVYCvQOn44GmD4rZjgLoPxfIv/QE1/vSkK32zyqDKtHWkLSXExbkkPkxA/V6dDw==",
|
|
1425
|
-
"signed_at": "2026-05-
|
|
1425
|
+
"signed_at": "2026-05-22T23:28:31.023Z"
|
|
1426
1426
|
},
|
|
1427
1427
|
{
|
|
1428
1428
|
"name": "sector-financial",
|
|
@@ -1503,7 +1503,7 @@
|
|
|
1503
1503
|
"TIBER-EU framework v2.0 alignment with DORA TLPT RTS (JC 2024/40); cross-recognition with CBEST and iCAST"
|
|
1504
1504
|
],
|
|
1505
1505
|
"signature": "HQgZvb4ReziEz5rNFr8i/O8/rJEZR+iHRROT7m/D2QUqhrcNISPkYXENsUZlG8xapzy/Ik92ehkseyj4hdmhCQ==",
|
|
1506
|
-
"signed_at": "2026-05-
|
|
1506
|
+
"signed_at": "2026-05-22T23:28:31.023Z"
|
|
1507
1507
|
},
|
|
1508
1508
|
{
|
|
1509
1509
|
"name": "sector-federal-government",
|
|
@@ -1572,7 +1572,7 @@
|
|
|
1572
1572
|
"Australia PSPF 2024 revision and ISM quarterly updates — track for Essential Eight Maturity Level requirements for federal entities"
|
|
1573
1573
|
],
|
|
1574
1574
|
"signature": "linxmsXZiOYtcs71sSWgGCrvb8xQfmxmtTY5PRvZJ0/8FgJulo0tQtejzexYG775s7XhjAmGsDP238BQTQ8ADA==",
|
|
1575
|
-
"signed_at": "2026-05-
|
|
1575
|
+
"signed_at": "2026-05-22T23:28:31.024Z"
|
|
1576
1576
|
},
|
|
1577
1577
|
{
|
|
1578
1578
|
"name": "sector-energy",
|
|
@@ -1637,7 +1637,7 @@
|
|
|
1637
1637
|
"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"
|
|
1638
1638
|
],
|
|
1639
1639
|
"signature": "JjBfc0ovta560Clk0x3QGRM5osFJDwcvpy3rT7QEGdCIL827jzE8QCow1C8deXq+4JhY2sA/d7/8IsxikdlkCg==",
|
|
1640
|
-
"signed_at": "2026-05-
|
|
1640
|
+
"signed_at": "2026-05-22T23:28:31.024Z"
|
|
1641
1641
|
},
|
|
1642
1642
|
{
|
|
1643
1643
|
"name": "sector-telecom",
|
|
@@ -1723,7 +1723,7 @@
|
|
|
1723
1723
|
"O-RAN SFG / WG11 security specifications"
|
|
1724
1724
|
],
|
|
1725
1725
|
"signature": "JWVxKFoKrbX4d+Tko1d4OBdwyg25MfFFKn4CT6E/CzH+YwnU3T6Y76uBQIKg3+gIGTvPduqyvQwQQ5FxKDuPBw==",
|
|
1726
|
-
"signed_at": "2026-05-
|
|
1726
|
+
"signed_at": "2026-05-22T23:28:31.025Z"
|
|
1727
1727
|
},
|
|
1728
1728
|
{
|
|
1729
1729
|
"name": "api-security",
|
|
@@ -1792,7 +1792,7 @@
|
|
|
1792
1792
|
"d3fend_refs": [],
|
|
1793
1793
|
"last_threat_review": "2026-05-11",
|
|
1794
1794
|
"signature": "BmCRCestWqr55+fCynEhtAl5NWLT+xLTkpwS0Icp3SaoZOw/ce3Y6TtqjHRSKn4CBJq7YDiLRWxmhO3MStvOAA==",
|
|
1795
|
-
"signed_at": "2026-05-
|
|
1795
|
+
"signed_at": "2026-05-22T23:28:31.025Z"
|
|
1796
1796
|
},
|
|
1797
1797
|
{
|
|
1798
1798
|
"name": "cloud-security",
|
|
@@ -1873,7 +1873,7 @@
|
|
|
1873
1873
|
"CISA KEV additions for cloud-control-plane CVEs (IMDSv1 abuses, federation token mishandling, cross-tenant boundary failures); CISA Cybersecurity Advisories for cross-cloud advisories"
|
|
1874
1874
|
],
|
|
1875
1875
|
"signature": "/DV3pmZwrRySrk1OCbyI+0BQESacjupJfUX3eC2NGtXuYOBro0vndIP+z27heFxumnjU3a9sfla7/U9X+pqnDw==",
|
|
1876
|
-
"signed_at": "2026-05-
|
|
1876
|
+
"signed_at": "2026-05-22T23:28:31.025Z"
|
|
1877
1877
|
},
|
|
1878
1878
|
{
|
|
1879
1879
|
"name": "container-runtime-security",
|
|
@@ -1935,7 +1935,7 @@
|
|
|
1935
1935
|
"d3fend_refs": [],
|
|
1936
1936
|
"last_threat_review": "2026-05-11",
|
|
1937
1937
|
"signature": "E2UGSf9ATyYgzBr8uM/0ubOUmDqo1jVA7f9mVxv6LHfWGCNuQNXDyuNou9VAmUCeeXEeUYIi3AFjXkJqpOkxDA==",
|
|
1938
|
-
"signed_at": "2026-05-
|
|
1938
|
+
"signed_at": "2026-05-22T23:28:31.026Z"
|
|
1939
1939
|
},
|
|
1940
1940
|
{
|
|
1941
1941
|
"name": "mlops-security",
|
|
@@ -2006,7 +2006,7 @@
|
|
|
2006
2006
|
"MITRE ATLAS v5.6.0 (released February 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: ATLAS v5.5 / v6.0 — track next-cadence updates to agentic-AI TTPs and MLOps-pipeline-specific techniques"
|
|
2007
2007
|
],
|
|
2008
2008
|
"signature": "BGNE6ZQWBA1LmsUFe8tU0L67iGDSrFqiuqaZD2f1KqfcyqqzQfMs9PWNHFzxxaJmXeKlm87eU8lgELF0bX+RBA==",
|
|
2009
|
-
"signed_at": "2026-05-
|
|
2009
|
+
"signed_at": "2026-05-22T23:28:31.026Z"
|
|
2010
2010
|
},
|
|
2011
2011
|
{
|
|
2012
2012
|
"name": "incident-response-playbook",
|
|
@@ -2068,7 +2068,7 @@
|
|
|
2068
2068
|
"NYDFS 23 NYCRR 500.17 amendments tightening ransom-payment 24h disclosure operationalization"
|
|
2069
2069
|
],
|
|
2070
2070
|
"signature": "FkZQerh3VHVJAwIcCktDyMRh5KE2+Em/i0ek8zEz7JG/PXtQx8ujHWTh3VjZbOLhPNtdB2qxgXOIAYIofaVOAQ==",
|
|
2071
|
-
"signed_at": "2026-05-
|
|
2071
|
+
"signed_at": "2026-05-22T23:28:31.027Z"
|
|
2072
2072
|
},
|
|
2073
2073
|
{
|
|
2074
2074
|
"name": "ransomware-response",
|
|
@@ -2148,7 +2148,7 @@
|
|
|
2148
2148
|
],
|
|
2149
2149
|
"last_threat_review": "2026-05-15",
|
|
2150
2150
|
"signature": "n3UToNuN3A1HgLvcuqmIx8vrZY71+r/79waK92jG+rSX4uYOzkmxMUpROrE5K9bDwMezNBHdjWv8Uul6zugyDQ==",
|
|
2151
|
-
"signed_at": "2026-05-
|
|
2151
|
+
"signed_at": "2026-05-22T23:28:31.027Z"
|
|
2152
2152
|
},
|
|
2153
2153
|
{
|
|
2154
2154
|
"name": "email-security-anti-phishing",
|
|
@@ -2201,7 +2201,7 @@
|
|
|
2201
2201
|
"d3fend_refs": [],
|
|
2202
2202
|
"last_threat_review": "2026-05-11",
|
|
2203
2203
|
"signature": "rK+WnuS+9tqEABmwc0jO/PEmxcLjG1/tmUb897HsClQeKzf+TQOlwBE+OsbtuKxpjYNwur62Xxs3TxObkwm8Cw==",
|
|
2204
|
-
"signed_at": "2026-05-
|
|
2204
|
+
"signed_at": "2026-05-22T23:28:31.027Z"
|
|
2205
2205
|
},
|
|
2206
2206
|
{
|
|
2207
2207
|
"name": "age-gates-child-safety",
|
|
@@ -2269,7 +2269,7 @@
|
|
|
2269
2269
|
"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"
|
|
2270
2270
|
],
|
|
2271
2271
|
"signature": "+OO0RhQ303RJV7kaH38IuZpLeQbapep6Ds4Re/WEZu0FHBwKSlwvF7jbtP7KQ57xldJYn/xZm2jaszyOacMfDg==",
|
|
2272
|
-
"signed_at": "2026-05-
|
|
2272
|
+
"signed_at": "2026-05-22T23:28:31.028Z"
|
|
2273
2273
|
},
|
|
2274
2274
|
{
|
|
2275
2275
|
"name": "cloud-iam-incident",
|
|
@@ -2349,7 +2349,7 @@
|
|
|
2349
2349
|
],
|
|
2350
2350
|
"last_threat_review": "2026-05-15",
|
|
2351
2351
|
"signature": "e/kij7GtKaytROyIj7V5RH+FC9WtmVFzrmG2kIlNDNn29ep/CRNlIQKwXLpzo/81AIf634pmdr1qy/+vwIuUDA==",
|
|
2352
|
-
"signed_at": "2026-05-
|
|
2352
|
+
"signed_at": "2026-05-22T23:28:31.028Z"
|
|
2353
2353
|
},
|
|
2354
2354
|
{
|
|
2355
2355
|
"name": "idp-incident-response",
|
|
@@ -2430,11 +2430,11 @@
|
|
|
2430
2430
|
],
|
|
2431
2431
|
"last_threat_review": "2026-05-15",
|
|
2432
2432
|
"signature": "ew9Kglc9fAZzbn0ZIfGP7WSK/j4eV2VhSvpy+s5bEfNEVYIMa2kZjnGBapgUsyGDLes9H9K2ovjQyX17+GKiBw==",
|
|
2433
|
-
"signed_at": "2026-05-
|
|
2433
|
+
"signed_at": "2026-05-22T23:28:31.028Z"
|
|
2434
2434
|
}
|
|
2435
2435
|
],
|
|
2436
2436
|
"manifest_signature": {
|
|
2437
2437
|
"algorithm": "Ed25519",
|
|
2438
|
-
"signature_base64": "
|
|
2438
|
+
"signature_base64": "jYdDFzIsg9EdwlNtBAT5NYGZYTtQzpZxo5lGAUJ4lG2hSWgmFy8dOqe4fL1o++uqDYDbUfh8tF/CkDOiGqJrCg=="
|
|
2439
2439
|
}
|
|
2440
2440
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blamejs/exceptd-skills",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.59",
|
|
4
4
|
"description": "AI security skills grounded in mid-2026 threat reality, not stale framework documentation. 42 skills, 10 catalogs (312 CVEs / 171 CWEs / 805 ATT&CK + ICS / 170 ATLAS / 468 D3FEND / 7476 RFCs), 34 jurisdictions, 10-class catalog gap detector + budget gate, real XML parser + canonical-form diff + content-pattern regression detection, Ed25519-signed.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-security",
|
package/sbom.cdx.json
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"bomFormat": "CycloneDX",
|
|
3
3
|
"specVersion": "1.6",
|
|
4
|
-
"serialNumber": "urn:uuid:
|
|
4
|
+
"serialNumber": "urn:uuid:eb996267-363d-46e0-9a5c-f259a8c46d9b",
|
|
5
5
|
"version": 1,
|
|
6
6
|
"metadata": {
|
|
7
|
-
"timestamp": "
|
|
7
|
+
"timestamp": "2151-04-04T19:39:19.000Z",
|
|
8
8
|
"tools": [
|
|
9
9
|
{
|
|
10
10
|
"vendor": "blamejs",
|
|
11
11
|
"name": "scripts/refresh-sbom.js",
|
|
12
|
-
"version": "0.13.
|
|
12
|
+
"version": "0.13.59"
|
|
13
13
|
}
|
|
14
14
|
],
|
|
15
15
|
"component": {
|
|
16
|
-
"bom-ref": "pkg:npm/@blamejs/exceptd-skills@0.13.
|
|
16
|
+
"bom-ref": "pkg:npm/@blamejs/exceptd-skills@0.13.59",
|
|
17
17
|
"type": "application",
|
|
18
18
|
"name": "@blamejs/exceptd-skills",
|
|
19
|
-
"version": "0.13.
|
|
19
|
+
"version": "0.13.59",
|
|
20
20
|
"description": "AI security skills grounded in mid-2026 threat reality, not stale framework documentation. 42 skills, 10 catalogs (312 CVEs / 171 CWEs / 805 ATT&CK + ICS / 170 ATLAS / 468 D3FEND / 7476 RFCs), 34 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.13.
|
|
28
|
+
"purl": "pkg:npm/%40blamejs/exceptd-skills@0.13.59",
|
|
29
29
|
"hashes": [
|
|
30
30
|
{
|
|
31
31
|
"alg": "SHA-256",
|
|
32
|
-
"content": "
|
|
32
|
+
"content": "97387465e487f40271bdfd6dc20958446c2772c571c9c7671e9f912e0a6b32ae"
|
|
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.13.
|
|
38
|
+
"url": "https://www.npmjs.com/package/@blamejs/exceptd-skills/v/0.13.59"
|
|
39
39
|
},
|
|
40
40
|
{
|
|
41
41
|
"type": "vcs",
|
|
@@ -116,11 +116,11 @@
|
|
|
116
116
|
"hashes": [
|
|
117
117
|
{
|
|
118
118
|
"alg": "SHA-256",
|
|
119
|
-
"content": "
|
|
119
|
+
"content": "d08eca001f3bd9547d899b0531b59b5bcf929bcc1134f5f920bc85453a96bee8"
|
|
120
120
|
},
|
|
121
121
|
{
|
|
122
122
|
"alg": "SHA3-512",
|
|
123
|
-
"content": "
|
|
123
|
+
"content": "9b6e0d750008f8b273c44016fd002d31f38ebb95b00787cd57081c29f8afed8c9c56b94a85c17b9a1bd4edacdbf12ba602acda6459d7743ab409ff3471114932"
|
|
124
124
|
}
|
|
125
125
|
]
|
|
126
126
|
},
|
|
@@ -281,11 +281,11 @@
|
|
|
281
281
|
"hashes": [
|
|
282
282
|
{
|
|
283
283
|
"alg": "SHA-256",
|
|
284
|
-
"content": "
|
|
284
|
+
"content": "679a543aec746f1e99d891099bd04ab88fd0b8ba6e20a3484c9d9e3a9d33fd7e"
|
|
285
285
|
},
|
|
286
286
|
{
|
|
287
287
|
"alg": "SHA3-512",
|
|
288
|
-
"content": "
|
|
288
|
+
"content": "694240b8a9268abc9ec54bdb52df56389fb115a81f99d70121ccb7e427d7bc670d7d4651fbaaec9b7be35f03c861b75b06177ba4e372890304542aa1a4615a83"
|
|
289
289
|
}
|
|
290
290
|
]
|
|
291
291
|
},
|
|
@@ -1271,11 +1271,11 @@
|
|
|
1271
1271
|
"hashes": [
|
|
1272
1272
|
{
|
|
1273
1273
|
"alg": "SHA-256",
|
|
1274
|
-
"content": "
|
|
1274
|
+
"content": "f769111b2412cd0ae3ff6f2adc54002355a271262d79a2c645a4e0f41e2a1c1a"
|
|
1275
1275
|
},
|
|
1276
1276
|
{
|
|
1277
1277
|
"alg": "SHA3-512",
|
|
1278
|
-
"content": "
|
|
1278
|
+
"content": "5f58125b5706702ad3c6f1c4738772d8a61f6183426af178ae3666f16c8201c623920f668f0b83075484bfd1b100b83563161b7e36e6ffba7c073af5dadbb4b9"
|
|
1279
1279
|
}
|
|
1280
1280
|
]
|
|
1281
1281
|
},
|
|
@@ -1661,11 +1661,11 @@
|
|
|
1661
1661
|
"hashes": [
|
|
1662
1662
|
{
|
|
1663
1663
|
"alg": "SHA-256",
|
|
1664
|
-
"content": "
|
|
1664
|
+
"content": "020990fd6a13ea3a2a0273e80d0607c1cf0fd8887f03bb48294d2568a22d2789"
|
|
1665
1665
|
},
|
|
1666
1666
|
{
|
|
1667
1667
|
"alg": "SHA3-512",
|
|
1668
|
-
"content": "
|
|
1668
|
+
"content": "4bbf18c5ec14dbae987465ebca8dc06b2fcce0347eae3f834fb1fc33115289acf6484385b25f728548b70e37a7f4f2ce3d4c314b36465961cc22dbaf7c974e14"
|
|
1669
1669
|
}
|
|
1670
1670
|
]
|
|
1671
1671
|
},
|