@blamejs/core 0.7.43 → 0.7.44
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 +2 -0
- package/index.js +2 -0
- package/lib/framework-error.js +8 -0
- package/lib/guard-all.js +1 -0
- package/lib/guard-uuid.js +389 -0
- package/package.json +1 -1
- package/sbom.cyclonedx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,8 @@ upgrading across more than a few patches at a time.
|
|
|
8
8
|
|
|
9
9
|
## v0.7.x
|
|
10
10
|
|
|
11
|
+
- **0.7.44** (2026-05-05) — `b.guardUuid` — UUID identifier-safety primitive (KIND="identifier"). Validates user-supplied UUID strings per RFC 9562 (May 2024, obsoletes RFC 4122). Threat catalog: shape malformation across the four canonical forms (hyphenated 8-4-4-4-12, hyphenless 32-hex, Microsoft GUID braces `{…}`, `urn:uuid:` prefix); RFC 9562 §4.2 unassigned version digits (only 1-8 are defined); non-RFC 4122 variant bits (only the `10xx` high-bits family is the canonical UUID variant); nil UUID §5.9 / max UUID §5.10 sentinel-leak refuse; format policy enforcement (strict default = `hyphenated-only`); BIDI / zero-width / control / null-byte universal refuse via `lib/codepoint-class.js`. `sanitize` returns canonical lowercase hyphenated form (strips braces / urn prefix). Profiles: `strict` (hyphenated-only, refuse all sentinels and non-canonical forms), `balanced` (accept any form, audit sentinels), `permissive` (universal-refuse class still refused). Postures: `hipaa` / `pci-dss` / `soc2` strict overlay; `gdpr` balanced overlay. Auto-registers into `b.guardAll` as a STANDALONE_GUARD; the adaptive integration harness picks it up automatically via the KIND="identifier" dispatcher.
|
|
12
|
+
|
|
11
13
|
- **0.7.43** (2026-05-05) — `b.guardDomain` — domain-name identifier-safety primitive (KIND="identifier"). Validates user-supplied DNS names destined for allowlists, redirect targets, webhook endpoints, email-domain extraction, and CORS origin checks. Threat catalog: RFC 1035 §2.3.4 length caps (63 octets per label, 253 octets per FQDN), RFC 952 / 1123 LDH-rule violations (no leading/trailing hyphen, no `--` at positions 3-4 except `xn--`), IDN homograph mixed-script confusables (Latin / Cyrillic / Greek / Cherokee / Armenian / Han / Hiragana / Katakana / Hangul / Arabic / Hebrew range tables), BIDI / zero-width / control / null universal refuse via `lib/codepoint-class.js` (CVE-2021-42574 Trojan Source class), Punycode A-label malformation (bare `xn--`, double-encoded), RFC 6761 special-use suffix matching (`.localhost` / `.local` / `.invalid` / `.test` / `.onion` / `.alt` / `.home.arpa` / `.internal`), IPv4-as-domain confusion (CVE-2021-22931 — dotted-decimal / octal / hex / long-decimal forms), IPv6 bracket-literal, single-label / TLD-only refuse, wildcard `*` label refuse at every profile, RFC 8552 underscore-service-label policy, DGA Shannon-entropy heuristic for high-entropy long single labels (Mirai / Conficker C2 shape). Profiles: `strict` (Latin-only scripts, refuse-on-everything), `balanced` (audit Punycode, allow major international scripts, audit DGA), `permissive` (universal-refuse class still refused; everything else audit / allow). Compliance postures: `hipaa` / `pci-dss` / `soc2` strict overlay; `gdpr` balanced overlay. Auto-registers into `b.guardAll` as a STANDALONE_GUARD; the adaptive integration harness at `test/layer-5-integration/guard-host-integration.test.js` picks it up automatically. Defer-with-condition: full UTS #46 ToASCII / ToUnicode round-trip and Public-Suffix-List boundary enforcement ship behind operator-supplied callbacks (`opts.idnToAscii`, `opts.publicSuffixList`); re-open conditions are documented in the wiki page.
|
|
12
14
|
|
|
13
15
|
- **0.7.42** (2026-05-05) — gitleaks allowlist for v0.7.28 CHANGELOG doc-snippet false-positive. The v0.7.28 release-notes entry for `b.crypto.encryptMlkem768X25519` includes a JS-object-literal code example with a `privateKey: mlkemPrivateKey` field; gitleaks' default `generic-api-key` rule fires on the high-entropy content adjacent to the `privateKey:` token, blocking every release-tag CI run since v0.7.38. Allowlisted by commit + fingerprint per the same pattern as the existing `74e627e` entry — surgical suppression of the documented false positive, no broader rule weakening. Working-tree-only or rule-disabling alternatives were rejected: gitleaks' `git`-history scan is the stricter gate (catches secrets that landed and were later removed) and the documented snippet contains no real secret.
|
package/index.js
CHANGED
|
@@ -111,6 +111,7 @@ var guardXml = require("./lib/guard-xml");
|
|
|
111
111
|
var guardMarkdown = require("./lib/guard-markdown");
|
|
112
112
|
var guardEmail = require("./lib/guard-email");
|
|
113
113
|
var guardDomain = require("./lib/guard-domain");
|
|
114
|
+
var guardUuid = require("./lib/guard-uuid");
|
|
114
115
|
var guardAll = require("./lib/guard-all");
|
|
115
116
|
var ssrfGuard = require("./lib/ssrf-guard");
|
|
116
117
|
var authHeader = require("./lib/auth-header");
|
|
@@ -253,6 +254,7 @@ module.exports = {
|
|
|
253
254
|
guardMarkdown: guardMarkdown,
|
|
254
255
|
guardEmail: guardEmail,
|
|
255
256
|
guardDomain: guardDomain,
|
|
257
|
+
guardUuid: guardUuid,
|
|
256
258
|
guardAll: guardAll,
|
|
257
259
|
ssrfGuard: ssrfGuard,
|
|
258
260
|
authHeader: authHeader,
|
package/lib/framework-error.js
CHANGED
|
@@ -257,6 +257,13 @@ var GuardEmailError = defineClass("GuardEmailError", { alwaysPermane
|
|
|
257
257
|
// only strings, wildcard labels, RFC 8552 underscore-label misuse, DGA
|
|
258
258
|
// high-entropy labels. alwaysPermanent.
|
|
259
259
|
var GuardDomainError = defineClass("GuardDomainError", { alwaysPermanent: true });
|
|
260
|
+
// GuardUuidError covers UUID identifier violations: shape malformation
|
|
261
|
+
// (non-canonical / non-hex), RFC 9562 §4.2 unassigned version digits,
|
|
262
|
+
// non-RFC 4122 variant bits, nil UUID (§5.9) / max UUID (§5.10) sentinel
|
|
263
|
+
// leakage, urn:uuid: + Microsoft GUID braces forms outside the operator's
|
|
264
|
+
// declared formatPolicy, BIDI / zero-width / control-byte / null-byte
|
|
265
|
+
// universal refuse. alwaysPermanent.
|
|
266
|
+
var GuardUuidError = defineClass("GuardUuidError", { alwaysPermanent: true });
|
|
260
267
|
// DoraError covers DORA Article 17 incident-reporting workflow errors
|
|
261
268
|
// (classification refusal, report-shape validation, ESA-template
|
|
262
269
|
// generation, audit-chain integration). Permanent — these are
|
|
@@ -317,6 +324,7 @@ module.exports = {
|
|
|
317
324
|
GuardMarkdownError: GuardMarkdownError,
|
|
318
325
|
GuardEmailError: GuardEmailError,
|
|
319
326
|
GuardDomainError: GuardDomainError,
|
|
327
|
+
GuardUuidError: GuardUuidError,
|
|
320
328
|
DoraError: DoraError,
|
|
321
329
|
ComplianceError: ComplianceError,
|
|
322
330
|
SmtpPolicyError: SmtpPolicyError,
|
package/lib/guard-all.js
CHANGED
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* guard-uuid — UUID identifier-safety primitive (b.guardUuid).
|
|
4
|
+
*
|
|
5
|
+
* Validates user-supplied UUID strings per RFC 9562 (May 2024,
|
|
6
|
+
* obsoletes RFC 4122). KIND="identifier" — consumes ctx.identifier
|
|
7
|
+
* (or ctx.uuid).
|
|
8
|
+
*
|
|
9
|
+
* Threat catalog:
|
|
10
|
+
* - Wrong length / shape — UUIDs are 36 chars with hyphens, 32 hex
|
|
11
|
+
* without, or 38 with Microsoft GUID braces; anything else is
|
|
12
|
+
* malformed and a downstream parser may diverge.
|
|
13
|
+
* - Wrong character class — non-hex characters anywhere.
|
|
14
|
+
* - Invalid version field (RFC 9562 §4.2) — versions 1-8 are
|
|
15
|
+
* defined; 0 and 9-F are reserved/unassigned and indicate
|
|
16
|
+
* hand-rolled or attacker-shaped IDs.
|
|
17
|
+
* - Variant bits (RFC 9562 §4.1) — only 10xx (RFC 4122/9562
|
|
18
|
+
* variant) is the canonical UUID variant; other variants
|
|
19
|
+
* (NCS-reserved 0xxx, Microsoft-reserved 110x, future-reserved
|
|
20
|
+
* 111x) often indicate non-UUID payloads coerced into the slot.
|
|
21
|
+
* - Nil UUID (RFC 9562 §5.9 — all zeros) — usually represents
|
|
22
|
+
* "no UUID set"; passing through can mask a missing-key bug.
|
|
23
|
+
* - Max UUID (RFC 9562 §5.10 — all FF) — sentinel value with the
|
|
24
|
+
* same semantic risk as nil.
|
|
25
|
+
* - urn:uuid: prefix (RFC 4122 §3) — when not requested by the
|
|
26
|
+
* caller, can disguise a UUID inside a URN-shape parser.
|
|
27
|
+
* - Microsoft GUID braces `{...}` — disguise a UUID inside a
|
|
28
|
+
* COM-style serialization parser.
|
|
29
|
+
* - BIDI / zero-width / control / null-byte — universal-refuse.
|
|
30
|
+
*
|
|
31
|
+
* var rv = b.guardUuid.validate("550e8400-e29b-41d4-a716-446655440000",
|
|
32
|
+
* { profile: "strict" });
|
|
33
|
+
* var safe = b.guardUuid.sanitize("urn:uuid:550E8400-...",
|
|
34
|
+
* { profile: "balanced" });
|
|
35
|
+
* var g = b.guardUuid.gate({ profile: "strict" });
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
var codepointClass = require("./codepoint-class");
|
|
39
|
+
var lazyRequire = require("./lazy-require");
|
|
40
|
+
var gateContract = require("./gate-contract");
|
|
41
|
+
var C = require("./constants");
|
|
42
|
+
var numericBounds = require("./numeric-bounds");
|
|
43
|
+
var { GuardUuidError } = require("./framework-error");
|
|
44
|
+
|
|
45
|
+
var observability = lazyRequire(function () { return require("./observability"); });
|
|
46
|
+
void observability;
|
|
47
|
+
|
|
48
|
+
var _err = GuardUuidError.factory;
|
|
49
|
+
|
|
50
|
+
// ---- Static patterns ----
|
|
51
|
+
|
|
52
|
+
// Canonical RFC 9562 form: 8-4-4-4-12 hex chars with dashes.
|
|
53
|
+
var UUID_HYPHENATED_RE = /^([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})$/i;
|
|
54
|
+
|
|
55
|
+
// Hyphenless 32-hex form (some serializers strip the hyphens).
|
|
56
|
+
var UUID_HYPHENLESS_RE = /^[0-9a-f]{32}$/i;
|
|
57
|
+
|
|
58
|
+
// Microsoft GUID-with-braces form.
|
|
59
|
+
var UUID_BRACED_RE = /^\{([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})\}$/i;
|
|
60
|
+
|
|
61
|
+
// urn:uuid: prefix form.
|
|
62
|
+
var UUID_URN_RE = /^urn:uuid:([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})$/i;
|
|
63
|
+
|
|
64
|
+
var NIL_HEX = "00000000000000000000000000000000";
|
|
65
|
+
var MAX_HEX = "ffffffffffffffffffffffffffffffff";
|
|
66
|
+
|
|
67
|
+
// ---- Profile presets ----
|
|
68
|
+
|
|
69
|
+
var PROFILES = Object.freeze({
|
|
70
|
+
"strict": {
|
|
71
|
+
bidiPolicy: "reject",
|
|
72
|
+
controlPolicy: "reject",
|
|
73
|
+
nullBytePolicy: "reject",
|
|
74
|
+
zeroWidthPolicy: "reject",
|
|
75
|
+
formatPolicy: "hyphenated-only", // hyphenated | hyphenless | braced | urn | hyphenated-only | any
|
|
76
|
+
versionPolicy: "reject-unassigned", // reject-unassigned | audit | allow
|
|
77
|
+
variantPolicy: "reject-non-rfc", // reject-non-rfc | audit | allow
|
|
78
|
+
nilPolicy: "reject",
|
|
79
|
+
maxPolicy: "reject",
|
|
80
|
+
urnPolicy: "reject",
|
|
81
|
+
bracedPolicy: "reject",
|
|
82
|
+
allowedVersions: [1, 2, 3, 4, 5, 6, 7, 8], // allow:raw-byte-literal — UUID version digits
|
|
83
|
+
maxBytes: C.BYTES.bytes(64),
|
|
84
|
+
maxRuntimeMs: C.TIME.seconds(2),
|
|
85
|
+
},
|
|
86
|
+
"balanced": {
|
|
87
|
+
bidiPolicy: "reject",
|
|
88
|
+
controlPolicy: "reject",
|
|
89
|
+
nullBytePolicy: "reject",
|
|
90
|
+
zeroWidthPolicy: "reject",
|
|
91
|
+
formatPolicy: "any",
|
|
92
|
+
versionPolicy: "reject-unassigned",
|
|
93
|
+
variantPolicy: "audit",
|
|
94
|
+
nilPolicy: "audit",
|
|
95
|
+
maxPolicy: "audit",
|
|
96
|
+
urnPolicy: "audit",
|
|
97
|
+
bracedPolicy: "audit",
|
|
98
|
+
allowedVersions: [1, 2, 3, 4, 5, 6, 7, 8], // allow:raw-byte-literal — UUID version digits
|
|
99
|
+
maxBytes: C.BYTES.bytes(64),
|
|
100
|
+
maxRuntimeMs: C.TIME.seconds(2),
|
|
101
|
+
},
|
|
102
|
+
"permissive": {
|
|
103
|
+
bidiPolicy: "reject", // BIDI refused at every profile
|
|
104
|
+
controlPolicy: "reject", // controls refused at every profile
|
|
105
|
+
nullBytePolicy: "reject", // null refused at every profile
|
|
106
|
+
zeroWidthPolicy: "reject", // zero-width refused at every profile
|
|
107
|
+
formatPolicy: "any",
|
|
108
|
+
versionPolicy: "audit",
|
|
109
|
+
variantPolicy: "allow",
|
|
110
|
+
nilPolicy: "allow",
|
|
111
|
+
maxPolicy: "allow",
|
|
112
|
+
urnPolicy: "allow",
|
|
113
|
+
bracedPolicy: "allow",
|
|
114
|
+
allowedVersions: null, // any version
|
|
115
|
+
maxBytes: C.BYTES.bytes(64),
|
|
116
|
+
maxRuntimeMs: C.TIME.seconds(2),
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
var DEFAULTS = Object.freeze(Object.assign({}, PROFILES["strict"], {
|
|
121
|
+
mode: "enforce",
|
|
122
|
+
}));
|
|
123
|
+
|
|
124
|
+
var COMPLIANCE_POSTURES = Object.freeze({
|
|
125
|
+
"hipaa": Object.assign({}, PROFILES["strict"], {
|
|
126
|
+
forensicSnippetBytes: C.BYTES.bytes(128),
|
|
127
|
+
}),
|
|
128
|
+
"pci-dss": Object.assign({}, PROFILES["strict"], {
|
|
129
|
+
forensicSnippetBytes: C.BYTES.bytes(128),
|
|
130
|
+
}),
|
|
131
|
+
"gdpr": Object.assign({}, PROFILES["balanced"], {
|
|
132
|
+
forensicSnippetBytes: C.BYTES.bytes(64),
|
|
133
|
+
}),
|
|
134
|
+
"soc2": Object.assign({}, PROFILES["strict"], {
|
|
135
|
+
forensicSnippetBytes: C.BYTES.bytes(256),
|
|
136
|
+
}),
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
function _resolveOpts(opts) {
|
|
140
|
+
return gateContract.resolveProfileAndPosture(opts, {
|
|
141
|
+
profiles: PROFILES,
|
|
142
|
+
compliancePostures: COMPLIANCE_POSTURES,
|
|
143
|
+
defaults: DEFAULTS,
|
|
144
|
+
errorClass: GuardUuidError,
|
|
145
|
+
errCodePrefix: "uuid",
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function _classifyForm(input) {
|
|
150
|
+
if (UUID_URN_RE.test(input)) return "urn"; // allow:regex-no-length-cap — input bounded by maxBytes
|
|
151
|
+
if (UUID_BRACED_RE.test(input)) return "braced"; // allow:regex-no-length-cap — input bounded by maxBytes
|
|
152
|
+
if (UUID_HYPHENATED_RE.test(input)) return "hyphenated"; // allow:regex-no-length-cap — input bounded by maxBytes
|
|
153
|
+
if (UUID_HYPHENLESS_RE.test(input)) return "hyphenless"; // allow:regex-no-length-cap — input bounded by maxBytes
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function _toCanonicalHex(input, form) {
|
|
158
|
+
// Strips dashes / braces / urn prefix, returns 32-char lowercase hex.
|
|
159
|
+
var s = input.toLowerCase();
|
|
160
|
+
if (form === "urn") s = s.slice("urn:uuid:".length); // allow:raw-byte-literal — string-length offset
|
|
161
|
+
if (form === "braced") s = s.slice(1, -1); // allow:raw-byte-literal — string-length offset
|
|
162
|
+
return s.replace(/-/g, "");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function _detectIssues(input, opts) {
|
|
166
|
+
var issues = [];
|
|
167
|
+
if (typeof input !== "string") {
|
|
168
|
+
return [{ kind: "bad-input", severity: "high",
|
|
169
|
+
ruleId: "uuid.bad-input",
|
|
170
|
+
snippet: "uuid is not a string" }];
|
|
171
|
+
}
|
|
172
|
+
if (input.length === 0) {
|
|
173
|
+
return [{ kind: "empty", severity: "high",
|
|
174
|
+
ruleId: "uuid.empty",
|
|
175
|
+
snippet: "uuid is empty" }];
|
|
176
|
+
}
|
|
177
|
+
if (Buffer.byteLength(input, "utf8") > opts.maxBytes) {
|
|
178
|
+
return [{ kind: "uuid-cap", severity: "high",
|
|
179
|
+
ruleId: "uuid.uuid-cap",
|
|
180
|
+
snippet: "uuid input exceeds maxBytes " + opts.maxBytes }];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Codepoint-class threats (universal refuse — runs first).
|
|
184
|
+
var charThreats = codepointClass.detectCharThreats(input, opts, "uuid");
|
|
185
|
+
for (var ci = 0; ci < charThreats.length; ci += 1) issues.push(charThreats[ci]);
|
|
186
|
+
|
|
187
|
+
// Format classification.
|
|
188
|
+
var form = _classifyForm(input);
|
|
189
|
+
if (form === null) {
|
|
190
|
+
issues.push({
|
|
191
|
+
kind: "uuid-shape", severity: "high",
|
|
192
|
+
ruleId: "uuid.uuid-shape",
|
|
193
|
+
snippet: "input does not match any RFC 9562 UUID form " +
|
|
194
|
+
"(hyphenated / hyphenless / braced / urn:uuid:)",
|
|
195
|
+
});
|
|
196
|
+
return issues;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Format-policy enforcement.
|
|
200
|
+
var formatPolicy = opts.formatPolicy;
|
|
201
|
+
var formAllowed = (
|
|
202
|
+
formatPolicy === "any" ||
|
|
203
|
+
formatPolicy === form ||
|
|
204
|
+
(formatPolicy === "hyphenated-only" && form === "hyphenated")
|
|
205
|
+
);
|
|
206
|
+
if (!formAllowed) {
|
|
207
|
+
issues.push({
|
|
208
|
+
kind: "uuid-form-disallowed",
|
|
209
|
+
severity: "high",
|
|
210
|
+
ruleId: "uuid.uuid-form-disallowed",
|
|
211
|
+
snippet: "uuid form `" + form + "` not permitted by formatPolicy `" +
|
|
212
|
+
formatPolicy + "`",
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
if (form === "urn" && opts.urnPolicy !== "allow") {
|
|
216
|
+
issues.push({
|
|
217
|
+
kind: "urn-prefix",
|
|
218
|
+
severity: opts.urnPolicy === "reject" ? "high" : "warn",
|
|
219
|
+
ruleId: "uuid.urn-prefix",
|
|
220
|
+
snippet: "uuid carries `urn:uuid:` prefix — would be processed " +
|
|
221
|
+
"by URN-shape parsers downstream",
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
if (form === "braced" && opts.bracedPolicy !== "allow") {
|
|
225
|
+
issues.push({
|
|
226
|
+
kind: "braced",
|
|
227
|
+
severity: opts.bracedPolicy === "reject" ? "high" : "warn",
|
|
228
|
+
ruleId: "uuid.braced",
|
|
229
|
+
snippet: "uuid uses Microsoft GUID braces `{...}` — non-canonical",
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
var hex = _toCanonicalHex(input, form);
|
|
234
|
+
|
|
235
|
+
// Nil / Max sentinel checks.
|
|
236
|
+
if (hex === NIL_HEX && opts.nilPolicy !== "allow") {
|
|
237
|
+
issues.push({
|
|
238
|
+
kind: "nil-uuid",
|
|
239
|
+
severity: opts.nilPolicy === "reject" ? "high" : "warn",
|
|
240
|
+
ruleId: "uuid.nil-uuid",
|
|
241
|
+
snippet: "uuid is the nil UUID (RFC 9562 §5.9) — sentinel often " +
|
|
242
|
+
"indicates missing-key bug",
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
if (hex === MAX_HEX && opts.maxPolicy !== "allow") {
|
|
246
|
+
issues.push({
|
|
247
|
+
kind: "max-uuid",
|
|
248
|
+
severity: opts.maxPolicy === "reject" ? "high" : "warn",
|
|
249
|
+
ruleId: "uuid.max-uuid",
|
|
250
|
+
snippet: "uuid is the max UUID (RFC 9562 §5.10) — sentinel often " +
|
|
251
|
+
"indicates missing-key bug",
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Version + variant inspection (skip for nil / max — those bypass the
|
|
256
|
+
// version-bits check by definition).
|
|
257
|
+
if (hex !== NIL_HEX && hex !== MAX_HEX) {
|
|
258
|
+
var versionDigit = parseInt(hex.charAt(12), 16); // allow:raw-byte-literal — hex digit position 12
|
|
259
|
+
var variantNibble = parseInt(hex.charAt(16), 16); // allow:raw-byte-literal — hex digit position 16
|
|
260
|
+
|
|
261
|
+
if (opts.versionPolicy !== "allow") {
|
|
262
|
+
var allowed = opts.allowedVersions;
|
|
263
|
+
var versionOk = !allowed || allowed.indexOf(versionDigit) !== -1;
|
|
264
|
+
if (!versionOk) {
|
|
265
|
+
issues.push({
|
|
266
|
+
kind: "version-unassigned",
|
|
267
|
+
severity: opts.versionPolicy === "reject-unassigned" ? "high" : "warn",
|
|
268
|
+
ruleId: "uuid.version-unassigned",
|
|
269
|
+
snippet: "uuid version digit " + versionDigit + " not in " +
|
|
270
|
+
"allowedVersions " + JSON.stringify(allowed) +
|
|
271
|
+
" (RFC 9562 §4.2 defines 1-8)",
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (opts.variantPolicy !== "allow") {
|
|
277
|
+
// RFC 4122 / 9562 variant: high two bits of the variant nibble are
|
|
278
|
+
// 10xx (i.e. nibble in 8/9/a/b).
|
|
279
|
+
var isRfcVariant = (variantNibble & 0xC) === 0x8; // allow:raw-byte-literal — variant-bit mask
|
|
280
|
+
if (!isRfcVariant) {
|
|
281
|
+
issues.push({
|
|
282
|
+
kind: "variant-non-rfc",
|
|
283
|
+
severity: opts.variantPolicy === "reject-non-rfc" ? "high" : "warn",
|
|
284
|
+
ruleId: "uuid.variant-non-rfc",
|
|
285
|
+
snippet: "uuid variant nibble `" + hex.charAt(16) + "` is not " + // allow:raw-byte-literal — hex digit position 16
|
|
286
|
+
"the RFC 4122 / 9562 variant (10xx — nibble 8-b)",
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return issues;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function validate(input, opts) {
|
|
296
|
+
opts = _resolveOpts(opts);
|
|
297
|
+
numericBounds.requireAllPositiveFiniteIntIfPresent(opts,
|
|
298
|
+
["maxBytes"],
|
|
299
|
+
"guardUuid.validate", GuardUuidError, "uuid.bad-opt");
|
|
300
|
+
if (typeof input !== "string") {
|
|
301
|
+
return {
|
|
302
|
+
ok: false,
|
|
303
|
+
issues: [{ kind: "bad-input", severity: "high",
|
|
304
|
+
ruleId: "uuid.bad-input",
|
|
305
|
+
snippet: "uuid is not a string" }],
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
return gateContract.aggregateIssues(_detectIssues(input, opts));
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function sanitize(input, opts) {
|
|
312
|
+
opts = _resolveOpts(opts);
|
|
313
|
+
if (typeof input !== "string") {
|
|
314
|
+
throw _err("uuid.bad-input", "sanitize requires string input");
|
|
315
|
+
}
|
|
316
|
+
var issues = _detectIssues(input, opts);
|
|
317
|
+
for (var i = 0; i < issues.length; i += 1) {
|
|
318
|
+
if (issues[i].severity === "critical" || issues[i].severity === "high") {
|
|
319
|
+
throw _err(issues[i].ruleId || "uuid.refused",
|
|
320
|
+
"guardUuid.sanitize: " + issues[i].snippet);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
// Safe transforms: lowercase + strip braces / urn prefix → canonical
|
|
324
|
+
// hyphenated form.
|
|
325
|
+
var form = _classifyForm(input);
|
|
326
|
+
if (!form) return input;
|
|
327
|
+
var hex = _toCanonicalHex(input, form);
|
|
328
|
+
return hex.slice(0, 8) + "-" + hex.slice(8, 12) + "-" + // allow:raw-byte-literal — UUID hex slice positions
|
|
329
|
+
hex.slice(12, 16) + "-" + hex.slice(16, 20) + "-" + // allow:raw-byte-literal — UUID hex slice positions
|
|
330
|
+
hex.slice(20); // allow:raw-byte-literal — UUID hex slice positions
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function gate(opts) {
|
|
334
|
+
opts = _resolveOpts(opts);
|
|
335
|
+
return gateContract.buildGuardGate(
|
|
336
|
+
opts.name || "guardUuid:" + (opts.profile || "default"),
|
|
337
|
+
opts,
|
|
338
|
+
async function (ctx) {
|
|
339
|
+
var identifier = ctx && (ctx.identifier || ctx.uuid || "");
|
|
340
|
+
if (!identifier) return { ok: true, action: "serve" };
|
|
341
|
+
var rv = validate(identifier, opts);
|
|
342
|
+
if (rv.issues.length === 0) return { ok: true, action: "serve" };
|
|
343
|
+
var hasCritical = rv.issues.some(function (i) {
|
|
344
|
+
return i.severity === "critical";
|
|
345
|
+
});
|
|
346
|
+
var hasHigh = rv.issues.some(function (i) {
|
|
347
|
+
return i.severity === "high";
|
|
348
|
+
});
|
|
349
|
+
if (!hasCritical && !hasHigh) {
|
|
350
|
+
return { ok: true, action: "audit-only", issues: rv.issues };
|
|
351
|
+
}
|
|
352
|
+
return { ok: false, action: "refuse", issues: rv.issues };
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
var buildProfile = gateContract.makeProfileBuilder(PROFILES);
|
|
357
|
+
|
|
358
|
+
function compliancePosture(name) {
|
|
359
|
+
return gateContract.lookupCompliancePosture(name, COMPLIANCE_POSTURES,
|
|
360
|
+
_err, "uuid");
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
var _uuidRulePacks = gateContract.makeRulePackLoader(GuardUuidError, "uuid");
|
|
364
|
+
var loadRulePack = _uuidRulePacks.load;
|
|
365
|
+
|
|
366
|
+
module.exports = {
|
|
367
|
+
// ---- guard-* family registry exports ----
|
|
368
|
+
NAME: "uuid",
|
|
369
|
+
KIND: "identifier",
|
|
370
|
+
INTEGRATION_FIXTURES: Object.freeze({
|
|
371
|
+
kind: "identifier",
|
|
372
|
+
benignBytes: Buffer.from("550e8400-e29b-41d4-a716-446655440000", "utf8"),
|
|
373
|
+
hostileBytes: Buffer.from("00000000-0000-0000-0000-000000000000", "utf8"),
|
|
374
|
+
benignIdentifier: "550e8400-e29b-41d4-a716-446655440000",
|
|
375
|
+
// Hostile: nil UUID — refused at strict (sentinel-leak class).
|
|
376
|
+
hostileIdentifier: "00000000-0000-0000-0000-000000000000",
|
|
377
|
+
}),
|
|
378
|
+
// ---- primitive surface ----
|
|
379
|
+
validate: validate,
|
|
380
|
+
sanitize: sanitize,
|
|
381
|
+
gate: gate,
|
|
382
|
+
buildProfile: buildProfile,
|
|
383
|
+
compliancePosture: compliancePosture,
|
|
384
|
+
loadRulePack: loadRulePack,
|
|
385
|
+
PROFILES: PROFILES,
|
|
386
|
+
DEFAULTS: DEFAULTS,
|
|
387
|
+
COMPLIANCE_POSTURES: COMPLIANCE_POSTURES,
|
|
388
|
+
GuardUuidError: GuardUuidError,
|
|
389
|
+
};
|
package/package.json
CHANGED
package/sbom.cyclonedx.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
|
|
3
3
|
"bomFormat": "CycloneDX",
|
|
4
4
|
"specVersion": "1.5",
|
|
5
|
-
"serialNumber": "urn:uuid:
|
|
5
|
+
"serialNumber": "urn:uuid:2273cc28-4cb1-4b48-8b09-c33dd95383b2",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-05-
|
|
8
|
+
"timestamp": "2026-05-05T21:06:13.872Z",
|
|
9
9
|
"lifecycles": [
|
|
10
10
|
{
|
|
11
11
|
"phase": "build"
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
}
|
|
20
20
|
],
|
|
21
21
|
"component": {
|
|
22
|
-
"bom-ref": "@blamejs/core@0.7.
|
|
22
|
+
"bom-ref": "@blamejs/core@0.7.44",
|
|
23
23
|
"type": "library",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.7.
|
|
25
|
+
"version": "0.7.44",
|
|
26
26
|
"scope": "required",
|
|
27
27
|
"author": "blamejs contributors",
|
|
28
28
|
"description": "The Node framework that owns its stack.",
|
|
29
|
-
"purl": "pkg:npm/%40blamejs/core@0.7.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/core@0.7.44",
|
|
30
30
|
"properties": [],
|
|
31
31
|
"externalReferences": [
|
|
32
32
|
{
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"components": [],
|
|
55
55
|
"dependencies": [
|
|
56
56
|
{
|
|
57
|
-
"ref": "@blamejs/core@0.7.
|
|
57
|
+
"ref": "@blamejs/core@0.7.44",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|