@blamejs/exceptd-skills 0.12.8 → 0.12.9
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 +2 -2
- package/ARCHITECTURE.md +21 -5
- package/CHANGELOG.md +83 -0
- package/README.md +1 -1
- package/bin/exceptd.js +227 -17
- package/data/_indexes/_meta.json +16 -16
- package/data/_indexes/activity-feed.json +8 -8
- package/data/_indexes/catalog-summaries.json +1 -1
- package/data/_indexes/chains.json +11 -11
- package/data/_indexes/section-offsets.json +463 -355
- package/data/_indexes/token-budget.json +113 -53
- package/data/cve-catalog.json +34 -22
- package/data/playbooks/mcp.json +1 -0
- package/lib/playbook-runner.js +119 -35
- package/lib/prefetch.js +27 -6
- package/lib/refresh-external.js +7 -4
- package/manifest-snapshot.json +1 -1
- package/manifest.json +51 -51
- package/orchestrator/index.js +1 -1
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
- package/scripts/check-test-coverage.js +27 -6
- package/scripts/predeploy.js +7 -9
- package/skills/ai-attack-surface/skill.md +25 -0
- package/skills/ai-c2-detection/skill.md +24 -0
- package/skills/compliance-theater/skill.md +6 -0
- package/skills/exploit-scoring/skill.md +6 -0
- package/skills/mcp-agent-trust/skill.md +24 -0
- package/skills/policy-exception-gen/skill.md +6 -0
- package/skills/rag-pipeline-security/skill.md +28 -2
- package/skills/researcher/skill.md +6 -0
- package/skills/security-maturity-tiers/skill.md +6 -0
- package/skills/skill-update-loop/skill.md +6 -0
- package/skills/threat-model-currency/skill.md +4 -0
- package/skills/zeroday-gap-learn/skill.md +6 -0
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
"schema_version": "1.0.0",
|
|
4
4
|
"tokenizer_note": "Character-density approximation: 1 token ≈ 4 chars. This is the canonical rule-of-thumb for OpenAI tokenizers on English+technical text. Claude's tokenizer is typically more efficient on prose; treat this as an upper-bound budget for both. Consumers with stricter precision needs should re-tokenize with their own tokenizer.",
|
|
5
5
|
"approx_chars_per_token": 4,
|
|
6
|
-
"total_chars":
|
|
7
|
-
"total_approx_tokens":
|
|
6
|
+
"total_chars": 1369448,
|
|
7
|
+
"total_approx_tokens": 342364,
|
|
8
8
|
"skill_count": 38
|
|
9
9
|
},
|
|
10
10
|
"skills": {
|
|
@@ -65,10 +65,10 @@
|
|
|
65
65
|
},
|
|
66
66
|
"ai-attack-surface": {
|
|
67
67
|
"path": "skills/ai-attack-surface/skill.md",
|
|
68
|
-
"bytes":
|
|
69
|
-
"chars":
|
|
70
|
-
"lines":
|
|
71
|
-
"approx_tokens":
|
|
68
|
+
"bytes": 20062,
|
|
69
|
+
"chars": 20024,
|
|
70
|
+
"lines": 311,
|
|
71
|
+
"approx_tokens": 5006,
|
|
72
72
|
"approx_chars_per_token": 4,
|
|
73
73
|
"sections": {
|
|
74
74
|
"threat-context": {
|
|
@@ -101,6 +101,11 @@
|
|
|
101
101
|
"chars": 1117,
|
|
102
102
|
"approx_tokens": 279
|
|
103
103
|
},
|
|
104
|
+
"defensive-countermeasure-mapping": {
|
|
105
|
+
"bytes": 3718,
|
|
106
|
+
"chars": 3706,
|
|
107
|
+
"approx_tokens": 927
|
|
108
|
+
},
|
|
104
109
|
"compliance-theater-check": {
|
|
105
110
|
"bytes": 1086,
|
|
106
111
|
"chars": 1086,
|
|
@@ -110,10 +115,10 @@
|
|
|
110
115
|
},
|
|
111
116
|
"mcp-agent-trust": {
|
|
112
117
|
"path": "skills/mcp-agent-trust/skill.md",
|
|
113
|
-
"bytes":
|
|
114
|
-
"chars":
|
|
115
|
-
"lines":
|
|
116
|
-
"approx_tokens":
|
|
118
|
+
"bytes": 23929,
|
|
119
|
+
"chars": 23861,
|
|
120
|
+
"lines": 354,
|
|
121
|
+
"approx_tokens": 5965,
|
|
117
122
|
"approx_chars_per_token": 4,
|
|
118
123
|
"sections": {
|
|
119
124
|
"threat-context": {
|
|
@@ -151,6 +156,11 @@
|
|
|
151
156
|
"chars": 2431,
|
|
152
157
|
"approx_tokens": 608
|
|
153
158
|
},
|
|
159
|
+
"defensive-countermeasure-mapping": {
|
|
160
|
+
"bytes": 4201,
|
|
161
|
+
"chars": 4195,
|
|
162
|
+
"approx_tokens": 1049
|
|
163
|
+
},
|
|
154
164
|
"compliance-theater-check": {
|
|
155
165
|
"bytes": 513,
|
|
156
166
|
"chars": 513,
|
|
@@ -215,12 +225,17 @@
|
|
|
215
225
|
},
|
|
216
226
|
"compliance-theater": {
|
|
217
227
|
"path": "skills/compliance-theater/skill.md",
|
|
218
|
-
"bytes":
|
|
219
|
-
"chars":
|
|
220
|
-
"lines":
|
|
221
|
-
"approx_tokens":
|
|
228
|
+
"bytes": 28379,
|
|
229
|
+
"chars": 28313,
|
|
230
|
+
"lines": 372,
|
|
231
|
+
"approx_tokens": 7078,
|
|
222
232
|
"approx_chars_per_token": 4,
|
|
223
233
|
"sections": {
|
|
234
|
+
"frontmatter-scope": {
|
|
235
|
+
"bytes": 811,
|
|
236
|
+
"chars": 807,
|
|
237
|
+
"approx_tokens": 202
|
|
238
|
+
},
|
|
224
239
|
"threat-context": {
|
|
225
240
|
"bytes": 1804,
|
|
226
241
|
"chars": 1798,
|
|
@@ -265,12 +280,17 @@
|
|
|
265
280
|
},
|
|
266
281
|
"exploit-scoring": {
|
|
267
282
|
"path": "skills/exploit-scoring/skill.md",
|
|
268
|
-
"bytes":
|
|
269
|
-
"chars":
|
|
270
|
-
"lines":
|
|
271
|
-
"approx_tokens":
|
|
283
|
+
"bytes": 21077,
|
|
284
|
+
"chars": 20949,
|
|
285
|
+
"lines": 338,
|
|
286
|
+
"approx_tokens": 5237,
|
|
272
287
|
"approx_chars_per_token": 4,
|
|
273
288
|
"sections": {
|
|
289
|
+
"frontmatter-scope": {
|
|
290
|
+
"bytes": 618,
|
|
291
|
+
"chars": 616,
|
|
292
|
+
"approx_tokens": 154
|
|
293
|
+
},
|
|
274
294
|
"threat-context": {
|
|
275
295
|
"bytes": 1685,
|
|
276
296
|
"chars": 1677,
|
|
@@ -325,10 +345,10 @@
|
|
|
325
345
|
},
|
|
326
346
|
"rag-pipeline-security": {
|
|
327
347
|
"path": "skills/rag-pipeline-security/skill.md",
|
|
328
|
-
"bytes":
|
|
329
|
-
"chars":
|
|
330
|
-
"lines":
|
|
331
|
-
"approx_tokens":
|
|
348
|
+
"bytes": 28775,
|
|
349
|
+
"chars": 28610,
|
|
350
|
+
"lines": 324,
|
|
351
|
+
"approx_tokens": 7153,
|
|
332
352
|
"approx_chars_per_token": 4,
|
|
333
353
|
"sections": {
|
|
334
354
|
"threat-context": {
|
|
@@ -362,9 +382,9 @@
|
|
|
362
382
|
"approx_tokens": 423
|
|
363
383
|
},
|
|
364
384
|
"framework-lag-declaration": {
|
|
365
|
-
"bytes":
|
|
366
|
-
"chars":
|
|
367
|
-
"approx_tokens":
|
|
385
|
+
"bytes": 2157,
|
|
386
|
+
"chars": 2151,
|
|
387
|
+
"approx_tokens": 538
|
|
368
388
|
},
|
|
369
389
|
"ttp-mapping": {
|
|
370
390
|
"bytes": 3642,
|
|
@@ -391,6 +411,11 @@
|
|
|
391
411
|
"chars": 2624,
|
|
392
412
|
"approx_tokens": 656
|
|
393
413
|
},
|
|
414
|
+
"defensive-countermeasure-mapping": {
|
|
415
|
+
"bytes": 3879,
|
|
416
|
+
"chars": 3857,
|
|
417
|
+
"approx_tokens": 964
|
|
418
|
+
},
|
|
394
419
|
"compliance-theater-check": {
|
|
395
420
|
"bytes": 644,
|
|
396
421
|
"chars": 640,
|
|
@@ -400,10 +425,10 @@
|
|
|
400
425
|
},
|
|
401
426
|
"ai-c2-detection": {
|
|
402
427
|
"path": "skills/ai-c2-detection/skill.md",
|
|
403
|
-
"bytes":
|
|
404
|
-
"chars":
|
|
405
|
-
"lines":
|
|
406
|
-
"approx_tokens":
|
|
428
|
+
"bytes": 33572,
|
|
429
|
+
"chars": 33436,
|
|
430
|
+
"lines": 470,
|
|
431
|
+
"approx_tokens": 8359,
|
|
407
432
|
"approx_chars_per_token": 4,
|
|
408
433
|
"sections": {
|
|
409
434
|
"threat-context": {
|
|
@@ -446,6 +471,11 @@
|
|
|
446
471
|
"chars": 4070,
|
|
447
472
|
"approx_tokens": 1018
|
|
448
473
|
},
|
|
474
|
+
"defensive-countermeasure-mapping": {
|
|
475
|
+
"bytes": 3942,
|
|
476
|
+
"chars": 3930,
|
|
477
|
+
"approx_tokens": 983
|
|
478
|
+
},
|
|
449
479
|
"compliance-theater-check": {
|
|
450
480
|
"bytes": 1390,
|
|
451
481
|
"chars": 1382,
|
|
@@ -465,12 +495,17 @@
|
|
|
465
495
|
},
|
|
466
496
|
"policy-exception-gen": {
|
|
467
497
|
"path": "skills/policy-exception-gen/skill.md",
|
|
468
|
-
"bytes":
|
|
469
|
-
"chars":
|
|
470
|
-
"lines":
|
|
471
|
-
"approx_tokens":
|
|
498
|
+
"bytes": 28886,
|
|
499
|
+
"chars": 28802,
|
|
500
|
+
"lines": 444,
|
|
501
|
+
"approx_tokens": 7201,
|
|
472
502
|
"approx_chars_per_token": 4,
|
|
473
503
|
"sections": {
|
|
504
|
+
"frontmatter-scope": {
|
|
505
|
+
"bytes": 483,
|
|
506
|
+
"chars": 481,
|
|
507
|
+
"approx_tokens": 120
|
|
508
|
+
},
|
|
474
509
|
"threat-context": {
|
|
475
510
|
"bytes": 2232,
|
|
476
511
|
"chars": 2226,
|
|
@@ -515,12 +550,17 @@
|
|
|
515
550
|
},
|
|
516
551
|
"threat-model-currency": {
|
|
517
552
|
"path": "skills/threat-model-currency/skill.md",
|
|
518
|
-
"bytes":
|
|
519
|
-
"chars":
|
|
520
|
-
"lines":
|
|
521
|
-
"approx_tokens":
|
|
553
|
+
"bytes": 25608,
|
|
554
|
+
"chars": 25506,
|
|
555
|
+
"lines": 409,
|
|
556
|
+
"approx_tokens": 6377,
|
|
522
557
|
"approx_chars_per_token": 4,
|
|
523
558
|
"sections": {
|
|
559
|
+
"frontmatter-scope": {
|
|
560
|
+
"bytes": 626,
|
|
561
|
+
"chars": 624,
|
|
562
|
+
"approx_tokens": 156
|
|
563
|
+
},
|
|
524
564
|
"purpose": {
|
|
525
565
|
"bytes": 545,
|
|
526
566
|
"chars": 541,
|
|
@@ -635,12 +675,17 @@
|
|
|
635
675
|
},
|
|
636
676
|
"zeroday-gap-learn": {
|
|
637
677
|
"path": "skills/zeroday-gap-learn/skill.md",
|
|
638
|
-
"bytes":
|
|
639
|
-
"chars":
|
|
640
|
-
"lines":
|
|
641
|
-
"approx_tokens":
|
|
678
|
+
"bytes": 22718,
|
|
679
|
+
"chars": 22600,
|
|
680
|
+
"lines": 357,
|
|
681
|
+
"approx_tokens": 5650,
|
|
642
682
|
"approx_chars_per_token": 4,
|
|
643
683
|
"sections": {
|
|
684
|
+
"frontmatter-scope": {
|
|
685
|
+
"bytes": 541,
|
|
686
|
+
"chars": 539,
|
|
687
|
+
"approx_tokens": 135
|
|
688
|
+
},
|
|
644
689
|
"threat-context": {
|
|
645
690
|
"bytes": 1673,
|
|
646
691
|
"chars": 1665,
|
|
@@ -770,12 +815,17 @@
|
|
|
770
815
|
},
|
|
771
816
|
"skill-update-loop": {
|
|
772
817
|
"path": "skills/skill-update-loop/skill.md",
|
|
773
|
-
"bytes":
|
|
774
|
-
"chars":
|
|
775
|
-
"lines":
|
|
776
|
-
"approx_tokens":
|
|
818
|
+
"bytes": 43027,
|
|
819
|
+
"chars": 42921,
|
|
820
|
+
"lines": 502,
|
|
821
|
+
"approx_tokens": 10730,
|
|
777
822
|
"approx_chars_per_token": 4,
|
|
778
823
|
"sections": {
|
|
824
|
+
"frontmatter-scope": {
|
|
825
|
+
"bytes": 453,
|
|
826
|
+
"chars": 451,
|
|
827
|
+
"approx_tokens": 113
|
|
828
|
+
},
|
|
779
829
|
"threat-context": {
|
|
780
830
|
"bytes": 1510,
|
|
781
831
|
"chars": 1504,
|
|
@@ -835,12 +885,17 @@
|
|
|
835
885
|
},
|
|
836
886
|
"security-maturity-tiers": {
|
|
837
887
|
"path": "skills/security-maturity-tiers/skill.md",
|
|
838
|
-
"bytes":
|
|
839
|
-
"chars":
|
|
840
|
-
"lines":
|
|
841
|
-
"approx_tokens":
|
|
888
|
+
"bytes": 29805,
|
|
889
|
+
"chars": 29641,
|
|
890
|
+
"lines": 489,
|
|
891
|
+
"approx_tokens": 7410,
|
|
842
892
|
"approx_chars_per_token": 4,
|
|
843
893
|
"sections": {
|
|
894
|
+
"frontmatter-scope": {
|
|
895
|
+
"bytes": 522,
|
|
896
|
+
"chars": 522,
|
|
897
|
+
"approx_tokens": 131
|
|
898
|
+
},
|
|
844
899
|
"how-to-use-this-skill": {
|
|
845
900
|
"bytes": 411,
|
|
846
901
|
"chars": 409,
|
|
@@ -915,12 +970,17 @@
|
|
|
915
970
|
},
|
|
916
971
|
"researcher": {
|
|
917
972
|
"path": "skills/researcher/skill.md",
|
|
918
|
-
"bytes":
|
|
919
|
-
"chars":
|
|
920
|
-
"lines":
|
|
921
|
-
"approx_tokens":
|
|
973
|
+
"bytes": 29009,
|
|
974
|
+
"chars": 28839,
|
|
975
|
+
"lines": 317,
|
|
976
|
+
"approx_tokens": 7210,
|
|
922
977
|
"approx_chars_per_token": 4,
|
|
923
978
|
"sections": {
|
|
979
|
+
"frontmatter-scope": {
|
|
980
|
+
"bytes": 537,
|
|
981
|
+
"chars": 535,
|
|
982
|
+
"approx_tokens": 134
|
|
983
|
+
},
|
|
924
984
|
"threat-context": {
|
|
925
985
|
"bytes": 2945,
|
|
926
986
|
"chars": 2937,
|
package/data/cve-catalog.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_meta": {
|
|
3
3
|
"schema_version": "1.0.0",
|
|
4
|
-
"last_updated": "2026-05-
|
|
4
|
+
"last_updated": "2026-05-13",
|
|
5
5
|
"source": "NVD + CISA KEV + vendor advisories — see sources/index.json",
|
|
6
6
|
"required_fields": [
|
|
7
7
|
"type",
|
|
@@ -43,8 +43,9 @@
|
|
|
43
43
|
"cvss_score": 7.8,
|
|
44
44
|
"cvss_vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
|
|
45
45
|
"cisa_kev": true,
|
|
46
|
-
"cisa_kev_date": "2026-
|
|
47
|
-
"cisa_kev_due_date": "2026-
|
|
46
|
+
"cisa_kev_date": "2026-05-01",
|
|
47
|
+
"cisa_kev_due_date": "2026-05-15",
|
|
48
|
+
"cisa_kev_date_correction_note": "v0.12.9 (2026-05-13): catalog previously stored 2026-03-15 / due 2026-04-05. CISA KEV JSON authoritative is dateAdded 2026-05-01 / dueDate 2026-05-15 (source: https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json filtered for CVE-2026-31431). The catalog was running six weeks ahead of the real KEV listing; downstream framework-SLA computations were anchored on a date that hadn't yet been authoritative.",
|
|
48
49
|
"poc_available": true,
|
|
49
50
|
"poc_description": "Public exploit script — single-stage, 732 bytes, no race condition, deterministic root escalation from any unprivileged user or container",
|
|
50
51
|
"ai_discovered": true,
|
|
@@ -91,11 +92,13 @@
|
|
|
91
92
|
"live_patch_available": -10,
|
|
92
93
|
"reboot_required": 5
|
|
93
94
|
},
|
|
94
|
-
"epss_score": 0.
|
|
95
|
-
"epss_percentile": 0.
|
|
96
|
-
"epss_date": "2026-05-
|
|
95
|
+
"epss_score": 0.0257,
|
|
96
|
+
"epss_percentile": 0.8569,
|
|
97
|
+
"epss_date": "2026-05-13",
|
|
97
98
|
"epss_source": "https://api.first.org/data/v1/epss?cve=CVE-2026-31431",
|
|
98
|
-
"
|
|
99
|
+
"epss_correction_note": "v0.12.9: refreshed from live FIRST API. Catalog previously stored 0.94 / 0.99 (estimate for newly-published CVE; EPSS model cold-start). Live values reflect post-disclosure exploitation telemetry through 2026-05-13.",
|
|
100
|
+
"cwe_refs": ["CWE-669"],
|
|
101
|
+
"source_verified": "2026-05-13",
|
|
99
102
|
"verification_sources": [
|
|
100
103
|
"https://nvd.nist.gov/vuln/detail/CVE-2026-31431",
|
|
101
104
|
"https://www.cisa.gov/known-exploited-vulnerabilities-catalog"
|
|
@@ -180,8 +183,11 @@
|
|
|
180
183
|
"CVE-2026-43284": {
|
|
181
184
|
"name": "Dirty Frag (ESP/IPsec component)",
|
|
182
185
|
"type": "LPE",
|
|
183
|
-
"cvss_score":
|
|
184
|
-
"cvss_vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:
|
|
186
|
+
"cvss_score": 8.8,
|
|
187
|
+
"cvss_vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H",
|
|
188
|
+
"cvss_correction_note": "v0.12.9 (2026-05-13): catalog previously stored 7.8 / Scope:U. NVD secondary CVSS block authoritative is 8.8 / Scope:C (Scope: Changed — kernel→user-namespace breakout is in-scope, which supports the container-escape framing). Source: https://nvd.nist.gov/vuln/detail/CVE-2026-43284. The lower-scored block remains valid for compatibility readers; the higher block is the operational risk floor.",
|
|
189
|
+
"cvss_score_alternate": 7.8,
|
|
190
|
+
"cvss_vector_alternate": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
|
|
185
191
|
"cisa_kev": false,
|
|
186
192
|
"cisa_kev_date": null,
|
|
187
193
|
"poc_available": true,
|
|
@@ -222,11 +228,13 @@
|
|
|
222
228
|
"live_patch_available": 0,
|
|
223
229
|
"reboot_required": 5
|
|
224
230
|
},
|
|
225
|
-
"epss_score": 0.
|
|
226
|
-
"epss_percentile": 0.
|
|
227
|
-
"epss_date": "2026-05-
|
|
231
|
+
"epss_score": 0.00007,
|
|
232
|
+
"epss_percentile": 0.0051,
|
|
233
|
+
"epss_date": "2026-05-13",
|
|
228
234
|
"epss_source": "https://api.first.org/data/v1/epss?cve=CVE-2026-43284",
|
|
229
|
-
"
|
|
235
|
+
"epss_correction_note": "v0.12.9: refreshed from live FIRST API. Previous values (0.18 / 0.88) were estimates for the newly-published CVE; cold-start EPSS routinely overstates newly-cataloged kernel CVEs.",
|
|
236
|
+
"cwe_refs": ["CWE-123"],
|
|
237
|
+
"source_verified": "2026-05-13",
|
|
230
238
|
"verification_sources": [
|
|
231
239
|
"https://nvd.nist.gov/vuln/detail/CVE-2026-43284"
|
|
232
240
|
],
|
|
@@ -346,11 +354,13 @@
|
|
|
346
354
|
"live_patch_available": 0,
|
|
347
355
|
"reboot_required": 5
|
|
348
356
|
},
|
|
349
|
-
"epss_score": 0.
|
|
350
|
-
"epss_percentile": 0.
|
|
351
|
-
"epss_date": "2026-05-
|
|
357
|
+
"epss_score": 0.0001,
|
|
358
|
+
"epss_percentile": 0.0115,
|
|
359
|
+
"epss_date": "2026-05-13",
|
|
352
360
|
"epss_source": "https://api.first.org/data/v1/epss?cve=CVE-2026-43500",
|
|
353
|
-
"
|
|
361
|
+
"epss_correction_note": "v0.12.9: refreshed from live FIRST API (cold-start cleanup).",
|
|
362
|
+
"cwe_refs": ["CWE-787"],
|
|
363
|
+
"source_verified": "2026-05-13",
|
|
354
364
|
"verification_sources": [
|
|
355
365
|
"https://nvd.nist.gov/vuln/detail/CVE-2026-43500"
|
|
356
366
|
],
|
|
@@ -553,10 +563,11 @@
|
|
|
553
563
|
"last_updated": "2026-05-13"
|
|
554
564
|
},
|
|
555
565
|
"CVE-2026-30615": {
|
|
556
|
-
"name": "Windsurf MCP
|
|
566
|
+
"name": "Windsurf MCP Local-Vector RCE via Adversarial Tool Response",
|
|
557
567
|
"type": "RCE-supply-chain",
|
|
558
|
-
"cvss_score":
|
|
559
|
-
"cvss_vector": "CVSS:3.1/AV:
|
|
568
|
+
"cvss_score": 8.0,
|
|
569
|
+
"cvss_vector": "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:H",
|
|
570
|
+
"cvss_correction_note": "v0.12.9 (2026-05-13): catalog previously stored CVSS 9.8 / AV:N. NVD authoritative is 8.0 / AV:L (local attack vector; attacker must control HTML content the Windsurf MCP client processes — not a network-vector zero-interaction RCE as initially cataloged). Source: https://nvd.nist.gov/vuln/detail/CVE-2026-30615 (published 2026-04-15, last_modified 2026-04-27, vulnStatus: Deferred). Recompute RWEP with blast_radius reduced from 30→20 to reflect local-vector + Scope:U.",
|
|
560
571
|
"cisa_kev": false,
|
|
561
572
|
"cisa_kev_date": null,
|
|
562
573
|
"poc_available": true,
|
|
@@ -722,10 +733,11 @@
|
|
|
722
733
|
"reboot_required": 0
|
|
723
734
|
},
|
|
724
735
|
"rwep_notes": "RWEP cap of 30 on blast_radius understates the real exposure (42 packages, ~150M+ weekly downloads combined). Operationally treat as P0; the formula caps blast_radius regardless of magnitude. Once CISA KEV-lists this CVE, the +25 boost will lift score to 70 (P1 territory).",
|
|
725
|
-
"epss_score": 0.
|
|
726
|
-
"epss_percentile": 0.
|
|
736
|
+
"epss_score": 0.00039,
|
|
737
|
+
"epss_percentile": 0.1179,
|
|
727
738
|
"epss_date": "2026-05-13",
|
|
728
739
|
"epss_source": "https://api.first.org/data/v1/epss?cve=CVE-2026-45321",
|
|
740
|
+
"epss_correction_note": "v0.12.9: refreshed from live FIRST API. Previous values (0.78 / 0.97) were cold-start estimates inconsistent with confirmed in-wild exploitation; the qualitative narrative (rwep_notes above) remains the authoritative risk signal — raw EPSS underreports newly-disclosed worm payload classes.",
|
|
729
741
|
"source_verified": "2026-05-13",
|
|
730
742
|
"verification_sources": [
|
|
731
743
|
"https://nvd.nist.gov/vuln/detail/CVE-2026-45321",
|
package/data/playbooks/mcp.json
CHANGED
package/lib/playbook-runner.js
CHANGED
|
@@ -418,26 +418,118 @@ function analyze(playbookId, directiveId, detectResult, agentSignals = {}) {
|
|
|
418
418
|
const an = resolvedPhase(playbook, directiveId, 'analyze');
|
|
419
419
|
const directive = findDirective(playbook, directiveId);
|
|
420
420
|
|
|
421
|
-
//
|
|
422
|
-
//
|
|
423
|
-
//
|
|
424
|
-
//
|
|
425
|
-
//
|
|
426
|
-
//
|
|
421
|
+
// Resolve catalogued CVEs from the domain.cve_refs list. This list is the
|
|
422
|
+
// playbook's CVE scan-coverage enumeration — every CVE this playbook can
|
|
423
|
+
// detect. By itself it is NOT a statement that the operator is affected by
|
|
424
|
+
// any of these CVEs; affected-ness requires evidence correlation in detect.
|
|
425
|
+
//
|
|
426
|
+
// Two distinct sets are computed below:
|
|
427
|
+
//
|
|
428
|
+
// catalogBaselineCves — every CVE the playbook scans for, with full
|
|
429
|
+
// per-CVE catalog context (RWEP / KEV / CVSS / AI-discovery /
|
|
430
|
+
// active-exploitation / patch state). Always populated when the
|
|
431
|
+
// playbook has domain.cve_refs. Each entry carries correlated_via=null
|
|
432
|
+
// and a `note` flagging it as catalog-only.
|
|
433
|
+
//
|
|
434
|
+
// matchedCves — CVEs the operator's submitted evidence actually
|
|
435
|
+
// correlates to. Correlation paths:
|
|
436
|
+
// (a) An indicator fired (verdict === 'hit') whose attack_ref or
|
|
437
|
+
// atlas_ref intersects the CVE's attack_refs / atlas_refs in
|
|
438
|
+
// the catalog.
|
|
439
|
+
// (b) An agentSignal explicitly references the CVE id with a
|
|
440
|
+
// truthy value (`agentSignals[cveId] === true`) or with a
|
|
441
|
+
// string value 'hit' / 'detected' / 'affected'.
|
|
442
|
+
// Each entry carries correlated_via=<reason string> so downstream
|
|
443
|
+
// consumers (CSAF / SARIF / OpenVEX / human renderer) can show the
|
|
444
|
+
// provenance, and so an empty matchedCves means "no evidence
|
|
445
|
+
// correlated to operator's submission" rather than "playbook has
|
|
446
|
+
// no CVEs of interest."
|
|
447
|
+
//
|
|
448
|
+
// VEX filter (agentSignals.vex_filter): a set of CVE IDs the operator has
|
|
449
|
+
// formally declared not_affected via CycloneDX/OpenVEX. VEX-dropped CVEs
|
|
450
|
+
// are removed from BOTH arrays (they're not affected — neither correlated
|
|
451
|
+
// nor part of effective scan coverage for this run).
|
|
427
452
|
const cveRefs = playbook.domain.cve_refs || [];
|
|
428
453
|
const vexFilter = agentSignals.vex_filter instanceof Set ? agentSignals.vex_filter
|
|
429
454
|
: (Array.isArray(agentSignals.vex_filter) ? new Set(agentSignals.vex_filter) : null);
|
|
430
|
-
const
|
|
431
|
-
const
|
|
432
|
-
?
|
|
433
|
-
:
|
|
455
|
+
const allCves = cveRefs.map(id => xref.byCve(id)).filter(r => r.found);
|
|
456
|
+
const catalogBaselineCves = vexFilter
|
|
457
|
+
? allCves.filter(c => !vexFilter.has(c.cve_id))
|
|
458
|
+
: allCves;
|
|
434
459
|
const vexDropped = vexFilter
|
|
435
|
-
?
|
|
460
|
+
? allCves.filter(c => vexFilter.has(c.cve_id)).map(c => c.cve_id)
|
|
436
461
|
: [];
|
|
437
462
|
|
|
438
|
-
//
|
|
439
|
-
|
|
440
|
-
|
|
463
|
+
// Build correlation map: cve_id -> array of "indicator_hit:<id>" / "signal:<id>" reasons.
|
|
464
|
+
const correlationsByCve = new Map();
|
|
465
|
+
const addCorrelation = (cveId, reason) => {
|
|
466
|
+
if (!correlationsByCve.has(cveId)) correlationsByCve.set(cveId, []);
|
|
467
|
+
const arr = correlationsByCve.get(cveId);
|
|
468
|
+
if (!arr.includes(reason)) arr.push(reason);
|
|
469
|
+
};
|
|
470
|
+
// (a) indicator-hit → CVE via shared attack_ref / atlas_ref.
|
|
471
|
+
const playbookDetect = resolvedPhase(playbook, directiveId, 'detect');
|
|
472
|
+
const indicatorRefs = new Map(); // indicator.id -> { attack_ref, atlas_ref }
|
|
473
|
+
for (const ind of (playbookDetect.indicators || [])) {
|
|
474
|
+
indicatorRefs.set(ind.id, { attack_ref: ind.attack_ref || null, atlas_ref: ind.atlas_ref || null });
|
|
475
|
+
}
|
|
476
|
+
const firedIndicators = (detectResult.indicators || []).filter(i => i.verdict === 'hit');
|
|
477
|
+
for (const fired of firedIndicators) {
|
|
478
|
+
const refs = indicatorRefs.get(fired.id) || { attack_ref: fired.attack_ref || null, atlas_ref: fired.atlas_ref || null };
|
|
479
|
+
if (!refs.attack_ref && !refs.atlas_ref) continue;
|
|
480
|
+
for (const c of catalogBaselineCves) {
|
|
481
|
+
const attackHit = refs.attack_ref && Array.isArray(c.attack_refs) && c.attack_refs.includes(refs.attack_ref);
|
|
482
|
+
const atlasHit = refs.atlas_ref && Array.isArray(c.atlas_refs) && c.atlas_refs.includes(refs.atlas_ref);
|
|
483
|
+
if (attackHit || atlasHit) addCorrelation(c.cve_id, `indicator_hit:${fired.id}`);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
// (b) agentSignals explicitly referencing a CVE id.
|
|
487
|
+
for (const c of catalogBaselineCves) {
|
|
488
|
+
const sig = agentSignals[c.cve_id];
|
|
489
|
+
if (sig === true || sig === 'hit' || sig === 'detected' || sig === 'affected') {
|
|
490
|
+
addCorrelation(c.cve_id, `signal:${c.cve_id}`);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const matchedCves = catalogBaselineCves.filter(c => correlationsByCve.has(c.cve_id));
|
|
495
|
+
|
|
496
|
+
// Per-CVE shape — identical between matched_cves and catalog_baseline_cves
|
|
497
|
+
// so consumers can iterate either without branching. matched_cves entries
|
|
498
|
+
// carry a non-null correlated_via array; catalog_baseline_cves entries
|
|
499
|
+
// carry correlated_via:null and a `note` clarifying the field's intent.
|
|
500
|
+
const cveShape = (c, correlatedVia) => ({
|
|
501
|
+
cve_id: c.cve_id,
|
|
502
|
+
rwep: c.rwep_score,
|
|
503
|
+
cvss_score: c.entry?.cvss_score ?? null,
|
|
504
|
+
cvss_vector: c.entry?.cvss_vector ?? null,
|
|
505
|
+
cisa_kev: c.cisa_kev,
|
|
506
|
+
cisa_kev_date: c.entry?.cisa_kev_date ?? null,
|
|
507
|
+
cisa_kev_due_date: c.entry?.cisa_kev_due_date ?? null,
|
|
508
|
+
poc_available: c.entry?.poc_available ?? null,
|
|
509
|
+
ai_discovered: c.ai_discovered,
|
|
510
|
+
ai_assisted_weaponization: c.entry?.ai_assisted_weaponization ?? null,
|
|
511
|
+
active_exploitation: c.active_exploitation,
|
|
512
|
+
patch_available: c.entry?.patch_available ?? null,
|
|
513
|
+
patch_required_reboot: c.entry?.patch_required_reboot ?? null,
|
|
514
|
+
live_patch_available: c.entry?.live_patch_available ?? null,
|
|
515
|
+
epss_score: c.entry?.epss_score ?? null,
|
|
516
|
+
epss_date: c.entry?.epss_date ?? null,
|
|
517
|
+
atlas_refs: c.atlas_refs,
|
|
518
|
+
attack_refs: c.attack_refs,
|
|
519
|
+
affected_versions: c.entry?.affected_versions ?? null,
|
|
520
|
+
correlated_via: correlatedVia,
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
const matchedCveEntries = matchedCves.map(c => cveShape(c, correlationsByCve.get(c.cve_id)));
|
|
524
|
+
const catalogBaselineEntries = catalogBaselineCves.map(c => ({
|
|
525
|
+
...cveShape(c, null),
|
|
526
|
+
note: 'Catalog-baseline entry — this CVE is in the playbook\'s scan coverage but no submitted evidence correlated to it. Not a statement that the operator is affected.',
|
|
527
|
+
}));
|
|
528
|
+
|
|
529
|
+
// RWEP composition: start from the per-CVE rwep_score of evidence-correlated
|
|
530
|
+
// matches (NOT catalog baseline) so RWEP base reflects what the operator's
|
|
531
|
+
// evidence actually surfaced. Adjust by playbook's rwep_inputs based on
|
|
532
|
+
// detect hits + agent signals.
|
|
441
533
|
const baseRwep = matchedCves.length ? Math.max(...matchedCves.map(c => c.rwep_score)) : 0;
|
|
442
534
|
let adjustedRwep = baseRwep;
|
|
443
535
|
const rwepBreakdown = [];
|
|
@@ -495,27 +587,19 @@ function analyze(playbookId, directiveId, detectResult, agentSignals = {}) {
|
|
|
495
587
|
// Pull every required field from the catalog entry; null is only emitted
|
|
496
588
|
// when the catalog itself lacks the value, never when we just forgot to
|
|
497
589
|
// forward it. EPSS is included because validate-cves --live populates it.
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
patch_required_reboot: c.entry?.patch_required_reboot ?? null,
|
|
512
|
-
live_patch_available: c.entry?.live_patch_available ?? null,
|
|
513
|
-
epss_score: c.entry?.epss_score ?? null,
|
|
514
|
-
epss_date: c.entry?.epss_date ?? null,
|
|
515
|
-
atlas_refs: c.atlas_refs,
|
|
516
|
-
attack_refs: c.attack_refs,
|
|
517
|
-
affected_versions: c.entry?.affected_versions ?? null,
|
|
518
|
-
})),
|
|
590
|
+
//
|
|
591
|
+
// matched_cves — evidence-correlated only. Each entry has a non-null
|
|
592
|
+
// correlated_via[] array naming the indicator hits or agent signals that
|
|
593
|
+
// tied the operator's submission to this CVE. Empty array means the
|
|
594
|
+
// playbook's scan coverage saw no matching evidence in this run.
|
|
595
|
+
matched_cves: matchedCveEntries,
|
|
596
|
+
// catalog_baseline_cves — every CVE the playbook scans for, with the
|
|
597
|
+
// same per-CVE shape but correlated_via=null and a note explaining the
|
|
598
|
+
// field is scan-coverage metadata, NOT an operator-affected list. Use
|
|
599
|
+
// this when surfacing "what CVEs does this playbook check for?" Use
|
|
600
|
+
// matched_cves when surfacing "what CVEs is the operator actually
|
|
601
|
+
// affected by based on submitted evidence?"
|
|
602
|
+
catalog_baseline_cves: catalogBaselineEntries,
|
|
519
603
|
rwep: { base: baseRwep, adjusted: adjustedRwep, breakdown: rwepBreakdown, threshold: directive ? resolvedPhase(playbook, directiveId, 'direct').rwep_threshold : null },
|
|
520
604
|
blast_radius_score: blastRadiusScore,
|
|
521
605
|
blast_radius_basis: blastRubric.find(r => r.blast_radius_score === blastRadiusScore) || null,
|
package/lib/prefetch.js
CHANGED
|
@@ -80,15 +80,24 @@ const SOURCES = {
|
|
|
80
80
|
.filter(Boolean),
|
|
81
81
|
},
|
|
82
82
|
pins: {
|
|
83
|
-
description: "MITRE GitHub releases for ATLAS / ATT&CK
|
|
83
|
+
description: "MITRE GitHub releases for ATLAS / ATT&CK pin checks",
|
|
84
84
|
rate: { tokens: 30, windowMs: 60 * 60_000 }, // anon: 60/h, leave headroom
|
|
85
85
|
rate_with_key: { tokens: 500, windowMs: 60 * 60_000 },
|
|
86
86
|
concurrency: 2,
|
|
87
|
+
// D3FEND and CWE were previously listed here but neither project
|
|
88
|
+
// publishes via GitHub Releases — D3FEND distributes the ontology
|
|
89
|
+
// from d3fend/d3fend-ontology without tagged releases, and CWE
|
|
90
|
+
// ships its catalog as XML/JSON downloads from cwe.mitre.org rather
|
|
91
|
+
// than a GitHub repo. The old api.github.com URLs (mitre/cwe and
|
|
92
|
+
// d3fend/d3fend-data) returned HTTP 404 on every refresh, surfacing
|
|
93
|
+
// as "2 error(s)" in the prefetch summary. Pin currency for those
|
|
94
|
+
// two frameworks is tracked via lib/upstream-check.js against
|
|
95
|
+
// cwe.mitre.org and d3fend.mitre.org respectively; the prefetch
|
|
96
|
+
// registry only contains sources that actually have a GitHub
|
|
97
|
+
// Releases feed to poll.
|
|
87
98
|
expand: () => [
|
|
88
99
|
{ id: "mitre-atlas__atlas-data__releases", url: "https://api.github.com/repos/mitre-atlas/atlas-data/releases?per_page=5" },
|
|
89
100
|
{ id: "mitre-attack__attack-stix-data__releases", url: "https://api.github.com/repos/mitre-attack/attack-stix-data/releases?per_page=5" },
|
|
90
|
-
{ id: "d3fend__d3fend-data__releases", url: "https://api.github.com/repos/d3fend/d3fend-data/releases?per_page=5" },
|
|
91
|
-
{ id: "mitre__cwe__releases", url: "https://api.github.com/repos/mitre/cwe/releases?per_page=5" },
|
|
92
101
|
],
|
|
93
102
|
},
|
|
94
103
|
};
|
|
@@ -364,14 +373,26 @@ async function main() {
|
|
|
364
373
|
const opts = parseArgs(process.argv);
|
|
365
374
|
if (opts.help) {
|
|
366
375
|
printHelp();
|
|
367
|
-
|
|
376
|
+
return;
|
|
368
377
|
}
|
|
378
|
+
// Why process.exitCode and not process.exit():
|
|
379
|
+
// On Windows + Node 25 (libuv), calling process.exit() synchronously
|
|
380
|
+
// while in-flight fetch / AbortController teardown is still mid-close
|
|
381
|
+
// produced `Assertion failed: !(handle->flags & UV_HANDLE_CLOSING),
|
|
382
|
+
// file src\win\async.c, line 76` followed by exit 3221226505
|
|
383
|
+
// (STATUS_STACK_BUFFER_OVERRUN). The summary line had already
|
|
384
|
+
// flushed, so operators saw the crash *after* their summary —
|
|
385
|
+
// contractually correct but visibly noisy. Letting the event loop
|
|
386
|
+
// drain naturally — via exitCode + return — lets undici's connection
|
|
387
|
+
// pool and the AbortController signal listeners finish teardown
|
|
388
|
+
// before the process exits, eliminating the assertion. Same pattern
|
|
389
|
+
// documented in CLAUDE.md for v0.11.11's `ci` #100 regression.
|
|
369
390
|
try {
|
|
370
391
|
const result = await prefetch(opts);
|
|
371
|
-
process.
|
|
392
|
+
process.exitCode = result.errors > 0 ? 1 : 0;
|
|
372
393
|
} catch (err) {
|
|
373
394
|
console.error(`prefetch: fatal: ${err.message}`);
|
|
374
|
-
process.
|
|
395
|
+
process.exitCode = 2;
|
|
375
396
|
}
|
|
376
397
|
}
|
|
377
398
|
|