@blamejs/blamejs-shop 0.1.38 → 0.2.1
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/lib/asset-manifest.json +3 -3
- package/lib/storefront.js +94 -21
- package/lib/vendor/MANIFEST.json +2 -2
- package/lib/vendor/blamejs/CHANGELOG.md +10 -0
- package/lib/vendor/blamejs/README.md +1 -1
- package/lib/vendor/blamejs/SECURITY.md +2 -1
- package/lib/vendor/blamejs/api-snapshot.json +2 -2
- package/lib/vendor/blamejs/lib/archive-read.js +17 -0
- package/lib/vendor/blamejs/lib/auth/fal.js +12 -0
- package/lib/vendor/blamejs/lib/auth/jwt-external.js +15 -11
- package/lib/vendor/blamejs/lib/auth/jwt.js +2 -2
- package/lib/vendor/blamejs/lib/auth/oauth.js +7 -6
- package/lib/vendor/blamejs/lib/auth/oid4vci.js +3 -3
- package/lib/vendor/blamejs/lib/auth/saml.js +15 -12
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc.js +3 -3
- package/lib/vendor/blamejs/lib/calendar.js +9 -2
- package/lib/vendor/blamejs/lib/circuit-breaker.js +5 -4
- package/lib/vendor/blamejs/lib/crypto-hpke.js +1 -1
- package/lib/vendor/blamejs/lib/crypto-oprf.js +2 -2
- package/lib/vendor/blamejs/lib/crypto.js +2 -2
- package/lib/vendor/blamejs/lib/framework-error.js +2 -1
- package/lib/vendor/blamejs/lib/guard-filename.js +90 -5
- package/lib/vendor/blamejs/lib/guard-jwt.js +3 -2
- package/lib/vendor/blamejs/lib/guard-smtp-command.js +2 -2
- package/lib/vendor/blamejs/lib/mail-auth.js +2 -2
- package/lib/vendor/blamejs/lib/mail-crypto-pgp.js +1 -1
- package/lib/vendor/blamejs/lib/mail-crypto-smime.js +7 -7
- package/lib/vendor/blamejs/lib/mail-crypto.js +1 -1
- package/lib/vendor/blamejs/lib/mail-dav.js +5 -4
- package/lib/vendor/blamejs/lib/mail-deploy.js +3 -2
- package/lib/vendor/blamejs/lib/mail-server-imap.js +1 -1
- package/lib/vendor/blamejs/lib/mail-server-jmap.js +1 -1
- package/lib/vendor/blamejs/lib/mail-server-managesieve.js +1 -1
- package/lib/vendor/blamejs/lib/mail-server-mx.js +142 -47
- package/lib/vendor/blamejs/lib/mail-server-submission.js +3 -3
- package/lib/vendor/blamejs/lib/mail-store.js +2 -2
- package/lib/vendor/blamejs/lib/mail.js +2 -2
- package/lib/vendor/blamejs/lib/network-tls.js +10 -7
- package/lib/vendor/blamejs/lib/safe-decompress.js +8 -6
- package/lib/vendor/blamejs/lib/safe-ical.js +12 -12
- package/lib/vendor/blamejs/lib/safe-mime.js +6 -6
- package/lib/vendor/blamejs/lib/safe-sieve.js +1 -1
- package/lib/vendor/blamejs/lib/safe-smtp.js +1 -1
- package/lib/vendor/blamejs/package.json +1 -1
- package/lib/vendor/blamejs/release-notes/v0.13.10.json +44 -0
- package/lib/vendor/blamejs/release-notes/v0.13.11.json +27 -0
- package/lib/vendor/blamejs/release-notes/v0.13.12.json +36 -0
- package/lib/vendor/blamejs/release-notes/v0.13.13.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.13.9.json +27 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/archive-read.test.js +76 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-jwt-defenses.test.js +4 -4
- package/lib/vendor/blamejs/test/layer-0-primitives/calendar.test.js +31 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +104 -7
- package/lib/vendor/blamejs/test/layer-0-primitives/fal.test.js +26 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-smime.test.js +5 -5
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-dav.test.js +1 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-mx.test.js +166 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/rate-limit-cluster.test.js +45 -27
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-ical.test.js +2 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/sandbox.test.js +12 -12
- package/lib/vendor/blamejs/test/layer-0-primitives/scheduler-exactly-once.test.js +15 -9
- package/lib/vendor/blamejs/test/layer-0-primitives/websocket-channels.test.js +0 -6
- package/package.json +1 -1
|
@@ -453,12 +453,14 @@ function create(opts) {
|
|
|
453
453
|
|
|
454
454
|
// XSW defense — refuse duplicate top-level security-critical
|
|
455
455
|
// elements. SAML XML signature wrapping (XSW) attacks shuffle
|
|
456
|
-
// signed elements alongside unsigned siblings;
|
|
457
|
-
//
|
|
458
|
-
//
|
|
459
|
-
//
|
|
460
|
-
//
|
|
461
|
-
//
|
|
456
|
+
// signed elements alongside unsigned siblings; a first-match
|
|
457
|
+
// child lookup combined with a signed-element-ID check is
|
|
458
|
+
// vulnerable to a multi-Assertion payload where the verifier
|
|
459
|
+
// signs one element but the consumer reads attributes from
|
|
460
|
+
// another. This is the class behind CVE-2024-45409 (ruby-saml,
|
|
461
|
+
// CVSS 10.0, actively exploited) and CVE-2025-25291/25292
|
|
462
|
+
// (omniauth-saml / ruby-saml namespace-confusion XSW). Reject
|
|
463
|
+
// any Response with more than one of these structural children.
|
|
462
464
|
var statusChildren = _findAllChildren(root, "Status", SAML_NS.protocol);
|
|
463
465
|
if (statusChildren.length > 1) {
|
|
464
466
|
throw new AuthError("auth-saml/duplicate-status",
|
|
@@ -1531,9 +1533,10 @@ function create(opts) {
|
|
|
1531
1533
|
// http://www.w3.org/2009/xmlenc11#rsa-oaep (XMLEnc 1.1 §5.4.2)
|
|
1532
1534
|
//
|
|
1533
1535
|
// AES-CBC content encryption (xmlenc#aes128-cbc / aes256-cbc) is
|
|
1534
|
-
// intentionally REFUSED:
|
|
1535
|
-
//
|
|
1536
|
-
// CBC mode under XMLEnc is exploitable without per-
|
|
1536
|
+
// intentionally REFUSED: the XML-Encryption padding-oracle research
|
|
1537
|
+
// (Jager & Somorovsky, "How to Break XML Encryption", CCS 2011)
|
|
1538
|
+
// demonstrates that CBC mode under XMLEnc is exploitable without per-
|
|
1539
|
+
// content MAC.
|
|
1537
1540
|
// Operators integrating with IdPs that default to CBC (older ADFS /
|
|
1538
1541
|
// Azure AD / Okta / Keycloak / OneLogin) MUST switch the IdP's
|
|
1539
1542
|
// content-encryption setting to AES-128-GCM or AES-256-GCM. The
|
|
@@ -1543,7 +1546,7 @@ function create(opts) {
|
|
|
1543
1546
|
//
|
|
1544
1547
|
// SHA-1 anywhere (rsa-oaep-mgf1p with SHA-1 OAEP DigestMethod,
|
|
1545
1548
|
// xmldsig#sha1 DigestMethod) is also refused — Bleichenbacher /
|
|
1546
|
-
// collision risk plus CVE-2023-49141
|
|
1549
|
+
// collision risk plus CVE-2023-49141 class advisories outweigh
|
|
1547
1550
|
// "interop with stale IdPs". Operators upgrade the IdP's digest
|
|
1548
1551
|
// algorithm to SHA-256+ rather than relax the framework defense.
|
|
1549
1552
|
//
|
|
@@ -1606,7 +1609,7 @@ function _decryptEncryptedAssertion(encAssertion, spPrivateKeyPem) {
|
|
|
1606
1609
|
}
|
|
1607
1610
|
if (oaepHashName === "sha1") {
|
|
1608
1611
|
throw new AuthError("auth-saml/encrypted-weak-oaep-digest",
|
|
1609
|
-
"EncryptedKey OAEP DigestMethod is SHA-1 — refused (CVE-2023-49141
|
|
1612
|
+
"EncryptedKey OAEP DigestMethod is SHA-1 — refused (CVE-2023-49141 class). " +
|
|
1610
1613
|
"Require SHA-256+ on IdP side.");
|
|
1611
1614
|
}
|
|
1612
1615
|
var spKey;
|
|
@@ -1710,7 +1713,7 @@ function _decryptEncryptedAssertion(encAssertion, spPrivateKeyPem) {
|
|
|
1710
1713
|
"(supported: W3C xmlenc11#aes128-gcm, xmlenc11#aes256-gcm, " +
|
|
1711
1714
|
"framework-experimental urn:blamejs:experimental:xmlenc:xchacha20-poly1305). " +
|
|
1712
1715
|
"AES-CBC content encryption is refused — switch the IdP to AES-128-GCM or AES-256-GCM " +
|
|
1713
|
-
"(
|
|
1716
|
+
"(XMLEnc CBC padding-oracle class, Jager & Somorovsky CCS 2011).");
|
|
1714
1717
|
}
|
|
1715
1718
|
return clearBytes.toString("utf8");
|
|
1716
1719
|
}
|
|
@@ -441,15 +441,15 @@ async function verify(presentation, opts) {
|
|
|
441
441
|
"verify: malformed JWT header: " + e.message);
|
|
442
442
|
}
|
|
443
443
|
var alg = headerObj.alg;
|
|
444
|
-
//
|
|
445
|
-
// resolution. The shared `_assertAlgKtyMatch` helper repeats this
|
|
444
|
+
// Alg-allowlist gate (CWE-347 / CWE-757) — refuse unknown / unsupported
|
|
445
|
+
// alg BEFORE any key resolution. The shared `_assertAlgKtyMatch` helper repeats this
|
|
446
446
|
// check after the issuer key is resolved; doing it here too closes
|
|
447
447
|
// the gap where an issuerKeyResolver with side effects (network
|
|
448
448
|
// fetch, audit emit) would run even when the alg is unsupported.
|
|
449
449
|
if (typeof alg !== "string" || SUPPORTED_ALGS.indexOf(alg) === -1) {
|
|
450
450
|
throw new AuthError("auth-sd-jwt-vc/unsupported-alg",
|
|
451
451
|
"verify: header alg \"" + alg + "\" not in supported set " +
|
|
452
|
-
"(
|
|
452
|
+
"(alg-allowlist gate — refused before key lookup)");
|
|
453
453
|
}
|
|
454
454
|
// draft-ietf-oauth-sd-jwt-vc §3.1 — typ MUST be `vc+sd-jwt` (or
|
|
455
455
|
// `dc+sd-jwt` for digital-credential profile). Pre-v0.9.x the absent-
|
|
@@ -354,7 +354,14 @@ function validate(jsCal) {
|
|
|
354
354
|
* // → { "@type":"Event", uid:"a@b", updated:"2026-05-21T10:00:00Z", ... }
|
|
355
355
|
*/
|
|
356
356
|
function fromIcal(text, opts) {
|
|
357
|
-
|
|
357
|
+
// Forward parser options to b.safeIcal.parse. Accept BOTH the
|
|
358
|
+
// documented nested form (`{ safeIcalOpts: { profile, caps, ... } }`)
|
|
359
|
+
// AND the historically-working top-level form (`{ profile: "balanced" }`)
|
|
360
|
+
// so neither caller regresses; the nested form wins on conflict. The
|
|
361
|
+
// `safeIcalOpts` wrapper key itself is stripped before forwarding.
|
|
362
|
+
var icalOpts = Object.assign({}, opts || {}, (opts && opts.safeIcalOpts) || {});
|
|
363
|
+
delete icalOpts.safeIcalOpts;
|
|
364
|
+
var ast = safeIcal.parse(text, icalOpts);
|
|
358
365
|
var events = (ast && ast.vcalendar && ast.vcalendar.vevent) || [];
|
|
359
366
|
var todos = (ast && ast.vcalendar && ast.vcalendar.vtodo) || [];
|
|
360
367
|
var journals = (ast && ast.vcalendar && ast.vcalendar.vjournal) || [];
|
|
@@ -515,7 +522,7 @@ function toIcal(jsCal, opts) {
|
|
|
515
522
|
* timestamps in the operator's `[from, to]` window. Returns an array
|
|
516
523
|
* of ISO 8601 UTC strings (`yyyy-mm-ddTHH:MM:SSZ`). Bounded by
|
|
517
524
|
* `MAX_EXPAND_INSTANCES` (4096) + `MAX_EXPAND_SPAN_MS` (10 years) to
|
|
518
|
-
* defend against
|
|
525
|
+
* defend against the RRULE recurrence-bomb expansion class.
|
|
519
526
|
*
|
|
520
527
|
* v1 supports FREQ=DAILY/WEEKLY/MONTHLY/YEARLY with INTERVAL, COUNT,
|
|
521
528
|
* UNTIL. BYDAY / BYMONTH / BYMONTHDAY / BYWEEKNO / BYYEARDAY /
|
|
@@ -42,10 +42,11 @@ var retryHelper = require("./retry");
|
|
|
42
42
|
* with the framework's `create(opts)` vocabulary.
|
|
43
43
|
*
|
|
44
44
|
* The `CIRCUIT_OPEN` error code is a pre-v1 artifact — every other
|
|
45
|
-
* framework error class uses namespaced codes (`retry/...`).
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
45
|
+
* framework error class uses namespaced codes (`retry/...`). It is
|
|
46
|
+
* kept through the pre-1.0 line so existing operators who match
|
|
47
|
+
* `err.code === "CIRCUIT_OPEN"` aren't broken in a patch; the rename
|
|
48
|
+
* to a namespaced code lands at v1.0 alongside the namespaced-error
|
|
49
|
+
* sweep, with a deprecation warning shipping a minor ahead.
|
|
49
50
|
*
|
|
50
51
|
* @opts
|
|
51
52
|
* name: string, // identifier used in audit + state-change events
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Suite (PQC-first per framework crypto policy):
|
|
6
6
|
* KEM: ML-KEM-1024 (FIPS 203) — post-quantum encapsulation
|
|
7
7
|
* KDF: HKDF-SHA3-512
|
|
8
|
-
* AEAD: ChaCha20-Poly1305 (RFC 7539)
|
|
8
|
+
* AEAD: ChaCha20-Poly1305 (RFC 8439, obsoletes RFC 7539)
|
|
9
9
|
*
|
|
10
10
|
* The classical HPKE suites in RFC 9180 §7 (DHKEM with X25519 / P-256 /
|
|
11
11
|
* P-384 / P-521 + HKDF-SHA256/384/512 + AES-GCM/ChaCha20) are NOT
|
|
@@ -30,14 +30,14 @@
|
|
|
30
30
|
* <code>suite(name)</code> returns the suite for one of the RFC 9497
|
|
31
31
|
* ciphersuites — <code>ristretto255-sha512</code> (the Privacy Pass
|
|
32
32
|
* default), <code>p256-sha256</code>, <code>p384-sha384</code>, or
|
|
33
|
-
* <code>p521-sha512</code> — each exposing
|
|
33
|
+
* <code>p521-sha512</code> — each exposing both shipped modes. Group and
|
|
34
34
|
* hash-to-curve operations come from the vendored <code>@noble/curves</code>.
|
|
35
35
|
* Byte arguments are <code>Uint8Array</code> / <code>Buffer</code>;
|
|
36
36
|
* returned elements and outputs are <code>Uint8Array</code>.
|
|
37
37
|
*
|
|
38
38
|
* @card
|
|
39
39
|
* RFC 9497 Oblivious PRFs — learn <code>F(key, input)</code> without the
|
|
40
|
-
* server seeing the input (oprf / voprf
|
|
40
|
+
* server seeing the input (oprf / voprf modes; ristretto255 / P-256
|
|
41
41
|
* / P-384 / P-521 suites). The primitive behind Privacy Pass, password
|
|
42
42
|
* hardening, and private set intersection.
|
|
43
43
|
*/
|
|
@@ -789,7 +789,7 @@ function toBase64Url(buf) {
|
|
|
789
789
|
*
|
|
790
790
|
* Strict mode (default) refuses non-canonical input — chars outside
|
|
791
791
|
* the RFC 4648 §5 alphabet, length-mod-4-of-1, mixed `+/` from
|
|
792
|
-
* standard base64, trailing garbage. Defends a CVE-2022-0235
|
|
792
|
+
* standard base64, trailing garbage. Defends a CVE-2022-0235 class
|
|
793
793
|
* footgun where Node's permissive decoder silently tolerated
|
|
794
794
|
* tampered JWT signatures. Operators with a documented lossy legacy
|
|
795
795
|
* payload opt out per call via `{ strict: false }`.
|
|
@@ -817,7 +817,7 @@ function fromBase64Url(s, opts) {
|
|
|
817
817
|
// OAuth `state` round-tripping) MUST reject non-canonical / malformed
|
|
818
818
|
// input. The Node base64url decoder silently tolerates trailing
|
|
819
819
|
// garbage, mixed `+/` from standard base64, missing padding errors,
|
|
820
|
-
// and length-mod-4 shapes — CVE-2022-0235
|
|
820
|
+
// and length-mod-4 shapes — CVE-2022-0235 class footgun. Strict mode
|
|
821
821
|
// (the default) refuses anything outside the RFC 4648 §5 alphabet +
|
|
822
822
|
// length rules. Operators with a known-lossy legacy payload pass
|
|
823
823
|
// `{ strict: false }` to opt out per call.
|
|
@@ -290,7 +290,8 @@ var GuardTimeError = defineClass("GuardTimeError", { alwaysPermane
|
|
|
290
290
|
var GuardMimeError = defineClass("GuardMimeError", { alwaysPermanent: true });
|
|
291
291
|
// GuardJwtError covers JWT identifier violations: shape malformation
|
|
292
292
|
// (not 3 base64url segments), alg=none refuse (canonical CVE-class —
|
|
293
|
-
// CVE-2015-9235 jsonwebtoken / CVE-2018-0114
|
|
293
|
+
// CVE-2015-9235 jsonwebtoken alg:none / CVE-2018-0114 Cisco node-jose
|
|
294
|
+
// embedded-JWK key confusion), alg-allowlist
|
|
294
295
|
// drift, kid path-traversal (operator keyResolver path-injection
|
|
295
296
|
// class), typ confusion, oversized header / payload / signature,
|
|
296
297
|
// exp / nbf / iat sanity, missing required claims, unknown crit
|
|
@@ -91,6 +91,20 @@ var WIN_RESERVED_NAMES = Object.freeze([
|
|
|
91
91
|
"CLOCK$", "CONFIG$",
|
|
92
92
|
]);
|
|
93
93
|
|
|
94
|
+
// Windows folds the superscript digits U+00B9 / U+00B2 / U+00B3 to
|
|
95
|
+
// 1 / 2 / 3 when matching COM/LPT device names, so a superscript-digit
|
|
96
|
+
// form resolves to the same device. Built from numeric codepoints so
|
|
97
|
+
// the source stays pure-ASCII (guard-family rule).
|
|
98
|
+
var _SUPERSCRIPT_DIGIT_MAP = (function () {
|
|
99
|
+
var m = {};
|
|
100
|
+
m[String.fromCharCode(0xB9)] = "1";
|
|
101
|
+
m[String.fromCharCode(0xB2)] = "2";
|
|
102
|
+
m[String.fromCharCode(0xB3)] = "3";
|
|
103
|
+
return m;
|
|
104
|
+
})();
|
|
105
|
+
var _SUPERSCRIPT_DIGIT_RE = new RegExp("[" + String.fromCharCode(0xB9, 0xB2, 0xB3) + "]", "g"); // allow:dynamic-regex — superscript-digit codepoints from a numeric table
|
|
106
|
+
|
|
107
|
+
|
|
94
108
|
// Path-traversal indicators (anchored matches on raw and percent-decoded
|
|
95
109
|
// forms).
|
|
96
110
|
var PATH_TRAVERSAL_RE = /(^|[/\\])\.\.($|[/\\])/;
|
|
@@ -243,7 +257,16 @@ function _normalizeNFC(s) {
|
|
|
243
257
|
function _isWinReserved(name) {
|
|
244
258
|
// Reserved-name check applies to the base (without extension) AND to
|
|
245
259
|
// the entire leaf — both `CON` and `CON.txt` collide with the device.
|
|
246
|
-
|
|
260
|
+
// Windows normalizes the superscript digits U+00B9 / U+00B2 / U+00B3
|
|
261
|
+
// to 1 / 2 / 3 when matching COM/LPT device names, so those superscript
|
|
262
|
+
// forms resolve to the same devices as COM1 / LPT3; fold them to ASCII
|
|
263
|
+
// before comparison so the spoofed forms are caught too. (Source stays
|
|
264
|
+
// pure-ASCII per the guard-family rule — the codepoints are escaped.)
|
|
265
|
+
// Fold COM/LPT superscript-digit spoofs to ASCII before matching
|
|
266
|
+
// (Windows treats them as the device). See _SUPERSCRIPT_DIGIT_* below.
|
|
267
|
+
var upper = name.toUpperCase().replace(_SUPERSCRIPT_DIGIT_RE, function (ch) {
|
|
268
|
+
return _SUPERSCRIPT_DIGIT_MAP[ch] || ch;
|
|
269
|
+
});
|
|
247
270
|
for (var i = 0; i < WIN_RESERVED_NAMES.length; i += 1) {
|
|
248
271
|
var r = WIN_RESERVED_NAMES[i];
|
|
249
272
|
if (upper === r) return true;
|
|
@@ -952,6 +975,29 @@ var PATH_MAX_BYTES = 4096;
|
|
|
952
975
|
* resolved absolute path on success; throws `GuardFilenameError` on
|
|
953
976
|
* any refusal.
|
|
954
977
|
*
|
|
978
|
+
* Per-segment Windows-extraction hazards are refused too — these are
|
|
979
|
+
* within-root write-target redirections / collisions that the
|
|
980
|
+
* containment + realpath checks structurally cannot see, so they need
|
|
981
|
+
* a name-level check the disk `validate` / `sanitize` paths already
|
|
982
|
+
* carry: a Windows reserved device name (`CON` / `NUL` / `COM1` / …,
|
|
983
|
+
* which resolves to the device), NTFS alternate-data-stream syntax
|
|
984
|
+
* (`name:stream`, which writes a hidden stream of the base file), and a
|
|
985
|
+
* trailing dot / leading-or-trailing whitespace (`secret.txt.`, which
|
|
986
|
+
* Windows strips so the entry overwrites an existing sibling). The
|
|
987
|
+
* checks are platform-unconditional — the verifier may run on Linux
|
|
988
|
+
* while extraction happens on Windows — and each has an opt-out for
|
|
989
|
+
* Linux-only targets (`reservedNamePolicy` / `adsPolicy` /
|
|
990
|
+
* `leadingTrailingPolicy: "allow"`), mirroring `validate`.
|
|
991
|
+
*
|
|
992
|
+
* Out of this primitive's scope (single-entry, name-only): 8.3 short-name
|
|
993
|
+
* aliasing (`PROGRA~1`), case-insensitive cross-entry collision
|
|
994
|
+
* (`Readme.txt` vs `README.TXT` on a case-preserving FS), and archive
|
|
995
|
+
* symlink/hardlink ENTRY-target validation. The first two are cross-entry
|
|
996
|
+
* properties and the third needs the entry's declared link target, which
|
|
997
|
+
* this function never sees — they belong to the extract orchestrator
|
|
998
|
+
* (`b.archive.read.zip.extract` / `b.safeArchive`), which owns the
|
|
999
|
+
* case-folded seen-set and the link-target gate.
|
|
1000
|
+
*
|
|
955
1001
|
* Companion to `b.guardArchive.checkExtractionPath` (the string-only
|
|
956
1002
|
* portable gate the guard-archive primitive keeps fs-free for use as
|
|
957
1003
|
* a posture cascade member). `verifyExtractionPath` deliberately
|
|
@@ -964,8 +1010,13 @@ var PATH_MAX_BYTES = 4096;
|
|
|
964
1010
|
* Operators rolling their own extract loop call it per entry.
|
|
965
1011
|
*
|
|
966
1012
|
* @opts
|
|
967
|
-
* followSymlinks:
|
|
1013
|
+
* followSymlinks: boolean, // default false — symlink in the
|
|
968
1014
|
* // resolved path refuses unless set
|
|
1015
|
+
* reservedNamePolicy: string, // "allow" opts out of the Windows
|
|
1016
|
+
* // reserved-device-name segment check
|
|
1017
|
+
* adsPolicy: string, // "allow" opts out of the NTFS-ADS check
|
|
1018
|
+
* leadingTrailingPolicy: string, // "allow" opts out of the trailing-dot /
|
|
1019
|
+
* // leading-or-trailing-whitespace check
|
|
969
1020
|
*
|
|
970
1021
|
* @example
|
|
971
1022
|
* var resolved = b.guardFilename.verifyExtractionPath(
|
|
@@ -1023,19 +1074,53 @@ function verifyExtractionPath(entryName, extractionRoot, opts) {
|
|
|
1023
1074
|
throw new GuardFilenameError("filename.extraction-unc",
|
|
1024
1075
|
"verifyExtractionPath: entryName starts with a UNC prefix");
|
|
1025
1076
|
}
|
|
1026
|
-
// `..` segment refuses — walk path components.
|
|
1077
|
+
// `..` segment refuses — walk path components. The same walk also
|
|
1078
|
+
// refuses per-segment Windows-extraction hazards the disk `validate`
|
|
1079
|
+
// / `sanitize` paths already catch but that string-containment +
|
|
1080
|
+
// realpath agreement cannot see, because they're WITHIN-root
|
|
1081
|
+
// collisions / write-target redirections rather than boundary
|
|
1082
|
+
// escapes. These checks are platform-UNCONDITIONAL: the verifier may
|
|
1083
|
+
// run on Linux while the archive is extracted on Windows, so a name
|
|
1084
|
+
// that's only dangerous on Windows must still be refused here.
|
|
1085
|
+
// Operators on a Linux-only target opt out per check, mirroring
|
|
1086
|
+
// `validate`'s policy vocabulary.
|
|
1027
1087
|
var segs = normalized.split("/");
|
|
1028
1088
|
for (var si = 0; si < segs.length; si += 1) {
|
|
1029
|
-
|
|
1089
|
+
var seg = segs[si];
|
|
1090
|
+
if (seg === ".." || seg === "..\\" || seg === "..%2f" || seg === "..%5c") {
|
|
1030
1091
|
throw new GuardFilenameError("filename.extraction-traversal",
|
|
1031
1092
|
"verifyExtractionPath: entryName contains .. segment");
|
|
1032
1093
|
}
|
|
1033
1094
|
// URL-encoded variants — explicit refusal so operators don't
|
|
1034
1095
|
// need to percent-decode before passing the entry name in.
|
|
1035
|
-
if (/%2e%2e/i.test(
|
|
1096
|
+
if (/%2e%2e/i.test(seg) || /%c0%ae/i.test(seg)) {
|
|
1036
1097
|
throw new GuardFilenameError("filename.extraction-traversal-encoded",
|
|
1037
1098
|
"verifyExtractionPath: entryName contains encoded .. segment");
|
|
1038
1099
|
}
|
|
1100
|
+
if (seg === "" || seg === ".") continue; // separators / current-dir — nothing to name-check
|
|
1101
|
+
// Windows reserved device name (CON / NUL / COM1 / LPT1 / …): on
|
|
1102
|
+
// Windows the segment resolves to the device, redirecting the write.
|
|
1103
|
+
if (opts.reservedNamePolicy !== "allow" && _isWinReserved(seg)) {
|
|
1104
|
+
throw new GuardFilenameError("filename.extraction-reserved-name",
|
|
1105
|
+
"verifyExtractionPath: entryName segment " + JSON.stringify(seg) +
|
|
1106
|
+
" collides with a Windows reserved device name");
|
|
1107
|
+
}
|
|
1108
|
+
// NTFS alternate data stream (name:stream): on Windows the write
|
|
1109
|
+
// lands on a hidden stream of the base file, not a normal file.
|
|
1110
|
+
if (opts.adsPolicy !== "allow" && /:[^:\\/]+$/.test(seg)) {
|
|
1111
|
+
throw new GuardFilenameError("filename.extraction-ntfs-ads",
|
|
1112
|
+
"verifyExtractionPath: entryName segment " + JSON.stringify(seg) +
|
|
1113
|
+
" uses NTFS alternate-data-stream syntax (name:stream)");
|
|
1114
|
+
}
|
|
1115
|
+
// Trailing dot / leading-or-trailing whitespace: Windows silently
|
|
1116
|
+
// strips these, so `secret.txt.` or `secret.txt ` collides with an
|
|
1117
|
+
// existing sibling — an in-root overwrite the containment check
|
|
1118
|
+
// cannot see.
|
|
1119
|
+
if (opts.leadingTrailingPolicy !== "allow" && /^\s|\s$|\.$/.test(seg)) {
|
|
1120
|
+
throw new GuardFilenameError("filename.extraction-leading-trailing",
|
|
1121
|
+
"verifyExtractionPath: entryName segment " + JSON.stringify(seg) +
|
|
1122
|
+
" has leading/trailing whitespace or a trailing dot (Windows strips it)");
|
|
1123
|
+
}
|
|
1039
1124
|
}
|
|
1040
1125
|
// Resolve the destination path against the root via path.resolve
|
|
1041
1126
|
// (string-level computation; no fs hits).
|
|
@@ -16,8 +16,9 @@
|
|
|
16
16
|
*
|
|
17
17
|
* Algorithm-confusion defense: `alg=none` is universally refused
|
|
18
18
|
* at every profile (RFC 7518 §3.6 explicit-no-signature, the
|
|
19
|
-
* canonical CVE-2015-9235 jsonwebtoken / CVE-2018-0114
|
|
20
|
-
*
|
|
19
|
+
* canonical CVE-2015-9235 jsonwebtoken alg:none / CVE-2018-0114
|
|
20
|
+
* Cisco node-jose embedded-JWK confusion class). The
|
|
21
|
+
* operator-supplied `allowedAlgs` allowlist defaults
|
|
21
22
|
* to the framework's PQC-first set (ML-DSA-87 / ML-DSA-65 /
|
|
22
23
|
* ML-DSA-44 / SLH-DSA-SHAKE-256{f,s} / SLH-DSA-SHA2-256{f,s} /
|
|
23
24
|
* EdDSA / ES* / RS* / PS*) so HS256-against-RSA-public-key
|
|
@@ -493,7 +493,7 @@ function gate(opts) {
|
|
|
493
493
|
*
|
|
494
494
|
* Scan a DATA-body byte buffer for the SMTP smuggling shape per
|
|
495
495
|
* CVE-2023-51764 (Postfix), CVE-2023-51765 (Sendmail), CVE-2023-51766
|
|
496
|
-
* (Exim)
|
|
496
|
+
* (Exim). RFC 5321 §2.3.8
|
|
497
497
|
* mandates canonical CRLF line termination; the smuggling exploit
|
|
498
498
|
* relies on parsers that accept `\n.\n` (bare LF before / after the
|
|
499
499
|
* dot) as an alternate body terminator and then resume parsing the
|
|
@@ -517,7 +517,7 @@ function detectBodySmuggling(buf) {
|
|
|
517
517
|
throw new GuardSmtpCommandError("guard-smtp-command/bad-input",
|
|
518
518
|
"detectBodySmuggling: input must be a Buffer");
|
|
519
519
|
}
|
|
520
|
-
// The CVE-2023-51764 / 51765 / 51766
|
|
520
|
+
// The CVE-2023-51764 / 51765 / 51766 class is any
|
|
521
521
|
// dot-line whose line boundary is anything OTHER than canonical
|
|
522
522
|
// \r\n on BOTH sides of the dot. The canonical-and-only terminator
|
|
523
523
|
// is `\r\n.\r\n`. Every other shape that some receiver might honor
|
|
@@ -1858,8 +1858,8 @@ function dmarcParseAggregateReport(input, opts) {
|
|
|
1858
1858
|
// "stream is malformed" (operator-level diagnostic) so audit/
|
|
1859
1859
|
// alert wiring can react differently. Node surfaces the bomb
|
|
1860
1860
|
// case with ERR_BUFFER_TOO_LARGE / "Output length exceeded the
|
|
1861
|
-
// limit" / the explicit `maxOutputLength` code.
|
|
1862
|
-
//
|
|
1861
|
+
// limit" / the explicit `maxOutputLength` code. Defends the
|
|
1862
|
+
// decompression-amplification class (CWE-409 / CVE-2025-0725).
|
|
1863
1863
|
var msg = (e && e.message) || String(e);
|
|
1864
1864
|
var isBomb = (e && (e.code === "ERR_BUFFER_TOO_LARGE" ||
|
|
1865
1865
|
e.code === "ERR_OUT_OF_RANGE")) ||
|
|
@@ -663,7 +663,7 @@ function _parseSignaturePacket(packetBytes) {
|
|
|
663
663
|
if (hashAlg !== HASH_ALG_SHA256 && hashAlg !== HASH_ALG_SHA512) {
|
|
664
664
|
throw new MailCryptoError("mail-crypto/pgp/bad-hash",
|
|
665
665
|
"hash alg " + hashAlg + " refused; only SHA-256 (8) and SHA-512 (10) are accepted. " +
|
|
666
|
-
"SHA-1 (id=2) refused per SHAttered (
|
|
666
|
+
"SHA-1 (id=2) refused per SHAttered (2017 SHA-1 collision).");
|
|
667
667
|
}
|
|
668
668
|
var hashedSubLen = body.readUInt16BE(4);
|
|
669
669
|
if (6 + hashedSubLen > body.length) {
|
|
@@ -21,9 +21,9 @@
|
|
|
21
21
|
* second part as base64-encoded DER.
|
|
22
22
|
*
|
|
23
23
|
* Posture (when the surface lights up):
|
|
24
|
-
* - Refuses SHA-1 as the signature hash (
|
|
25
|
-
*
|
|
26
|
-
* certificate signature algorithm.
|
|
24
|
+
* - Refuses SHA-1 as the signature hash (SHAttered, 2017 — practical
|
|
25
|
+
* SHA-1 collision; RFC 8551 §2.5 mandates SHA-256+ for S/MIME) and
|
|
26
|
+
* as the certificate signature algorithm.
|
|
27
27
|
* - Refuses RSA keys < 2048 bits (RFC 8301 §3.1 — same posture
|
|
28
28
|
* as the rest of the mail surface).
|
|
29
29
|
* - Refuses MD5 anywhere (the historical S/MIME-v2 default; long
|
|
@@ -87,8 +87,8 @@
|
|
|
87
87
|
* CVE citations:
|
|
88
88
|
* - CVE-2017-17688 / CVE-2017-17689 (EFAIL — S/MIME variant; informs
|
|
89
89
|
* the encrypt+decrypt deferral when that surface lights up)
|
|
90
|
-
* -
|
|
91
|
-
*
|
|
90
|
+
* - SHAttered (2017 practical SHA-1 collision) + RFC 8551 §2.5 (SHA-256
|
|
91
|
+
* floor for S/MIME) — inform the SHA-1 signature-hash refusal posture
|
|
92
92
|
* - CVE-2018-5407 (PortSmash — informs the side-channel hardening
|
|
93
93
|
* posture when private operations land in v2)
|
|
94
94
|
*/
|
|
@@ -109,7 +109,7 @@ var MailCryptoError = defineClass("MailCryptoError", { alwaysPermanent: true });
|
|
|
109
109
|
// hand-copying strings. These reflect RFC 8551 §2.5 + RFC 8301 floors.
|
|
110
110
|
var RSA_MIN_BITS = 2048; // allow:raw-byte-literal — RFC 8301 §3.1
|
|
111
111
|
var ALLOWED_HASHES = ["sha256", "sha384", "sha512"];
|
|
112
|
-
var REFUSED_HASHES = ["md5", "sha1"]; // allow:raw-byte-literal —
|
|
112
|
+
var REFUSED_HASHES = ["md5", "sha1"]; // allow:raw-byte-literal — SHAttered / RFC 8551 §2.5
|
|
113
113
|
|
|
114
114
|
// PROFILES + COMPLIANCE_POSTURES — the framework's standard cross-
|
|
115
115
|
// primitive contract. sign() and verify() (live since v0.10.16) read
|
|
@@ -708,7 +708,7 @@ function checkCert(opts) {
|
|
|
708
708
|
throw new MailCryptoError("mail-crypto/smime/refused-hash",
|
|
709
709
|
"cert signature algorithm '" + sigAlgName +
|
|
710
710
|
"' refused — SHA-1 / MD5 in cert signatures is forbidden " +
|
|
711
|
-
"(
|
|
711
|
+
"(SHAttered SHA-1 collision; RFC 8551 §2.5). Acceptable hashes: " + ALLOWED_HASHES.join(", "));
|
|
712
712
|
}
|
|
713
713
|
}
|
|
714
714
|
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
*
|
|
63
63
|
* CVE citations:
|
|
64
64
|
* - CVE-2017-17688 / CVE-2017-17689 (EFAIL)
|
|
65
|
-
* -
|
|
65
|
+
* - SHAttered (2017 SHA-1 collision) + RFC 8551 §2.5 — SHA-1 signature-hash refusal
|
|
66
66
|
*/
|
|
67
67
|
|
|
68
68
|
var pgp = require("./mail-crypto-pgp");
|
|
@@ -112,8 +112,9 @@
|
|
|
112
112
|
* ## CVE defense composition
|
|
113
113
|
*
|
|
114
114
|
* - `b.safeIcal` rejects RRULE COUNT > 10000 / BYxxx list > 24 →
|
|
115
|
-
* defends
|
|
116
|
-
*
|
|
115
|
+
* defends the ical4j RRULE-recursion / recurrence-expansion DoS
|
|
116
|
+
* class (unbounded RRULE expansion exhausts CPU/memory) on the
|
|
117
|
+
* PUT path.
|
|
117
118
|
* - `b.xmlC14n.parse` rejects DOCTYPE / ENTITY in the
|
|
118
119
|
* PROPFIND / REPORT body → defends XXE / billion-laughs on the
|
|
119
120
|
* query path.
|
|
@@ -125,7 +126,7 @@
|
|
|
125
126
|
* mount under their HTTP router. Composes b.safeIcal / b.safeVcard
|
|
126
127
|
* for PUT-body validation, b.xmlC14n.parse for PROPFIND / REPORT
|
|
127
128
|
* bodies. Per-principal URL isolation; operator-supplied storage
|
|
128
|
-
* backend. Defends
|
|
129
|
+
* backend. Defends the RRULE-recursion expansion-DoS class at the PUT boundary.
|
|
129
130
|
*/
|
|
130
131
|
|
|
131
132
|
var lazyRequire = require("./lazy-require");
|
|
@@ -749,7 +750,7 @@ function create(opts) {
|
|
|
749
750
|
return _refuseStatus(res, 400, "PUT requires a component path");
|
|
750
751
|
}
|
|
751
752
|
var bodyBuf = await _readBodyBytes(req);
|
|
752
|
-
// Validate iCal body via safeIcal — defends
|
|
753
|
+
// Validate iCal body via safeIcal — defends the RRULE-recursion expansion-DoS class at the
|
|
753
754
|
// ingest boundary.
|
|
754
755
|
try {
|
|
755
756
|
safeIcal.parse(bodyBuf, {
|
|
@@ -527,8 +527,9 @@ function autoDiscoverXml(opts) {
|
|
|
527
527
|
// brotli or the in-progress UTA-draft requires it.
|
|
528
528
|
|
|
529
529
|
// Hard caps — defensive against CVE-2025-0725 (libcurl/zlib
|
|
530
|
-
// integer overflow)
|
|
531
|
-
// the §5.2 community ceiling (receivers commonly cap
|
|
530
|
+
// integer overflow) and the decompression-amplification class
|
|
531
|
+
// (CWE-409), plus the §5.2 community ceiling (receivers commonly cap
|
|
532
|
+
// at 10 MiB).
|
|
532
533
|
var TLSRPT_MAX_COMPRESSED_BYTES = C.BYTES.mib(4); // allow:raw-byte-literal — 4 MiB compressed cap per §5.2 community practice
|
|
533
534
|
var TLSRPT_MAX_DECOMPRESSED_BYTES = C.BYTES.mib(32); // allow:raw-byte-literal — 32 MiB decompressed cap (operators override via opts)
|
|
534
535
|
var TLSRPT_MAX_RATIO = 50; // allow:raw-byte-literal — 50:1 compression ratio refusal
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
* pipelined command queued before TLS is refused with
|
|
54
54
|
* `BAD Pipelined post-STARTTLS not permitted`.
|
|
55
55
|
*
|
|
56
|
-
* - **Literal-injection
|
|
56
|
+
* - **Literal-injection / command-continuation smuggling** —
|
|
57
57
|
* `{n}` literal continuation MUST come on a line of its own
|
|
58
58
|
* (per `b.guardImapCommand.detectLiteralSmuggling`); oversize
|
|
59
59
|
* literals refused (default 64 MiB); LITERAL+ (RFC 7888) non-
|
|
@@ -95,7 +95,7 @@
|
|
|
95
95
|
*
|
|
96
96
|
* - **CHECKSCRIPT** (RFC 5804 §2.12) — parse-only verb. Operators
|
|
97
97
|
* who want it compose `b.safeSieve.validate` directly via JMAP
|
|
98
|
-
* `SieveScript/validate` (RFC
|
|
98
|
+
* `SieveScript/validate` (RFC 9661). The MTA-side ManageSieve
|
|
99
99
|
* surface is `PUTSCRIPT` + `HAVESPACE`; CHECKSCRIPT adds a third
|
|
100
100
|
* entry point with no operator demand yet.
|
|
101
101
|
* - **UNAUTHENTICATE** (RFC 5804 §2.14) — exotic. Operators close
|