@blamejs/exceptd-skills 0.15.49 → 0.15.50
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 +6 -0
- package/bin/exceptd.js +15 -4
- package/data/_indexes/_meta.json +2 -2
- package/lib/collectors/scan-excludes.js +1 -1
- package/lib/playbook-runner.js +15 -3
- package/lib/verify.js +1 -0
- package/manifest.json +44 -44
- package/package.json +1 -1
- package/sbom.cdx.json +86 -31
- package/scripts/check-codebase-patterns.js +41 -0
- package/scripts/predeploy.js +1 -1
- package/scripts/release.js +16 -0
- package/vendor/blamejs/README.md +1 -0
- package/vendor/blamejs/_PROVENANCE.json +16 -0
- package/vendor/blamejs/codepoint-class.js +262 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.15.50 — 2026-05-30
|
|
4
|
+
|
|
5
|
+
Hardening: `--operator` validation and the operator-text sanitizer now classify and strip Unicode threat codepoints — Trojan-Source bidirectional overrides (CVE-2021-42574), zero-width/invisible marks, C0 controls, and null — through a shared vendored codepoint-threat table, and the `--operator` rejection now names the specific codepoint family (for example "bidirectional-override codepoint") instead of a generic message. Unicode General Category C remains the reject/strip backstop, so the broader control / private-use / unassigned set is still refused.
|
|
6
|
+
|
|
7
|
+
Internal: a new codebase-pattern gate class, `bidi-codepoint-literal`, blocks raw bidi-override / zero-width / null codepoints embedded literally in source (invisible-in-review code reordering — the Trojan-Source class); source must escape them or route through the shared table.
|
|
8
|
+
|
|
3
9
|
## 0.15.49 — 2026-05-30
|
|
4
10
|
|
|
5
11
|
Internal: a new predeploy gate, `scripts/check-codebase-patterns.js`, enforces code-shape bug classes that recurred across releases. It blocks a library-callable function that writes to stdout and then calls `process.exit()` (which truncates buffered output when the stream is piped — the class the v0.15.47 validate-cves fix addressed) and a stale or reason-less `// allow:` suppression marker, and warns on dynamic `RegExp` construction. The flagged `process.exit` sites across the catalog, playbook, package, and vendor validators were converted to the flush-safe `safeExit` form, and the dynamic-`RegExp` sites carry inline justification markers. A companion advisory, wired into the release `prepare` step, flags when the upstream pattern catalog grows a class exceptd hasn't triaged. No change to the shipped CLI surface, catalogs, or skills.
|
package/bin/exceptd.js
CHANGED
|
@@ -63,6 +63,7 @@ const PKG_ROOT = path.resolve(__dirname, "..");
|
|
|
63
63
|
const { EXIT_CODES, listExitCodes } = require(path.join(PKG_ROOT, "lib", "exit-codes.js"));
|
|
64
64
|
const { validateIdComponent } = require(path.join(PKG_ROOT, "lib", "id-validation.js"));
|
|
65
65
|
const { suggestFlag, flagsFor, VERB_FLAG_ALLOWLIST } = require(path.join(PKG_ROOT, "lib", "flag-suggest.js"));
|
|
66
|
+
const codepointClass = require(path.join(PKG_ROOT, "vendor", "blamejs", "codepoint-class.js"));
|
|
66
67
|
|
|
67
68
|
// Union of every flag known to ANY verb. A flag that is valid somewhere but
|
|
68
69
|
// not on the active verb (e.g. `--csaf-status` on `brief`) is cross-verb
|
|
@@ -1524,6 +1525,7 @@ function dispatchPlaybook(cmd, argv) {
|
|
|
1524
1525
|
// Cc / Cf / Co / Cn — bidi overrides (U+202E "RTL OVERRIDE"),
|
|
1525
1526
|
// zero-width joiners (U+200B-D), invisible format chars, private-use
|
|
1526
1527
|
// codepoints, unassigned codepoints. An operator string like
|
|
1528
|
+
// allow:bidi-codepoint-literal — illustrative bidi-forgery example in the --operator reject-path doc comment
|
|
1527
1529
|
// "aliceevilbob" renders as "alicebobevila" in any UI that respects
|
|
1528
1530
|
// bidi — a forgery surface where the attested name looks like Bob but the
|
|
1529
1531
|
// bytes are Alice. Reject anything outside a positive allowlist of
|
|
@@ -1552,18 +1554,27 @@ function dispatchPlaybook(cmd, argv) {
|
|
|
1552
1554
|
);
|
|
1553
1555
|
}
|
|
1554
1556
|
if (/\p{C}/u.test(normalized)) {
|
|
1555
|
-
//
|
|
1556
|
-
//
|
|
1557
|
+
// \p{C} (Cc/Cf/Cs/Co/Cn) is the reject gate — it is strictly broader
|
|
1558
|
+
// than the named family regexes (bidi / C0-control / zero-width / null),
|
|
1559
|
+
// so it stays the backstop and catches the divergent remainder the
|
|
1560
|
+
// family tables miss (U+007F, U+0080-009F, private-use, unassigned).
|
|
1561
|
+
// The vendored codepoint tables only CLASSIFY the first offending
|
|
1562
|
+
// codepoint into a human family name for the hint.
|
|
1557
1563
|
let offending = "";
|
|
1564
|
+
let family = "control / format / private-use / unassigned codepoint";
|
|
1558
1565
|
for (const cp of normalized) {
|
|
1559
1566
|
if (/\p{C}/u.test(cp)) {
|
|
1560
1567
|
offending = "U+" + cp.codePointAt(0).toString(16).toUpperCase().padStart(4, "0");
|
|
1568
|
+
if (codepointClass.BIDI_RE.test(cp)) family = "bidirectional-override codepoint";
|
|
1569
|
+
else if (codepointClass.ZERO_WIDTH_RE.test(cp)) family = "zero-width / invisible codepoint";
|
|
1570
|
+
else if (cp === codepointClass.NULL_BYTE) family = "null byte";
|
|
1571
|
+
else if (codepointClass.C0_CTRL_RE.test(cp)) family = "C0 control character";
|
|
1561
1572
|
break;
|
|
1562
1573
|
}
|
|
1563
1574
|
}
|
|
1564
1575
|
return emitError(
|
|
1565
|
-
`${cmd}: --operator contains a Unicode
|
|
1566
|
-
{ verb: cmd, provided_length: args.operator.length, offending_codepoint: offending },
|
|
1576
|
+
`${cmd}: --operator contains a Unicode ${family} (${offending}). Bidi overrides, zero-width joiners, and format marks corrupt attestation rendering and enable name-forgery. Use printable identifiers only.`,
|
|
1577
|
+
{ verb: cmd, provided_length: args.operator.length, offending_codepoint: offending, offending_family: family },
|
|
1567
1578
|
pretty
|
|
1568
1579
|
);
|
|
1569
1580
|
}
|
package/data/_indexes/_meta.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema_version": "1.1.0",
|
|
3
|
-
"generated_at": "2026-05-
|
|
3
|
+
"generated_at": "2026-05-30T22:44:21.522Z",
|
|
4
4
|
"generator": "scripts/build-indexes.js",
|
|
5
5
|
"source_count": 54,
|
|
6
6
|
"source_hashes": {
|
|
7
|
-
"manifest.json": "
|
|
7
|
+
"manifest.json": "33da1072778152239ab47e8b4ef930f702678299bfa641e297a233dc9022dbfa",
|
|
8
8
|
"data/atlas-ttps.json": "878b4a08bb73c8d20396d85cf433a88f2bc5e7a8cbf7f6ab773ce7ede0a11251",
|
|
9
9
|
"data/attack-techniques.json": "84fad74c8497cab922ed64b814752f54aa4620c2a938cb06642ff1510e1c5cb3",
|
|
10
10
|
"data/cve-catalog.json": "7a5f4e31401505e53330cdc4b54b39f8a8b04459d6b9411676d291c583ae535f",
|
|
@@ -189,7 +189,7 @@ function buildEvidenceLocations(hits) {
|
|
|
189
189
|
const uri = raw.replace(/\\/g, "/");
|
|
190
190
|
const line = Number(h.line);
|
|
191
191
|
const hasLine = Number.isInteger(line) && line > 0;
|
|
192
|
-
const key = hasLine ? `${uri}
|
|
192
|
+
const key = hasLine ? `${uri}\u0000${line}` : uri;
|
|
193
193
|
if (seen.has(key)) continue;
|
|
194
194
|
seen.add(key);
|
|
195
195
|
out.push(hasLine ? { uri, startLine: line } : { uri });
|
package/lib/playbook-runner.js
CHANGED
|
@@ -48,6 +48,7 @@ const path = require('path');
|
|
|
48
48
|
const os = require('os');
|
|
49
49
|
const crypto = require('crypto');
|
|
50
50
|
const scoring = require('./scoring');
|
|
51
|
+
const codepointClass = require('../vendor/blamejs/codepoint-class.js');
|
|
51
52
|
|
|
52
53
|
// cross-ref-api wraps catalog reads. If cve-catalog.json is corrupt
|
|
53
54
|
// JSON, cross-ref-api's loadCatalog (post-v0.12.14) catches the parse
|
|
@@ -2184,9 +2185,20 @@ function sanitizeOperatorText(s) {
|
|
|
2184
2185
|
let normalised;
|
|
2185
2186
|
try { normalised = s.normalize('NFC'); }
|
|
2186
2187
|
catch { return null; }
|
|
2187
|
-
//
|
|
2188
|
-
//
|
|
2189
|
-
|
|
2188
|
+
// Two-pass strip. First remove the named threat families (bidi-override /
|
|
2189
|
+
// C0-control / zero-width / null) via the shared vendored codepoint tables,
|
|
2190
|
+
// so the family vocabulary has a single source of truth. Then strip any
|
|
2191
|
+
// remaining General Category C codepoint: \p{C} (Cc/Cf/Cs/Co/Cn) is the
|
|
2192
|
+
// backstop — it is strictly broader than the family union (also catches
|
|
2193
|
+
// U+007F, U+0080-009F, private-use, unassigned), so the family pass is a
|
|
2194
|
+
// documented-intent superset removal and the result is identical to the
|
|
2195
|
+
// single \p{C} strip.
|
|
2196
|
+
const familyStripped = normalised
|
|
2197
|
+
.replace(codepointClass.BIDI_RE_G, '')
|
|
2198
|
+
.replace(codepointClass.C0_CTRL_RE_G, '')
|
|
2199
|
+
.replace(codepointClass.ZW_RE_G, '')
|
|
2200
|
+
.replace(codepointClass.NULL_RE_G, '');
|
|
2201
|
+
const stripped = familyStripped.replace(/\p{C}/gu, '');
|
|
2190
2202
|
const trimmed = stripped.trim();
|
|
2191
2203
|
if (trimmed.length === 0) return null;
|
|
2192
2204
|
// Cap at 256 codepoints (Array.from counts codepoints, not UTF-16 code
|
package/lib/verify.js
CHANGED
|
@@ -648,6 +648,7 @@ function checkExpectedFingerprint(liveFp, pinPath) {
|
|
|
648
648
|
// Route through the shared loader so a BOM-prefixed pin file
|
|
649
649
|
// (Notepad with files.encoding=utf8bom) is tolerated identically across
|
|
650
650
|
// every verify site. Pre-fix the verbatim split-trim-find produced a
|
|
651
|
+
// allow:bidi-codepoint-literal — illustrative BOM-prefixed first-line in the pin-loader doc comment
|
|
651
652
|
// first-line of "SHA256:..." (with leading BOM) that would never equal
|
|
652
653
|
// a live fingerprint.
|
|
653
654
|
const firstLine = loadExpectedFingerprintFirstLine(p) || '';
|
package/manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "exceptd-security",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.50",
|
|
4
4
|
"description": "AI security skills grounded in mid-2026 threat reality, not stale framework documentation",
|
|
5
5
|
"homepage": "https://exceptd.com",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
],
|
|
54
54
|
"last_threat_review": "2026-05-15",
|
|
55
55
|
"signature": "0H+JfyUVmo/pVFEi5rLENATHjlukPVUqnOWmNPEH77wm8svKGK0aNJ46k6QU5GdHb8c9X9pVJKiuhON6AxDjDw==",
|
|
56
|
-
"signed_at": "2026-05-
|
|
56
|
+
"signed_at": "2026-05-30T22:22:04.669Z",
|
|
57
57
|
"cwe_refs": [
|
|
58
58
|
"CWE-125",
|
|
59
59
|
"CWE-362",
|
|
@@ -123,7 +123,7 @@
|
|
|
123
123
|
],
|
|
124
124
|
"last_threat_review": "2026-05-17",
|
|
125
125
|
"signature": "PHwHEsoy7ctBYOtlAfAdCDVfsq2Bpk9+qESSF+5dVkDcez2zp2v9Ihsv2vqMEs3QxMndyQ+t7NVezyt5VamSCg==",
|
|
126
|
-
"signed_at": "2026-05-
|
|
126
|
+
"signed_at": "2026-05-30T22:22:04.671Z",
|
|
127
127
|
"cwe_refs": [
|
|
128
128
|
"CWE-1039",
|
|
129
129
|
"CWE-1426",
|
|
@@ -196,7 +196,7 @@
|
|
|
196
196
|
],
|
|
197
197
|
"last_threat_review": "2026-05-17",
|
|
198
198
|
"signature": "dD4p7lcRtMyfITOncqLkpOeMy6x6gM0V7UlWHgLEdcxqODb1s75ar1cBtTqDWPbMv6ZAzVo2HJLDK1hVjjU2AQ==",
|
|
199
|
-
"signed_at": "2026-05-
|
|
199
|
+
"signed_at": "2026-05-30T22:22:04.671Z",
|
|
200
200
|
"cwe_refs": [
|
|
201
201
|
"CWE-22",
|
|
202
202
|
"CWE-345",
|
|
@@ -248,7 +248,7 @@
|
|
|
248
248
|
"framework_gaps": [],
|
|
249
249
|
"last_threat_review": "2026-05-22",
|
|
250
250
|
"signature": "wsw8Mlr/gyw6S7Iaao9BVHdU5LFPWl8WVymW17Lkq9J1Mui0+fCrTg6UbrsaeE3s7EW3TVgzBuK+8EFd1+H5AA==",
|
|
251
|
-
"signed_at": "2026-05-
|
|
251
|
+
"signed_at": "2026-05-30T22:22:04.672Z"
|
|
252
252
|
},
|
|
253
253
|
{
|
|
254
254
|
"name": "compliance-theater",
|
|
@@ -279,7 +279,7 @@
|
|
|
279
279
|
],
|
|
280
280
|
"last_threat_review": "2026-05-22",
|
|
281
281
|
"signature": "uVTc1QRKOKcIVDajBz+q2egjiEAyOQaDNsvVI2ghj5FD0VvquoUBBE5Naca2FkaZa790EHWCsVZ4hhdaSQs2DQ==",
|
|
282
|
-
"signed_at": "2026-05-
|
|
282
|
+
"signed_at": "2026-05-30T22:22:04.672Z"
|
|
283
283
|
},
|
|
284
284
|
{
|
|
285
285
|
"name": "exploit-scoring",
|
|
@@ -308,7 +308,7 @@
|
|
|
308
308
|
],
|
|
309
309
|
"last_threat_review": "2026-05-18",
|
|
310
310
|
"signature": "QuNpwnZ6HkCEAXTPC/jLbXSmMIc1JnBczqZAAIZmZj8OcEMVnw9mJYAnU3CxaEI7rvbcMkN2uS5E8yUCm/NiAg==",
|
|
311
|
-
"signed_at": "2026-05-
|
|
311
|
+
"signed_at": "2026-05-30T22:22:04.673Z"
|
|
312
312
|
},
|
|
313
313
|
{
|
|
314
314
|
"name": "rag-pipeline-security",
|
|
@@ -345,7 +345,7 @@
|
|
|
345
345
|
],
|
|
346
346
|
"last_threat_review": "2026-05-22",
|
|
347
347
|
"signature": "5rw2i39SxY2WphBbDLEP28wufnbPPE9+PWt54hmaGdwHXr9RLiVt5liL/5xp14sehlVgFsfpR/bg9vy//xV0DA==",
|
|
348
|
-
"signed_at": "2026-05-
|
|
348
|
+
"signed_at": "2026-05-30T22:22:04.673Z",
|
|
349
349
|
"cwe_refs": [
|
|
350
350
|
"CWE-1395",
|
|
351
351
|
"CWE-1426"
|
|
@@ -405,7 +405,7 @@
|
|
|
405
405
|
],
|
|
406
406
|
"last_threat_review": "2026-05-17",
|
|
407
407
|
"signature": "Vqu49nzntFWjn9A/QeJzm7q/2xk/cZJ6HFQKtiNi1zgcxzXKm+MlFdkaLgYHWj5/9HJohxyIDyBJQTvcJ20eDQ==",
|
|
408
|
-
"signed_at": "2026-05-
|
|
408
|
+
"signed_at": "2026-05-30T22:22:04.673Z",
|
|
409
409
|
"d3fend_refs": [
|
|
410
410
|
"D3-CA",
|
|
411
411
|
"D3-CSPP",
|
|
@@ -440,7 +440,7 @@
|
|
|
440
440
|
"framework_gaps": [],
|
|
441
441
|
"last_threat_review": "2026-05-22",
|
|
442
442
|
"signature": "W87VdyVdAxAdcRI6P/8StaV+MS8ZSPKM9HOCK9n/bBO6BM3ZSE3uImVoyJVpAXQlUpUGN+A3lCJZXv64LuxwDg==",
|
|
443
|
-
"signed_at": "2026-05-
|
|
443
|
+
"signed_at": "2026-05-30T22:22:04.674Z",
|
|
444
444
|
"cwe_refs": [
|
|
445
445
|
"CWE-1188"
|
|
446
446
|
],
|
|
@@ -474,7 +474,7 @@
|
|
|
474
474
|
"framework_gaps": [],
|
|
475
475
|
"last_threat_review": "2026-05-18",
|
|
476
476
|
"signature": "wdVX+edeNekpaIldqkhvtraV6DquLvIsKAjuZVwPQYn3l1vS99HXuFxmNsD7UeMlO3qgC6Dysfsto9EnuH0RBg==",
|
|
477
|
-
"signed_at": "2026-05-
|
|
477
|
+
"signed_at": "2026-05-30T22:22:04.674Z",
|
|
478
478
|
"forward_watch": [
|
|
479
479
|
"New AI attack classes as ATLAS v6 publishes",
|
|
480
480
|
"Post-quantum adversary capability timeline",
|
|
@@ -513,7 +513,7 @@
|
|
|
513
513
|
"framework_gaps": [],
|
|
514
514
|
"last_threat_review": "2026-05-01",
|
|
515
515
|
"signature": "b5miTiY0cnxETd2btxorfZBdJKt/fLnQx20sGYUb9zEqGqtm0LMLpghkW68j4/9k48KNyuGMtNWiKTSnodUGBw==",
|
|
516
|
-
"signed_at": "2026-05-
|
|
516
|
+
"signed_at": "2026-05-30T22:22:04.674Z"
|
|
517
517
|
},
|
|
518
518
|
{
|
|
519
519
|
"name": "zeroday-gap-learn",
|
|
@@ -540,7 +540,7 @@
|
|
|
540
540
|
"framework_gaps": [],
|
|
541
541
|
"last_threat_review": "2026-05-18",
|
|
542
542
|
"signature": "xbkip0AQtWQKAu+O6r/gYECNjezS6O9k9xkkJsYbMlr+j8CdqH3p5/0l+GZmDidImRC/DL07GCnKrk9HRR/yDQ==",
|
|
543
|
-
"signed_at": "2026-05-
|
|
543
|
+
"signed_at": "2026-05-30T22:22:04.675Z",
|
|
544
544
|
"forward_watch": [
|
|
545
545
|
"New CISA KEV entries",
|
|
546
546
|
"New ATLAS TTP additions in each ATLAS release",
|
|
@@ -604,7 +604,7 @@
|
|
|
604
604
|
],
|
|
605
605
|
"last_threat_review": "2026-05-22",
|
|
606
606
|
"signature": "li2NnC1oeVIr22ComP5QbcQoh5xpWITuaKpza1s2SsUkH6kGnnt4wFfFAzaC1ORmH9x2cr8hN8kaNANG/eIMBQ==",
|
|
607
|
-
"signed_at": "2026-05-
|
|
607
|
+
"signed_at": "2026-05-30T22:22:04.675Z",
|
|
608
608
|
"cwe_refs": [
|
|
609
609
|
"CWE-327"
|
|
610
610
|
],
|
|
@@ -652,7 +652,7 @@
|
|
|
652
652
|
],
|
|
653
653
|
"last_threat_review": "2026-05-22",
|
|
654
654
|
"signature": "sZHlJ7ueHPdtzVbR+yXQ5+wKgNyjWsa1LKVg9aWTmg/Onl71DvEILMyJiLpPQjseT56Mnr1DMYJE8xOGlffBAw==",
|
|
655
|
-
"signed_at": "2026-05-
|
|
655
|
+
"signed_at": "2026-05-30T22:22:04.675Z"
|
|
656
656
|
},
|
|
657
657
|
{
|
|
658
658
|
"name": "security-maturity-tiers",
|
|
@@ -689,7 +689,7 @@
|
|
|
689
689
|
],
|
|
690
690
|
"last_threat_review": "2026-05-01",
|
|
691
691
|
"signature": "3AwFnEJu6DukPPNep/3SnuPWEuV060fJEQIwThFm7ujmdbFk0/Ii0XwGv1dkvbbK7ymMdOQpp35l4aLONAucDA==",
|
|
692
|
-
"signed_at": "2026-05-
|
|
692
|
+
"signed_at": "2026-05-30T22:22:04.676Z",
|
|
693
693
|
"cwe_refs": [
|
|
694
694
|
"CWE-1188"
|
|
695
695
|
]
|
|
@@ -724,7 +724,7 @@
|
|
|
724
724
|
"framework_gaps": [],
|
|
725
725
|
"last_threat_review": "2026-05-11",
|
|
726
726
|
"signature": "iJWevUBurLvt2v8X+Ch2eHmZkPWpKeAtIpxTIP4MwbUHyco3igDeBywJCyaR2vURYRx8LkzzIMM8DxQM4LAXBQ==",
|
|
727
|
-
"signed_at": "2026-05-
|
|
727
|
+
"signed_at": "2026-05-30T22:22:04.676Z"
|
|
728
728
|
},
|
|
729
729
|
{
|
|
730
730
|
"name": "attack-surface-pentest",
|
|
@@ -796,7 +796,7 @@
|
|
|
796
796
|
"Pwn2Own Berlin 2026 (disclosed 2026-05-14, embargo ends 2026-08-12) — Microsoft Edge 4-bug sandbox escape by Orange Tsai (DEVCORE); forward-watch only (browser sandbox, out of current playbook scope); track Microsoft Edge security advisory and KEV add"
|
|
797
797
|
],
|
|
798
798
|
"signature": "DDMzI+4En4aIkwBUCGW6nj1eEkCyLqHGn2LJ2rnwWfYatjPI1U5HrTZNAN/n9JqWtAzk8F3rmsKehaaz5iNWDA==",
|
|
799
|
-
"signed_at": "2026-05-
|
|
799
|
+
"signed_at": "2026-05-30T22:22:04.676Z"
|
|
800
800
|
},
|
|
801
801
|
{
|
|
802
802
|
"name": "fuzz-testing-strategy",
|
|
@@ -856,7 +856,7 @@
|
|
|
856
856
|
"OSS-Fuzz-Gen / AI-assisted harness generation becoming the default expectation for OSS maintainers"
|
|
857
857
|
],
|
|
858
858
|
"signature": "dJB0iAstIUbyny+udl3OIkaLScEmqS97LNP73yQ8mxt+0bcqxZjpfXaWLzLuIQblGYvUvz75/H6rO2EJuGd4AQ==",
|
|
859
|
-
"signed_at": "2026-05-
|
|
859
|
+
"signed_at": "2026-05-30T22:22:04.677Z"
|
|
860
860
|
},
|
|
861
861
|
{
|
|
862
862
|
"name": "dlp-gap-analysis",
|
|
@@ -931,7 +931,7 @@
|
|
|
931
931
|
"Quebec Law 25, India DPDPA, KSA PDPL enforcement actions naming AI-tool prompt data as in-scope personal information"
|
|
932
932
|
],
|
|
933
933
|
"signature": "KEAoMji3VcPX/ZXXqVe6OStxSkTssfY9fIRPyPcDYqh50GzOFQ6koNOTBVAiWOvjDjQ38g12xun5srbqgmvRAw==",
|
|
934
|
-
"signed_at": "2026-05-
|
|
934
|
+
"signed_at": "2026-05-30T22:22:04.677Z"
|
|
935
935
|
},
|
|
936
936
|
{
|
|
937
937
|
"name": "supply-chain-integrity",
|
|
@@ -1010,7 +1010,7 @@
|
|
|
1010
1010
|
"Pwn2Own Berlin 2026 (disclosed 2026-05-14, embargo ends 2026-08-12) — NVIDIA Megatron Bridge path traversal by haehae; AI training-stack file-system trust boundary; track patch and SBOM-attestation impact"
|
|
1011
1011
|
],
|
|
1012
1012
|
"signature": "UY3tBi0n1K/OtSrWPkHcOCSuHEwKuPmRqGIf3MyPVXGWS72elGTWGXt4AN/uStLmefeEody1LuhnJR9PWjr4Cg==",
|
|
1013
|
-
"signed_at": "2026-05-
|
|
1013
|
+
"signed_at": "2026-05-30T22:22:04.677Z"
|
|
1014
1014
|
},
|
|
1015
1015
|
{
|
|
1016
1016
|
"name": "defensive-countermeasure-mapping",
|
|
@@ -1067,7 +1067,7 @@
|
|
|
1067
1067
|
],
|
|
1068
1068
|
"last_threat_review": "2026-05-11",
|
|
1069
1069
|
"signature": "Qe0Hg9BrX3Zm5pj0n2z/oiHbAXWdA2Dq461zc4izkkUjEX2CZ02rODjCI2ELbrVOU3GC7edxqAxA+5U/ObnHDQ==",
|
|
1070
|
-
"signed_at": "2026-05-
|
|
1070
|
+
"signed_at": "2026-05-30T22:22:04.678Z"
|
|
1071
1071
|
},
|
|
1072
1072
|
{
|
|
1073
1073
|
"name": "identity-assurance",
|
|
@@ -1134,7 +1134,7 @@
|
|
|
1134
1134
|
"d3fend_refs": [],
|
|
1135
1135
|
"last_threat_review": "2026-05-11",
|
|
1136
1136
|
"signature": "UV3458QXSkEpenzrOmdlTTfPHUD4hNyKMDHoeZDq/kiFb4mAG0ghQGTTgI9Ru8cJbSmYM1++m9N5TFIJ6JJPBg==",
|
|
1137
|
-
"signed_at": "2026-05-
|
|
1137
|
+
"signed_at": "2026-05-30T22:22:04.678Z"
|
|
1138
1138
|
},
|
|
1139
1139
|
{
|
|
1140
1140
|
"name": "ot-ics-security",
|
|
@@ -1190,7 +1190,7 @@
|
|
|
1190
1190
|
"d3fend_refs": [],
|
|
1191
1191
|
"last_threat_review": "2026-05-11",
|
|
1192
1192
|
"signature": "kIVzsPsJ72PzzWQwTuvjoHHoVEDCday5I52M9ohjB3/Ak+zlA8oyWLO/BKb/XuYY4fOApjfxTErSWv5uHQ2zDw==",
|
|
1193
|
-
"signed_at": "2026-05-
|
|
1193
|
+
"signed_at": "2026-05-30T22:22:04.678Z"
|
|
1194
1194
|
},
|
|
1195
1195
|
{
|
|
1196
1196
|
"name": "coordinated-vuln-disclosure",
|
|
@@ -1242,7 +1242,7 @@
|
|
|
1242
1242
|
"NYDFS 23 NYCRR 500 amendments potentially adding explicit CVD program requirements"
|
|
1243
1243
|
],
|
|
1244
1244
|
"signature": "bWr27Q1uN9xCe1ib4QulszBa7YIDNkGqo72k5nm2cK98LyPblicD+sO9MnGckAyB22BTN/cIB+FwFMcI5IxvBw==",
|
|
1245
|
-
"signed_at": "2026-05-
|
|
1245
|
+
"signed_at": "2026-05-30T22:22:04.679Z"
|
|
1246
1246
|
},
|
|
1247
1247
|
{
|
|
1248
1248
|
"name": "threat-modeling-methodology",
|
|
@@ -1292,7 +1292,7 @@
|
|
|
1292
1292
|
"PASTA v2 updates incorporating AI/ML application threats"
|
|
1293
1293
|
],
|
|
1294
1294
|
"signature": "Q854yzLqXdOazc6EyQbZzgAlivuq2vGFDVUCrxSldSvx/HX/ZM/uzmJyP7aBG7ZsMHxj6Lmj/H82YQoo1e+NCQ==",
|
|
1295
|
-
"signed_at": "2026-05-
|
|
1295
|
+
"signed_at": "2026-05-30T22:22:04.679Z"
|
|
1296
1296
|
},
|
|
1297
1297
|
{
|
|
1298
1298
|
"name": "webapp-security",
|
|
@@ -1366,7 +1366,7 @@
|
|
|
1366
1366
|
"d3fend_refs": [],
|
|
1367
1367
|
"last_threat_review": "2026-05-11",
|
|
1368
1368
|
"signature": "4ccahkJpGJZtwD7EBpnGcN0sEGPMEw8eqV+tvePVS04YAkLgYVWtlkasI/8n0be9xB+77x+Sjj3kIi2j2Lf9CA==",
|
|
1369
|
-
"signed_at": "2026-05-
|
|
1369
|
+
"signed_at": "2026-05-30T22:22:04.679Z",
|
|
1370
1370
|
"forward_watch": [
|
|
1371
1371
|
"NGINX Rift CVE-2026-42945 (disclosed 2026-05-13, source depthfirst) — KEV-watch predicted CISA KEV listing by 2026-05-29; AI-assisted discovery angle; track for active-exploitation confirmation and patch advisory affecting front-door web app deployments"
|
|
1372
1372
|
]
|
|
@@ -1419,7 +1419,7 @@
|
|
|
1419
1419
|
"d3fend_refs": [],
|
|
1420
1420
|
"last_threat_review": "2026-05-15",
|
|
1421
1421
|
"signature": "SBB7c3wNYfIdkyOp4g4nW0WP7xS+YokMzg32aaeJdbf14LTGQRzQUvSqb2TCj2HFUSHESOyKT1JpkAfyHLSQBQ==",
|
|
1422
|
-
"signed_at": "2026-05-
|
|
1422
|
+
"signed_at": "2026-05-30T22:22:04.680Z"
|
|
1423
1423
|
},
|
|
1424
1424
|
{
|
|
1425
1425
|
"name": "sector-healthcare",
|
|
@@ -1479,7 +1479,7 @@
|
|
|
1479
1479
|
"d3fend_refs": [],
|
|
1480
1480
|
"last_threat_review": "2026-05-11",
|
|
1481
1481
|
"signature": "U04GNLyRas1VmfEsB8khH4iqFZPwx96sPY0Kw9iVsSPU+KTeEFqwgtWK1X1pzgb+T16Pc7HSrCaXDOpTFvQEDw==",
|
|
1482
|
-
"signed_at": "2026-05-
|
|
1482
|
+
"signed_at": "2026-05-30T22:22:04.680Z"
|
|
1483
1483
|
},
|
|
1484
1484
|
{
|
|
1485
1485
|
"name": "sector-financial",
|
|
@@ -1560,7 +1560,7 @@
|
|
|
1560
1560
|
"TIBER-EU framework v2.0 alignment with DORA TLPT RTS (JC 2024/40); cross-recognition with CBEST and iCAST"
|
|
1561
1561
|
],
|
|
1562
1562
|
"signature": "xbylLqNPBuEsFE/MNVeGy/01K6yiJXMxQbzC1F4RWU5aseDGbNy5HrAv2JWI2+Aft05ozreNPjccvu66yJ5EBw==",
|
|
1563
|
-
"signed_at": "2026-05-
|
|
1563
|
+
"signed_at": "2026-05-30T22:22:04.681Z"
|
|
1564
1564
|
},
|
|
1565
1565
|
{
|
|
1566
1566
|
"name": "sector-federal-government",
|
|
@@ -1629,7 +1629,7 @@
|
|
|
1629
1629
|
"Australia PSPF 2024 revision and ISM quarterly updates — track for Essential Eight Maturity Level requirements for federal entities"
|
|
1630
1630
|
],
|
|
1631
1631
|
"signature": "C9c3JuBhUbwcb7uZpDdy+PNT8sYmYIxzD4uRHu421ePW1aSFJ8fkMvuTzSO8vD/F/jOOg5opM4kov/xSAn+qCg==",
|
|
1632
|
-
"signed_at": "2026-05-
|
|
1632
|
+
"signed_at": "2026-05-30T22:22:04.681Z"
|
|
1633
1633
|
},
|
|
1634
1634
|
{
|
|
1635
1635
|
"name": "sector-energy",
|
|
@@ -1694,7 +1694,7 @@
|
|
|
1694
1694
|
"ICS-CERT advisory feed (https://www.cisa.gov/news-events/cybersecurity-advisories/ics-advisories) for vendor CVEs in Siemens, Rockwell, Schneider Electric, ABB, GE Vernova, Hitachi Energy, AVEVA / OSIsoft PI"
|
|
1695
1695
|
],
|
|
1696
1696
|
"signature": "oz8Q5WVaY8au4IjbaZahx/DSaC00Q44ylSL3mDkTerCEpW/EyPUeiLeGxSrWxBCwVFEKSSJvnhJjhvX5lDPcCg==",
|
|
1697
|
-
"signed_at": "2026-05-
|
|
1697
|
+
"signed_at": "2026-05-30T22:22:04.681Z"
|
|
1698
1698
|
},
|
|
1699
1699
|
{
|
|
1700
1700
|
"name": "sector-telecom",
|
|
@@ -1780,7 +1780,7 @@
|
|
|
1780
1780
|
"O-RAN SFG / WG11 security specifications"
|
|
1781
1781
|
],
|
|
1782
1782
|
"signature": "NAtyzfLPXlUuB78Snb9nWmbZalC1CNlIYN9rYhdEmtB/xQGC6vVnThgrEAHlm7v/jMCFuknvEpUHKdscUnUADw==",
|
|
1783
|
-
"signed_at": "2026-05-
|
|
1783
|
+
"signed_at": "2026-05-30T22:22:04.682Z"
|
|
1784
1784
|
},
|
|
1785
1785
|
{
|
|
1786
1786
|
"name": "api-security",
|
|
@@ -1849,7 +1849,7 @@
|
|
|
1849
1849
|
"d3fend_refs": [],
|
|
1850
1850
|
"last_threat_review": "2026-05-18",
|
|
1851
1851
|
"signature": "1UTjZNC5Lyrgw93LAizdXVeSmv3jS8YQNT1db5OKsldub50+o1FXmAH4+3MxZozaOGDCX3yXbdDJSJaaSmfuAA==",
|
|
1852
|
-
"signed_at": "2026-05-
|
|
1852
|
+
"signed_at": "2026-05-30T22:22:04.682Z",
|
|
1853
1853
|
"forward_watch": [
|
|
1854
1854
|
"NGINX Rift CVE-2026-42945 (disclosed 2026-05-13, source depthfirst) — KEV-watch predicted CISA KEV listing by 2026-05-29; track for active-exploitation confirmation and patch advisory affecting API gateway / reverse-proxy deployments",
|
|
1855
1855
|
"Pwn2Own Berlin 2026 (disclosed 2026-05-14, embargo ends 2026-08-12) — LiteLLM 3-bug SSRF + Code Injection chain by k3vg3n; LLM-proxy API surface; track upstream patch and CVE assignments",
|
|
@@ -1935,7 +1935,7 @@
|
|
|
1935
1935
|
"CISA KEV additions for cloud-control-plane CVEs (IMDSv1 abuses, federation token mishandling, cross-tenant boundary failures); CISA Cybersecurity Advisories for cross-cloud advisories"
|
|
1936
1936
|
],
|
|
1937
1937
|
"signature": "EdsY4xe7YA8X8m+KZUbq49JwoCXgRKEz2eg3m86O37rvBmpm8ppvl9hrsekygvpBh2VmCHL2dEYiOD8OM2n7CA==",
|
|
1938
|
-
"signed_at": "2026-05-
|
|
1938
|
+
"signed_at": "2026-05-30T22:22:04.683Z"
|
|
1939
1939
|
},
|
|
1940
1940
|
{
|
|
1941
1941
|
"name": "container-runtime-security",
|
|
@@ -1997,7 +1997,7 @@
|
|
|
1997
1997
|
"d3fend_refs": [],
|
|
1998
1998
|
"last_threat_review": "2026-05-15",
|
|
1999
1999
|
"signature": "fnLKPLkjjRCJ/F9wdmZ1w1lXmqEJvTYkv6Uu+9OTd5vZTWKz3QMuxKOsas+ctCdOvTaeloqPUUprXx+ZZdDpCg==",
|
|
2000
|
-
"signed_at": "2026-05-
|
|
2000
|
+
"signed_at": "2026-05-30T22:22:04.683Z",
|
|
2001
2001
|
"forward_watch": [
|
|
2002
2002
|
"Pwn2Own Berlin 2026 (disclosed 2026-05-14, embargo ends 2026-08-12) — NVIDIA Container Toolkit container escape ($50K award) by chompie / IBM X-Force XOR; high-severity container/hypervisor boundary break; track patch and KEV add post-embargo"
|
|
2003
2003
|
]
|
|
@@ -2071,7 +2071,7 @@
|
|
|
2071
2071
|
"MITRE ATLAS v5.6.0 (released May 2026) shipped the AML.T0010 sub-technique expansion this forecast tracked plus new techniques (\"Publish Poisoned AI Agent Tool\", \"Escape to Host\"); inventory now 16 tactics, 84 techniques, 56 sub-techniques. Forward watch: subsequent ATLAS minor and major releases — track next-cadence updates to agentic-AI TTPs and MLOps-pipeline-specific techniques"
|
|
2072
2072
|
],
|
|
2073
2073
|
"signature": "t3dkdpTX04zvjitEeOJThpgjurLd1UO9GOut4LXSZgY3ULhfknI4zT7G5+m2RSZZTo7yyeZrwpg+7vEg9K6mAw==",
|
|
2074
|
-
"signed_at": "2026-05-
|
|
2074
|
+
"signed_at": "2026-05-30T22:22:04.683Z"
|
|
2075
2075
|
},
|
|
2076
2076
|
{
|
|
2077
2077
|
"name": "incident-response-playbook",
|
|
@@ -2133,7 +2133,7 @@
|
|
|
2133
2133
|
"NYDFS 23 NYCRR 500.17 amendments tightening ransom-payment 24h disclosure operationalization"
|
|
2134
2134
|
],
|
|
2135
2135
|
"signature": "+1kmtA6rAvIyDjjy+cJHK6BcfylyVsa5cUjRFijlFR9GsQfB93JnmkEJOqML50pdlcxtJI3yUodHpL3/YJGtCA==",
|
|
2136
|
-
"signed_at": "2026-05-
|
|
2136
|
+
"signed_at": "2026-05-30T22:22:04.684Z"
|
|
2137
2137
|
},
|
|
2138
2138
|
{
|
|
2139
2139
|
"name": "ransomware-response",
|
|
@@ -2213,7 +2213,7 @@
|
|
|
2213
2213
|
],
|
|
2214
2214
|
"last_threat_review": "2026-05-22",
|
|
2215
2215
|
"signature": "h48ASCz63aBfHzLKxMVDADMuT4atriK0iE6bJeVzZTsx/e8+hyv4fLP7+zYxT9Oe0Gss3v/Xy+t+Wd9uwzV+Aw==",
|
|
2216
|
-
"signed_at": "2026-05-
|
|
2216
|
+
"signed_at": "2026-05-30T22:22:04.684Z"
|
|
2217
2217
|
},
|
|
2218
2218
|
{
|
|
2219
2219
|
"name": "email-security-anti-phishing",
|
|
@@ -2266,7 +2266,7 @@
|
|
|
2266
2266
|
"d3fend_refs": [],
|
|
2267
2267
|
"last_threat_review": "2026-05-18",
|
|
2268
2268
|
"signature": "FVBn4ex2qPIo9SHMVJ6tntoz4tVwjbIq3m6wDjjZyv2JODlS+90GBYCOkNamxxkmw/6de6SMs0YHQiF/xjo/DQ==",
|
|
2269
|
-
"signed_at": "2026-05-
|
|
2269
|
+
"signed_at": "2026-05-30T22:22:04.684Z"
|
|
2270
2270
|
},
|
|
2271
2271
|
{
|
|
2272
2272
|
"name": "age-gates-child-safety",
|
|
@@ -2334,7 +2334,7 @@
|
|
|
2334
2334
|
"US state adult-site age-verification laws — 19+ states by mid-2026 (TX HB 18 upheld by SCOTUS June 2025 in Free Speech Coalition v. Paxton); track ongoing challenges in remaining states"
|
|
2335
2335
|
],
|
|
2336
2336
|
"signature": "ZHVdGWCcfG98tSVB0b9mwrsYwv71V3uUEl+6ss7omSQhmNvqV5s6MAZM5YladBt9MK/8T/zBrTYN4gAonOP+BQ==",
|
|
2337
|
-
"signed_at": "2026-05-
|
|
2337
|
+
"signed_at": "2026-05-30T22:22:04.685Z"
|
|
2338
2338
|
},
|
|
2339
2339
|
{
|
|
2340
2340
|
"name": "cloud-iam-incident",
|
|
@@ -2414,7 +2414,7 @@
|
|
|
2414
2414
|
],
|
|
2415
2415
|
"last_threat_review": "2026-05-15",
|
|
2416
2416
|
"signature": "r9ii4nb3HJELdtKCGF5qy9PHOiot3GC24yfxfGAKlLENHkdRvRkvvL99eV/6RXyfUaMyrnc2Te8tPQcNu5bsDg==",
|
|
2417
|
-
"signed_at": "2026-05-
|
|
2417
|
+
"signed_at": "2026-05-30T22:22:04.685Z",
|
|
2418
2418
|
"forward_watch": [
|
|
2419
2419
|
"AWS IAM Identity Center session-policy refresh and step-up-on-admin enforcement (anticipated 2026-H2 release)",
|
|
2420
2420
|
"GCP Workload Identity Federation principal-set attribute mapping tightening (post-2026 Q3 Federation hardening guide)",
|
|
@@ -2508,7 +2508,7 @@
|
|
|
2508
2508
|
],
|
|
2509
2509
|
"last_threat_review": "2026-05-15",
|
|
2510
2510
|
"signature": "9mfDtMApMAg9V/lmwpniNxo/6gNZoOEoYDfyFvyWvKrPMtc7H9F8uz06FVoARe/J49saAKTVXOurNE1D/KtpCQ==",
|
|
2511
|
-
"signed_at": "2026-05-
|
|
2511
|
+
"signed_at": "2026-05-30T22:22:04.685Z",
|
|
2512
2512
|
"forward_watch": [
|
|
2513
2513
|
"Entra ID conditional access evolution post-Midnight Blizzard — Microsoft's 2025-2026 commitments on legacy-tenant MFA enforcement and OAuth-app consent gating",
|
|
2514
2514
|
"Okta IPSIE (Interoperability Profile for Secure Identity in the Enterprise) OpenID Foundation working-group output and adoption timeline",
|
|
@@ -2526,6 +2526,6 @@
|
|
|
2526
2526
|
],
|
|
2527
2527
|
"manifest_signature": {
|
|
2528
2528
|
"algorithm": "Ed25519",
|
|
2529
|
-
"signature_base64": "
|
|
2529
|
+
"signature_base64": "xgINyQHj7n0aBdrPHpafpahet+NxaqnesUO4gQwIl13hMaS7Zw85ixlZ/dl/Znrz7vW++uTLIU0m6ZqEzMYfAg=="
|
|
2530
2530
|
}
|
|
2531
2531
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blamejs/exceptd-skills",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.50",
|
|
4
4
|
"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.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-security",
|
package/sbom.cdx.json
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"bomFormat": "CycloneDX",
|
|
3
3
|
"specVersion": "1.6",
|
|
4
|
-
"serialNumber": "urn:uuid:
|
|
4
|
+
"serialNumber": "urn:uuid:ede2338f-0db3-4a5c-859d-567abe4a25ef",
|
|
5
5
|
"version": 1,
|
|
6
6
|
"metadata": {
|
|
7
|
-
"timestamp": "
|
|
7
|
+
"timestamp": "2152-06-21T09:55:27.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.50"
|
|
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.50",
|
|
17
17
|
"type": "application",
|
|
18
18
|
"name": "@blamejs/exceptd-skills",
|
|
19
|
-
"version": "0.15.
|
|
19
|
+
"version": "0.15.50",
|
|
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.50",
|
|
29
29
|
"hashes": [
|
|
30
30
|
{
|
|
31
31
|
"alg": "SHA-256",
|
|
32
|
-
"content": "
|
|
32
|
+
"content": "332e8187d86819842a9abb646bcd8645e87c14dcaf6596cc28ca6dbb18af82c5"
|
|
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.50"
|
|
39
39
|
},
|
|
40
40
|
{
|
|
41
41
|
"type": "vcs",
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
},
|
|
71
71
|
{
|
|
72
72
|
"name": "exceptd:vendor:count",
|
|
73
|
-
"value": "
|
|
73
|
+
"value": "3"
|
|
74
74
|
},
|
|
75
75
|
{
|
|
76
76
|
"name": "exceptd:vendor:pin",
|
|
@@ -116,11 +116,11 @@
|
|
|
116
116
|
"hashes": [
|
|
117
117
|
{
|
|
118
118
|
"alg": "SHA-256",
|
|
119
|
-
"content": "
|
|
119
|
+
"content": "127bec3e5780f53bf98eaa33157c004423edda4c0c9931018bc8d67bd690d2d7"
|
|
120
120
|
},
|
|
121
121
|
{
|
|
122
122
|
"alg": "SHA3-512",
|
|
123
|
-
"content": "
|
|
123
|
+
"content": "aebfca6cb624dfddaee726ba8760cd3318347278a107629bd10c3266d2bb2ef132bf916b3a8d38f83f5a712b647bd770adc98d0635d9348a6f0cf940baeea75b"
|
|
124
124
|
}
|
|
125
125
|
]
|
|
126
126
|
},
|
|
@@ -281,11 +281,11 @@
|
|
|
281
281
|
"hashes": [
|
|
282
282
|
{
|
|
283
283
|
"alg": "SHA-256",
|
|
284
|
-
"content": "
|
|
284
|
+
"content": "cf9b74140298bdee14d4826d11ed9f7d6b1266db8a782d8dce48425e76ec9af5"
|
|
285
285
|
},
|
|
286
286
|
{
|
|
287
287
|
"alg": "SHA3-512",
|
|
288
|
-
"content": "
|
|
288
|
+
"content": "0528bbd4277fe3b8f4cb99eeaf35d5e9f310cfd48a0164fa4e4edb03154f1c7302af7d44617d7b6f1d6ab52e301a7772042e4c609693c49b8ce6eb64991419b9"
|
|
289
289
|
}
|
|
290
290
|
]
|
|
291
291
|
},
|
|
@@ -1106,11 +1106,11 @@
|
|
|
1106
1106
|
"hashes": [
|
|
1107
1107
|
{
|
|
1108
1108
|
"alg": "SHA-256",
|
|
1109
|
-
"content": "
|
|
1109
|
+
"content": "17c8544a8043fd8a24aa22c1b5c061806983bc896a51b120f1976dea1c770a3d"
|
|
1110
1110
|
},
|
|
1111
1111
|
{
|
|
1112
1112
|
"alg": "SHA3-512",
|
|
1113
|
-
"content": "
|
|
1113
|
+
"content": "fdca23d1585d169fa2dad9be3f840de556b2673fefc6406c4ad0fb6cdf4e85a98103afc125f725c2fb3146fead224e444f963c17e031d6c64581a8552da96eca"
|
|
1114
1114
|
}
|
|
1115
1115
|
]
|
|
1116
1116
|
},
|
|
@@ -1316,11 +1316,11 @@
|
|
|
1316
1316
|
"hashes": [
|
|
1317
1317
|
{
|
|
1318
1318
|
"alg": "SHA-256",
|
|
1319
|
-
"content": "
|
|
1319
|
+
"content": "f9452a58e4916a512b61ac8c6179dff6162f3634cf766b129d521233daec3c0e"
|
|
1320
1320
|
},
|
|
1321
1321
|
{
|
|
1322
1322
|
"alg": "SHA3-512",
|
|
1323
|
-
"content": "
|
|
1323
|
+
"content": "d6ebf5ffca3fee2398034f411eb477d393e8ee8a7f12ed576e72f0e785c42b37e760fb743bf47d0a4c704c71f726ba56e373fbd4149e2750c00ee89e8af74efe"
|
|
1324
1324
|
}
|
|
1325
1325
|
]
|
|
1326
1326
|
},
|
|
@@ -1661,11 +1661,11 @@
|
|
|
1661
1661
|
"hashes": [
|
|
1662
1662
|
{
|
|
1663
1663
|
"alg": "SHA-256",
|
|
1664
|
-
"content": "
|
|
1664
|
+
"content": "484c2c4747699b7a32658126ade2fb498656cde204a8a30efde0730c04305a48"
|
|
1665
1665
|
},
|
|
1666
1666
|
{
|
|
1667
1667
|
"alg": "SHA3-512",
|
|
1668
|
-
"content": "
|
|
1668
|
+
"content": "b9162623770ee4a9879f501417f123fe02d819cfac322ca9fde4ca86c0319a4785a6a2bd719a2b9248e531cd8eac0a79c98b5f627c69b4dfb0d5e62f69f600d5"
|
|
1669
1669
|
}
|
|
1670
1670
|
]
|
|
1671
1671
|
},
|
|
@@ -1751,11 +1751,11 @@
|
|
|
1751
1751
|
"hashes": [
|
|
1752
1752
|
{
|
|
1753
1753
|
"alg": "SHA-256",
|
|
1754
|
-
"content": "
|
|
1754
|
+
"content": "33da1072778152239ab47e8b4ef930f702678299bfa641e297a233dc9022dbfa"
|
|
1755
1755
|
},
|
|
1756
1756
|
{
|
|
1757
1757
|
"alg": "SHA3-512",
|
|
1758
|
-
"content": "
|
|
1758
|
+
"content": "3125320c4384560007a8ee89bd9bc426445f80b5db11526ec5f7c666c92ee0f6ceac1e578a09c916e6b3fc0e3e3cea364ec968223e5827e66088fdda217edbc4"
|
|
1759
1759
|
}
|
|
1760
1760
|
]
|
|
1761
1761
|
},
|
|
@@ -2201,11 +2201,11 @@
|
|
|
2201
2201
|
"hashes": [
|
|
2202
2202
|
{
|
|
2203
2203
|
"alg": "SHA-256",
|
|
2204
|
-
"content": "
|
|
2204
|
+
"content": "576480c2ad29550a06e5a3e48b7dae04aadc1b505d76e3a5b1db9179d61263c4"
|
|
2205
2205
|
},
|
|
2206
2206
|
{
|
|
2207
2207
|
"alg": "SHA3-512",
|
|
2208
|
-
"content": "
|
|
2208
|
+
"content": "e3370698400f8b807680cebda861e73acedbf1602c9a7c2a8372c2285b2f88650a9d6e2931ebdb2cb229ad23e7c592787dea15a91adea5b4a854c6fc6842d38a"
|
|
2209
2209
|
}
|
|
2210
2210
|
]
|
|
2211
2211
|
},
|
|
@@ -2321,11 +2321,11 @@
|
|
|
2321
2321
|
"hashes": [
|
|
2322
2322
|
{
|
|
2323
2323
|
"alg": "SHA-256",
|
|
2324
|
-
"content": "
|
|
2324
|
+
"content": "4622fd121015535bb0a1b47a7bf57abe3a195b02e42a0be99dfadc9d67a72160"
|
|
2325
2325
|
},
|
|
2326
2326
|
{
|
|
2327
2327
|
"alg": "SHA3-512",
|
|
2328
|
-
"content": "
|
|
2328
|
+
"content": "85f755a9228afe24b704d39d58ee11436c8dd663df47e8c7c84da9454c5bad1cf9f166c833a9648517f9c2689730103617b41bec0739c637d91fafb041f7db63"
|
|
2329
2329
|
}
|
|
2330
2330
|
]
|
|
2331
2331
|
},
|
|
@@ -2471,11 +2471,11 @@
|
|
|
2471
2471
|
"hashes": [
|
|
2472
2472
|
{
|
|
2473
2473
|
"alg": "SHA-256",
|
|
2474
|
-
"content": "
|
|
2474
|
+
"content": "a5a43d931bcc09f0e5866e5860efd373943e5ce797ff368dc009ea4161d03cd6"
|
|
2475
2475
|
},
|
|
2476
2476
|
{
|
|
2477
2477
|
"alg": "SHA3-512",
|
|
2478
|
-
"content": "
|
|
2478
|
+
"content": "dff963f00485e37546f605542d94d5e8730be9b888d98dd9bc9d256f38b4820d3c3346b1bfdbacedefc4b47d7b07afffdd1b3d7a77ba4cf71593efdd5530b9af"
|
|
2479
2479
|
}
|
|
2480
2480
|
]
|
|
2481
2481
|
},
|
|
@@ -3266,11 +3266,11 @@
|
|
|
3266
3266
|
"hashes": [
|
|
3267
3267
|
{
|
|
3268
3268
|
"alg": "SHA-256",
|
|
3269
|
-
"content": "
|
|
3269
|
+
"content": "bb7cb7e49f82a7bb2a75ebcacc0b673d564384c0382ee7b271c32dd46fde8928"
|
|
3270
3270
|
},
|
|
3271
3271
|
{
|
|
3272
3272
|
"alg": "SHA3-512",
|
|
3273
|
-
"content": "
|
|
3273
|
+
"content": "49cd6309d13cfde3a266a7a499bce75849e61b562eaba579b814bd2d553f0dc192fd987eba449e86691645a22459b3579bd32dac8bd1a50697d3bb2a2045d6a0"
|
|
3274
3274
|
}
|
|
3275
3275
|
]
|
|
3276
3276
|
},
|
|
@@ -3281,11 +3281,26 @@
|
|
|
3281
3281
|
"hashes": [
|
|
3282
3282
|
{
|
|
3283
3283
|
"alg": "SHA-256",
|
|
3284
|
-
"content": "
|
|
3284
|
+
"content": "31a30c4d7ea405ad1799322c02334993f7985bf922246e621b3d3e8a402654f5"
|
|
3285
3285
|
},
|
|
3286
3286
|
{
|
|
3287
3287
|
"alg": "SHA3-512",
|
|
3288
|
-
"content": "
|
|
3288
|
+
"content": "1a33019f5533ecff0b7e97ce9d2433e83e054afd2ef3cd3902428037a4b34590820b064deeffcc4cfb92724e67f9e035fd2f2bc7f70e99128c2c7baf8d82070e"
|
|
3289
|
+
}
|
|
3290
|
+
]
|
|
3291
|
+
},
|
|
3292
|
+
{
|
|
3293
|
+
"bom-ref": "file:vendor/blamejs/codepoint-class.js",
|
|
3294
|
+
"type": "file",
|
|
3295
|
+
"name": "vendor/blamejs/codepoint-class.js",
|
|
3296
|
+
"hashes": [
|
|
3297
|
+
{
|
|
3298
|
+
"alg": "SHA-256",
|
|
3299
|
+
"content": "2be79cf25de87f46b608aec98ee790f4cf1035ffee48fe70ff082d3cf6f324ba"
|
|
3300
|
+
},
|
|
3301
|
+
{
|
|
3302
|
+
"alg": "SHA3-512",
|
|
3303
|
+
"content": "8e243451500312f0836651525e31150f649b39ec34caa662bfff86f65975af91e479fd7b74a971ddb4a1fc30a379dc958b0407f7c5193c4e28a215a56a8e2bd4"
|
|
3289
3304
|
}
|
|
3290
3305
|
]
|
|
3291
3306
|
},
|
|
@@ -3319,6 +3334,46 @@
|
|
|
3319
3334
|
}
|
|
3320
3335
|
]
|
|
3321
3336
|
},
|
|
3337
|
+
{
|
|
3338
|
+
"bom-ref": "vendor:blamejs:codepoint-class.js",
|
|
3339
|
+
"type": "library",
|
|
3340
|
+
"name": "blamejs/codepoint-class.js",
|
|
3341
|
+
"version": "1442f17758a4",
|
|
3342
|
+
"description": "Vendored from blamejs/lib/codepoint-class.js (flattened + stripped). See vendor/blamejs/README.md.",
|
|
3343
|
+
"licenses": [
|
|
3344
|
+
{
|
|
3345
|
+
"license": {
|
|
3346
|
+
"id": "Apache-2.0"
|
|
3347
|
+
}
|
|
3348
|
+
}
|
|
3349
|
+
],
|
|
3350
|
+
"hashes": [
|
|
3351
|
+
{
|
|
3352
|
+
"alg": "SHA-256",
|
|
3353
|
+
"content": "2be79cf25de87f46b608aec98ee790f4cf1035ffee48fe70ff082d3cf6f324ba"
|
|
3354
|
+
}
|
|
3355
|
+
],
|
|
3356
|
+
"externalReferences": [
|
|
3357
|
+
{
|
|
3358
|
+
"type": "vcs",
|
|
3359
|
+
"url": "https://github.com/blamejs/blamejs"
|
|
3360
|
+
},
|
|
3361
|
+
{
|
|
3362
|
+
"type": "distribution",
|
|
3363
|
+
"url": "https://github.com/blamejs/blamejs/blob/1442f17758a4bd511c63877561c0ffa759f66a87/lib/codepoint-class.js"
|
|
3364
|
+
}
|
|
3365
|
+
],
|
|
3366
|
+
"properties": [
|
|
3367
|
+
{
|
|
3368
|
+
"name": "exceptd:vendor:upstream_sha256_at_pin",
|
|
3369
|
+
"value": "2be79cf25de87f46b608aec98ee790f4cf1035ffee48fe70ff082d3cf6f324ba"
|
|
3370
|
+
},
|
|
3371
|
+
{
|
|
3372
|
+
"name": "exceptd:vendor:strip_summary",
|
|
3373
|
+
"value": ""
|
|
3374
|
+
}
|
|
3375
|
+
]
|
|
3376
|
+
},
|
|
3322
3377
|
{
|
|
3323
3378
|
"bom-ref": "vendor:blamejs:retry.js",
|
|
3324
3379
|
"type": "library",
|
|
@@ -41,6 +41,7 @@ const ROOT = path.resolve(__dirname, "..");
|
|
|
41
41
|
const VALID_ALLOW_CLASSES = Object.freeze({
|
|
42
42
|
"process-exit-after-stdout-write": true,
|
|
43
43
|
"dynamic-regex": true,
|
|
44
|
+
"bidi-codepoint-literal": true,
|
|
44
45
|
});
|
|
45
46
|
|
|
46
47
|
const EXCLUDE_DIRS = new Set([
|
|
@@ -205,6 +206,39 @@ function detectDynamicRegex(files) {
|
|
|
205
206
|
return filterMarkers(hits, "dynamic-regex");
|
|
206
207
|
}
|
|
207
208
|
|
|
209
|
+
// Raw bidi-override / zero-width / invisible / null codepoints embedded as
|
|
210
|
+
// literals in source — the Trojan-Source class (CVE-2021-42574). A literal
|
|
211
|
+
// such codepoint is invisible in review and can reorder or hide code. Source
|
|
212
|
+
// should emit them programmatically (via vendor/blamejs/codepoint-class) or
|
|
213
|
+
// escape them (\uXXXX), never type them literally. The range table holds only
|
|
214
|
+
// numeric codepoints + the regex is built from escapes, so this detector's own
|
|
215
|
+
// source is clean (and the file self-skips below regardless).
|
|
216
|
+
const _BIDI_LITERAL_RANGES = [
|
|
217
|
+
[0x202A, 0x202E], [0x2066, 0x2069], 0x200E, 0x200F, 0x061C, // bidi overrides + isolates
|
|
218
|
+
0x200B, 0x200C, 0x200D, 0x00AD, 0x2060, 0xFEFF, // zero-width / invisible
|
|
219
|
+
0x0000, // null
|
|
220
|
+
];
|
|
221
|
+
function _bidiLiteralRe() {
|
|
222
|
+
const body = _BIDI_LITERAL_RANGES.map((r) =>
|
|
223
|
+
Array.isArray(r)
|
|
224
|
+
? "\\u" + r[0].toString(16).padStart(4, "0") + "-\\u" + r[1].toString(16).padStart(4, "0")
|
|
225
|
+
: "\\u" + r.toString(16).padStart(4, "0")
|
|
226
|
+
).join("");
|
|
227
|
+
return new RegExp("[" + body + "]"); // allow:dynamic-regex — codepoints from a static literal range table, not operator input
|
|
228
|
+
}
|
|
229
|
+
function detectBidiCodepointLiteral(files) {
|
|
230
|
+
const re = _bidiLiteralRe();
|
|
231
|
+
const hits = [];
|
|
232
|
+
for (const rel of (files || filesUnder(["bin/exceptd.js", "lib", "orchestrator", "scripts"]))) {
|
|
233
|
+
if (rel === "scripts/check-codebase-patterns.js") continue; // holds the range table itself
|
|
234
|
+
const lines = readLines(rel);
|
|
235
|
+
for (let i = 0; i < lines.length; i++) {
|
|
236
|
+
if (re.test(lines[i])) hits.push({ file: rel, line: i + 1, content: lines[i].trim() });
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return filterMarkers(hits, "bidi-codepoint-literal");
|
|
240
|
+
}
|
|
241
|
+
|
|
208
242
|
function detectOrphanAllowClass(files) {
|
|
209
243
|
const hits = [];
|
|
210
244
|
for (const rel of (files || filesUnder(["bin/exceptd.js", "lib", "orchestrator", "scripts"]))) {
|
|
@@ -251,6 +285,12 @@ const CLASSES = [
|
|
|
251
285
|
warnOnly: true, // flip to false next release once the known sites carry markers
|
|
252
286
|
hint: "RegExp from operator input is a ReDoS sink — anchor + length-cap, or `// allow:dynamic-regex — <reason>` when the pattern is a trusted bundled schema",
|
|
253
287
|
},
|
|
288
|
+
{
|
|
289
|
+
id: "bidi-codepoint-literal",
|
|
290
|
+
run: detectBidiCodepointLiteral,
|
|
291
|
+
warnOnly: false,
|
|
292
|
+
hint: "raw bidi/zero-width/null codepoint in source — emit it via vendor/blamejs/codepoint-class tables or a \\uXXXX escape, or `// allow:bidi-codepoint-literal — <reason>` if the literal is load-bearing test/illustrative data",
|
|
293
|
+
},
|
|
254
294
|
{
|
|
255
295
|
id: "orphan-allow-class",
|
|
256
296
|
run: detectOrphanAllowClass,
|
|
@@ -289,6 +329,7 @@ module.exports = {
|
|
|
289
329
|
CLASSES,
|
|
290
330
|
detectProcessExitAfterStdout,
|
|
291
331
|
detectDynamicRegex,
|
|
332
|
+
detectBidiCodepointLiteral,
|
|
292
333
|
detectOrphanAllowClass,
|
|
293
334
|
filesUnder,
|
|
294
335
|
};
|
package/scripts/predeploy.js
CHANGED
|
@@ -244,7 +244,7 @@ const GATES = [
|
|
|
244
244
|
// dynamic-RegExp construction is surfaced warn-only this release. The
|
|
245
245
|
// exception mechanism + the "owned elsewhere" boundary are documented in
|
|
246
246
|
// the script header.
|
|
247
|
-
name: "Codebase-pattern gates (
|
|
247
|
+
name: "Codebase-pattern gates (stdout-flush, dynamic RegExp, bidi codepoints, orphan markers)",
|
|
248
248
|
command: process.execPath,
|
|
249
249
|
args: [path.join(ROOT, "scripts", "check-codebase-patterns.js")],
|
|
250
250
|
ciJobName: "Data integrity (catalog + manifest snapshot)",
|
package/scripts/release.js
CHANGED
|
@@ -375,6 +375,22 @@ function cmdWatch() {
|
|
|
375
375
|
// run doesn't throw before we get to inspect + rerun it.
|
|
376
376
|
_run("gh", ["pr", "checks", prNum, "--watch"], { allowFail: true });
|
|
377
377
|
|
|
378
|
+
// Gate on check CONCLUSIONS, not only review threads. A red required check
|
|
379
|
+
// leaves the PR BLOCKED at merge, so surfacing failures here (the whole
|
|
380
|
+
// point of the watch phase) beats advancing to "next: merge" and letting
|
|
381
|
+
// cmdMerge reject it. Bucket is gh's normalized verdict: pass / fail /
|
|
382
|
+
// pending / skipping / cancel.
|
|
383
|
+
var checksRaw = _capture("gh", ["pr", "checks", prNum, "--json", "name,bucket,link"]).stdout;
|
|
384
|
+
var checks = [];
|
|
385
|
+
try { checks = JSON.parse(checksRaw || "[]"); } catch (_e) { checks = []; }
|
|
386
|
+
var failed = checks.filter(function (c) { return c.bucket === "fail" || c.bucket === "cancel"; });
|
|
387
|
+
if (failed.length > 0) {
|
|
388
|
+
console.log("\nfailed checks (" + failed.length + "):");
|
|
389
|
+
failed.forEach(function (c) { console.log(" ✗ " + c.name + " " + (c.link || "")); });
|
|
390
|
+
console.log("\nFix in code, push, then re-run: node scripts/release.js watch");
|
|
391
|
+
process.exit(3);
|
|
392
|
+
}
|
|
393
|
+
|
|
378
394
|
var unresolved = _unresolvedThreads(prNum);
|
|
379
395
|
if (unresolved.length > 0) {
|
|
380
396
|
console.log("\nunresolved review threads (" + unresolved.length + "):");
|
package/vendor/blamejs/README.md
CHANGED
|
@@ -9,6 +9,7 @@ upstream commit [`1442f17`](https://github.com/blamejs/blamejs/commit/1442f17758
|
|
|
9
9
|
|---|---|---|
|
|
10
10
|
| `retry.js` | `lib/retry.js` | Battle-tested exponential backoff + crypto jitter + AbortSignal + circuit-breaker. Used by `lib/job-queue.js` and `lib/refresh-external.js` for HTTP retry semantics on KEV/EPSS/NVD/IETF/GitHub fetches. |
|
|
11
11
|
| `worker-pool.js` | `lib/worker-pool.js` | Generic worker_threads pool with bounded queue, per-task timeout, worker recycle. Used by `scripts/build-indexes.js --parallel` and any future CPU-bound fan-out work. |
|
|
12
|
+
| `codepoint-class.js` | `lib/codepoint-class.js` | Trojan-Source (CVE-2021-42574) codepoint threat tables — bidi-override / C0-control / zero-width / null ranges + compiled regexes + `applyCharStripPolicies`. Used by the `--operator` reject path (`bin/exceptd.js`) and `sanitizeOperatorText` (`lib/playbook-runner.js`) to classify which family an offending codepoint belongs to; `\p{C}` remains the category backstop. |
|
|
12
13
|
| `LICENSE` | `LICENSE` | Apache-2.0 license text (identical to exceptd's). |
|
|
13
14
|
| `_PROVENANCE.json` | — | sha256 of each vendored file + upstream file at pin, plus the strip rules applied. `lib/validate-vendor.js` re-hashes on every predeploy run. |
|
|
14
15
|
|
|
@@ -52,6 +52,22 @@
|
|
|
52
52
|
"exceptd_deltas": [
|
|
53
53
|
"scriptPath validator rejects Windows UNC + extended-path prefixes (\\\\?\\, \\\\.\\, \\\\<server>\\) — defense-in-depth against worker-spawn from network shares on win32 platforms"
|
|
54
54
|
]
|
|
55
|
+
},
|
|
56
|
+
"codepoint-class.js": {
|
|
57
|
+
"vendored_path": "vendor/blamejs/codepoint-class.js",
|
|
58
|
+
"vendored_sha256": "2be79cf25de87f46b608aec98ee790f4cf1035ffee48fe70ff082d3cf6f324ba",
|
|
59
|
+
"upstream_path": "lib/codepoint-class.js",
|
|
60
|
+
"upstream_sha256_at_pin": "2be79cf25de87f46b608aec98ee790f4cf1035ffee48fe70ff082d3cf6f324ba",
|
|
61
|
+
"stripped": [],
|
|
62
|
+
"surface_preserved": [
|
|
63
|
+
"BIDI_RE / C0_CTRL_RE / ZERO_WIDTH_RE / NULL_BYTE (classification regexes)",
|
|
64
|
+
"BIDI_RE_G / C0_CTRL_RE_G / ZW_RE_G / NULL_RE_G (global strip regexes)",
|
|
65
|
+
"applyCharStripPolicies(text, opts)",
|
|
66
|
+
"hex4 / charClass / fromCp / range tables"
|
|
67
|
+
],
|
|
68
|
+
"exceptd_deltas": [
|
|
69
|
+
"Used for codepoint-family CLASSIFICATION + family-strip only. The BIDI|C0|ZERO_WIDTH|NULL union is a strict subset of Unicode General Category C, so \\p{C} remains the reject/strip backstop at both consumer sites (bin/exceptd.js --operator validation, lib/playbook-runner.js sanitizeOperatorText) — it catches the divergent remainder (U+007F, U+0080-009F, U+FFF9-FFFB, private-use, unassigned) the named-family regexes miss."
|
|
70
|
+
]
|
|
55
71
|
}
|
|
56
72
|
}
|
|
57
73
|
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* codepoint-class — shared codepoint-table threat catalog and regex
|
|
4
|
+
* compiler for the guard-* family.
|
|
5
|
+
*
|
|
6
|
+
* Threat detectors that need to match Unicode bidi overrides, C0
|
|
7
|
+
* control characters, zero-width / invisible chars, etc. compose
|
|
8
|
+
* regex character classes from numeric codepoint range tables here
|
|
9
|
+
* instead of embedding the attack characters directly in their
|
|
10
|
+
* source files. Centralizing the tables means:
|
|
11
|
+
*
|
|
12
|
+
* - Source files in lib/guard-* stay pure ASCII (zero
|
|
13
|
+
* irregular-whitespace lint findings, no eslint-disable comments
|
|
14
|
+
* for this category).
|
|
15
|
+
* - Adding / removing a codepoint from the catalog is a single
|
|
16
|
+
* edit; every guard picks up the change.
|
|
17
|
+
* - The detector composes the way an attacker would compose the
|
|
18
|
+
* payload (programmatic codepoint emission, not literal typing).
|
|
19
|
+
*
|
|
20
|
+
* Surface:
|
|
21
|
+
*
|
|
22
|
+
* hex4(cp) -> "\\uXXXX" escape for a single codepoint
|
|
23
|
+
* charClass(ranges) -> regex character class body for a range
|
|
24
|
+
* table (e.g. [0x200E, [0x202A,0x202E]])
|
|
25
|
+
* fromCp(cp) -> String.fromCharCode shorthand
|
|
26
|
+
* ranges() -> { BIDI_RANGES, C0_CTRL_RANGES,
|
|
27
|
+
* ZERO_WIDTH_RANGES }
|
|
28
|
+
* compiled() -> { BIDI_RE, BIDI_RE_G, C0_CTRL_RE,
|
|
29
|
+
* C0_CTRL_RE_G, ZERO_WIDTH_RE, ZW_RE_G,
|
|
30
|
+
* NULL_RE_G, NULL_BYTE, BOM_CHAR }
|
|
31
|
+
*
|
|
32
|
+
* The compiled() exports are RegExp instances built from the
|
|
33
|
+
* codepoint tables at module load. Consumers grab them once at boot.
|
|
34
|
+
*
|
|
35
|
+
* Codepoint tables:
|
|
36
|
+
*
|
|
37
|
+
* BIDI_RANGES — Unicode bidi-override family (CVE-2021-42574
|
|
38
|
+
* Trojan Source). LRM U+200E / RLM U+200F / ALM U+061C / LRE
|
|
39
|
+
* U+202A / RLE U+202B / PDF U+202C / LRO U+202D / RLO U+202E /
|
|
40
|
+
* LRI U+2066 / RLI U+2067 / FSI U+2068 / PDI U+2069.
|
|
41
|
+
*
|
|
42
|
+
* C0_CTRL_RANGES — C0 control characters minus tab (U+09) / lf
|
|
43
|
+
* (U+0A) / cr (U+0D) — those are dialect-shaped chars that
|
|
44
|
+
* parsers handle separately. Everything else (U+00, U+01-U+08,
|
|
45
|
+
* U+0B-U+0C, U+0E-U+1F) flagged as control-byte injection.
|
|
46
|
+
*
|
|
47
|
+
* ZERO_WIDTH_RANGES — invisible-formatting / zero-width chars
|
|
48
|
+
* attackers use to hide payloads:
|
|
49
|
+
* SHY U+00AD ZWSP U+200B ZWNJ U+200C ZWJ U+200D
|
|
50
|
+
* WJ U+2060 BOM U+FEFF
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
var HEX_RADIX = 16; // base-16 radix, not byte size
|
|
54
|
+
|
|
55
|
+
function hex4(cp) {
|
|
56
|
+
var s = cp.toString(HEX_RADIX).toUpperCase();
|
|
57
|
+
while (s.length < 4) s = "0" + s;
|
|
58
|
+
return "\\u" + s;
|
|
59
|
+
}
|
|
60
|
+
function charClass(rangeList) {
|
|
61
|
+
return rangeList.map(function (r) {
|
|
62
|
+
return Array.isArray(r) ? hex4(r[0]) + "-" + hex4(r[1]) : hex4(r);
|
|
63
|
+
}).join("");
|
|
64
|
+
}
|
|
65
|
+
function fromCp(cp) { return String.fromCharCode(cp); }
|
|
66
|
+
|
|
67
|
+
var BIDI_RANGES = [0x200E, 0x200F, 0x061C, [0x202A, 0x202E], [0x2066, 0x2069]];
|
|
68
|
+
var C0_CTRL_RANGES = [[0x0000, 0x0008], 0x000B, 0x000C, [0x000E, 0x001F]];
|
|
69
|
+
var ZERO_WIDTH_RANGES = [0x00AD, [0x200B, 0x200D], 0x2060, 0xFEFF];
|
|
70
|
+
|
|
71
|
+
// allow:dynamic-regex — codepoints from BIDI_RANGES literal table
|
|
72
|
+
var BIDI_RE = new RegExp("[" + charClass(BIDI_RANGES) + "]");
|
|
73
|
+
// allow:dynamic-regex — codepoints from BIDI_RANGES literal table
|
|
74
|
+
var BIDI_RE_G = new RegExp("[" + charClass(BIDI_RANGES) + "]", "g");
|
|
75
|
+
// allow:dynamic-regex — codepoints from C0_CTRL_RANGES literal table
|
|
76
|
+
var C0_CTRL_RE = new RegExp("[" + charClass(C0_CTRL_RANGES) + "]");
|
|
77
|
+
// allow:dynamic-regex — codepoints from C0_CTRL_RANGES literal table
|
|
78
|
+
var C0_CTRL_RE_G = new RegExp("[" + charClass(C0_CTRL_RANGES) + "]", "g");
|
|
79
|
+
// allow:dynamic-regex — codepoints from ZERO_WIDTH_RANGES literal table
|
|
80
|
+
var ZERO_WIDTH_RE = new RegExp("[" + charClass(ZERO_WIDTH_RANGES) + "]");
|
|
81
|
+
// allow:dynamic-regex — codepoints from ZERO_WIDTH_RANGES literal table
|
|
82
|
+
var ZW_RE_G = new RegExp("[" + charClass(ZERO_WIDTH_RANGES) + "]", "g");
|
|
83
|
+
// allow:dynamic-regex — single literal codepoint U+0000
|
|
84
|
+
var NULL_RE_G = new RegExp(hex4(0x0000), "g");
|
|
85
|
+
|
|
86
|
+
var NULL_BYTE = fromCp(0x0000);
|
|
87
|
+
var BOM_CHAR = fromCp(0xFEFF);
|
|
88
|
+
|
|
89
|
+
// Unicode script-range catalog for IDN-homograph / mixed-script
|
|
90
|
+
// confusable detection (UTS #39). Used by guard-domain, guard-email,
|
|
91
|
+
// safe-url IDN host-label classification, and any future caller that
|
|
92
|
+
// needs "is this label entirely one writing system?". Centralizing the
|
|
93
|
+
// table keeps the codepoint definitions in one place — adding a script
|
|
94
|
+
// is a single edit.
|
|
95
|
+
var SCRIPT_RANGES = {
|
|
96
|
+
latin: [[0x0041, 0x005A], [0x0061, 0x007A],
|
|
97
|
+
[0x00C0, 0x024F], [0x1E00, 0x1EFF]], // Unicode script ranges
|
|
98
|
+
cyrillic: [[0x0400, 0x04FF], [0x0500, 0x052F]], // Unicode Cyrillic + Cyrillic Supplement
|
|
99
|
+
greek: [[0x0370, 0x03FF], [0x1F00, 0x1FFF]], // Unicode Greek + Greek Extended
|
|
100
|
+
armenian: [[0x0530, 0x058F]], // Unicode Armenian
|
|
101
|
+
cherokee: [[0x13A0, 0x13FF], [0xAB70, 0xABBF]], // Unicode Cherokee + Cherokee Supplement
|
|
102
|
+
han: [[0x4E00, 0x9FFF]], // CJK Unified Ideographs
|
|
103
|
+
hiragana: [[0x3040, 0x309F]], // Hiragana
|
|
104
|
+
katakana: [[0x30A0, 0x30FF]], // Katakana
|
|
105
|
+
hangul: [[0xAC00, 0xD7AF]], // Hangul Syllables
|
|
106
|
+
arabic: [[0x0600, 0x06FF]], // Arabic
|
|
107
|
+
hebrew: [[0x0590, 0x05FF]], // Hebrew
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// scriptFor(cp) — returns the script-name string for a codepoint, or
|
|
111
|
+
// null when the codepoint is in a script not in the catalog (digits,
|
|
112
|
+
// punctuation, symbols, etc. are not script-classifying).
|
|
113
|
+
function scriptFor(cp) {
|
|
114
|
+
var keys = Object.keys(SCRIPT_RANGES);
|
|
115
|
+
for (var i = 0; i < keys.length; i += 1) {
|
|
116
|
+
var ranges = SCRIPT_RANGES[keys[i]];
|
|
117
|
+
for (var j = 0; j < ranges.length; j += 1) {
|
|
118
|
+
if (cp >= ranges[j][0] && cp <= ranges[j][1]) return keys[i];
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// detectMixedScripts(label, allowedScripts?) — returns null when the
|
|
125
|
+
// label is single-script (or every script appears in the optional
|
|
126
|
+
// allowedScripts allowlist), or an array of the detected script names
|
|
127
|
+
// when the label mixes scripts (homograph attack shape — Cyrillic 'а'
|
|
128
|
+
// inside an otherwise-Latin label, etc.). The result is the FULL set
|
|
129
|
+
// of scripts seen; callers decide refuse / audit / strip.
|
|
130
|
+
//
|
|
131
|
+
// allowedScripts: an array of script names the caller treats as
|
|
132
|
+
// acceptable; when supplied, a label whose every script is on the list
|
|
133
|
+
// returns null even if multiple scripts appear (legitimate mixed-
|
|
134
|
+
// script content like an English word inside a Japanese label).
|
|
135
|
+
function detectMixedScripts(label, allowedScripts) {
|
|
136
|
+
if (typeof label !== "string" || label.length === 0) return null;
|
|
137
|
+
var seen = {};
|
|
138
|
+
for (var i = 0; i < label.length; i += 1) {
|
|
139
|
+
var script = scriptFor(label.charCodeAt(i));
|
|
140
|
+
if (script === null) continue;
|
|
141
|
+
seen[script] = true;
|
|
142
|
+
}
|
|
143
|
+
var scripts = Object.keys(seen);
|
|
144
|
+
if (scripts.length <= 1) return null;
|
|
145
|
+
if (!allowedScripts) return scripts;
|
|
146
|
+
for (var k = 0; k < scripts.length; k += 1) {
|
|
147
|
+
if (allowedScripts.indexOf(scripts[k]) === -1) return scripts;
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// detectCharThreats — returns an array of issue objects for character-
|
|
153
|
+
// class threats (bidi / null / C0-control) per the opts policy. Emits
|
|
154
|
+
// at most one issue per class. Used by guard-* primitives' detection
|
|
155
|
+
// pass instead of repeating the per-class match-and-push block.
|
|
156
|
+
//
|
|
157
|
+
// Issue shape mirrors guard-* convention:
|
|
158
|
+
// { kind, severity, ruleId, location, snippet }
|
|
159
|
+
//
|
|
160
|
+
// issues.push.apply(issues,
|
|
161
|
+
// codepointClass.detectCharThreats(text, opts, "html"));
|
|
162
|
+
function detectCharThreats(text, opts, codePrefix) {
|
|
163
|
+
var issues = [];
|
|
164
|
+
if (typeof text !== "string") return issues;
|
|
165
|
+
if (opts && opts.bidiPolicy !== "allow") {
|
|
166
|
+
var bidiMatch = text.match(BIDI_RE);
|
|
167
|
+
if (bidiMatch) {
|
|
168
|
+
issues.push({
|
|
169
|
+
kind: "bidi-override", severity: "critical",
|
|
170
|
+
ruleId: codePrefix + ".bidi",
|
|
171
|
+
location: bidiMatch.index,
|
|
172
|
+
snippet: "Unicode bidi override (CVE-2021-42574 Trojan Source)",
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (opts && opts.nullBytePolicy !== "allow") {
|
|
177
|
+
var nullIdx = text.indexOf(NULL_BYTE);
|
|
178
|
+
if (nullIdx >= 0) {
|
|
179
|
+
issues.push({
|
|
180
|
+
kind: "null-byte", severity: "critical",
|
|
181
|
+
ruleId: codePrefix + ".null-byte",
|
|
182
|
+
location: nullIdx,
|
|
183
|
+
snippet: "null byte at byte " + nullIdx,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (opts && opts.controlPolicy !== "allow") {
|
|
188
|
+
var ctrlMatch = text.match(C0_CTRL_RE);
|
|
189
|
+
if (ctrlMatch) {
|
|
190
|
+
issues.push({
|
|
191
|
+
kind: "control-char", severity: "high",
|
|
192
|
+
ruleId: codePrefix + ".control",
|
|
193
|
+
location: ctrlMatch.index,
|
|
194
|
+
snippet: "C0 control char U+" + ctrlMatch[0].charCodeAt(0).toString(HEX_RADIX),
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return issues;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// assertNoCharThreats — throws an instance of errorFactory(code, msg)
|
|
202
|
+
// when the text contains a class that's set to "reject" in opts.
|
|
203
|
+
// Opt-name vocabulary: bidiPolicy / nullBytePolicy / controlPolicy
|
|
204
|
+
// (the standard guard-* family naming; older guard-csv uses different
|
|
205
|
+
// names and keeps its inline checks).
|
|
206
|
+
function assertNoCharThreats(text, opts, errorFactory, codePrefix) {
|
|
207
|
+
if (typeof text !== "string") return;
|
|
208
|
+
if (opts && opts.bidiPolicy === "reject" && BIDI_RE.test(text)) { // allow:regex-no-length-cap — caller bounds length before invoking
|
|
209
|
+
throw errorFactory(codePrefix + ".bidi",
|
|
210
|
+
"input contains Unicode bidi override (CVE-2021-42574)");
|
|
211
|
+
}
|
|
212
|
+
if (opts && opts.nullBytePolicy === "reject" && text.indexOf(NULL_BYTE) !== -1) {
|
|
213
|
+
throw errorFactory(codePrefix + ".null-byte",
|
|
214
|
+
"input contains null byte");
|
|
215
|
+
}
|
|
216
|
+
if (opts && opts.controlPolicy === "reject" && C0_CTRL_RE.test(text)) { // allow:regex-no-length-cap — caller bounds length before invoking
|
|
217
|
+
throw errorFactory(codePrefix + ".control",
|
|
218
|
+
"input contains C0 control character");
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// applyCharStripPolicies — given a text and a policy object, apply
|
|
223
|
+
// strip-mode replacements for each character-class threat. Reads:
|
|
224
|
+
// opts.bidiPolicy === "strip" -> strip BIDI overrides
|
|
225
|
+
// opts.controlPolicy === "strip" -> strip C0 controls
|
|
226
|
+
// opts.nullBytePolicy === "strip" -> strip null bytes
|
|
227
|
+
// opts.zeroWidthPolicy === "strip" -> strip zero-widths
|
|
228
|
+
// Returns the cleaned string. Used by every guard's sanitize path so
|
|
229
|
+
// each one doesn't reinvent the same sequence of replace() calls.
|
|
230
|
+
function applyCharStripPolicies(text, opts) {
|
|
231
|
+
if (typeof text !== "string") return text;
|
|
232
|
+
var out = text;
|
|
233
|
+
if (opts && opts.bidiPolicy === "strip") out = out.replace(BIDI_RE_G, "");
|
|
234
|
+
if (opts && opts.controlPolicy === "strip") out = out.replace(C0_CTRL_RE_G, "");
|
|
235
|
+
if (opts && opts.nullBytePolicy === "strip") out = out.replace(NULL_RE_G, "");
|
|
236
|
+
if (opts && opts.zeroWidthPolicy === "strip") out = out.replace(ZW_RE_G, "");
|
|
237
|
+
return out;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
module.exports = {
|
|
241
|
+
hex4: hex4,
|
|
242
|
+
charClass: charClass,
|
|
243
|
+
fromCp: fromCp,
|
|
244
|
+
BIDI_RANGES: BIDI_RANGES,
|
|
245
|
+
C0_CTRL_RANGES: C0_CTRL_RANGES,
|
|
246
|
+
ZERO_WIDTH_RANGES: ZERO_WIDTH_RANGES,
|
|
247
|
+
BIDI_RE: BIDI_RE,
|
|
248
|
+
BIDI_RE_G: BIDI_RE_G,
|
|
249
|
+
C0_CTRL_RE: C0_CTRL_RE,
|
|
250
|
+
C0_CTRL_RE_G: C0_CTRL_RE_G,
|
|
251
|
+
ZERO_WIDTH_RE: ZERO_WIDTH_RE,
|
|
252
|
+
ZW_RE_G: ZW_RE_G,
|
|
253
|
+
NULL_RE_G: NULL_RE_G,
|
|
254
|
+
NULL_BYTE: NULL_BYTE,
|
|
255
|
+
BOM_CHAR: BOM_CHAR,
|
|
256
|
+
applyCharStripPolicies: applyCharStripPolicies,
|
|
257
|
+
assertNoCharThreats: assertNoCharThreats,
|
|
258
|
+
detectCharThreats: detectCharThreats,
|
|
259
|
+
SCRIPT_RANGES: SCRIPT_RANGES,
|
|
260
|
+
scriptFor: scriptFor,
|
|
261
|
+
detectMixedScripts: detectMixedScripts,
|
|
262
|
+
};
|