@blamejs/exceptd-skills 0.9.1 → 0.9.2
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 +57 -0
- package/data/_indexes/_meta.json +2 -2
- package/lib/auto-discovery.js +480 -0
- package/lib/refresh-external.js +69 -5
- package/manifest-snapshot.json +1 -1
- package/manifest.json +39 -39
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,62 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.2 — 2026-05-12
|
|
4
|
+
|
|
5
|
+
**Pin: auto-discovery for KEV + IETF catalogs.** The refresh workflow now adds *new* catalog entries automatically instead of only updating existing ones.
|
|
6
|
+
|
|
7
|
+
### What changed
|
|
8
|
+
|
|
9
|
+
- **CISA KEV discovery** — when CISA adds a new CVE to the Known Exploited Vulnerabilities list, the next nightly refresh detects it (cached KEV feed entry, not in local `data/cve-catalog.json`) and emits a draft entry. NVD CVSS metrics + EPSS score pulled from the prefetch cache when available; nulled otherwise. Initial RWEP score computed via `lib/scoring.js` with KEV=true + suspected exploitation + reboot-required = baseline ~55.
|
|
10
|
+
- **IETF RFC discovery** — Datatracker query against project-relevant working groups returns recent RFCs not in `data/rfc-references.json`. WG filter is the union of (a) dynamically derived from cached Datatracker docs on currently-cited RFCs, plus (b) a curated seed list of 35 WGs covering crypto/PKI/TLS, identity/auth/SSO, supply chain/attestation (`scitt` / `rats` / `suit` / `teep`), threat intel (`mile` / `sacm`), DNS security, messaging E2E, and IoT mgmt. Seed list documented in `lib/auto-discovery.js`.
|
|
11
|
+
- **Draft entry annotation** — every auto-imported entry carries an `_auto_imported` block:
|
|
12
|
+
```jsonc
|
|
13
|
+
"_auto_imported": {
|
|
14
|
+
"source": "KEV discovery",
|
|
15
|
+
"imported_at": "2026-05-12",
|
|
16
|
+
"curation_needed": [
|
|
17
|
+
"type (LPE/RCE/SSRF/etc.)",
|
|
18
|
+
"framework_control_gaps mapping",
|
|
19
|
+
"atlas_refs + attack_refs categorization",
|
|
20
|
+
...
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
Mechanical fields (CVSS, KEV, EPSS, name, vendor) get populated; analytical fields (framework_control_gaps, ATLAS/ATT&CK refs, type classification) stay null and are listed for human curation.
|
|
25
|
+
- **PR body** in `refresh.yml` now splits cleanly: **"New entries (auto-imported — needs human curation)"** table first, then **"Updates to existing entries"** table. New label `needs-curation` added alongside the existing `data-refresh` + `automation`.
|
|
26
|
+
- **Volume cap** — 20 new entries per PR per source (configurable via `DEFAULT_CAP`). Spill is reported in the summary so a CISA mass-add doesn't generate an unreviewable PR.
|
|
27
|
+
|
|
28
|
+
### `lib/auto-discovery.js` (new module, ~280 lines, zero deps)
|
|
29
|
+
|
|
30
|
+
- `discoverNewKev(ctx, cap?)` — KEV → array of `op:"add"` diffs
|
|
31
|
+
- `discoverNewRfcs(ctx, opts?)` — RFC discovery via Datatracker WG queries
|
|
32
|
+
- `buildKevDraftEntry(kev, nvd?, epss?)` — pure function, no I/O, easy to test
|
|
33
|
+
- `getProjectRfcGroups(ctx)` — union of cache-derived + `SEED_RFC_GROUPS`
|
|
34
|
+
- `SEED_RFC_GROUPS` — curated WG list (exported for testing + transparency)
|
|
35
|
+
|
|
36
|
+
### `lib/refresh-external.js` changes
|
|
37
|
+
|
|
38
|
+
- `KEV_SOURCE.fetchDiff` now merges drift-check + discovery in cache mode (`kevDiffWithDiscoveryFromCache`)
|
|
39
|
+
- `RFC_SOURCE.fetchDiff` same pattern (`rfcDiffWithDiscoveryFromCache` — drift from cache, discovery live)
|
|
40
|
+
- `applyDiff` handlers learn the new `op: "add"` diff shape and insert entries verbatim. Returns enriched stats: `{ updated, added, drift_updated, errors }`.
|
|
41
|
+
|
|
42
|
+
### Tests
|
|
43
|
+
|
|
44
|
+
`tests/auto-discovery.test.js` — 9 new tests:
|
|
45
|
+
- Seed WG breadth (must include `tls`, `oauth`, `scitt`, `rats`, `dnsop`, `acme`, `mls`, etc.)
|
|
46
|
+
- `buildKevDraftEntry` populates all required schema fields
|
|
47
|
+
- NVD CVSS + CWE extraction
|
|
48
|
+
- EPSS score extraction
|
|
49
|
+
- Empty result when KEV cache missing
|
|
50
|
+
- New CVE detection (filters out CVEs already in local catalog)
|
|
51
|
+
- Volume cap + spill counting
|
|
52
|
+
- RWEP score bounded 0–100
|
|
53
|
+
|
|
54
|
+
Total: 192 → **201 tests**. 13/13 predeploy gates green.
|
|
55
|
+
|
|
56
|
+
### Operational note
|
|
57
|
+
|
|
58
|
+
The first run after deploy will likely pick up **8 new KEV entries** from the past ~5 days of CISA activity (visible in `/api/intel` already). These appear in the next auto-PR as a curated batch.
|
|
59
|
+
|
|
3
60
|
## 0.9.1 — 2026-05-11
|
|
4
61
|
|
|
5
62
|
**Patch: test-runner concurrency fix for first npm publish.**
|
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-12T03:19:12.090Z",
|
|
4
4
|
"generator": "scripts/build-indexes.js",
|
|
5
5
|
"source_count": 49,
|
|
6
6
|
"source_hashes": {
|
|
7
|
-
"manifest.json": "
|
|
7
|
+
"manifest.json": "05cbd5fc644f6681abd62ce63f393112511a06df1db74a366ee8156447ba0427",
|
|
8
8
|
"data/atlas-ttps.json": "1500b5830dab070c4252496964a8c0948e1052a656e2c7c6e1efaf0350645e13",
|
|
9
9
|
"data/cve-catalog.json": "a81d3e4b491b27ccc084596b063a6108ff10c9eb01d7776922fc393980b534fe",
|
|
10
10
|
"data/cwe-catalog.json": "c3367d469b4b3d31e4c56397dd7a8305a0be338ecd85afa27804c0c9ce12157b",
|
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* lib/auto-discovery.js
|
|
4
|
+
*
|
|
5
|
+
* Discovers NEW catalog entries upstream and builds draft entries for
|
|
6
|
+
* `refresh-external.js` to include as `op:"add"` diffs in its auto-PR.
|
|
7
|
+
*
|
|
8
|
+
* Sources covered:
|
|
9
|
+
* - KEV: every CVE in the CISA KEV feed that's not in local
|
|
10
|
+
* data/cve-catalog.json. NVD + EPSS data is pulled from the same
|
|
11
|
+
* prefetch cache the drift-check uses; missing cache entries fall
|
|
12
|
+
* through to a draft with null mechanical fields.
|
|
13
|
+
* - RFC: every recent IETF RFC published in a working group the
|
|
14
|
+
* project's existing rfc-references.json already cites. Queried
|
|
15
|
+
* live against Datatracker (small N — typically 1-5 RFCs per
|
|
16
|
+
* month across all project-relevant WGs).
|
|
17
|
+
*
|
|
18
|
+
* Each draft entry carries an `_auto_imported` block with the source,
|
|
19
|
+
* import date, and a `curation_needed` list of analytical fields a
|
|
20
|
+
* human still needs to fill (framework_control_gaps, atlas_refs,
|
|
21
|
+
* attack_refs, type classification, etc.). `validate-cve-catalog.js`
|
|
22
|
+
* is tolerant of this annotation; the audit / stale-content index
|
|
23
|
+
* surfaces uncurated entries so they don't sit indefinitely.
|
|
24
|
+
*
|
|
25
|
+
* Both discovery functions accept a `cap` (default 20) so a burst
|
|
26
|
+
* upstream addition doesn't generate an unreviewable PR. Items past
|
|
27
|
+
* the cap spill to the next run.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
const fs = require("fs");
|
|
31
|
+
const path = require("path");
|
|
32
|
+
const { scoreCustom } = require("./scoring");
|
|
33
|
+
|
|
34
|
+
const TODAY = new Date().toISOString().slice(0, 10);
|
|
35
|
+
const TIMEOUT_MS = 10_000;
|
|
36
|
+
const USER_AGENT = "exceptd-security/auto-discovery (+https://exceptd.com)";
|
|
37
|
+
const DEFAULT_CAP = 20;
|
|
38
|
+
|
|
39
|
+
// IETF Datatracker codes → human-readable status strings used in
|
|
40
|
+
// data/rfc-references.json.
|
|
41
|
+
const RFC_STATUS_MAP = {
|
|
42
|
+
std: "Internet Standard",
|
|
43
|
+
ps: "Proposed Standard",
|
|
44
|
+
ds: "Draft Standard",
|
|
45
|
+
bcp: "Best Current Practice",
|
|
46
|
+
inf: "Informational",
|
|
47
|
+
exp: "Experimental",
|
|
48
|
+
his: "Historic",
|
|
49
|
+
unkn: "Unknown",
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
function readCachedJson(cacheDir, source, id) {
|
|
53
|
+
if (!cacheDir) return null;
|
|
54
|
+
const safe = String(id).replace(/[^A-Za-z0-9._-]/g, "_");
|
|
55
|
+
const p = path.join(cacheDir, source, `${safe}.json`);
|
|
56
|
+
if (!fs.existsSync(p)) return null;
|
|
57
|
+
try { return JSON.parse(fs.readFileSync(p, "utf8")); } catch { return null; }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function extractNvdMetrics(payload) {
|
|
61
|
+
const vuln = payload?.vulnerabilities?.[0]?.cve;
|
|
62
|
+
if (!vuln) return null;
|
|
63
|
+
const m = vuln.metrics || {};
|
|
64
|
+
const ordered = [
|
|
65
|
+
...(m.cvssMetricV31 || []),
|
|
66
|
+
...(m.cvssMetricV30 || []),
|
|
67
|
+
...(m.cvssMetricV2 || []),
|
|
68
|
+
];
|
|
69
|
+
const primary = ordered.find((x) => x.type === "Primary") || ordered[0];
|
|
70
|
+
return {
|
|
71
|
+
cvss_score: typeof primary?.cvssData?.baseScore === "number" ? primary.cvssData.baseScore : null,
|
|
72
|
+
cvss_vector: primary?.cvssData?.vectorString || null,
|
|
73
|
+
description: (vuln.descriptions || []).find((d) => d.lang === "en")?.value || null,
|
|
74
|
+
cwe_refs: ((vuln.weaknesses || [])
|
|
75
|
+
.flatMap((w) => (w.description || []))
|
|
76
|
+
.map((d) => d.value)
|
|
77
|
+
.filter((v) => /^CWE-\d+$/.test(v))
|
|
78
|
+
),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function extractEpss(payload, id) {
|
|
83
|
+
const data = Array.isArray(payload?.data) ? payload.data : [];
|
|
84
|
+
const row = data.find((r) => r?.cve === id) || data[0];
|
|
85
|
+
if (!row) return null;
|
|
86
|
+
return {
|
|
87
|
+
score: row.epss != null ? Number(row.epss) : null,
|
|
88
|
+
percentile: row.percentile != null ? Number(row.percentile) : null,
|
|
89
|
+
date: typeof row.date === "string" ? row.date : null,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// --- KEV discovery -----------------------------------------------------
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Build a draft CVE catalog entry from a KEV record + optional cached
|
|
97
|
+
* NVD/EPSS payloads. Required-schema fields are populated where
|
|
98
|
+
* mechanically derivable; analytical fields are nulled and listed in
|
|
99
|
+
* `_auto_imported.curation_needed`.
|
|
100
|
+
*
|
|
101
|
+
* @param {object} kevEntry Single vulnerability from CISA KEV feed
|
|
102
|
+
* @param {object|null} nvdPayload Cached NVD 2.0 response (or null)
|
|
103
|
+
* @param {object|null} epssPayload Cached EPSS response (or null)
|
|
104
|
+
*/
|
|
105
|
+
function buildKevDraftEntry(kevEntry, nvdPayload, epssPayload) {
|
|
106
|
+
const id = String(kevEntry.cveID);
|
|
107
|
+
const nvd = nvdPayload ? extractNvdMetrics(nvdPayload) : null;
|
|
108
|
+
const epss = epssPayload ? extractEpss(epssPayload, id) : null;
|
|
109
|
+
|
|
110
|
+
const knownRansomware =
|
|
111
|
+
String(kevEntry.knownRansomwareCampaignUse || "").toLowerCase() === "known";
|
|
112
|
+
|
|
113
|
+
// Compute initial RWEP. KEV → +25, suspected exploitation → +10.
|
|
114
|
+
// Unknown PoC/AI flags default to false (conservative — we don't
|
|
115
|
+
// claim more than we know). Blast radius defaults to 15 (mid-range)
|
|
116
|
+
// since we can't infer it from KEV metadata alone.
|
|
117
|
+
const rwep_factors = {
|
|
118
|
+
cisa_kev: true,
|
|
119
|
+
poc_available: null, // unknown — curation needed
|
|
120
|
+
ai_assisted_weapon: null,
|
|
121
|
+
ai_discovered: null,
|
|
122
|
+
active_exploitation: "suspected", // KEV listing implies exploitation
|
|
123
|
+
blast_radius: 15,
|
|
124
|
+
patch_available: null,
|
|
125
|
+
live_patch_available: null,
|
|
126
|
+
reboot_required: null,
|
|
127
|
+
};
|
|
128
|
+
// scoreCustom() treats null fields as false, which under-counts the
|
|
129
|
+
// score. Pass concrete defaults for unknowns: poc_available=true is
|
|
130
|
+
// the conservative assumption for KEV entries (CISA generally only
|
|
131
|
+
// adds entries with documented exploitation), and reboot_required=
|
|
132
|
+
// true biases toward urgency.
|
|
133
|
+
const rwep_score = scoreCustom({
|
|
134
|
+
cisa_kev: true,
|
|
135
|
+
poc_available: true,
|
|
136
|
+
ai_assisted_weapon: false,
|
|
137
|
+
ai_discovered: false,
|
|
138
|
+
active_exploitation: "suspected",
|
|
139
|
+
blast_radius: 15,
|
|
140
|
+
patch_available: false,
|
|
141
|
+
live_patch_available: false,
|
|
142
|
+
reboot_required: true,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const product = [kevEntry.vendorProject, kevEntry.product]
|
|
146
|
+
.filter(Boolean)
|
|
147
|
+
.join(" ");
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
name: String(kevEntry.vulnerabilityName || "TBD — verify against vendor advisory"),
|
|
151
|
+
type: "TBD",
|
|
152
|
+
cvss_score: nvd?.cvss_score ?? null,
|
|
153
|
+
cvss_vector: nvd?.cvss_vector ?? null,
|
|
154
|
+
cisa_kev: true,
|
|
155
|
+
cisa_kev_date: kevEntry.dateAdded || null,
|
|
156
|
+
cisa_kev_due_date: kevEntry.dueDate || null,
|
|
157
|
+
poc_available: null,
|
|
158
|
+
poc_description: null,
|
|
159
|
+
ai_discovered: null,
|
|
160
|
+
ai_discovery_notes: null,
|
|
161
|
+
ai_assisted_weaponization: null,
|
|
162
|
+
active_exploitation: "suspected",
|
|
163
|
+
affected: product || "See vendor advisory",
|
|
164
|
+
affected_versions: [],
|
|
165
|
+
vector: nvd?.description || kevEntry.shortDescription || "TBD",
|
|
166
|
+
complexity: null,
|
|
167
|
+
complexity_notes: null,
|
|
168
|
+
patch_available: null,
|
|
169
|
+
patch_required_reboot: null,
|
|
170
|
+
live_patch_available: null,
|
|
171
|
+
live_patch_tools: [],
|
|
172
|
+
live_patch_notes: null,
|
|
173
|
+
framework_control_gaps: {},
|
|
174
|
+
atlas_refs: [],
|
|
175
|
+
attack_refs: [],
|
|
176
|
+
cwe_refs: nvd?.cwe_refs || [],
|
|
177
|
+
known_ransomware_use: knownRansomware,
|
|
178
|
+
epss_score: epss?.score ?? null,
|
|
179
|
+
epss_percentile: epss?.percentile ?? null,
|
|
180
|
+
epss_date: epss?.date ?? null,
|
|
181
|
+
rwep_score,
|
|
182
|
+
rwep_factors,
|
|
183
|
+
verification_sources: [
|
|
184
|
+
"https://www.cisa.gov/known-exploited-vulnerabilities-catalog",
|
|
185
|
+
kevEntry.notes ? String(kevEntry.notes) : null,
|
|
186
|
+
].filter(Boolean),
|
|
187
|
+
source_verified: false,
|
|
188
|
+
last_updated: TODAY,
|
|
189
|
+
last_verified: TODAY,
|
|
190
|
+
_auto_imported: {
|
|
191
|
+
source: "KEV discovery",
|
|
192
|
+
imported_at: TODAY,
|
|
193
|
+
curation_needed: [
|
|
194
|
+
"type (LPE/RCE/SSRF/etc.)",
|
|
195
|
+
"poc_available + poc_description (link to public PoC if any)",
|
|
196
|
+
"ai_discovered + ai_assisted_weaponization classification",
|
|
197
|
+
"active_exploitation upgrade from 'suspected' to 'confirmed' once a campaign is documented",
|
|
198
|
+
"framework_control_gaps mapping (NIST/ISO/PCI/SOC 2 controls this defeats)",
|
|
199
|
+
"atlas_refs + attack_refs categorization",
|
|
200
|
+
"complexity assessment + complexity_notes",
|
|
201
|
+
"patch_available + live_patch_available + live_patch_tools",
|
|
202
|
+
"blast_radius numeric in rwep_factors (currently default 15)",
|
|
203
|
+
"RWEP score recompute after the above land",
|
|
204
|
+
"source_verified once a project maintainer has confirmed the upstream",
|
|
205
|
+
],
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Find KEV entries upstream that are not in local cve-catalog.json.
|
|
212
|
+
* Returns an array of { id, op:"add", entry, severity } diffs capped
|
|
213
|
+
* at `cap` items. Spill past the cap is logged on the diff object's
|
|
214
|
+
* `_spilled` count so the PR body can mention it.
|
|
215
|
+
*/
|
|
216
|
+
function discoverNewKev(ctx, cap = DEFAULT_CAP) {
|
|
217
|
+
const feed = readCachedJson(ctx.cacheDir, "kev", "known_exploited_vulnerabilities");
|
|
218
|
+
if (!feed || !Array.isArray(feed.vulnerabilities)) {
|
|
219
|
+
return { diffs: [], errors: 1, spilled: 0, summary: "KEV discovery: no cached feed" };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const localCves = new Set(
|
|
223
|
+
Object.keys(ctx.cveCatalog).filter((k) => /^CVE-\d{4}-\d{4,7}$/.test(k))
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
// Sort by dateAdded descending so the most recent additions are kept
|
|
227
|
+
// when the cap clips the list.
|
|
228
|
+
const candidates = feed.vulnerabilities
|
|
229
|
+
.filter((v) => v && v.cveID && !localCves.has(String(v.cveID)))
|
|
230
|
+
.sort((a, b) => String(b.dateAdded || "").localeCompare(String(a.dateAdded || "")));
|
|
231
|
+
|
|
232
|
+
const total = candidates.length;
|
|
233
|
+
const picks = candidates.slice(0, cap);
|
|
234
|
+
const spilled = Math.max(0, total - picks.length);
|
|
235
|
+
|
|
236
|
+
const diffs = picks.map((kev) => {
|
|
237
|
+
const id = String(kev.cveID);
|
|
238
|
+
const nvd = readCachedJson(ctx.cacheDir, "nvd", id);
|
|
239
|
+
const epss = readCachedJson(ctx.cacheDir, "epss", id);
|
|
240
|
+
const entry = buildKevDraftEntry(kev, nvd, epss);
|
|
241
|
+
return {
|
|
242
|
+
id,
|
|
243
|
+
op: "add",
|
|
244
|
+
target: "cveCatalog",
|
|
245
|
+
entry,
|
|
246
|
+
severity: "high",
|
|
247
|
+
meta: {
|
|
248
|
+
date_added: kev.dateAdded || null,
|
|
249
|
+
vendor: kev.vendorProject || null,
|
|
250
|
+
product: kev.product || null,
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
diffs,
|
|
257
|
+
errors: 0,
|
|
258
|
+
spilled,
|
|
259
|
+
summary: total === 0
|
|
260
|
+
? "KEV discovery: no new entries"
|
|
261
|
+
: `KEV discovery: ${diffs.length} new entries${spilled > 0 ? ` (+${spilled} spilled past cap)` : ""}`,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// --- RFC discovery -----------------------------------------------------
|
|
266
|
+
|
|
267
|
+
async function fetchDatatracker(url) {
|
|
268
|
+
const ac = new AbortController();
|
|
269
|
+
const t = setTimeout(() => ac.abort(), TIMEOUT_MS);
|
|
270
|
+
try {
|
|
271
|
+
const res = await fetch(url, {
|
|
272
|
+
signal: ac.signal,
|
|
273
|
+
headers: { "User-Agent": USER_AGENT, Accept: "application/json" },
|
|
274
|
+
});
|
|
275
|
+
if (!res.ok) return null;
|
|
276
|
+
return await res.json();
|
|
277
|
+
} catch {
|
|
278
|
+
return null;
|
|
279
|
+
} finally {
|
|
280
|
+
clearTimeout(t);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Derive the set of IETF working-group acronyms the project already
|
|
286
|
+
* cares about. Reads each entry in data/rfc-references.json, looks up
|
|
287
|
+
* its Datatracker doc in the prefetch cache, extracts the group
|
|
288
|
+
* acronym, returns the union.
|
|
289
|
+
*
|
|
290
|
+
* Two layers in the result:
|
|
291
|
+
*
|
|
292
|
+
* 1. DYNAMICALLY DERIVED — every WG that appears on a project-cited
|
|
293
|
+
* RFC's Datatracker record. Grows organically as catalog grows.
|
|
294
|
+
*
|
|
295
|
+
* 2. SEEDED — a curated baseline of IETF WGs that publish RFCs
|
|
296
|
+
* directly relevant to the project's mid-2026 threat model and
|
|
297
|
+
* compliance frameworks, even when the catalog doesn't yet cite
|
|
298
|
+
* one of their RFCs. Without this, RFC discovery would be blind
|
|
299
|
+
* to e.g. SCITT (supply chain) until a SCITT RFC was already
|
|
300
|
+
* manually added — defeating the point of discovery.
|
|
301
|
+
*
|
|
302
|
+
* SEED groups by project area:
|
|
303
|
+
*
|
|
304
|
+
* Transport / crypto / PKI:
|
|
305
|
+
* tls, uta, cfrg, lamps, ipsecme
|
|
306
|
+
* HTTP / web / QUIC:
|
|
307
|
+
* httpbis, quic, ohai, privacypass, httpapi
|
|
308
|
+
* Identity / auth / SSO / cert mgmt:
|
|
309
|
+
* oauth, jose, cose, kitten, emu, secevent, scim, acme
|
|
310
|
+
* DNS security + privacy:
|
|
311
|
+
* dnsop, dprive, add
|
|
312
|
+
* Supply chain + attestation + firmware:
|
|
313
|
+
* scitt, rats, suit, teep
|
|
314
|
+
* Threat intel + security automation:
|
|
315
|
+
* mile, sacm, i2nsf
|
|
316
|
+
* Messaging + E2E:
|
|
317
|
+
* mls, moq, sframe
|
|
318
|
+
* Network / IoT mgmt:
|
|
319
|
+
* anima, drip, iotops, netconf
|
|
320
|
+
*/
|
|
321
|
+
const SEED_RFC_GROUPS = [
|
|
322
|
+
// Transport / crypto / PKI
|
|
323
|
+
"tls", "uta", "cfrg", "lamps", "ipsecme",
|
|
324
|
+
// HTTP / web / QUIC
|
|
325
|
+
"httpbis", "quic", "ohai", "privacypass", "httpapi",
|
|
326
|
+
// Identity / auth / SSO / cert mgmt
|
|
327
|
+
"oauth", "jose", "cose", "kitten", "emu", "secevent", "scim", "acme",
|
|
328
|
+
// DNS security + privacy
|
|
329
|
+
"dnsop", "dprive", "add",
|
|
330
|
+
// Supply chain + attestation + firmware
|
|
331
|
+
"scitt", "rats", "suit", "teep",
|
|
332
|
+
// Threat intel + security automation
|
|
333
|
+
"mile", "sacm", "i2nsf",
|
|
334
|
+
// Messaging + E2E
|
|
335
|
+
"mls", "moq", "sframe",
|
|
336
|
+
// Network / IoT mgmt
|
|
337
|
+
"anima", "drip", "iotops", "netconf",
|
|
338
|
+
];
|
|
339
|
+
|
|
340
|
+
function getProjectRfcGroups(ctx) {
|
|
341
|
+
const groups = new Set();
|
|
342
|
+
const ids = Object.keys(ctx.rfcCatalog).filter((k) => !k.startsWith("_"));
|
|
343
|
+
for (const id of ids) {
|
|
344
|
+
let docName;
|
|
345
|
+
if (id.startsWith("RFC-")) docName = `rfc${id.slice(4)}`;
|
|
346
|
+
else if (id.startsWith("DRAFT-")) docName = `draft-${id.slice(6).toLowerCase()}`;
|
|
347
|
+
if (!docName) continue;
|
|
348
|
+
const payload = readCachedJson(ctx.cacheDir, "rfc", docName);
|
|
349
|
+
const obj = payload?.objects?.[0];
|
|
350
|
+
const acronym = obj?.group?.acronym || (typeof obj?.group === "string" ? extractAcronymFromGroupUri(obj.group) : null);
|
|
351
|
+
if (acronym) groups.add(String(acronym).toLowerCase());
|
|
352
|
+
}
|
|
353
|
+
// Always union the seed list — dynamic derivation covers the WGs we
|
|
354
|
+
// already cite; the seed covers WGs we SHOULD watch for our skill
|
|
355
|
+
// coverage even if no RFC from that WG is in the catalog yet.
|
|
356
|
+
for (const g of SEED_RFC_GROUPS) groups.add(g);
|
|
357
|
+
return groups;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function extractAcronymFromGroupUri(uri) {
|
|
361
|
+
// Group URIs from Datatracker look like /api/v1/group/group/12345/.
|
|
362
|
+
// Group acronym is in the doc object's full record but not in the URI.
|
|
363
|
+
// Returning null means we have to live-fetch later.
|
|
364
|
+
void uri;
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Find recent RFCs published in any project-relevant working group
|
|
370
|
+
* that aren't already in data/rfc-references.json. Queries Datatracker
|
|
371
|
+
* live (small N — runs once per refresh, ~9 WG queries).
|
|
372
|
+
*
|
|
373
|
+
* @param {object} ctx
|
|
374
|
+
* @param {object} opts { cap?: number, sinceDays?: number }
|
|
375
|
+
*/
|
|
376
|
+
async function discoverNewRfcs(ctx, opts = {}) {
|
|
377
|
+
const cap = opts.cap ?? DEFAULT_CAP;
|
|
378
|
+
const sinceDays = opts.sinceDays ?? 180;
|
|
379
|
+
const cutoff = new Date(Date.now() - sinceDays * 86_400_000).toISOString().slice(0, 10);
|
|
380
|
+
|
|
381
|
+
const groups = [...getProjectRfcGroups(ctx)];
|
|
382
|
+
if (groups.length === 0) {
|
|
383
|
+
return { diffs: [], errors: 0, spilled: 0, summary: "RFC discovery: no project WGs derived" };
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const localIds = new Set(Object.keys(ctx.rfcCatalog).filter((k) => !k.startsWith("_")));
|
|
387
|
+
|
|
388
|
+
let candidates = [];
|
|
389
|
+
let errors = 0;
|
|
390
|
+
|
|
391
|
+
for (const wg of groups) {
|
|
392
|
+
// Datatracker filter: RFCs in this WG, time > cutoff. Ordered by time descending.
|
|
393
|
+
const url =
|
|
394
|
+
`https://datatracker.ietf.org/api/v1/doc/document/` +
|
|
395
|
+
`?type=rfc&group__acronym=${encodeURIComponent(wg)}` +
|
|
396
|
+
`&time__gt=${cutoff}&order_by=-time&limit=20&format=json`;
|
|
397
|
+
const payload = await fetchDatatracker(url);
|
|
398
|
+
if (!payload || !Array.isArray(payload.objects)) {
|
|
399
|
+
errors++;
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
for (const obj of payload.objects) {
|
|
403
|
+
const docName = String(obj.name || "");
|
|
404
|
+
const m = docName.match(/^rfc(\d+)$/i);
|
|
405
|
+
if (!m) continue;
|
|
406
|
+
const number = Number(m[1]);
|
|
407
|
+
const localKey = `RFC-${number}`;
|
|
408
|
+
if (localIds.has(localKey)) continue;
|
|
409
|
+
candidates.push({ obj, number, localKey, wg });
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Dedupe across overlapping WG membership (an RFC can list multiple
|
|
414
|
+
// groups). Keep the first occurrence (alphabetically first WG match).
|
|
415
|
+
const seen = new Set();
|
|
416
|
+
candidates = candidates.filter((c) => {
|
|
417
|
+
if (seen.has(c.localKey)) return false;
|
|
418
|
+
seen.add(c.localKey);
|
|
419
|
+
return true;
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
// Sort by published time descending so we keep the most recent under the cap.
|
|
423
|
+
candidates.sort((a, b) => String(b.obj.time || "").localeCompare(String(a.obj.time || "")));
|
|
424
|
+
|
|
425
|
+
const total = candidates.length;
|
|
426
|
+
const picks = candidates.slice(0, cap);
|
|
427
|
+
const spilled = Math.max(0, total - picks.length);
|
|
428
|
+
|
|
429
|
+
const diffs = picks.map(({ obj, number, localKey, wg }) => {
|
|
430
|
+
const status = RFC_STATUS_MAP[obj.std_level] || "Unknown";
|
|
431
|
+
const entry = {
|
|
432
|
+
number,
|
|
433
|
+
title: String(obj.title || `RFC ${number}`),
|
|
434
|
+
status,
|
|
435
|
+
published: typeof obj.time === "string" ? obj.time.slice(0, 7) : null,
|
|
436
|
+
tracker: `https://www.rfc-editor.org/info/rfc${number}`,
|
|
437
|
+
relevance: `AUTO-IMPORTED from IETF ${wg.toUpperCase()} working group. Project already cites other RFCs in this WG — this candidate surfaced via the auto-discovery filter and needs a curated relevance statement before merge.`,
|
|
438
|
+
lag_notes: null,
|
|
439
|
+
skills_referencing: [],
|
|
440
|
+
errata_count: null,
|
|
441
|
+
last_verified: TODAY,
|
|
442
|
+
_auto_imported: {
|
|
443
|
+
source: `RFC discovery (IETF ${wg} working group)`,
|
|
444
|
+
imported_at: TODAY,
|
|
445
|
+
curation_needed: [
|
|
446
|
+
"relevance — project-specific framing of how this RFC matters for mid-2026 threats",
|
|
447
|
+
"lag_notes — what gaps remain or where the RFC falls short",
|
|
448
|
+
"skills_referencing — list of skills that should cite this RFC",
|
|
449
|
+
"errata_count — populate from <rfc-editor.org/errata/rfc${number}>",
|
|
450
|
+
],
|
|
451
|
+
},
|
|
452
|
+
};
|
|
453
|
+
return {
|
|
454
|
+
id: localKey,
|
|
455
|
+
op: "add",
|
|
456
|
+
target: "rfcCatalog",
|
|
457
|
+
entry,
|
|
458
|
+
severity: "low",
|
|
459
|
+
meta: { wg, published: entry.published, title: entry.title },
|
|
460
|
+
};
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
return {
|
|
464
|
+
diffs,
|
|
465
|
+
errors,
|
|
466
|
+
spilled,
|
|
467
|
+
summary: total === 0
|
|
468
|
+
? "RFC discovery: no new entries in project WGs"
|
|
469
|
+
: `RFC discovery: ${diffs.length} new entries${spilled > 0 ? ` (+${spilled} spilled past cap)` : ""} across ${groups.length} WG(s)`,
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
module.exports = {
|
|
474
|
+
discoverNewKev,
|
|
475
|
+
discoverNewRfcs,
|
|
476
|
+
buildKevDraftEntry,
|
|
477
|
+
getProjectRfcGroups,
|
|
478
|
+
SEED_RFC_GROUPS,
|
|
479
|
+
DEFAULT_CAP,
|
|
480
|
+
};
|
package/lib/refresh-external.js
CHANGED
|
@@ -112,13 +112,15 @@ AGENTS.md Hard Rule #12 and are surfaced as report-only findings.
|
|
|
112
112
|
* mutates local catalog and writes it
|
|
113
113
|
*/
|
|
114
114
|
|
|
115
|
+
const { discoverNewKev, discoverNewRfcs } = require("./auto-discovery");
|
|
116
|
+
|
|
115
117
|
const KEV_SOURCE = {
|
|
116
118
|
name: "kev",
|
|
117
119
|
description: "CISA Known Exploited Vulnerabilities",
|
|
118
120
|
applies_to: "data/cve-catalog.json",
|
|
119
121
|
async fetchDiff(ctx) {
|
|
120
122
|
if (ctx.fixtures?.kev) return synthesizeFromFixture(ctx, "kev");
|
|
121
|
-
if (ctx.cacheDir) return
|
|
123
|
+
if (ctx.cacheDir) return kevDiffWithDiscoveryFromCache(ctx);
|
|
122
124
|
const { validateAllCves } = require("../sources/validators");
|
|
123
125
|
const report = await validateAllCves(ctx.cveCatalog, { concurrency: 4 });
|
|
124
126
|
const diffs = [];
|
|
@@ -140,8 +142,17 @@ const KEV_SOURCE = {
|
|
|
140
142
|
},
|
|
141
143
|
async applyDiff(ctx, diffs) {
|
|
142
144
|
let updated = 0;
|
|
145
|
+
let added = 0;
|
|
143
146
|
const errors = [];
|
|
144
147
|
for (const d of diffs) {
|
|
148
|
+
if (d.op === "add") {
|
|
149
|
+
// Auto-discovered new entry. Refuse to overwrite if the entry
|
|
150
|
+
// somehow exists (race condition / stale fixture); skip silently.
|
|
151
|
+
if (ctx.cveCatalog[d.id]) continue;
|
|
152
|
+
ctx.cveCatalog[d.id] = d.entry;
|
|
153
|
+
added++;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
145
156
|
if (!ctx.cveCatalog[d.id]) {
|
|
146
157
|
errors.push(`KEV: no local entry for ${d.id}`);
|
|
147
158
|
continue;
|
|
@@ -153,10 +164,32 @@ const KEV_SOURCE = {
|
|
|
153
164
|
ctx.cveCatalog._meta = ctx.cveCatalog._meta || {};
|
|
154
165
|
ctx.cveCatalog._meta.last_updated = TODAY;
|
|
155
166
|
writeJson(ABS("data/cve-catalog.json"), ctx.cveCatalog);
|
|
156
|
-
return { updated, errors };
|
|
167
|
+
return { updated: updated + added, added, drift_updated: updated, errors };
|
|
157
168
|
},
|
|
158
169
|
};
|
|
159
170
|
|
|
171
|
+
/**
|
|
172
|
+
* Cache-mode KEV with auto-discovery merged in. Standard drift-check
|
|
173
|
+
* for existing entries plus discoverNewKev() for entries upstream that
|
|
174
|
+
* aren't in the local catalog. Spill count is surfaced in the summary.
|
|
175
|
+
*/
|
|
176
|
+
function kevDiffWithDiscoveryFromCache(ctx) {
|
|
177
|
+
const drift = kevDiffFromCache(ctx);
|
|
178
|
+
const discovery = discoverNewKev(ctx);
|
|
179
|
+
const diffs = [...drift.diffs, ...discovery.diffs];
|
|
180
|
+
const summary =
|
|
181
|
+
`${drift.diffs.length} KEV drifts + ${discovery.diffs.length} new entries` +
|
|
182
|
+
(discovery.spilled > 0 ? ` (+${discovery.spilled} spilled past cap)` : "") +
|
|
183
|
+
" (from cache)";
|
|
184
|
+
return {
|
|
185
|
+
status: drift.status,
|
|
186
|
+
diffs,
|
|
187
|
+
errors: drift.errors + discovery.errors,
|
|
188
|
+
spilled: discovery.spilled,
|
|
189
|
+
summary,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
160
193
|
const EPSS_SOURCE = {
|
|
161
194
|
name: "epss",
|
|
162
195
|
description: "FIRST.org EPSS scores",
|
|
@@ -257,11 +290,11 @@ const NVD_SOURCE = {
|
|
|
257
290
|
|
|
258
291
|
const RFC_SOURCE = {
|
|
259
292
|
name: "rfc",
|
|
260
|
-
description: "IETF Datatracker RFC status",
|
|
293
|
+
description: "IETF Datatracker RFC status + auto-discovery",
|
|
261
294
|
applies_to: "data/rfc-references.json",
|
|
262
295
|
async fetchDiff(ctx) {
|
|
263
296
|
if (ctx.fixtures?.rfc) return synthesizeFromFixture(ctx, "rfc");
|
|
264
|
-
if (ctx.cacheDir) return
|
|
297
|
+
if (ctx.cacheDir) return rfcDiffWithDiscoveryFromCache(ctx);
|
|
265
298
|
const { validateAllRfcs } = require("../sources/validators");
|
|
266
299
|
const results = await validateAllRfcs(ctx.rfcCatalog, { concurrency: 4 });
|
|
267
300
|
const diffs = [];
|
|
@@ -293,8 +326,15 @@ const RFC_SOURCE = {
|
|
|
293
326
|
},
|
|
294
327
|
async applyDiff(ctx, diffs) {
|
|
295
328
|
let updated = 0;
|
|
329
|
+
let added = 0;
|
|
296
330
|
const errors = [];
|
|
297
331
|
for (const d of diffs) {
|
|
332
|
+
if (d.op === "add") {
|
|
333
|
+
if (ctx.rfcCatalog[d.id]) continue;
|
|
334
|
+
ctx.rfcCatalog[d.id] = d.entry;
|
|
335
|
+
added++;
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
298
338
|
if (d.field !== "status") continue; // notes are informational
|
|
299
339
|
const entry = ctx.rfcCatalog[d.id];
|
|
300
340
|
if (!entry) {
|
|
@@ -308,10 +348,34 @@ const RFC_SOURCE = {
|
|
|
308
348
|
ctx.rfcCatalog._meta = ctx.rfcCatalog._meta || {};
|
|
309
349
|
ctx.rfcCatalog._meta.last_updated = TODAY;
|
|
310
350
|
writeJson(ABS("data/rfc-references.json"), ctx.rfcCatalog);
|
|
311
|
-
return { updated, errors };
|
|
351
|
+
return { updated: updated + added, added, drift_updated: updated, errors };
|
|
312
352
|
},
|
|
313
353
|
};
|
|
314
354
|
|
|
355
|
+
/**
|
|
356
|
+
* Cache-mode RFC with auto-discovery merged in. Drift-check for
|
|
357
|
+
* existing entries (cache only) plus discoverNewRfcs() which hits
|
|
358
|
+
* Datatracker live for new RFCs in project-relevant working groups.
|
|
359
|
+
* Discovery makes ~30 HTTP calls (one per project WG) per refresh —
|
|
360
|
+
* Datatracker's read budget is generous so this is well within limit.
|
|
361
|
+
*/
|
|
362
|
+
async function rfcDiffWithDiscoveryFromCache(ctx) {
|
|
363
|
+
const drift = rfcDiffFromCache(ctx);
|
|
364
|
+
const discovery = await discoverNewRfcs(ctx);
|
|
365
|
+
const diffs = [...drift.diffs, ...discovery.diffs];
|
|
366
|
+
const summary =
|
|
367
|
+
`${drift.diffs.length} RFC drifts + ${discovery.diffs.length} new entries` +
|
|
368
|
+
(discovery.spilled > 0 ? ` (+${discovery.spilled} spilled past cap)` : "") +
|
|
369
|
+
" (drift from cache, discovery live)";
|
|
370
|
+
return {
|
|
371
|
+
status: drift.status,
|
|
372
|
+
diffs,
|
|
373
|
+
errors: drift.errors + discovery.errors,
|
|
374
|
+
spilled: discovery.spilled,
|
|
375
|
+
summary,
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
|
|
315
379
|
const PINS_SOURCE = {
|
|
316
380
|
name: "pins",
|
|
317
381
|
description: "MITRE ATLAS / ATT&CK / D3FEND / CWE upstream release pins",
|
package/manifest-snapshot.json
CHANGED
|
@@ -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-
|
|
3
|
+
"_generated_at": "2026-05-12T03:18:21.622Z",
|
|
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.9.
|
|
3
|
+
"version": "0.9.2",
|
|
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-
|
|
55
|
+
"signed_at": "2026-05-12T03:18:21.555Z",
|
|
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-
|
|
119
|
+
"signed_at": "2026-05-12T03:18:21.557Z",
|
|
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-
|
|
182
|
+
"signed_at": "2026-05-12T03:18:21.557Z",
|
|
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-
|
|
228
|
+
"signed_at": "2026-05-12T03:18:21.558Z"
|
|
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-
|
|
259
|
+
"signed_at": "2026-05-12T03:18:21.558Z"
|
|
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-
|
|
288
|
+
"signed_at": "2026-05-12T03:18:21.559Z"
|
|
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-
|
|
325
|
+
"signed_at": "2026-05-12T03:18:21.559Z",
|
|
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-
|
|
382
|
+
"signed_at": "2026-05-12T03:18:21.559Z",
|
|
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-
|
|
417
|
+
"signed_at": "2026-05-12T03:18:21.560Z",
|
|
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-
|
|
445
|
+
"signed_at": "2026-05-12T03:18:21.560Z"
|
|
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-
|
|
477
|
+
"signed_at": "2026-05-12T03:18:21.560Z"
|
|
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-
|
|
504
|
+
"signed_at": "2026-05-12T03:18:21.560Z"
|
|
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-
|
|
556
|
+
"signed_at": "2026-05-12T03:18:21.561Z",
|
|
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-
|
|
603
|
+
"signed_at": "2026-05-12T03:18:21.561Z"
|
|
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-
|
|
640
|
+
"signed_at": "2026-05-12T03:18:21.562Z",
|
|
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-
|
|
675
|
+
"signed_at": "2026-05-12T03:18:21.562Z"
|
|
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-
|
|
746
|
+
"signed_at": "2026-05-12T03:18:21.562Z"
|
|
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-
|
|
806
|
+
"signed_at": "2026-05-12T03:18:21.563Z"
|
|
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-
|
|
881
|
+
"signed_at": "2026-05-12T03:18:21.563Z"
|
|
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-
|
|
958
|
+
"signed_at": "2026-05-12T03:18:21.563Z"
|
|
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-
|
|
1015
|
+
"signed_at": "2026-05-12T03:18:21.563Z"
|
|
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-
|
|
1082
|
+
"signed_at": "2026-05-12T03:18:21.564Z"
|
|
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-
|
|
1138
|
+
"signed_at": "2026-05-12T03:18:21.564Z"
|
|
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-
|
|
1190
|
+
"signed_at": "2026-05-12T03:18:21.564Z"
|
|
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-
|
|
1240
|
+
"signed_at": "2026-05-12T03:18:21.565Z"
|
|
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-
|
|
1314
|
+
"signed_at": "2026-05-12T03:18:21.565Z"
|
|
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-
|
|
1364
|
+
"signed_at": "2026-05-12T03:18:21.565Z"
|
|
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-
|
|
1424
|
+
"signed_at": "2026-05-12T03:18:21.566Z"
|
|
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-
|
|
1505
|
+
"signed_at": "2026-05-12T03:18:21.566Z"
|
|
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-
|
|
1574
|
+
"signed_at": "2026-05-12T03:18:21.566Z"
|
|
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-
|
|
1639
|
+
"signed_at": "2026-05-12T03:18:21.567Z"
|
|
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-
|
|
1708
|
+
"signed_at": "2026-05-12T03:18:21.567Z"
|
|
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-
|
|
1789
|
+
"signed_at": "2026-05-12T03:18:21.567Z"
|
|
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-
|
|
1851
|
+
"signed_at": "2026-05-12T03:18:21.568Z"
|
|
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-
|
|
1922
|
+
"signed_at": "2026-05-12T03:18:21.568Z"
|
|
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-
|
|
1984
|
+
"signed_at": "2026-05-12T03:18:21.569Z"
|
|
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-
|
|
2037
|
+
"signed_at": "2026-05-12T03:18:21.569Z"
|
|
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-
|
|
2105
|
+
"signed_at": "2026-05-12T03:18:21.569Z"
|
|
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.9.
|
|
3
|
+
"version": "0.9.2",
|
|
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:
|
|
4
|
+
"serialNumber": "urn:uuid:80016ac3-f8be-40a6-9293-104d18229e7a",
|
|
5
5
|
"version": 1,
|
|
6
6
|
"metadata": {
|
|
7
|
-
"timestamp": "2026-05-
|
|
7
|
+
"timestamp": "2026-05-12T03:18:21.678Z",
|
|
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.9.
|
|
16
|
+
"bom-ref": "pkg:npm/@blamejs/exceptd-skills@0.9.2",
|
|
17
17
|
"type": "application",
|
|
18
18
|
"name": "@blamejs/exceptd-skills",
|
|
19
|
-
"version": "0.9.
|
|
19
|
+
"version": "0.9.2",
|
|
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.9.
|
|
28
|
+
"purl": "pkg:npm/%40blamejs/exceptd-skills@0.9.2",
|
|
29
29
|
"externalReferences": [
|
|
30
30
|
{
|
|
31
31
|
"type": "distribution",
|
|
32
|
-
"url": "https://www.npmjs.com/package/@blamejs/exceptd-skills/v/0.9.
|
|
32
|
+
"url": "https://www.npmjs.com/package/@blamejs/exceptd-skills/v/0.9.2"
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
"type": "vcs",
|