@blamejs/core 0.15.34 → 0.15.36
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 +4 -0
- package/lib/calendar.js +6 -6
- package/lib/http-client-cache.js +2 -2
- package/lib/http-message-signature.js +3 -3
- package/lib/metrics.js +45 -26
- package/lib/middleware/body-parser.js +1 -1
- package/lib/middleware/scim-server.js +2 -2
- package/lib/network-smtp-policy.js +1 -1
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,10 @@ upgrading across more than a few patches at a time.
|
|
|
8
8
|
|
|
9
9
|
## v0.15.x
|
|
10
10
|
|
|
11
|
+
- v0.15.36 (2026-06-27) — **Internal test-suite hardening only — the published library's runtime behavior and public API are unchanged (source-comment marker text aside).** The codebase-patterns guard class that allows a bare comma/semicolon split on token-only RFC header grammars was re-verified from scratch and renamed to a descriptive token, with its old name recorded as retired. Each live marked site (RRULE, RFC 9421 component identifiers, TLS-RPT rua, SCIM attribute paths) was re-read and confirmed to split a grammar with no quoted-string members. Five marker comments that suppressed nothing were removed or turned into plain explanatory comments. No runtime code, public API, or wire format changed. **Detectors:** *Bare token-only header-split suppression class re-verified, renamed, and pruned of inert markers* — Each marked bare `.split(",")` / `.split(";")` on an RFC header value was re-read and confirmed to operate on a token-only grammar (no quoted-string members, so a quote-aware splitter is unnecessary): RFC 5545 RRULE, RFC 9421 signature component identifiers, RFC 8460 TLS-RPT rua, and RFC 7644 SCIM attribute paths. The guard class was renamed to a descriptive token and its old name added to the retired-token set. Five marker comments that the detector never actually evaluated (two sat on a date-normalizing `.replace`, three in header parsers the guard intentionally does not scan) were removed or converted to plain comments. This is test-suite tooling plus source-comment text; no shipped framework behavior changed.
|
|
12
|
+
|
|
13
|
+
- v0.15.35 (2026-06-26) — **`b.metrics` shadow registry no longer lets a comma in a label value forge extra Prometheus label pairs — a label-injection that downstream tenant-scoping or authorization selectors could be tricked by.** The shadow registry serialized a metric's label set into a `name=value,name=value` string key and re-split it on `,` to build the Prometheus exposition. A label VALUE containing a comma therefore split into multiple label pairs, so a single operator-set label could forge additional label pairs in the scrape output (for example turning one `route` label into a `route` plus an attacker-named label), which downstream tenant-scoping filters, authorization selectors, recording rules, and alerting trust as a boundary; two distinct label sets could also collide into one cardinality bucket. The label key now uses canonical-JSON of the string-coerced label set (the same approach the main registry already used) and the render path emits the structured label set kept alongside that key rather than splitting or re-parsing, so a `,` or `=` inside a label value stays inside the value and a label NAMED `constructor`, `prototype`, or `__proto__` (all valid Prometheus label names) is preserved instead of being dropped. Note: the cardinality keys exposed by `shadowRegistry().snapshot()` are now canonical-JSON strings (e.g. `{"route":"/api"}`) rather than `route=/api`. **Changed:** *shadowRegistry().snapshot() cardinality keys are canonical-JSON* — As a consequence of the label-injection fix, the per-series cardinality keys in a shadow-registry snapshot are now canonical-JSON strings (e.g. `{"tenant":"a"}`) instead of the previous `tenant=a` form. Code that reads those keys directly from snapshot() should parse them as JSON; the Prometheus render output is unchanged for conforming label values. **Security:** *Label values can no longer forge extra Prometheus label pairs in the shadow registry* — b.metrics shadowRegistry built a metric's cardinality key by joining `name=value` pairs with commas, then split that string on commas when rendering the Prometheus exposition. A comma (and `=`) in a label value therefore broke one value into several forged label pairs in the scrape output, and two different label sets could collide into the same cardinality bucket. The key is now canonical-JSON of the string-coerced label set and the render emits the structured label set kept alongside that key rather than splitting or re-parsing, so a label value is emitted as a single quoted value with its commas/equals intact, distinct label sets get distinct keys, and a label NAMED `constructor`, `prototype`, or `__proto__` (each a valid Prometheus label name) is preserved rather than dropped. Deterministic regression tests assert a comma-bearing label value produces exactly one label pair and that the reserved-name labels survive render.
|
|
14
|
+
|
|
11
15
|
- v0.15.34 (2026-06-26) — **`b.mail.crypto.pgp.verify` now accepts every valid RSA OpenPGP signature — one whose value happened to have a high zero byte (about 1 in 256) was being rejected.** An OpenPGP RSA signature is an integer modulo the key's modulus, and ~1 in 256 signatures have a value that begins with a zero byte. The MPI encoding strips those leading zero bytes (RFC 9580 §3.2), but b.mail.crypto.pgp.verify passed the stripped value straight to the RSA verification, which requires a signature exactly the modulus byte length — so a perfectly valid signature was intermittently reported invalid. The signature is now left-padded back to the modulus width before verification, exactly as the Ed25519 path already pads its components. Verification of every valid RSA signature is now reliable, including signatures produced by other OpenPGP implementations. Also includes internal test-tooling: two more guard-suite suppression classes were re-verified and their tokens retired. **Fixed:** *RSA OpenPGP signatures with a high zero byte now verify reliably* — b.mail.crypto.pgp.verify read the RSA signature MPI (whose leading zero bytes the OpenPGP wire format strips) and handed it to the RSA verification without restoring the stripped bytes. Node's RSA verify requires the signature to be exactly the modulus byte length, so a signature whose value had one or more high zero bytes — about 1 in 256 of all signatures, for any key — was rejected as invalid even though it was correct. The signature is now left-padded to the modulus width before verification (the same correction the Ed25519 verification path already applied to its R/S components). This affects signatures the framework produces and signatures from other OpenPGP implementations alike. A deterministic regression test searches for a short-MPI signature and asserts it verifies. **Detectors:** *Two more suppression-marker classes re-verified and their tokens retired* — The raw-hash-compare and seal-without-aad guard classes were re-read and confirmed (a data-residency region tag compared with === — not a secret; and two intentional non-AEAD-bound seals — a non-regulated plain-mode column whose AAD is enforced by the posture seal-envelope floor where it matters, and a throwaway vault-readiness probe), then renamed to descriptive tokens with their old names added to the retired-token set. Test-suite tooling only.
|
|
12
16
|
|
|
13
17
|
- v0.15.33 (2026-06-26) — **Internal test-suite hardening only — the published library's runtime behavior and public API are unchanged (source-comment marker text aside).** Three more suppression classes in the codebase-patterns guard suite were re-verified from scratch and renamed to descriptive tokens, with their old names recorded as retired and their in-source marker comments updated to match. No runtime code, public API, or wire format changed. **Detectors:** *Three more suppression-marker classes re-verified and their tokens retired* — Continuing the re-verification pass: each marked site for the math-random-noncrypto, raw-new-url, and dynamic-require guard classes was re-read and confirmed (non-security jitter/sampling; URL parsing for shape/origin inspection or behind the safe wrapper; operator-supplied module loads), then the class was renamed to a descriptive token and its old name added to the retired-token set. This is test-suite tooling; no shipped framework behavior changed.
|
package/lib/calendar.js
CHANGED
|
@@ -1119,7 +1119,7 @@ function _firstParamValue(prop, paramName) {
|
|
|
1119
1119
|
|
|
1120
1120
|
function _icalRruleToJscal(rrule) {
|
|
1121
1121
|
var out = { "@type": "RecurrenceRule", frequency: "daily" };
|
|
1122
|
-
var parts = String(rrule).split(";"); // allow:bare-split-on-quoted-header — RFC 5545 RRULE grammar has no quoted-string members; values are token-only
|
|
1122
|
+
var parts = String(rrule).split(";"); // allow:bare-split-on-quoted-header-token-grammar — RFC 5545 RRULE grammar has no quoted-string members; values are token-only
|
|
1123
1123
|
for (var i = 0; i < parts.length; i += 1) {
|
|
1124
1124
|
var kv = parts[i].split("=");
|
|
1125
1125
|
if (kv.length !== 2) continue;
|
|
@@ -1129,11 +1129,11 @@ function _icalRruleToJscal(rrule) {
|
|
|
1129
1129
|
else if (key === "INTERVAL") out.interval = parseInt(val, 10);
|
|
1130
1130
|
else if (key === "COUNT") out.count = parseInt(val, 10);
|
|
1131
1131
|
else if (key === "UNTIL") out.until = _icalDateTimeToUtc(val);
|
|
1132
|
-
else if (key === "BYDAY") out.byDay = val.split(",").map(function (d) { // allow:bare-split-on-quoted-header — RFC 5545 BYDAY values are token-only
|
|
1132
|
+
else if (key === "BYDAY") out.byDay = val.split(",").map(function (d) { // allow:bare-split-on-quoted-header-token-grammar — RFC 5545 BYDAY values are token-only
|
|
1133
1133
|
return { "@type": "NDay", day: d.slice(-2).toLowerCase() };
|
|
1134
1134
|
});
|
|
1135
|
-
else if (key === "BYMONTH") out.byMonth = val.split(","); // allow:bare-split-on-quoted-header — RFC 5545 BYMONTH values are integer-only
|
|
1136
|
-
else if (key === "BYMONTHDAY") out.byMonthDay = val.split(",").map(function (n) { return parseInt(n, 10); }); // allow:bare-split-on-quoted-header — RFC 5545 BYMONTHDAY values are integer-only
|
|
1135
|
+
else if (key === "BYMONTH") out.byMonth = val.split(","); // allow:bare-split-on-quoted-header-token-grammar — RFC 5545 BYMONTH values are integer-only
|
|
1136
|
+
else if (key === "BYMONTHDAY") out.byMonthDay = val.split(",").map(function (n) { return parseInt(n, 10); }); // allow:bare-split-on-quoted-header-token-grammar — RFC 5545 BYMONTHDAY values are integer-only
|
|
1137
1137
|
}
|
|
1138
1138
|
return out;
|
|
1139
1139
|
}
|
|
@@ -1168,13 +1168,13 @@ function _utcDateTimeToIcal(s) {
|
|
|
1168
1168
|
// JSCalendar UTCDateTime "2026-05-22T10:00:00.123Z" →
|
|
1169
1169
|
// "20260522T100000Z" (RFC 5545 §3.3.5 form 2 has NO fractional
|
|
1170
1170
|
// seconds; strict ICS consumers reject `T100000.123Z`).
|
|
1171
|
-
return String(s).replace(/\.\d+/, "").replace(/[-:]/g, "");
|
|
1171
|
+
return String(s).replace(/\.\d+/, "").replace(/[-:]/g, "");
|
|
1172
1172
|
}
|
|
1173
1173
|
|
|
1174
1174
|
function _localDateTimeToIcal(s) {
|
|
1175
1175
|
// JSCalendar LocalDateTime "2026-05-22T09:00:00.123" →
|
|
1176
1176
|
// "20260522T090000" (same fractional-second strip as the UTC form).
|
|
1177
|
-
return String(s).replace(/\.\d+/, "").replace(/[-:]/g, "");
|
|
1177
|
+
return String(s).replace(/\.\d+/, "").replace(/[-:]/g, "");
|
|
1178
1178
|
}
|
|
1179
1179
|
|
|
1180
1180
|
function _isUtcDateTime(s) {
|
package/lib/http-client-cache.js
CHANGED
|
@@ -212,7 +212,7 @@ function _buildCacheKey(method, url, varyHeaderValues) {
|
|
|
212
212
|
// uncacheable.
|
|
213
213
|
function _extractVaryValues(varyHeader, requestHeaders) {
|
|
214
214
|
if (typeof varyHeader !== "string" || varyHeader.length === 0) return [];
|
|
215
|
-
var names = varyHeader.split(",").map(function (s) { //
|
|
215
|
+
var names = varyHeader.split(",").map(function (s) { // RFC 9110 §12.5.5 Vary is a comma-list of field-names (token grammar); no quoted-string, so a bare split is correct
|
|
216
216
|
return s.trim().toLowerCase();
|
|
217
217
|
}).filter(function (s) { return s.length > 0; });
|
|
218
218
|
if (names.indexOf("*") !== -1) return null; // sentinel: "uncacheable"
|
|
@@ -262,7 +262,7 @@ function _evaluateStorage(method, statusCode, responseHeaders, sharedCache) {
|
|
|
262
262
|
|
|
263
263
|
// Vary: * is uncacheable per RFC 9110 §12.5.5.
|
|
264
264
|
if (typeof varyHeader === "string" && varyHeader.indexOf("*") !== -1) {
|
|
265
|
-
var trimmed = varyHeader.split(",").map(function (s) { return s.trim(); }); //
|
|
265
|
+
var trimmed = varyHeader.split(",").map(function (s) { return s.trim(); }); // RFC 9110 §12.5.5 Vary field-names; token grammar only, so a bare split is correct
|
|
266
266
|
if (trimmed.indexOf("*") !== -1) {
|
|
267
267
|
return { cacheable: false, reason: "vary-star", freshnessMs: -1, directives: directives, varyHeader: varyHeader };
|
|
268
268
|
}
|
|
@@ -319,7 +319,7 @@ function sign(msg, opts) {
|
|
|
319
319
|
// header isn't already supplied. Operators wanting to use the
|
|
320
320
|
// RFC 9530 "sha-512" identifier (SHA-512 instead of SHA3-512) supply
|
|
321
321
|
// the header themselves; the framework emits SHA3-512.
|
|
322
|
-
var coveredLower = opts.covered.map(function (c) { return c.split(";")[0].toLowerCase(); }); // allow:bare-split-on-quoted-header — opts.covered is operator-supplied component-id list (e.g. "content-digest;sf"); component identifiers are RFC 9421 §2.1 derived-field names with token-only grammar; no quoted-string
|
|
322
|
+
var coveredLower = opts.covered.map(function (c) { return c.split(";")[0].toLowerCase(); }); // allow:bare-split-on-quoted-header-token-grammar — opts.covered is operator-supplied component-id list (e.g. "content-digest;sf"); component identifiers are RFC 9421 §2.1 derived-field names with token-only grammar; no quoted-string
|
|
323
323
|
if (coveredLower.indexOf("content-digest") !== -1 &&
|
|
324
324
|
_resolveHeader(m.headers, "content-digest") === null) {
|
|
325
325
|
if (m.body == null) {
|
|
@@ -451,7 +451,7 @@ function _parseSignature(headerValue, label) {
|
|
|
451
451
|
if (headerValue.indexOf(prefix) !== 0) {
|
|
452
452
|
// Multiple signature labels can appear; comma-separated. Find the
|
|
453
453
|
// matching label.
|
|
454
|
-
var parts = headerValue.split(","); // allow:bare-split-on-quoted-header — RFC 9421 §2.4 Signature header values are `label=:b64:` form; base64 alphabet excludes `,` and the label tokens are RFC 8941 §3.3.4 sf-token (no DQUOTE in practice)
|
|
454
|
+
var parts = headerValue.split(","); // allow:bare-split-on-quoted-header-token-grammar — RFC 9421 §2.4 Signature header values are `label=:b64:` form; base64 alphabet excludes `,` and the label tokens are RFC 8941 §3.3.4 sf-token (no DQUOTE in practice)
|
|
455
455
|
for (var i = 0; i < parts.length; i++) {
|
|
456
456
|
var p = parts[i].trim();
|
|
457
457
|
if (p.indexOf(prefix) === 0) {
|
|
@@ -519,7 +519,7 @@ function verify(msg, opts) {
|
|
|
519
519
|
// If content-digest is covered, recompute and compare. RFC 9421 §B.2.5
|
|
520
520
|
// mandates that verifiers re-run the digest over the body — a stale
|
|
521
521
|
// header from a proxy would otherwise verify trivially.
|
|
522
|
-
var coveredLower = parsedInput.covered.map(function (c) { return c.split(";")[0].toLowerCase(); }); // allow:bare-split-on-quoted-header — same as sign() above: covered items are RFC 9421 §2.1 component-ids, token grammar
|
|
522
|
+
var coveredLower = parsedInput.covered.map(function (c) { return c.split(";")[0].toLowerCase(); }); // allow:bare-split-on-quoted-header-token-grammar — same as sign() above: covered items are RFC 9421 §2.1 component-ids, token grammar
|
|
523
523
|
if (coveredLower.indexOf("content-digest") !== -1) {
|
|
524
524
|
if (m.body == null) {
|
|
525
525
|
return { valid: false, reason: "content-digest-no-body" };
|
package/lib/metrics.js
CHANGED
|
@@ -1352,6 +1352,14 @@ function shadowRegistry(opts) {
|
|
|
1352
1352
|
var counters = Object.create(null);
|
|
1353
1353
|
var gauges = Object.create(null);
|
|
1354
1354
|
var info = Object.create(null);
|
|
1355
|
+
// Maps a canonical-JSON cardinality key → the string-coerced, null-prototype
|
|
1356
|
+
// label object that produced it, so the render path reads the structured
|
|
1357
|
+
// labels directly instead of re-parsing the key. Re-parsing would both lose a
|
|
1358
|
+
// label NAMED `constructor` / `prototype` / `__proto__` (all valid Prometheus
|
|
1359
|
+
// label names) to a prototype-pollution-hardened JSON parse, and reintroduce a
|
|
1360
|
+
// serialize-then-split round-trip. Populated only when a value is actually
|
|
1361
|
+
// stored, so it stays bounded by the cardinality cap.
|
|
1362
|
+
var labelSets = Object.create(null);
|
|
1355
1363
|
var lastCardinalityAuditMs = 0;
|
|
1356
1364
|
|
|
1357
1365
|
function _cardinalityHit(metric) {
|
|
@@ -1375,19 +1383,27 @@ function shadowRegistry(opts) {
|
|
|
1375
1383
|
}
|
|
1376
1384
|
}
|
|
1377
1385
|
|
|
1378
|
-
function
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
+
function _coerceLabels(labels) {
|
|
1387
|
+
// → { key, set }: the canonical-JSON cardinality key (collision-proof and
|
|
1388
|
+
// injection-proof, matching the main registry's _labelsKey) plus the
|
|
1389
|
+
// string-coerced, null-prototype label object it came from. The render path
|
|
1390
|
+
// reads `set` directly rather than re-parsing `key`, so a `,` or `=` in a
|
|
1391
|
+
// label VALUE can never forge extra label pairs and a label NAMED
|
|
1392
|
+
// `constructor` / `prototype` / `__proto__` survives verbatim. The
|
|
1393
|
+
// null-prototype object makes a `__proto__` label a normal own property
|
|
1394
|
+
// rather than a prototype assignment.
|
|
1395
|
+
if (!labels || typeof labels !== "object") return { key: "", set: null };
|
|
1396
|
+
var keys = Object.keys(labels);
|
|
1397
|
+
if (keys.length === 0) return { key: "", set: null };
|
|
1398
|
+
var coerced = Object.create(null);
|
|
1399
|
+
for (var i = 0; i < keys.length; i += 1) coerced[keys[i]] = String(labels[keys[i]]);
|
|
1400
|
+
return { key: canonicalJson.stringify(coerced), set: coerced };
|
|
1386
1401
|
}
|
|
1387
1402
|
|
|
1388
1403
|
function inc(name, labels) {
|
|
1389
1404
|
if (!counterSet[name]) return;
|
|
1390
|
-
var
|
|
1405
|
+
var ck = _coerceLabels(labels);
|
|
1406
|
+
var lk = ck.key;
|
|
1391
1407
|
if (!counters[name]) counters[name] = Object.create(null);
|
|
1392
1408
|
var current = counters[name][lk];
|
|
1393
1409
|
if (current === undefined) {
|
|
@@ -1396,6 +1412,7 @@ function shadowRegistry(opts) {
|
|
|
1396
1412
|
return;
|
|
1397
1413
|
}
|
|
1398
1414
|
counters[name][lk] = 1;
|
|
1415
|
+
if (ck.set) labelSets[lk] = ck.set;
|
|
1399
1416
|
} else {
|
|
1400
1417
|
counters[name][lk] = current + 1;
|
|
1401
1418
|
}
|
|
@@ -1407,13 +1424,15 @@ function shadowRegistry(opts) {
|
|
|
1407
1424
|
throw new MetricsError("metrics-shadow/bad-gauge-value",
|
|
1408
1425
|
"shadowRegistry.set: '" + name + "' value must be a finite number");
|
|
1409
1426
|
}
|
|
1410
|
-
var
|
|
1427
|
+
var ck = _coerceLabels(labels);
|
|
1428
|
+
var lk = ck.key;
|
|
1411
1429
|
if (!gauges[name]) gauges[name] = Object.create(null);
|
|
1412
1430
|
if (gauges[name][lk] === undefined && Object.keys(gauges[name]).length >= cap) {
|
|
1413
1431
|
_cardinalityHit(name);
|
|
1414
1432
|
return;
|
|
1415
1433
|
}
|
|
1416
1434
|
gauges[name][lk] = value;
|
|
1435
|
+
if (ck.set) labelSets[lk] = ck.set;
|
|
1417
1436
|
}
|
|
1418
1437
|
|
|
1419
1438
|
function setInfo(name, value) {
|
|
@@ -1449,22 +1468,21 @@ function shadowRegistry(opts) {
|
|
|
1449
1468
|
for (var li = 0; li < lks.length; li += 1) {
|
|
1450
1469
|
var lk = lks[li];
|
|
1451
1470
|
if (lk === "") { out.push(metric + " " + labelMap[lk]); continue; }
|
|
1452
|
-
//
|
|
1453
|
-
//
|
|
1454
|
-
//
|
|
1455
|
-
//
|
|
1456
|
-
//
|
|
1457
|
-
//
|
|
1458
|
-
var
|
|
1471
|
+
// Read the structured label set kept alongside the canonical key (see
|
|
1472
|
+
// _coerceLabels) rather than re-parsing the key. No serialize-then-
|
|
1473
|
+
// split round-trip, so a `,` or `=` in a label value stays inside the
|
|
1474
|
+
// value and can never forge extra label pairs; and a label NAMED
|
|
1475
|
+
// `constructor` / `prototype` / `__proto__` survives instead of being
|
|
1476
|
+
// stripped by a prototype-pollution-hardened parse.
|
|
1477
|
+
var labelObj = labelSets[lk];
|
|
1478
|
+
if (!labelObj || typeof labelObj !== "object") { out.push(metric + " " + labelMap[lk]); continue; }
|
|
1479
|
+
var lnames = Object.keys(labelObj).sort(); // allow:bare-canonicalize-walk — deterministic label ordering in the exposition line
|
|
1459
1480
|
var formatted = [];
|
|
1460
|
-
for (var pi = 0; pi <
|
|
1461
|
-
var
|
|
1462
|
-
if (eqIdx === -1) continue;
|
|
1463
|
-
var lname = lpairs[pi].slice(0, eqIdx);
|
|
1464
|
-
var lvalue = lpairs[pi].slice(eqIdx + 1);
|
|
1481
|
+
for (var pi = 0; pi < lnames.length; pi += 1) {
|
|
1482
|
+
var lname = lnames[pi];
|
|
1465
1483
|
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(lname)) continue; // allow:regex-no-length-cap — Prometheus label-name shape
|
|
1466
1484
|
// Prometheus exposition: escape `\`, `"`, `\n` in label values.
|
|
1467
|
-
lvalue = String(
|
|
1485
|
+
var lvalue = String(labelObj[lname]).replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n"); // allow:regex-no-length-cap — fixed-char-set escape // allow:duplicate-regex — Prometheus value escape shape
|
|
1468
1486
|
formatted.push(lname + '="' + lvalue + '"');
|
|
1469
1487
|
}
|
|
1470
1488
|
out.push(metric + "{" + formatted.join(",") + "} " + labelMap[lk]);
|
|
@@ -1516,9 +1534,10 @@ function shadowRegistry(opts) {
|
|
|
1516
1534
|
}
|
|
1517
1535
|
|
|
1518
1536
|
function reset() {
|
|
1519
|
-
counters
|
|
1520
|
-
gauges
|
|
1521
|
-
info
|
|
1537
|
+
counters = Object.create(null);
|
|
1538
|
+
gauges = Object.create(null);
|
|
1539
|
+
info = Object.create(null);
|
|
1540
|
+
labelSets = Object.create(null);
|
|
1522
1541
|
lastCardinalityAuditMs = 0;
|
|
1523
1542
|
}
|
|
1524
1543
|
|
|
@@ -351,7 +351,7 @@ function _detectSmuggling(req) {
|
|
|
351
351
|
// (RFC 9112 §6.1). Anything else is a smuggling vector or
|
|
352
352
|
// server-side decode error.
|
|
353
353
|
if (typeof te === "string" && te.length > 0) {
|
|
354
|
-
var tokens = te.toLowerCase().split(",").map(function (t) { return t.trim(); }); //
|
|
354
|
+
var tokens = te.toLowerCase().split(",").map(function (t) { return t.trim(); }); // RFC 9112 §6.1 Transfer-Encoding values (chunked / gzip / deflate / identity) are token-only; no quoted-string in the grammar, so a bare split is correct
|
|
355
355
|
var last = tokens[tokens.length - 1];
|
|
356
356
|
if (last !== "chunked") {
|
|
357
357
|
return {
|
|
@@ -237,8 +237,8 @@ async function _dispatch(req, res, basePath, bearer, opts, maxPageSize, bulkCfg)
|
|
|
237
237
|
count: pageSize,
|
|
238
238
|
sortBy: query.sortBy || null,
|
|
239
239
|
sortOrder: query.sortOrder || null,
|
|
240
|
-
attributes: query.attributes ? query.attributes.split(",") : null, // allow:bare-split-on-quoted-header — RFC 7644 §3.9 attributes/excludedAttributes are SCIM attribute paths (URN-ish identifiers); grammar excludes DQUOTE
|
|
241
|
-
excludedAttributes: query.excludedAttributes ? query.excludedAttributes.split(",") : null, // allow:bare-split-on-quoted-header — same SCIM attribute-name grammar
|
|
240
|
+
attributes: query.attributes ? query.attributes.split(",") : null, // allow:bare-split-on-quoted-header-token-grammar — RFC 7644 §3.9 attributes/excludedAttributes are SCIM attribute paths (URN-ish identifiers); grammar excludes DQUOTE
|
|
241
|
+
excludedAttributes: query.excludedAttributes ? query.excludedAttributes.split(",") : null, // allow:bare-split-on-quoted-header-token-grammar — same SCIM attribute-name grammar
|
|
242
242
|
}, ctx);
|
|
243
243
|
_writeJson(res, H.OK, {
|
|
244
244
|
schemas: [SCIM_MESSAGE_LIST],
|
|
@@ -613,7 +613,7 @@ async function tlsRptFetchPolicy(domain, opts) {
|
|
|
613
613
|
var rua = [];
|
|
614
614
|
for (var p = 0; p < pairs.length; p += 1) {
|
|
615
615
|
if (pairs[p][0] === "rua") {
|
|
616
|
-
var uris = pairs[p][1].split(","); // allow:bare-split-on-quoted-header — allow:raw-time-literal — TLS-RPT rua grammar (RFC 8460 §3): rua = tlsrpt-uri *("," tlsrpt-uri); URIs percent-encode reserved chars, no quoted-string
|
|
616
|
+
var uris = pairs[p][1].split(","); // allow:bare-split-on-quoted-header-token-grammar — allow:raw-time-literal — TLS-RPT rua grammar (RFC 8460 §3): rua = tlsrpt-uri *("," tlsrpt-uri); URIs percent-encode reserved chars, no quoted-string
|
|
617
617
|
for (var u = 0; u < uris.length; u += 1) {
|
|
618
618
|
var uri = uris[u].trim();
|
|
619
619
|
if (uri.length > 0) rua.push(uri);
|
package/package.json
CHANGED
package/sbom.cdx.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:88f8b769-23a3-43f0-824e-6239b53c0528",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-06-
|
|
8
|
+
"timestamp": "2026-06-27T08:05:51.707Z",
|
|
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.15.
|
|
22
|
+
"bom-ref": "@blamejs/core@0.15.36",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.15.
|
|
25
|
+
"version": "0.15.36",
|
|
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.15.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/core@0.15.36",
|
|
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.15.
|
|
57
|
+
"ref": "@blamejs/core@0.15.36",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|