@blamejs/exceptd-skills 0.19.1 → 0.19.3
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/AGENTS.md +3 -3
- package/CHANGELOG.md +25 -1
- package/CONTEXT.md +6 -6
- package/README.md +1 -1
- package/data/_indexes/_meta.json +15 -15
- package/data/_indexes/activity-feed.json +1 -1
- package/data/_indexes/catalog-summaries.json +1 -1
- package/data/_indexes/chains.json +175 -83
- package/data/_indexes/frequency.json +3 -0
- package/data/_indexes/section-offsets.json +65 -65
- package/data/_indexes/token-budget.json +35 -35
- package/data/atlas-ttps.json +60 -16
- package/data/attack-techniques.json +1 -1
- package/data/cve-catalog.json +432 -324
- package/data/d3fend-catalog.json +5 -5
- package/data/dlp-controls.json +1 -1
- package/data/zeroday-lessons.json +2 -2
- package/lib/validate-package.js +49 -1
- package/manifest.json +58 -58
- package/package.json +2 -2
- package/sbom.cdx.json +121 -53
- package/scripts/check-epss-consistency.js +189 -0
- package/scripts/check-sbom-currency.js +5 -1
- package/scripts/check-ttp-references.js +152 -0
- package/scripts/check-version-tags.js +50 -6
- package/scripts/predeploy.js +26 -0
- package/scripts/refresh-sbom.js +84 -11
- package/scripts/release.js +9 -2
- package/skills/defensive-countermeasure-mapping/skill.md +1 -1
- package/skills/incident-response-playbook/skill.md +5 -5
- package/skills/mlops-security/skill.md +1 -1
- package/skills/rag-pipeline-security/skill.md +1 -1
- package/skills/ransomware-response/skill.md +1 -1
package/sbom.cdx.json
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"bomFormat": "CycloneDX",
|
|
3
3
|
"specVersion": "1.6",
|
|
4
|
-
"serialNumber": "urn:uuid:
|
|
4
|
+
"serialNumber": "urn:uuid:7f211e54-0c0b-4b04-9d54-f70a5568ac78",
|
|
5
5
|
"version": 1,
|
|
6
6
|
"metadata": {
|
|
7
|
-
"timestamp": "
|
|
7
|
+
"timestamp": "2026-08-08T00:00:00.000Z",
|
|
8
8
|
"tools": [
|
|
9
9
|
{
|
|
10
10
|
"vendor": "blamejs",
|
|
11
11
|
"name": "scripts/refresh-sbom.js",
|
|
12
|
-
"version": "0.19.
|
|
12
|
+
"version": "0.19.3"
|
|
13
13
|
}
|
|
14
14
|
],
|
|
15
15
|
"component": {
|
|
16
|
-
"bom-ref": "pkg:npm/@blamejs/exceptd-skills@0.19.
|
|
16
|
+
"bom-ref": "pkg:npm/@blamejs/exceptd-skills@0.19.3",
|
|
17
17
|
"type": "application",
|
|
18
18
|
"name": "@blamejs/exceptd-skills",
|
|
19
|
-
"version": "0.19.
|
|
20
|
-
"description": "AI security skills grounded in mid-2026 threat reality, not stale framework documentation. 51 skills, 11 catalogs (1025 CVEs / 232 CWEs / 794 ATT&CK + ICS /
|
|
19
|
+
"version": "0.19.3",
|
|
20
|
+
"description": "AI security skills grounded in mid-2026 threat reality, not stale framework documentation. 51 skills, 11 catalogs (1025 CVEs / 232 CWEs / 794 ATT&CK + ICS / 174 ATLAS / 468 D3FEND / 8889 RFCs), 35 jurisdictions, 10-class catalog gap detector + budget gate, real XML parser + canonical-form diff + content-pattern regression detection, Ed25519-signed.",
|
|
21
21
|
"licenses": [
|
|
22
22
|
{
|
|
23
23
|
"license": {
|
|
@@ -25,17 +25,17 @@
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
],
|
|
28
|
-
"purl": "pkg:npm/%40blamejs/exceptd-skills@0.19.
|
|
28
|
+
"purl": "pkg:npm/%40blamejs/exceptd-skills@0.19.3",
|
|
29
29
|
"hashes": [
|
|
30
30
|
{
|
|
31
31
|
"alg": "SHA-256",
|
|
32
|
-
"content": "
|
|
32
|
+
"content": "534717aa72715652e635959c41d58e67926743edc15a0557b8f8570a174e6e60"
|
|
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.19.
|
|
38
|
+
"url": "https://www.npmjs.com/package/@blamejs/exceptd-skills/v/0.19.3"
|
|
39
39
|
},
|
|
40
40
|
{
|
|
41
41
|
"type": "vcs",
|
|
@@ -60,6 +60,14 @@
|
|
|
60
60
|
"name": "exceptd:integrity:method",
|
|
61
61
|
"value": "Ed25519 per-skill (lib/sign.js)"
|
|
62
62
|
},
|
|
63
|
+
{
|
|
64
|
+
"name": "exceptd:integrity:uncovered:prefix",
|
|
65
|
+
"value": "data/_indexes/"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"name": "exceptd:integrity:uncovered:rationale",
|
|
69
|
+
"value": "Regenerated by `npm run build-indexes` and mutated by the test suite, so a pinned per-file hash would race any run between generation and verification. Covered instead by the pre-computed-index freshness gate in `npm run predeploy`."
|
|
70
|
+
},
|
|
63
71
|
{
|
|
64
72
|
"name": "exceptd:runtime:dependency:count",
|
|
65
73
|
"value": "0"
|
|
@@ -86,11 +94,11 @@
|
|
|
86
94
|
"hashes": [
|
|
87
95
|
{
|
|
88
96
|
"alg": "SHA-256",
|
|
89
|
-
"content": "
|
|
97
|
+
"content": "043939a747f3262127c7ab12a689af5fe81dce394059fdba502d37936f5c5c4b"
|
|
90
98
|
},
|
|
91
99
|
{
|
|
92
100
|
"alg": "SHA3-512",
|
|
93
|
-
"content": "
|
|
101
|
+
"content": "c72920fd325dcc0bf50b09b20b1832cd7a48ccc60be755ba5736eab61ceed714cd1686e9bdfc8dfc4b225f616b7474ad8d2aff2f6d6b89ef39bc38991e48fd1d"
|
|
94
102
|
}
|
|
95
103
|
]
|
|
96
104
|
},
|
|
@@ -116,11 +124,11 @@
|
|
|
116
124
|
"hashes": [
|
|
117
125
|
{
|
|
118
126
|
"alg": "SHA-256",
|
|
119
|
-
"content": "
|
|
127
|
+
"content": "0886406bdf81bbb64f3b461d9c62d9deb53f5e5142af0fb955a90f38991d34bb"
|
|
120
128
|
},
|
|
121
129
|
{
|
|
122
130
|
"alg": "SHA3-512",
|
|
123
|
-
"content": "
|
|
131
|
+
"content": "5f8a142a79db22e647b8e61002361c1e94e3e41f4607d3202b7673a709dc50abc60e687639af283145cc40a3ece292dd4595b8a03683982f9ecbfb3bcfda9f52"
|
|
124
132
|
}
|
|
125
133
|
]
|
|
126
134
|
},
|
|
@@ -131,11 +139,11 @@
|
|
|
131
139
|
"hashes": [
|
|
132
140
|
{
|
|
133
141
|
"alg": "SHA-256",
|
|
134
|
-
"content": "
|
|
142
|
+
"content": "ce0272bf7bfeb46ec24f2852953b5fa1cd2409e58ee4d4afab31395c566df2c3"
|
|
135
143
|
},
|
|
136
144
|
{
|
|
137
145
|
"alg": "SHA3-512",
|
|
138
|
-
"content": "
|
|
146
|
+
"content": "6f36bd5552bff87dcd3af1d6b38ae0a5be7cf3abc576033db7a2dcc978b3d2be9eeb98dabaf6c8d8fc1afdfb5860d595d0156e7edcebaf43b0c69fe87130d39d"
|
|
139
147
|
}
|
|
140
148
|
]
|
|
141
149
|
},
|
|
@@ -176,11 +184,11 @@
|
|
|
176
184
|
"hashes": [
|
|
177
185
|
{
|
|
178
186
|
"alg": "SHA-256",
|
|
179
|
-
"content": "
|
|
187
|
+
"content": "0fa9b167172f104810e5ee6040f11ed741217f7a51c2552a15c69d2a15a164aa"
|
|
180
188
|
},
|
|
181
189
|
{
|
|
182
190
|
"alg": "SHA3-512",
|
|
183
|
-
"content": "
|
|
191
|
+
"content": "952e6b7436e34fc28df676c6eb81f73fe62f0c702c634acf154f2bb36145ffbae9655ab36360b99c17f9aa5b5822ff157cb0ecb8ce6151973979587c493b61c1"
|
|
184
192
|
}
|
|
185
193
|
]
|
|
186
194
|
},
|
|
@@ -296,11 +304,11 @@
|
|
|
296
304
|
"hashes": [
|
|
297
305
|
{
|
|
298
306
|
"alg": "SHA-256",
|
|
299
|
-
"content": "
|
|
307
|
+
"content": "a14089671efcb4f06dd0f82d688fe49276c1370e167e4483d76e9c0a40ef41eb"
|
|
300
308
|
},
|
|
301
309
|
{
|
|
302
310
|
"alg": "SHA3-512",
|
|
303
|
-
"content": "
|
|
311
|
+
"content": "b11841d54798d111d3124125a5adc0627244f0c363ae686b668ee9afc8b387d8d334f45b0c02f07b8eab14a9893456bfb16bc4b0dc81f0e5d2f26c46b88ac962"
|
|
304
312
|
}
|
|
305
313
|
]
|
|
306
314
|
},
|
|
@@ -311,11 +319,11 @@
|
|
|
311
319
|
"hashes": [
|
|
312
320
|
{
|
|
313
321
|
"alg": "SHA-256",
|
|
314
|
-
"content": "
|
|
322
|
+
"content": "6bc0619bcccceccc8d5c12aefa6c11197c625e5d7313e70474f2831e271b23f0"
|
|
315
323
|
},
|
|
316
324
|
{
|
|
317
325
|
"alg": "SHA3-512",
|
|
318
|
-
"content": "
|
|
326
|
+
"content": "9a17e810705eb143ee311305c15b6b40255b5bd29e56e37af5ecfda6e6d39288b450d00fe76bd451a8822951fe334cb468da145f69e7ce98865f532e5aeb0a35"
|
|
319
327
|
}
|
|
320
328
|
]
|
|
321
329
|
},
|
|
@@ -326,11 +334,11 @@
|
|
|
326
334
|
"hashes": [
|
|
327
335
|
{
|
|
328
336
|
"alg": "SHA-256",
|
|
329
|
-
"content": "
|
|
337
|
+
"content": "0f440663a16dfffa241e173eb8ba8613c790015e2eb5f81a5a33ea88975ca1d5"
|
|
330
338
|
},
|
|
331
339
|
{
|
|
332
340
|
"alg": "SHA3-512",
|
|
333
|
-
"content": "
|
|
341
|
+
"content": "7ef98f11c8d421831592481693241618c7e16c8d8f44d528fd5b10de28b75555c6fa2bd2bceb8ec4f091f652655602ce716cceece2cf2683caf989a2295950c0"
|
|
334
342
|
}
|
|
335
343
|
]
|
|
336
344
|
},
|
|
@@ -356,11 +364,11 @@
|
|
|
356
364
|
"hashes": [
|
|
357
365
|
{
|
|
358
366
|
"alg": "SHA-256",
|
|
359
|
-
"content": "
|
|
367
|
+
"content": "9a4a356a1446d7db8be8b6684fa789802405a96cdaf769f06db4136bd07db6ac"
|
|
360
368
|
},
|
|
361
369
|
{
|
|
362
370
|
"alg": "SHA3-512",
|
|
363
|
-
"content": "
|
|
371
|
+
"content": "b60ab890bb6075800ddf571976fb2ef1a563d1f187c243ac3f29b96a3355c62c9ecca40e660742ff6bc1a7ba1a5b9374a05cca2d8b6dbacf7c224e050f6ef105"
|
|
364
372
|
}
|
|
365
373
|
]
|
|
366
374
|
},
|
|
@@ -371,11 +379,11 @@
|
|
|
371
379
|
"hashes": [
|
|
372
380
|
{
|
|
373
381
|
"alg": "SHA-256",
|
|
374
|
-
"content": "
|
|
382
|
+
"content": "791f6a46b19b0c0fa2f46630ac8b398afb7ec9c2f2019056828c8820fe255f19"
|
|
375
383
|
},
|
|
376
384
|
{
|
|
377
385
|
"alg": "SHA3-512",
|
|
378
|
-
"content": "
|
|
386
|
+
"content": "06c3aa8961bb08694c07ec65e791f0005d802385145ed53db92cc88301e2d339dfe33a3934509879dc70d1a9529028b50dcc44ff5c2ccbaa5d3302a639744228"
|
|
379
387
|
}
|
|
380
388
|
]
|
|
381
389
|
},
|
|
@@ -941,11 +949,11 @@
|
|
|
941
949
|
"hashes": [
|
|
942
950
|
{
|
|
943
951
|
"alg": "SHA-256",
|
|
944
|
-
"content": "
|
|
952
|
+
"content": "9bc4f85c15d1618ef438a9221346c6913f823c5b8368acc2b2d5756c3a07c21e"
|
|
945
953
|
},
|
|
946
954
|
{
|
|
947
955
|
"alg": "SHA3-512",
|
|
948
|
-
"content": "
|
|
956
|
+
"content": "43dac02cda23b8a73d857f474fe37e7754e61c57d5c882c06af239f6262fc5505b42181a88c44ebeaf5f5e79f0d73e01f39af27c418e92371193a06f2c22e220"
|
|
949
957
|
}
|
|
950
958
|
]
|
|
951
959
|
},
|
|
@@ -1811,11 +1819,11 @@
|
|
|
1811
1819
|
"hashes": [
|
|
1812
1820
|
{
|
|
1813
1821
|
"alg": "SHA-256",
|
|
1814
|
-
"content": "
|
|
1822
|
+
"content": "5aeebc43db5477b9a342f783d72a5a28a9632205b8aab6e08ea346d3310eac47"
|
|
1815
1823
|
},
|
|
1816
1824
|
{
|
|
1817
1825
|
"alg": "SHA3-512",
|
|
1818
|
-
"content": "
|
|
1826
|
+
"content": "08b23fc717d26133ee455690692b79c3a5bb8386cb4aaa54d6ba6f3e8705adab8e217881b3418534d38bf18b53761f76d8f6ccd1a20567ec5bc39e715a207304"
|
|
1819
1827
|
}
|
|
1820
1828
|
]
|
|
1821
1829
|
},
|
|
@@ -1946,11 +1954,11 @@
|
|
|
1946
1954
|
"hashes": [
|
|
1947
1955
|
{
|
|
1948
1956
|
"alg": "SHA-256",
|
|
1949
|
-
"content": "
|
|
1957
|
+
"content": "ea761dae093a3f57552dc54d1c4cda0f8a0c1c48aac6a4fbaebf6f0fb1e89416"
|
|
1950
1958
|
},
|
|
1951
1959
|
{
|
|
1952
1960
|
"alg": "SHA3-512",
|
|
1953
|
-
"content": "
|
|
1961
|
+
"content": "a399cae2a99e4a613878bfbe3de82d04604559f228bd615435ac43624671060f875641aa40d1c4978f6259b79767c42aad8d6e87a710bb33c223f97e0f9355d4"
|
|
1954
1962
|
}
|
|
1955
1963
|
]
|
|
1956
1964
|
},
|
|
@@ -2059,6 +2067,21 @@
|
|
|
2059
2067
|
}
|
|
2060
2068
|
]
|
|
2061
2069
|
},
|
|
2070
|
+
{
|
|
2071
|
+
"bom-ref": "file:package.json",
|
|
2072
|
+
"type": "file",
|
|
2073
|
+
"name": "package.json",
|
|
2074
|
+
"hashes": [
|
|
2075
|
+
{
|
|
2076
|
+
"alg": "SHA-256",
|
|
2077
|
+
"content": "a4f0243303f43e0aff7b62082761136da51b6157a6da1d53357e9c8b41659641"
|
|
2078
|
+
},
|
|
2079
|
+
{
|
|
2080
|
+
"alg": "SHA3-512",
|
|
2081
|
+
"content": "7547730b7a85504e16231b8959b66322c79e85bf3e8629298ffccf1453a20a13eea71f27550a76d2a24c75bd90b021679eb4120d8c96db32000e57b954b3ef33"
|
|
2082
|
+
}
|
|
2083
|
+
]
|
|
2084
|
+
},
|
|
2062
2085
|
{
|
|
2063
2086
|
"bom-ref": "file:scripts/audit-catalog-gaps.js",
|
|
2064
2087
|
"type": "file",
|
|
@@ -2419,6 +2442,21 @@
|
|
|
2419
2442
|
}
|
|
2420
2443
|
]
|
|
2421
2444
|
},
|
|
2445
|
+
{
|
|
2446
|
+
"bom-ref": "file:scripts/check-epss-consistency.js",
|
|
2447
|
+
"type": "file",
|
|
2448
|
+
"name": "scripts/check-epss-consistency.js",
|
|
2449
|
+
"hashes": [
|
|
2450
|
+
{
|
|
2451
|
+
"alg": "SHA-256",
|
|
2452
|
+
"content": "6054e10941a8e397d3ec666f08e464fe4d1df1750165c0b59543d878a9474cfa"
|
|
2453
|
+
},
|
|
2454
|
+
{
|
|
2455
|
+
"alg": "SHA3-512",
|
|
2456
|
+
"content": "c787f5fb7be2bad1b62a87e90d1d69b78e6577d426e9a6d3b0f10d29035ccb2c75aeaad07fb4a019c74d4731f4570374e645a4c9884e3288c7b52d51c754952f"
|
|
2457
|
+
}
|
|
2458
|
+
]
|
|
2459
|
+
},
|
|
2422
2460
|
{
|
|
2423
2461
|
"bom-ref": "file:scripts/check-framework-gap-coverage.js",
|
|
2424
2462
|
"type": "file",
|
|
@@ -2456,11 +2494,11 @@
|
|
|
2456
2494
|
"hashes": [
|
|
2457
2495
|
{
|
|
2458
2496
|
"alg": "SHA-256",
|
|
2459
|
-
"content": "
|
|
2497
|
+
"content": "c360d8fce5cb264662a355b05224fa2a3f16a25ff3d6199ad5db1c08919754e2"
|
|
2460
2498
|
},
|
|
2461
2499
|
{
|
|
2462
2500
|
"alg": "SHA3-512",
|
|
2463
|
-
"content": "
|
|
2501
|
+
"content": "f364223d705ed8b4dcf6e54d234891b3508cd79c0fe5832706514c9dc3079b594322fb63e16ea9fe1d135181b93e0c153ad073fb92cdc40d59386d781006eed0"
|
|
2464
2502
|
}
|
|
2465
2503
|
]
|
|
2466
2504
|
},
|
|
@@ -2524,6 +2562,21 @@
|
|
|
2524
2562
|
}
|
|
2525
2563
|
]
|
|
2526
2564
|
},
|
|
2565
|
+
{
|
|
2566
|
+
"bom-ref": "file:scripts/check-ttp-references.js",
|
|
2567
|
+
"type": "file",
|
|
2568
|
+
"name": "scripts/check-ttp-references.js",
|
|
2569
|
+
"hashes": [
|
|
2570
|
+
{
|
|
2571
|
+
"alg": "SHA-256",
|
|
2572
|
+
"content": "86861618b14f086180d32d123affe21be9d331ecc67b22fde17017f7d49aa054"
|
|
2573
|
+
},
|
|
2574
|
+
{
|
|
2575
|
+
"alg": "SHA3-512",
|
|
2576
|
+
"content": "d0bff495ccf1672f1c90bc9f8deeca57c1597c4c59d929c9a209985a140385c5ffe90e72833f607ccc18be97a7daa270d19a87e4591a3d36cdd008c3380b2fcc"
|
|
2577
|
+
}
|
|
2578
|
+
]
|
|
2579
|
+
},
|
|
2527
2580
|
{
|
|
2528
2581
|
"bom-ref": "file:scripts/check-ttp-upstream.js",
|
|
2529
2582
|
"type": "file",
|
|
@@ -2561,11 +2614,11 @@
|
|
|
2561
2614
|
"hashes": [
|
|
2562
2615
|
{
|
|
2563
2616
|
"alg": "SHA-256",
|
|
2564
|
-
"content": "
|
|
2617
|
+
"content": "bc7aa74003392c3d8693ca1c9ee7c38718d96caa97e50cedff65556c26f5ceb9"
|
|
2565
2618
|
},
|
|
2566
2619
|
{
|
|
2567
2620
|
"alg": "SHA3-512",
|
|
2568
|
-
"content": "
|
|
2621
|
+
"content": "320c1691433f81aa16761d04a20f271fd4054b5b17da2a5c2cb08d7146c8f6ad970c6cc2d7b295e2cc58f1a0abdccf6b1402d8b5b89b0ef029bdf2b01c7265c5"
|
|
2569
2622
|
}
|
|
2570
2623
|
]
|
|
2571
2624
|
},
|
|
@@ -2591,11 +2644,11 @@
|
|
|
2591
2644
|
"hashes": [
|
|
2592
2645
|
{
|
|
2593
2646
|
"alg": "SHA-256",
|
|
2594
|
-
"content": "
|
|
2647
|
+
"content": "b641f8f91c21ecda0bd35b647f1be19ca4564e4d5801467105122f262c13e973"
|
|
2595
2648
|
},
|
|
2596
2649
|
{
|
|
2597
2650
|
"alg": "SHA3-512",
|
|
2598
|
-
"content": "
|
|
2651
|
+
"content": "e98f5af374a1a40fc876d0d564a092ecbef2c9e93fb6653b6905c4b44332d12be78aa5ee4b550b4c1bad5270f2480cc862b02fac36b14266b74ec26abb391690"
|
|
2599
2652
|
}
|
|
2600
2653
|
]
|
|
2601
2654
|
},
|
|
@@ -2711,11 +2764,11 @@
|
|
|
2711
2764
|
"hashes": [
|
|
2712
2765
|
{
|
|
2713
2766
|
"alg": "SHA-256",
|
|
2714
|
-
"content": "
|
|
2767
|
+
"content": "c5f38a759980ae125f1f30195abd60dae2c4d9547895ee6e8290f47c80bab0c4"
|
|
2715
2768
|
},
|
|
2716
2769
|
{
|
|
2717
2770
|
"alg": "SHA3-512",
|
|
2718
|
-
"content": "
|
|
2771
|
+
"content": "a5b7a4bed681c7c066aa51316bb1d602134721906eeada0ab6ce8a67227f09eb191dce0e530a6c15486a1f308bc71b304916d45dec15ec8fc53b6f0aebf910e3"
|
|
2719
2772
|
}
|
|
2720
2773
|
]
|
|
2721
2774
|
},
|
|
@@ -2741,11 +2794,11 @@
|
|
|
2741
2794
|
"hashes": [
|
|
2742
2795
|
{
|
|
2743
2796
|
"alg": "SHA-256",
|
|
2744
|
-
"content": "
|
|
2797
|
+
"content": "7425bd4cefc0185b889e7389a1bdb952ad4f576cab03e63928ad4b991f48ccc1"
|
|
2745
2798
|
},
|
|
2746
2799
|
{
|
|
2747
2800
|
"alg": "SHA3-512",
|
|
2748
|
-
"content": "
|
|
2801
|
+
"content": "4b169deaefb58b617f0748a65409277c0592db50b7024f03cc290eda17299ec4aa4d682a294ba2f2251f015331ebbe07b3ca88549d354d5ee9b51589bd778ce7"
|
|
2749
2802
|
}
|
|
2750
2803
|
]
|
|
2751
2804
|
},
|
|
@@ -3026,11 +3079,11 @@
|
|
|
3026
3079
|
"hashes": [
|
|
3027
3080
|
{
|
|
3028
3081
|
"alg": "SHA-256",
|
|
3029
|
-
"content": "
|
|
3082
|
+
"content": "e182f3130fe78e403db6c81c24757bc81dfbc86997e0b5f304f05d5572f7abb2"
|
|
3030
3083
|
},
|
|
3031
3084
|
{
|
|
3032
3085
|
"alg": "SHA3-512",
|
|
3033
|
-
"content": "
|
|
3086
|
+
"content": "4c8391b78968dddfa405d7e5a4d5bac7b30c156b7d86cc44a3d9f3ab3e5cbf80c350ce2c96968b6f76eabc7c4255d03e511092c55135cee4657a2a95c111013e"
|
|
3034
3087
|
}
|
|
3035
3088
|
]
|
|
3036
3089
|
},
|
|
@@ -3161,11 +3214,11 @@
|
|
|
3161
3214
|
"hashes": [
|
|
3162
3215
|
{
|
|
3163
3216
|
"alg": "SHA-256",
|
|
3164
|
-
"content": "
|
|
3217
|
+
"content": "5a4de978515f974cb88cb4dcfb69d02eefa7a0d29a22a2f8a732fb7f8dd5d107"
|
|
3165
3218
|
},
|
|
3166
3219
|
{
|
|
3167
3220
|
"alg": "SHA3-512",
|
|
3168
|
-
"content": "
|
|
3221
|
+
"content": "05b0c3def0635de0202bc93a54ff30acc9197e684d81c48c27cca5fa2299489ed410983d65268702fa3c467932ce89d33db3329c2b5d1788e0e5080244b1ed26"
|
|
3169
3222
|
}
|
|
3170
3223
|
]
|
|
3171
3224
|
},
|
|
@@ -3236,11 +3289,11 @@
|
|
|
3236
3289
|
"hashes": [
|
|
3237
3290
|
{
|
|
3238
3291
|
"alg": "SHA-256",
|
|
3239
|
-
"content": "
|
|
3292
|
+
"content": "156965bdf43f8d3973ac9bc95f71d39d36a8c1a72e4833eb83faee3d4e5168eb"
|
|
3240
3293
|
},
|
|
3241
3294
|
{
|
|
3242
3295
|
"alg": "SHA3-512",
|
|
3243
|
-
"content": "
|
|
3296
|
+
"content": "7381a02c760258e16513bbb3c9d7210130f49232b366b4e70e3e66d39b0a8258d72bff2410edb9fa2aae000cc66506ecda8a70fa05a136b5cf1c905a15a81911"
|
|
3244
3297
|
}
|
|
3245
3298
|
]
|
|
3246
3299
|
},
|
|
@@ -3341,11 +3394,11 @@
|
|
|
3341
3394
|
"hashes": [
|
|
3342
3395
|
{
|
|
3343
3396
|
"alg": "SHA-256",
|
|
3344
|
-
"content": "
|
|
3397
|
+
"content": "360caa6c03913625214e4f2f8430e7306f64ce5f44e8cc3d52af1368c7d8a6b4"
|
|
3345
3398
|
},
|
|
3346
3399
|
{
|
|
3347
3400
|
"alg": "SHA3-512",
|
|
3348
|
-
"content": "
|
|
3401
|
+
"content": "4e1a8cee480cc389a3eb56ce3773b645f4d09353cfc34f65aeecc84c18aae0ccc15f6ce4665c39306b7cdcf638413d240960b641a92146ea270189ed07f63282"
|
|
3349
3402
|
}
|
|
3350
3403
|
]
|
|
3351
3404
|
},
|
|
@@ -3356,11 +3409,11 @@
|
|
|
3356
3409
|
"hashes": [
|
|
3357
3410
|
{
|
|
3358
3411
|
"alg": "SHA-256",
|
|
3359
|
-
"content": "
|
|
3412
|
+
"content": "ed9855c8ce1fb2830f1cf69a4554bf7600964f32fd75898b41f8a23003ae9a6d"
|
|
3360
3413
|
},
|
|
3361
3414
|
{
|
|
3362
3415
|
"alg": "SHA3-512",
|
|
3363
|
-
"content": "
|
|
3416
|
+
"content": "3eb5148995be926186f2ec4f61eb3b5ccb64645c0d513d74ed780ed7ed356a299632059285484a37399d64dc3f68d1d44c44ccd113e480b8d3fe2b95790b509d"
|
|
3364
3417
|
}
|
|
3365
3418
|
]
|
|
3366
3419
|
},
|
|
@@ -3589,6 +3642,21 @@
|
|
|
3589
3642
|
}
|
|
3590
3643
|
]
|
|
3591
3644
|
},
|
|
3645
|
+
{
|
|
3646
|
+
"bom-ref": "file:sources/README.md",
|
|
3647
|
+
"type": "file",
|
|
3648
|
+
"name": "sources/README.md",
|
|
3649
|
+
"hashes": [
|
|
3650
|
+
{
|
|
3651
|
+
"alg": "SHA-256",
|
|
3652
|
+
"content": "37356d40c4db4a0b5060911cd20f7a4ec3fc96c8b394308de646ba17e69493b7"
|
|
3653
|
+
},
|
|
3654
|
+
{
|
|
3655
|
+
"alg": "SHA3-512",
|
|
3656
|
+
"content": "963c00719163eedb931bcc4713be3f2aca2377f3d6492ff44c2d3e8dd37c6d6ffc4411ecfeda697c0406fffc2bb8eaeb38195b230d9e6a0ecfbfe4671ac0b7c3"
|
|
3657
|
+
}
|
|
3658
|
+
]
|
|
3659
|
+
},
|
|
3592
3660
|
{
|
|
3593
3661
|
"bom-ref": "file:sources/validators/atlas-validator.js",
|
|
3594
3662
|
"type": "file",
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* EPSS score/percentile consistency gate.
|
|
6
|
+
*
|
|
7
|
+
* EPSS publishes a score and a percentile for every CVE, refreshed daily. The
|
|
8
|
+
* percentile is the score's RANK within that day's publication: if A scores
|
|
9
|
+
* higher than B on a given day, A's percentile is at least B's. That makes the
|
|
10
|
+
* pair self-checking with no network access — sort a day's entries by score and
|
|
11
|
+
* the percentiles must come out sorted too.
|
|
12
|
+
*
|
|
13
|
+
* The failure this catches is updating one field without the other. A refresh
|
|
14
|
+
* that writes a new score but keeps yesterday's percentile leaves a plausible
|
|
15
|
+
* entry: both numbers are in range, both look like EPSS values, and nothing
|
|
16
|
+
* downstream can tell they describe different days. The result is a CVE ranked
|
|
17
|
+
* as top-decile urgency on a score that no longer supports it, which is exactly
|
|
18
|
+
* the input the prioritisation model trusts most.
|
|
19
|
+
*
|
|
20
|
+
* What the ordering check does and does not establish. Monotonicity is a
|
|
21
|
+
* necessary condition, not a sufficient one: it catches a stale percentile that
|
|
22
|
+
* contradicts the new score's rank, which is what a partial refresh usually
|
|
23
|
+
* produces, but a stale value that happens to preserve the ordering passes.
|
|
24
|
+
* Proving two fields share a publication would require the fetched row, and
|
|
25
|
+
* that is not recoverable from the catalog offline. So this is a corruption
|
|
26
|
+
* detector, not a provenance proof — the provenance guarantee has to come from
|
|
27
|
+
* the write side, by fetching and writing score, percentile and date together.
|
|
28
|
+
*
|
|
29
|
+
* Three checks run:
|
|
30
|
+
* - within each epss_date, sorting by score must sort by percentile;
|
|
31
|
+
* - an entry carries both numeric fields or neither;
|
|
32
|
+
* - epss_note, which is derived text, must restate the current fields.
|
|
33
|
+
*
|
|
34
|
+
* Entries are grouped by epss_date so each publication is checked against
|
|
35
|
+
* itself; comparing across days is meaningless because the whole distribution
|
|
36
|
+
* shifts.
|
|
37
|
+
*
|
|
38
|
+
* Exit codes: 0 consistent, 1 inconsistent.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
const fs = require("node:fs");
|
|
42
|
+
const path = require("node:path");
|
|
43
|
+
|
|
44
|
+
const ROOT = path.resolve(__dirname, "..");
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Percentiles are published rounded to five decimals, so two scores that are
|
|
48
|
+
* adjacent in the ranking can round to percentiles that invert by a hair. That
|
|
49
|
+
* is arithmetic, not drift. Real mismatches are pairs pulled from different
|
|
50
|
+
* days, which land orders of magnitude above this: the widest rounding artifact
|
|
51
|
+
* observed in the catalog is 8e-5, while a stale-field mismatch runs 0.05-0.9.
|
|
52
|
+
* The threshold sits between the two, far enough from each that neither
|
|
53
|
+
* classification is a close call.
|
|
54
|
+
*/
|
|
55
|
+
const ROUNDING_TOLERANCE = 1e-3;
|
|
56
|
+
|
|
57
|
+
function loadCatalog(file) {
|
|
58
|
+
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function ordinal(n) {
|
|
62
|
+
const suffixes = ["th", "st", "nd", "rd"];
|
|
63
|
+
const v = n % 100;
|
|
64
|
+
return `${n}${suffixes[(v - 20) % 10] || suffixes[v] || suffixes[0]}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The operator-readable restatement of an entry's EPSS fields. Entries carry it
|
|
69
|
+
* as `epss_note`, and it is derived, not authored — so it must be rebuilt
|
|
70
|
+
* whenever the numbers move.
|
|
71
|
+
*/
|
|
72
|
+
function renderNote(entry) {
|
|
73
|
+
const pct = Math.round(entry.epss_percentile * 100);
|
|
74
|
+
return `FIRST EPSS ${entry.epss_score} (${ordinal(pct)} percentile) as of ${entry.epss_date}.`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Group entries by publication date. Entries missing either field are reported
|
|
79
|
+
* separately rather than skipped — a half-populated pair is its own defect, and
|
|
80
|
+
* silently ignoring it would let the gate pass on the very shape it exists to
|
|
81
|
+
* catch.
|
|
82
|
+
*/
|
|
83
|
+
function cohorts(catalog) {
|
|
84
|
+
const groups = new Map();
|
|
85
|
+
const incomplete = [];
|
|
86
|
+
|
|
87
|
+
for (const [id, entry] of Object.entries(catalog)) {
|
|
88
|
+
const hasScore = typeof entry.epss_score === "number";
|
|
89
|
+
const hasPercentile = typeof entry.epss_percentile === "number";
|
|
90
|
+
|
|
91
|
+
if (!hasScore && !hasPercentile) continue;
|
|
92
|
+
if (!hasScore || !hasPercentile) {
|
|
93
|
+
incomplete.push({
|
|
94
|
+
id,
|
|
95
|
+
missing: hasScore ? "epss_percentile" : "epss_score",
|
|
96
|
+
});
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const date = entry.epss_date || "(no epss_date)";
|
|
101
|
+
if (!groups.has(date)) groups.set(date, []);
|
|
102
|
+
groups.get(date).push({ id, score: entry.epss_score, percentile: entry.epss_percentile });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return { groups, incomplete };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Sort a cohort by score and report every place the percentile goes backwards
|
|
110
|
+
* by more than rounding can explain.
|
|
111
|
+
*/
|
|
112
|
+
function inversions(rows) {
|
|
113
|
+
const sorted = [...rows].sort((a, b) => a.score - b.score);
|
|
114
|
+
const found = [];
|
|
115
|
+
|
|
116
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
117
|
+
const prev = sorted[i - 1];
|
|
118
|
+
const cur = sorted[i];
|
|
119
|
+
const delta = prev.percentile - cur.percentile;
|
|
120
|
+
if (delta > ROUNDING_TOLERANCE) {
|
|
121
|
+
found.push({ lower: cur, higher: prev, delta });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return found;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function check(catalogFile) {
|
|
129
|
+
const catalog = loadCatalog(catalogFile);
|
|
130
|
+
const { groups, incomplete } = cohorts(catalog);
|
|
131
|
+
const failures = [];
|
|
132
|
+
|
|
133
|
+
for (const [date, rows] of groups) {
|
|
134
|
+
for (const bad of inversions(rows)) {
|
|
135
|
+
failures.push(
|
|
136
|
+
`${date}: ${bad.lower.id} scores ${bad.lower.score} but ranks ` +
|
|
137
|
+
`${bad.lower.percentile}, below ${bad.higher.id} which scores ` +
|
|
138
|
+
`${bad.higher.score} at ${bad.higher.percentile} ` +
|
|
139
|
+
`(gap ${bad.delta.toFixed(5)}) — the two fields are from different publications`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
for (const row of incomplete) {
|
|
145
|
+
failures.push(`${row.id}: has one EPSS field but not ${row.missing} — write both together or neither`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// The prose restatement drifts the same way the percentile does: a refresh
|
|
149
|
+
// moves the numbers and leaves the sentence describing the previous
|
|
150
|
+
// publication, so the entry states two different scores as of two different
|
|
151
|
+
// dates. It is derived text, so it can be checked exactly.
|
|
152
|
+
for (const [id, entry] of Object.entries(catalog)) {
|
|
153
|
+
if (typeof entry.epss_note !== "string") continue;
|
|
154
|
+
if (typeof entry.epss_score !== "number" || typeof entry.epss_percentile !== "number") continue;
|
|
155
|
+
const expected = renderNote(entry);
|
|
156
|
+
if (entry.epss_note !== expected) {
|
|
157
|
+
failures.push(
|
|
158
|
+
`${id}: epss_note reads ${JSON.stringify(entry.epss_note)} but the fields say ` +
|
|
159
|
+
`${JSON.stringify(expected)} — regenerate the note when the numbers move`
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return { failures, cohortCount: groups.size, entryCount: [...groups.values()].reduce((n, r) => n + r.length, 0) };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function main() {
|
|
168
|
+
const file = process.argv[2] || path.join(ROOT, "data", "cve-catalog.json");
|
|
169
|
+
const { failures, cohortCount, entryCount } = check(file);
|
|
170
|
+
|
|
171
|
+
if (failures.length) {
|
|
172
|
+
console.error("EPSS consistency: FAIL");
|
|
173
|
+
for (const f of failures) console.error(` ${f}`);
|
|
174
|
+
console.error(
|
|
175
|
+
`\n${failures.length} inconsistent ${failures.length === 1 ? "entry" : "entries"}. ` +
|
|
176
|
+
`Re-fetch score and percentile together from api.first.org and write them in one pass.`
|
|
177
|
+
);
|
|
178
|
+
process.exitCode = 1;
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
console.log(
|
|
183
|
+
`EPSS consistency: PASS — ${entryCount} entries across ${cohortCount} publication ${cohortCount === 1 ? "date" : "dates"}, every cohort ranks monotonically`
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (require.main === module) main();
|
|
188
|
+
|
|
189
|
+
module.exports = { check, cohorts, inversions, renderNote, ROUNDING_TOLERANCE };
|
|
@@ -59,6 +59,10 @@ function catalogEntryCount(dataDir, file) {
|
|
|
59
59
|
// emits one for.
|
|
60
60
|
const SBOM_SELF_EXCLUDED = new Set(["sbom.cdx.json"]);
|
|
61
61
|
const SBOM_DERIVABLE_PREFIXES = ["data/_indexes/"];
|
|
62
|
+
// Mirrors refresh-sbom's ALWAYS_SHIPPED: npm adds package.json to every tarball
|
|
63
|
+
// without it appearing in `files`, so the completeness check has to expect a
|
|
64
|
+
// component for it the same way the generator now emits one.
|
|
65
|
+
const SBOM_ALWAYS_SHIPPED = ["package.json", "sources/README.md"];
|
|
62
66
|
|
|
63
67
|
function sbomIsDerivable(rel) {
|
|
64
68
|
return SBOM_DERIVABLE_PREFIXES.some(
|
|
@@ -93,7 +97,7 @@ function walkFilesAbs(absDir) {
|
|
|
93
97
|
// module-level root.
|
|
94
98
|
function expandAllowlistAt(allowlist, root) {
|
|
95
99
|
const abs = [];
|
|
96
|
-
for (const entry of allowlist) {
|
|
100
|
+
for (const entry of [...allowlist, ...SBOM_ALWAYS_SHIPPED]) {
|
|
97
101
|
const full = path.join(root, entry);
|
|
98
102
|
let stat;
|
|
99
103
|
try { stat = fs.statSync(full); }
|