@blamejs/exceptd-skills 0.15.50 → 0.15.52
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 +10 -0
- package/NOTICE +1 -1
- package/bin/exceptd.js +1 -1
- package/data/_indexes/_meta.json +10 -10
- package/data/_indexes/activity-feed.json +2 -2
- package/data/_indexes/catalog-summaries.json +6 -6
- package/data/_indexes/chains.json +775 -0
- package/data/_indexes/section-offsets.json +25 -25
- package/data/_indexes/token-budget.json +9 -9
- package/data/attack-techniques.json +30 -9
- package/data/cve-catalog.json +455 -7
- package/data/cwe-catalog.json +15 -5
- package/data/framework-control-gaps.json +29 -10
- package/data/playbooks/sbom.json +150 -1
- package/data/zeroday-lessons.json +251 -1
- package/lib/prefetch.js +2 -2
- package/manifest.json +45 -45
- package/package.json +1 -1
- package/sbom.cdx.json +59 -44
- package/scripts/check-changelog-extract.js +158 -0
- package/scripts/check-test-coverage.README.md +1 -1
- package/scripts/check-test-coverage.js +3 -3
- package/scripts/check-version-tags.js +31 -2
- package/scripts/predeploy.js +13 -0
- package/scripts/release.js +5 -0
- package/scripts/verify-shipped-tarball.js +1 -1
- package/skills/supply-chain-integrity/skill.md +2 -0
package/sbom.cdx.json
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"bomFormat": "CycloneDX",
|
|
3
3
|
"specVersion": "1.6",
|
|
4
|
-
"serialNumber": "urn:uuid:
|
|
4
|
+
"serialNumber": "urn:uuid:7ed2b616-e18e-40a0-bf4e-b08af006675e",
|
|
5
5
|
"version": 1,
|
|
6
6
|
"metadata": {
|
|
7
|
-
"timestamp": "
|
|
7
|
+
"timestamp": "2093-06-04T14:26:30.000Z",
|
|
8
8
|
"tools": [
|
|
9
9
|
{
|
|
10
10
|
"vendor": "blamejs",
|
|
11
11
|
"name": "scripts/refresh-sbom.js",
|
|
12
|
-
"version": "0.15.
|
|
12
|
+
"version": "0.15.52"
|
|
13
13
|
}
|
|
14
14
|
],
|
|
15
15
|
"component": {
|
|
16
|
-
"bom-ref": "pkg:npm/@blamejs/exceptd-skills@0.15.
|
|
16
|
+
"bom-ref": "pkg:npm/@blamejs/exceptd-skills@0.15.52",
|
|
17
17
|
"type": "application",
|
|
18
18
|
"name": "@blamejs/exceptd-skills",
|
|
19
|
-
"version": "0.15.
|
|
19
|
+
"version": "0.15.52",
|
|
20
20
|
"description": "AI security skills grounded in mid-2026 threat reality, not stale framework documentation. 42 skills, 11 catalogs (427 CVEs / 173 CWEs / 805 ATT&CK + ICS / 170 ATLAS / 468 D3FEND / 8888 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
|
{
|
|
@@ -25,17 +25,17 @@
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
],
|
|
28
|
-
"purl": "pkg:npm/%40blamejs/exceptd-skills@0.15.
|
|
28
|
+
"purl": "pkg:npm/%40blamejs/exceptd-skills@0.15.52",
|
|
29
29
|
"hashes": [
|
|
30
30
|
{
|
|
31
31
|
"alg": "SHA-256",
|
|
32
|
-
"content": "
|
|
32
|
+
"content": "926aefd5a4e9417e02388bf3fba187507cb15c4d562d1b73383978b440923626"
|
|
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.15.
|
|
38
|
+
"url": "https://www.npmjs.com/package/@blamejs/exceptd-skills/v/0.15.52"
|
|
39
39
|
},
|
|
40
40
|
{
|
|
41
41
|
"type": "vcs",
|
|
@@ -116,11 +116,11 @@
|
|
|
116
116
|
"hashes": [
|
|
117
117
|
{
|
|
118
118
|
"alg": "SHA-256",
|
|
119
|
-
"content": "
|
|
119
|
+
"content": "efc1b8a8b88115b6f3c39a68fd41633df8bc7992f72ec4683e3c1760323df012"
|
|
120
120
|
},
|
|
121
121
|
{
|
|
122
122
|
"alg": "SHA3-512",
|
|
123
|
-
"content": "
|
|
123
|
+
"content": "8191b5774e3f765a359738f31c9f1dae30e1fae21e9d65c6dd2abc4531823c08761b037bb808ab6cd57815145b15057aeefef9a103e9222e19502315e13bcf7b"
|
|
124
124
|
}
|
|
125
125
|
]
|
|
126
126
|
},
|
|
@@ -161,11 +161,11 @@
|
|
|
161
161
|
"hashes": [
|
|
162
162
|
{
|
|
163
163
|
"alg": "SHA-256",
|
|
164
|
-
"content": "
|
|
164
|
+
"content": "34dd0f6eab14ad5b2c974b81e7016c841c7849f862b58fcc94fd499a5ed2b8a9"
|
|
165
165
|
},
|
|
166
166
|
{
|
|
167
167
|
"alg": "SHA3-512",
|
|
168
|
-
"content": "
|
|
168
|
+
"content": "7f16c6f48c03f0fa2858edeab6388a9c2b6a952e7d40186bac5748e2ca90d4b3b085422416640eff8c02ead3dd8e19071c696c8cf9f8f2dcc13e8b9d725fc36c"
|
|
169
169
|
}
|
|
170
170
|
]
|
|
171
171
|
},
|
|
@@ -281,11 +281,11 @@
|
|
|
281
281
|
"hashes": [
|
|
282
282
|
{
|
|
283
283
|
"alg": "SHA-256",
|
|
284
|
-
"content": "
|
|
284
|
+
"content": "d0ca92931278d72229edb9f2526157f2832df1d97cd7e8440a0a841f8b1c664d"
|
|
285
285
|
},
|
|
286
286
|
{
|
|
287
287
|
"alg": "SHA3-512",
|
|
288
|
-
"content": "
|
|
288
|
+
"content": "93377a5256af65b898ffda3586e081e64f6bb3d820305b6995dd37f022857a66e929bec0c167fed5aed5c5b18a8dec7e7e4214b3d5cfacea32e93dc8065f01c0"
|
|
289
289
|
}
|
|
290
290
|
]
|
|
291
291
|
},
|
|
@@ -311,11 +311,11 @@
|
|
|
311
311
|
"hashes": [
|
|
312
312
|
{
|
|
313
313
|
"alg": "SHA-256",
|
|
314
|
-
"content": "
|
|
314
|
+
"content": "318bf8e9c5aee1d0a4a1dc37c4b211f2fbc937bf332a401a22483cc7d0547252"
|
|
315
315
|
},
|
|
316
316
|
{
|
|
317
317
|
"alg": "SHA3-512",
|
|
318
|
-
"content": "
|
|
318
|
+
"content": "d929874af7a8091416f2adc116fcfe98383325c83ece819a3e07320e29b4d8645b27256a662a6fff3267156ff70cfe2f53d7b0a82b5f249902c495f5d668b962"
|
|
319
319
|
}
|
|
320
320
|
]
|
|
321
321
|
},
|
|
@@ -326,11 +326,11 @@
|
|
|
326
326
|
"hashes": [
|
|
327
327
|
{
|
|
328
328
|
"alg": "SHA-256",
|
|
329
|
-
"content": "
|
|
329
|
+
"content": "cb5e305b5488a2a02e177f10e913d22f602d6016109f152903093e9614e0b470"
|
|
330
330
|
},
|
|
331
331
|
{
|
|
332
332
|
"alg": "SHA3-512",
|
|
333
|
-
"content": "
|
|
333
|
+
"content": "a7abc804efd7dfd46056a857ee13e19b8d99cdd46db7390845f4036b1e4b97bce0e00ec3f677322716f6c74b071340561fdf8cfda9b2f5ff3b502353eaf4d135"
|
|
334
334
|
}
|
|
335
335
|
]
|
|
336
336
|
},
|
|
@@ -341,11 +341,11 @@
|
|
|
341
341
|
"hashes": [
|
|
342
342
|
{
|
|
343
343
|
"alg": "SHA-256",
|
|
344
|
-
"content": "
|
|
344
|
+
"content": "b0e4d8f90b655b2b35b1e91c682ee66f2aa51ae5d38efb14f0e1b77f75ec5f7b"
|
|
345
345
|
},
|
|
346
346
|
{
|
|
347
347
|
"alg": "SHA3-512",
|
|
348
|
-
"content": "
|
|
348
|
+
"content": "ac9ca3b420093c62dd5a53b0349f1d5e9f10f90c958d0ab921654bd0bcf674d3aef478fc5bc3d359a224b03cd26b32578fe162f55ba2423c37c0061aaa80c862"
|
|
349
349
|
}
|
|
350
350
|
]
|
|
351
351
|
},
|
|
@@ -401,11 +401,11 @@
|
|
|
401
401
|
"hashes": [
|
|
402
402
|
{
|
|
403
403
|
"alg": "SHA-256",
|
|
404
|
-
"content": "
|
|
404
|
+
"content": "49cfbcaf0f27662db7e12340839c29f05d4ae31bc255dc9fa49ad1b4a45d0fa3"
|
|
405
405
|
},
|
|
406
406
|
{
|
|
407
407
|
"alg": "SHA3-512",
|
|
408
|
-
"content": "
|
|
408
|
+
"content": "677e753faee573812cfcb3b33e351b36430b38d36c409149a471b0561139ba1dccc23f1a15c9b836f0c5c7384abaa5dc7a56b3f0d8c747fc7d5f216f03911028"
|
|
409
409
|
}
|
|
410
410
|
]
|
|
411
411
|
},
|
|
@@ -731,11 +731,11 @@
|
|
|
731
731
|
"hashes": [
|
|
732
732
|
{
|
|
733
733
|
"alg": "SHA-256",
|
|
734
|
-
"content": "
|
|
734
|
+
"content": "1e2f307a7207a260f8c9ab23f95e8eebb4950349d79eceb2af5b8c4df2b37dc4"
|
|
735
735
|
},
|
|
736
736
|
{
|
|
737
737
|
"alg": "SHA3-512",
|
|
738
|
-
"content": "
|
|
738
|
+
"content": "9298e037ccac473c5ac994e5ef4804d93a7abfbb3fa14c836a97c4503de2e9d10b1ad8a2f371115bf17c1f63851fd252214cb183c8655f1f4150b0e3a2dc9248"
|
|
739
739
|
}
|
|
740
740
|
]
|
|
741
741
|
},
|
|
@@ -806,11 +806,11 @@
|
|
|
806
806
|
"hashes": [
|
|
807
807
|
{
|
|
808
808
|
"alg": "SHA-256",
|
|
809
|
-
"content": "
|
|
809
|
+
"content": "c7419ef8265a8385ab29e37e3f3237f120dd2fa448692f9dc1aa2fd79339fc76"
|
|
810
810
|
},
|
|
811
811
|
{
|
|
812
812
|
"alg": "SHA3-512",
|
|
813
|
-
"content": "
|
|
813
|
+
"content": "7aea30cb368c94ff5e9fa3d1d6e60a71f81a1c36d5b358310b372b99d1e448ff5c19c34f2fb33316841906e3f988bc305a8c9c158d9fb33c62b91538a56fcde5"
|
|
814
814
|
}
|
|
815
815
|
]
|
|
816
816
|
},
|
|
@@ -1331,11 +1331,11 @@
|
|
|
1331
1331
|
"hashes": [
|
|
1332
1332
|
{
|
|
1333
1333
|
"alg": "SHA-256",
|
|
1334
|
-
"content": "
|
|
1334
|
+
"content": "bd757082645bbe88e0d03e5d533ee7c5c8e2ea922e31e13e28a336f69cd3a756"
|
|
1335
1335
|
},
|
|
1336
1336
|
{
|
|
1337
1337
|
"alg": "SHA3-512",
|
|
1338
|
-
"content": "
|
|
1338
|
+
"content": "85c3844a6562a1df8b6b71e54c6fc4788ad62ad918856ceb441c443625032f547c30e39618631f397c49c02708af4e1420bf2ee78885c23248f9c7f8885cef2f"
|
|
1339
1339
|
}
|
|
1340
1340
|
]
|
|
1341
1341
|
},
|
|
@@ -1751,11 +1751,11 @@
|
|
|
1751
1751
|
"hashes": [
|
|
1752
1752
|
{
|
|
1753
1753
|
"alg": "SHA-256",
|
|
1754
|
-
"content": "
|
|
1754
|
+
"content": "e5f2d2a803f6972ef1759593ddbec1e3badc297b8f83e667fedeaf4b68fd9819"
|
|
1755
1755
|
},
|
|
1756
1756
|
{
|
|
1757
1757
|
"alg": "SHA3-512",
|
|
1758
|
-
"content": "
|
|
1758
|
+
"content": "3388f49da3b6e08d553623d467dc0114c41c3ff39cc780751867908fcc0ba503f8236f1e740700befc5b862e471196a20ebe9590c2200c81e62db12bebe63dfd"
|
|
1759
1759
|
}
|
|
1760
1760
|
]
|
|
1761
1761
|
},
|
|
@@ -2179,6 +2179,21 @@
|
|
|
2179
2179
|
}
|
|
2180
2180
|
]
|
|
2181
2181
|
},
|
|
2182
|
+
{
|
|
2183
|
+
"bom-ref": "file:scripts/check-changelog-extract.js",
|
|
2184
|
+
"type": "file",
|
|
2185
|
+
"name": "scripts/check-changelog-extract.js",
|
|
2186
|
+
"hashes": [
|
|
2187
|
+
{
|
|
2188
|
+
"alg": "SHA-256",
|
|
2189
|
+
"content": "eeee0adf320e7aefee1ebcfa2283c021ca43c80938aafe1873abcf21b927686d"
|
|
2190
|
+
},
|
|
2191
|
+
{
|
|
2192
|
+
"alg": "SHA3-512",
|
|
2193
|
+
"content": "710b6d5b6d1a60c1934a2bd42c7a64aab24150a14d74368d2f5eea27bd568708ae0daa2ca3c7fdee286faec577a926e5cfbaa1b6740ad3886fc5bf3f7ed026c9"
|
|
2194
|
+
}
|
|
2195
|
+
]
|
|
2196
|
+
},
|
|
2182
2197
|
{
|
|
2183
2198
|
"bom-ref": "file:scripts/check-codebase-patterns-currency.js",
|
|
2184
2199
|
"type": "file",
|
|
@@ -2261,11 +2276,11 @@
|
|
|
2261
2276
|
"hashes": [
|
|
2262
2277
|
{
|
|
2263
2278
|
"alg": "SHA-256",
|
|
2264
|
-
"content": "
|
|
2279
|
+
"content": "d43e59b96a252c1228ca709e3753582fc02d4c32d2766b247a69ee78c0a18e28"
|
|
2265
2280
|
},
|
|
2266
2281
|
{
|
|
2267
2282
|
"alg": "SHA3-512",
|
|
2268
|
-
"content": "
|
|
2283
|
+
"content": "994eea61e1d84f9f8c9ab77085f855d167b772e31f07ad49f5b4094d7c939f7854595f37902c4a0f360c2ce3ec7754e9db58eee0d7c865dfa2ea20d0e33885e3"
|
|
2269
2284
|
}
|
|
2270
2285
|
]
|
|
2271
2286
|
},
|
|
@@ -2276,11 +2291,11 @@
|
|
|
2276
2291
|
"hashes": [
|
|
2277
2292
|
{
|
|
2278
2293
|
"alg": "SHA-256",
|
|
2279
|
-
"content": "
|
|
2294
|
+
"content": "906735e02f452962cc221e129a9d7d08cb645f0b79b48168fea62038c35f6b71"
|
|
2280
2295
|
},
|
|
2281
2296
|
{
|
|
2282
2297
|
"alg": "SHA3-512",
|
|
2283
|
-
"content": "
|
|
2298
|
+
"content": "09590d90712ecec967caf198f33f0ea62b24152dcfda51b1bd0422e64add9cf2f97a5873310d11d654846b83dd92c9c82ca014973ca51888d14607e2b3092e0f"
|
|
2284
2299
|
}
|
|
2285
2300
|
]
|
|
2286
2301
|
},
|
|
@@ -2291,11 +2306,11 @@
|
|
|
2291
2306
|
"hashes": [
|
|
2292
2307
|
{
|
|
2293
2308
|
"alg": "SHA-256",
|
|
2294
|
-
"content": "
|
|
2309
|
+
"content": "2306d6560552040baf57e9a552c14f2344967338babdc1a4fff8e3b4fc0d4d9b"
|
|
2295
2310
|
},
|
|
2296
2311
|
{
|
|
2297
2312
|
"alg": "SHA3-512",
|
|
2298
|
-
"content": "
|
|
2313
|
+
"content": "12d2a7a167fa40712453f60e5e1f8f57d35a2396e77b40afc38986ddf866984157ed5c312fe6eddb9803df4025e52628d7d636ee123118b64f9eef5b5644cf23"
|
|
2299
2314
|
}
|
|
2300
2315
|
]
|
|
2301
2316
|
},
|
|
@@ -2321,11 +2336,11 @@
|
|
|
2321
2336
|
"hashes": [
|
|
2322
2337
|
{
|
|
2323
2338
|
"alg": "SHA-256",
|
|
2324
|
-
"content": "
|
|
2339
|
+
"content": "3e37c0d93dfc1ff3c6fd5e90bb65417f3ebcabd8435925c4292b4ca86bdd11c2"
|
|
2325
2340
|
},
|
|
2326
2341
|
{
|
|
2327
2342
|
"alg": "SHA3-512",
|
|
2328
|
-
"content": "
|
|
2343
|
+
"content": "8c1be019521e12c483cb5e71ee94e6601497c514b09080c2560ca942bb2979f0b08928866213b13fc882105f6aa6e33901fb7edb8cc7dad9a1f03d8bd8a45ce6"
|
|
2329
2344
|
}
|
|
2330
2345
|
]
|
|
2331
2346
|
},
|
|
@@ -2471,11 +2486,11 @@
|
|
|
2471
2486
|
"hashes": [
|
|
2472
2487
|
{
|
|
2473
2488
|
"alg": "SHA-256",
|
|
2474
|
-
"content": "
|
|
2489
|
+
"content": "f199cc754f202fa994c1769af5e02a4ef995ee02e87221422b00f945b03e99db"
|
|
2475
2490
|
},
|
|
2476
2491
|
{
|
|
2477
2492
|
"alg": "SHA3-512",
|
|
2478
|
-
"content": "
|
|
2493
|
+
"content": "d504c65cda545acda811997487ca8b6fef3ee5b5447b537f502295d5f6f4266cb2ecb743389b04aa2b6118b021af6f2ce9c6f00e44cf60bd7c06b72456a500f8"
|
|
2479
2494
|
}
|
|
2480
2495
|
]
|
|
2481
2496
|
},
|
|
@@ -2531,11 +2546,11 @@
|
|
|
2531
2546
|
"hashes": [
|
|
2532
2547
|
{
|
|
2533
2548
|
"alg": "SHA-256",
|
|
2534
|
-
"content": "
|
|
2549
|
+
"content": "fd04f3e4122b4f3ca7e9a266c763dd4e954710d496da8072f5c2e500a4dbe32c"
|
|
2535
2550
|
},
|
|
2536
2551
|
{
|
|
2537
2552
|
"alg": "SHA3-512",
|
|
2538
|
-
"content": "
|
|
2553
|
+
"content": "9b606b4d5afcc79a7a39650c3f425af0a8abb21f484e70e5469431a0be26b27969108bd51f167cab23b41d6e0ec7d14b8e6d82d597ca6166942605e15fc016c0"
|
|
2539
2554
|
}
|
|
2540
2555
|
]
|
|
2541
2556
|
},
|
|
@@ -3101,11 +3116,11 @@
|
|
|
3101
3116
|
"hashes": [
|
|
3102
3117
|
{
|
|
3103
3118
|
"alg": "SHA-256",
|
|
3104
|
-
"content": "
|
|
3119
|
+
"content": "23d15c234afedec011d9c3e588334132373a22d09ef33cd2d943e479c88fcb43"
|
|
3105
3120
|
},
|
|
3106
3121
|
{
|
|
3107
3122
|
"alg": "SHA3-512",
|
|
3108
|
-
"content": "
|
|
3123
|
+
"content": "d4da986f67c1b537431731f28ac048bdca8bba2269896fe674aecc91d5da99d4963624e62309c60ec4c8a2422e0ed3899290a1f951db0eaf0fe46b43a90ebc74"
|
|
3109
3124
|
}
|
|
3110
3125
|
]
|
|
3111
3126
|
},
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Release-notes extraction + quality gate.
|
|
6
|
+
*
|
|
7
|
+
* The release workflow (.github/workflows/release.yml) publishes the GitHub
|
|
8
|
+
* Release body by awk-extracting the `## <version> ...` CHANGELOG section
|
|
9
|
+
* between the version heading and the next `## ` heading, falling back to a
|
|
10
|
+
* generic "Release of v<version>." line if the extract is empty. This gate
|
|
11
|
+
* runs that SAME extraction locally before tag-push, and additionally lints
|
|
12
|
+
* the extracted notes for operator-facing quality, so a malformed or
|
|
13
|
+
* internal-narrative-laced section fails here rather than shipping as the
|
|
14
|
+
* public release body.
|
|
15
|
+
*
|
|
16
|
+
* Two layers:
|
|
17
|
+
* 1. EXTRACT — the `## <version> — <date>` section exists, is non-empty
|
|
18
|
+
* (won't trigger the workflow's "Release of v…" fallback),
|
|
19
|
+
* the heading version matches package.json, and the heading
|
|
20
|
+
* carries an ISO date.
|
|
21
|
+
* 2. LINT — the extracted body is operator-facing-clean: no internal
|
|
22
|
+
* phase/pass/slice/sweep narrative, no agent-dispatch /
|
|
23
|
+
* conversation residue, no tautological "all tests pass"
|
|
24
|
+
* noise. (Mirrors the operator-facing discipline; the release
|
|
25
|
+
* body is the most public surface there is.)
|
|
26
|
+
*
|
|
27
|
+
* Exit: process.exitCode 0 on pass, 1 on any failure. Functions are exported
|
|
28
|
+
* for fixture-based testing (no subprocess needed).
|
|
29
|
+
*
|
|
30
|
+
* Usage:
|
|
31
|
+
* node scripts/check-changelog-extract.js # uses package.json version
|
|
32
|
+
* node scripts/check-changelog-extract.js <version> # explicit MAJOR.MINOR.PATCH
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
const fs = require('node:fs');
|
|
36
|
+
const path = require('node:path');
|
|
37
|
+
|
|
38
|
+
const ROOT = path.resolve(__dirname, '..');
|
|
39
|
+
const CHANGELOG = path.join(ROOT, 'CHANGELOG.md');
|
|
40
|
+
const PACKAGE_JSON = path.join(ROOT, 'package.json');
|
|
41
|
+
|
|
42
|
+
// Replicates the release.yml awk: capture lines AFTER the `## <version> `
|
|
43
|
+
// heading up to (not including) the next `## ` heading. The trailing space in
|
|
44
|
+
// the heading match mirrors the workflow's `"^## " v " "` so a shorter version
|
|
45
|
+
// heading can't accidentally match a longer one that shares its prefix.
|
|
46
|
+
function extractSection(text, version) {
|
|
47
|
+
const lines = text.split(/\r?\n/);
|
|
48
|
+
const out = [];
|
|
49
|
+
let capturing = false;
|
|
50
|
+
const startRe = new RegExp('^## ' + version.replace(/\./g, '\\.') + ' ');
|
|
51
|
+
for (const ln of lines) {
|
|
52
|
+
if (capturing) {
|
|
53
|
+
if (/^## /.test(ln)) break;
|
|
54
|
+
out.push(ln);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (startRe.test(ln)) capturing = true;
|
|
58
|
+
}
|
|
59
|
+
// Trim leading/trailing blank lines (awk keeps them; the body is the same
|
|
60
|
+
// either way, but trimming makes the non-empty test honest).
|
|
61
|
+
while (out.length && out[0].trim() === '') out.shift();
|
|
62
|
+
while (out.length && out[out.length - 1].trim() === '') out.pop();
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Returns the `## <version> — <date>` heading line for the version, or null.
|
|
67
|
+
function headingLine(text, version) {
|
|
68
|
+
const re = new RegExp('^## ' + version.replace(/\./g, '\\.') + ' ');
|
|
69
|
+
return text.split(/\r?\n/).find((l) => re.test(l)) || null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Operator-facing forbidden patterns. Tight, high-confidence internal-narrative
|
|
73
|
+
// markers only — must not false-positive on legitimate operator prose (e.g. a
|
|
74
|
+
// bare "phase" in "multi-phase attack" is fine; "Phase 9" is the tell). Each
|
|
75
|
+
// entry: { id, re, why }.
|
|
76
|
+
const FORBIDDEN = [
|
|
77
|
+
{ id: 'phase-number', re: /\bphase\s+\d/i, why: 'internal phase number (operators have no roadmap)' },
|
|
78
|
+
{ id: 'pass-number', re: /\b(?:audit|curation|drift|fix|bug)?[- ]?pass\s+\d/i, why: 'internal pass/batch number' },
|
|
79
|
+
{ id: 'slice-number', re: /\bslice\s+\d/i, why: 'internal slice number' },
|
|
80
|
+
{ id: 'sweep-number', re: /\bsweep\s+\d/i, why: 'internal sweep number' },
|
|
81
|
+
{ id: 'tier-letter', re: /\bTier-[ABC]\b/, why: 'internal tier label' },
|
|
82
|
+
{ id: 'agent-dispatch', re: /\b(?:sub-?agent|parallel agent|agent dispatch|fan(?:ned)?[ -]out|multi-agent)\b/i, why: 'implementation detail (agent/parallelization)' },
|
|
83
|
+
{ id: 'conversation-residue', re: /\b(?:as discussed|per your|operator-confirmed|as you (?:noted|requested)|per the conversation|PR feedback:)\b/i, why: 'conversation residue (invisible to the reader)' },
|
|
84
|
+
{ id: 'process-narrative', re: /\b(?:audit-derived|post-phase-\d|as part of the \d|the \d+-gap closure)\b/i, why: 'internal-process narrative' },
|
|
85
|
+
{ id: 'tautological-green', re: /\b(?:all tests (?:pass|passing|green)|CI green|smoke \+ e2e (?:clean|pass)|tests? (?:are )?passing)\b/i, why: 'tautological pass/green claim (noise — the release exists)' },
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
function lintOperatorClean(sectionLines) {
|
|
89
|
+
const findings = [];
|
|
90
|
+
sectionLines.forEach((ln, i) => {
|
|
91
|
+
for (const rule of FORBIDDEN) {
|
|
92
|
+
const m = ln.match(rule.re);
|
|
93
|
+
if (m) findings.push({ rule: rule.id, why: rule.why, line: i + 1, match: m[0], text: ln.trim().slice(0, 100) });
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
return findings;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function readPackageVersion() {
|
|
100
|
+
return JSON.parse(fs.readFileSync(PACKAGE_JSON, 'utf8')).version;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function main() {
|
|
104
|
+
const version = process.argv[2] || readPackageVersion();
|
|
105
|
+
if (!/^\d+\.\d+\.\d+$/.test(version)) {
|
|
106
|
+
console.error('[check-changelog-extract] FAIL: bad version ' + JSON.stringify(version) + ' (expected MAJOR.MINOR.PATCH)');
|
|
107
|
+
process.exitCode = 1;
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
let text;
|
|
112
|
+
try { text = fs.readFileSync(CHANGELOG, 'utf8'); }
|
|
113
|
+
catch (e) {
|
|
114
|
+
console.error('[check-changelog-extract] FAIL: cannot read CHANGELOG.md: ' + (e && e.message || e));
|
|
115
|
+
process.exitCode = 1;
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const heading = headingLine(text, version);
|
|
120
|
+
if (!heading) {
|
|
121
|
+
console.error('[check-changelog-extract] FAIL: no `## ' + version + ' …` heading in CHANGELOG.md — the release workflow extract would be empty and fall back to "Release of v' + version + '."');
|
|
122
|
+
process.exitCode = 1;
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
// Heading must carry an ISO date: `## <version> — YYYY-MM-DD`.
|
|
126
|
+
if (!new RegExp('^## ' + version.replace(/\./g, '\\.') + ' [—-] \\d{4}-\\d{2}-\\d{2}\\s*$').test(heading)) {
|
|
127
|
+
console.error('[check-changelog-extract] FAIL: heading does not match `## ' + version + ' — YYYY-MM-DD`:');
|
|
128
|
+
console.error('[check-changelog-extract] got: ' + JSON.stringify(heading));
|
|
129
|
+
process.exitCode = 1;
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const section = extractSection(text, version);
|
|
134
|
+
if (section.length === 0) {
|
|
135
|
+
console.error('[check-changelog-extract] FAIL: v' + version + ' section is empty — the release body would fall back to the generic "Release of v' + version + '." line.');
|
|
136
|
+
process.exitCode = 1;
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const findings = lintOperatorClean(section);
|
|
141
|
+
if (findings.length > 0) {
|
|
142
|
+
console.error('[check-changelog-extract] FAIL: v' + version + ' release notes carry ' + findings.length + ' operator-facing violation(s):');
|
|
143
|
+
for (const f of findings) {
|
|
144
|
+
console.error(' • [' + f.rule + '] "' + f.match + '" — ' + f.why);
|
|
145
|
+
console.error(' ' + f.text);
|
|
146
|
+
}
|
|
147
|
+
console.error('[check-changelog-extract] The CHANGELOG section IS the public GitHub Release body. Describe the change, not how you arrived at it.');
|
|
148
|
+
process.exitCode = 1;
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
console.log('[check-changelog-extract] OK — v' + version + ' release notes extract cleanly (' + section.length + ' line(s)) and pass the operator-facing lint.');
|
|
153
|
+
process.exitCode = 0;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
module.exports = { extractSection, headingLine, lintOperatorClean, FORBIDDEN };
|
|
157
|
+
|
|
158
|
+
if (require.main === module) main();
|
|
@@ -44,7 +44,7 @@ exports). Missing references become findings.
|
|
|
44
44
|
Changes in these locations are accepted without a covering test:
|
|
45
45
|
|
|
46
46
|
- `*.md` outside `data/`, `.gitignore`, `.npmrc`, `.editorconfig`
|
|
47
|
-
- `CHANGELOG.md` / `README.md` / `CONTRIBUTING.md` / `SECURITY.md` / `LICENSE` / `NOTICE` / `CODE_OF_CONDUCT.md` / `AGENTS.md`
|
|
47
|
+
- `CHANGELOG.md` / `README.md` / `CONTRIBUTING.md` / `SECURITY.md` / `LICENSE` / `NOTICE` / `CODE_OF_CONDUCT.md` / `AGENTS.md`
|
|
48
48
|
- Whitespace-only diffs (detected via `git diff --ignore-all-space --ignore-blank-lines`)
|
|
49
49
|
- Any file under `tests/` (no test-of-tests recursion)
|
|
50
50
|
- `skills/<name>/skill.md` (signature gate already covers content integrity)
|
|
@@ -198,7 +198,7 @@ function readMaybe(p) {
|
|
|
198
198
|
// / .editorconfig are tooling). Edits here never need a regression test.
|
|
199
199
|
const DOCS_ALWAYS_GREEN = new Set([
|
|
200
200
|
"CONTRIBUTING.md", "LICENSE", "NOTICE", "CODE_OF_CONDUCT.md",
|
|
201
|
-
"
|
|
201
|
+
"SUPPORT.md", ".gitignore", ".npmrc", ".editorconfig",
|
|
202
202
|
]);
|
|
203
203
|
|
|
204
204
|
// Operator-facing docs (release notes, install instructions, security
|
|
@@ -497,7 +497,7 @@ function coversCveIoc(corpus, cveId) {
|
|
|
497
497
|
|
|
498
498
|
// --- Class-level lint: ban coincidence-passing notEqual(r.status, 0) --------
|
|
499
499
|
//
|
|
500
|
-
//
|
|
500
|
+
// Anti-coincidence rule: every exit-code assertion must pin the
|
|
501
501
|
// EXACT code. `assert.notEqual(r.status, 0)` silently passes when an
|
|
502
502
|
// unrelated failure produces ANY non-zero exit, hiding the regression the
|
|
503
503
|
// test was meant to catch. This lint walks tests/*.test.js and rejects the
|
|
@@ -635,7 +635,7 @@ function analyze(opts) {
|
|
|
635
635
|
file: f.file,
|
|
636
636
|
kind: "coincidence-assert",
|
|
637
637
|
surface: f.snippet,
|
|
638
|
-
change: `line ${f.line}: pin to exact exit code
|
|
638
|
+
change: `line ${f.line}: pin to exact exit code (anti-coincidence rule). Opt out only with \`// allow-notEqual: <reason>\` on the same line for genuine refusal-pins.`,
|
|
639
639
|
});
|
|
640
640
|
}
|
|
641
641
|
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
|
|
35
35
|
const fs = require("node:fs");
|
|
36
36
|
const path = require("node:path");
|
|
37
|
+
const { execFileSync } = require("node:child_process");
|
|
37
38
|
|
|
38
39
|
const ROOT = path.join(__dirname, "..");
|
|
39
40
|
const BASELINE_PATH = path.join(ROOT, "tests", ".version-tag-baseline.json");
|
|
@@ -64,10 +65,33 @@ const COMMENT_EXEMPT = new Set([
|
|
|
64
65
|
"CHANGELOG.md",
|
|
65
66
|
"lib/version-pins.js",
|
|
66
67
|
"scripts/check-version-tags.js",
|
|
67
|
-
//
|
|
68
|
-
|
|
68
|
+
// The release-notes-extract gate test asserts version-based CHANGELOG
|
|
69
|
+
// extraction + the shorter-vs-longer prefix-collision guard, so its fixtures
|
|
70
|
+
// MUST embed real `## X.Y.Z` headings (e.g. 0.15.5 vs 0.15.50) — load-bearing
|
|
71
|
+
// test data, not sprinkled release tags.
|
|
72
|
+
"tests/check-changelog-extract.test.js",
|
|
69
73
|
]);
|
|
70
74
|
|
|
75
|
+
// Git-ignored files (a contributor's local-only working docs, scratch) are
|
|
76
|
+
// never scanned — the gate enforces on the would-be-shipped surface, with no
|
|
77
|
+
// need to name individual local-only files. Untracked-but-NOT-ignored files
|
|
78
|
+
// ARE still scanned: a new file a contributor is about to commit is exactly
|
|
79
|
+
// what the gate must catch. Computed via `git check-ignore` over the walked set.
|
|
80
|
+
function gitIgnoredSet(relPaths) {
|
|
81
|
+
if (!relPaths.length) return new Set();
|
|
82
|
+
try {
|
|
83
|
+
const out = execFileSync("git", ["check-ignore", "--stdin"], {
|
|
84
|
+
cwd: ROOT, input: relPaths.join("\n"), encoding: "utf8", maxBuffer: 64 * 1024 * 1024,
|
|
85
|
+
});
|
|
86
|
+
return new Set(out.split(/\r?\n/).filter(Boolean));
|
|
87
|
+
} catch (e) {
|
|
88
|
+
// `git check-ignore --stdin` exits 1 when NO path is ignored (not an
|
|
89
|
+
// error); any paths it did match are on stdout. Absent that, none ignored.
|
|
90
|
+
const out = e && e.stdout ? String(e.stdout) : "";
|
|
91
|
+
return new Set(out.split(/\r?\n/).filter(Boolean));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
71
95
|
// Pattern: project version like `v0.13.22` or bare `0.13.22`. Matches
|
|
72
96
|
// our pre-1.0 release range. External package versions like ATLAS
|
|
73
97
|
// `v5.6.0` or CycloneDX `1.6` don't match because the major is 0.
|
|
@@ -119,9 +143,14 @@ function countCommentViolations(rel) {
|
|
|
119
143
|
|
|
120
144
|
function scanCurrent() {
|
|
121
145
|
const files = walk(ROOT);
|
|
146
|
+
const ignored = gitIgnoredSet(files);
|
|
122
147
|
const byFile = {};
|
|
123
148
|
const filenameViolations = [];
|
|
124
149
|
for (const rel of files) {
|
|
150
|
+
// Skip git-ignored, local-only files (a contributor's private working notes
|
|
151
|
+
// that `git clone` never ships). Untracked-but-not-ignored files are still
|
|
152
|
+
// scanned — a new file about to be committed is what the gate guards.
|
|
153
|
+
if (ignored.has(rel)) continue;
|
|
125
154
|
if (FILENAME_VERSION_RE.test(rel)) filenameViolations.push(rel);
|
|
126
155
|
const n = countCommentViolations(rel);
|
|
127
156
|
if (n > 0) byFile[rel] = n;
|
package/scripts/predeploy.js
CHANGED
|
@@ -249,6 +249,19 @@ const GATES = [
|
|
|
249
249
|
args: [path.join(ROOT, "scripts", "check-codebase-patterns.js")],
|
|
250
250
|
ciJobName: "Data integrity (catalog + manifest snapshot)",
|
|
251
251
|
},
|
|
252
|
+
{
|
|
253
|
+
// Release-notes extract + quality gate. Runs the same `## <version>`
|
|
254
|
+
// CHANGELOG extraction the release workflow publishes as the GitHub
|
|
255
|
+
// Release body, and lints it for operator-facing quality (no internal
|
|
256
|
+
// phase/pass/slice narrative, no agent-dispatch / conversation residue,
|
|
257
|
+
// no tautological green claims). A malformed or internal-narrative section
|
|
258
|
+
// fails here rather than shipping as the public release body / falling
|
|
259
|
+
// back to the generic "Release of v<version>." line.
|
|
260
|
+
name: "Release-notes extract + operator-facing lint (CHANGELOG section)",
|
|
261
|
+
command: process.execPath,
|
|
262
|
+
args: [path.join(ROOT, "scripts", "check-changelog-extract.js")],
|
|
263
|
+
ciJobName: "Data integrity (catalog + manifest snapshot)",
|
|
264
|
+
},
|
|
252
265
|
];
|
|
253
266
|
|
|
254
267
|
function runGate(gate) {
|
package/scripts/release.js
CHANGED
|
@@ -254,6 +254,11 @@ function cmdPrepare(opts) {
|
|
|
254
254
|
process.exit(2);
|
|
255
255
|
}
|
|
256
256
|
|
|
257
|
+
// The `## <next>` heading exists; confirm the section extracts cleanly and
|
|
258
|
+
// passes the operator-facing lint (the release workflow publishes it verbatim
|
|
259
|
+
// as the GitHub Release body). Fail fast here rather than at the gates phase.
|
|
260
|
+
_run("node", ["scripts/check-changelog-extract.js", next]);
|
|
261
|
+
|
|
257
262
|
_writeJsonVersion("package.json", next);
|
|
258
263
|
_writeJsonVersion("manifest.json", next);
|
|
259
264
|
_ok("bumped package.json + manifest.json → " + next);
|
|
@@ -344,7 +344,7 @@ try {
|
|
|
344
344
|
// itself report 0/38 on any tree where line-ending normalization
|
|
345
345
|
// touched the source between sign and pack — a Windows contributor
|
|
346
346
|
// with `core.autocrlf=true`, or a tool like Prettier between sign and
|
|
347
|
-
// pack.
|
|
347
|
+
// pack. This is the recurring CRLF/line-ending-bypass class.
|
|
348
348
|
const rawContent = fs.readFileSync(skillPath);
|
|
349
349
|
const normalizedContent = normalizeSkillBytes(rawContent);
|
|
350
350
|
const ok = crypto.verify(null, normalizedContent, pubKey, Buffer.from(s.signature, "base64"));
|
|
@@ -85,6 +85,8 @@ The defining incidents driving this expansion:
|
|
|
85
85
|
- **Typosquat campaigns target the MCP, Hugging Face, npm `@modelcontextprotocol/*`, and PyPI ML namespaces.** The MITRE ATLAS technique AML.T0010 (ML Supply Chain Compromise) is the umbrella class; AML.T0018 (compromised model weight) is the specific artifact.
|
|
86
86
|
- **The XZ Utils backdoor (2024, CVE-2024-3094)** remains the canonical example of a maintainer-position long-game supply-chain compromise that no SBOM-only program detects. The defense is in-toto attestation chain plus reproducible builds plus maintainer-key transparency — none of which are mandated by current compliance frameworks.
|
|
87
87
|
|
|
88
|
+
**Capability surface is a CVE-independent screening lens.** Beyond matching dependencies against catalogued CVEs, classify every package by the capabilities it actually exercises — network egress, filesystem write, shell/process spawn, environment and credential access, dynamic `eval`, install-lifecycle scripts, telemetry, and native-binary builds. A package with zero catalogued CVEs can still carry the supply-chain delivery primitive: an install-script that spawns a shell, opens the network, and reads environment variables is the credential-harvesting shape shared by the node-ipc protestware wiper (CVE-2022-23812), the TrapDoor cross-ecosystem stealer, the MOIKA dependency-confusion campaign, and the XZ backdoor — independent of whether any of them is yet in a vulnerability feed. Two deltas matter most: a capability surface disproportionate to a package's stated function (a date-formatting utility that opens the network and reads env), and a capability *gained across a version bump* the dependent never opted into when it first selected the package — the in-the-wild signature of a maintainer-account takeover or protestware injection before any CVE is assigned. Build-tooling and native-addon packages (node-gyp, esbuild, sharp, prebuild-install) legitimately carry install-script and native-binary capability, so treat the lens as a high-recall screen that routes to adjudication against publisher provenance and changelog evidence — not an auto-verdict.
|
|
89
|
+
|
|
88
90
|
Mid-2026 baseline expectations have shifted to: SLSA Build L3 minimum on every release pipeline; in-toto attestations for every step in the build graph; Sigstore keyless signing (cosign + Fulcio + Rekor) for container images, packages, and model weights; CycloneDX 1.6 or SPDX 3.0 SBOM at build time and re-verification at deploy time; CSAF 2.0 VEX statements published by vendors and consumed by operators to filter false-positive vulnerability noise. Adoption is uneven — security-mature orgs and open-source foundations (Kubernetes, Sigstore, Linux Foundation projects) are at or near this baseline; most enterprise pipelines are between SLSA L1 and L2 with no attestation chain and no VEX consumption.
|
|
89
91
|
|
|
90
92
|
This project itself uses Ed25519 (RFC 8032) signing for skill integrity (`lib/sign.js`, `lib/verify.js`, public key at `keys/public.pem`). That is the smallest-scale living example of the broader pattern this skill defines: artifacts are signed by an identified key, signatures are verified before load, tampered artifacts are rejected, and the signing key is operationally separated from the artifact registry. The same pattern scales to every layer of the build pipeline — exceptd is just the scoped-to-one-repo version.
|