@blamejs/core 0.18.55 → 0.18.57

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.
@@ -24,7 +24,8 @@
24
24
  * });
25
25
  *
26
26
  * var mx = b.mail.server.mx.create({
27
- * tlsContext: tlsCtx.secureContext,
27
+ * // A getter, not `tlsCtx.secureContext` — see "Picking up a rotation".
28
+ * get tlsContext() { return tlsCtx.secureContext; },
28
29
  * ...
29
30
  * });
30
31
  * ```
@@ -75,10 +76,20 @@
75
76
  *
76
77
  * // Once per process at boot:
77
78
  * var tls = b.mail.server.tls.context({ certFile, keyFile, watch: true });
78
- * var mx = b.mail.server.mx.create({ tlsContext: tls.secureContext, ... });
79
- * tls.onReload(function (newCtx) { mx.replaceTlsContext(newCtx); });
79
+ * var mx = b.mail.server.mx.create({
80
+ * get tlsContext() { return tls.secureContext; },
81
+ * ...
82
+ * });
80
83
  * ```
81
84
  *
85
+ * **Picking up a rotation.** Every listener reads `opts.tlsContext` at the
86
+ * point it needs a context rather than capturing it at construction, so a
87
+ * getter is all a rotation needs: the next connection sees the reloaded
88
+ * context and nothing has to be swapped or restarted. Passing
89
+ * `tlsContext: tls.secureContext` instead copies the context that happened
90
+ * to be current at boot, and that one keeps being served after it expires.
91
+ * There is no listener method to swap a context, and none is needed.
92
+ *
82
93
  * The cleartext-refused error message from `b.mail.server.mx` /
83
94
  * `b.mail.server.submission` points at this primitive so the
84
95
  * operator's boot dead-end becomes a one-line fix.
@@ -137,12 +148,16 @@ var DEFAULT_POLL_MS = C.TIME.seconds(30);
137
148
  * keyFile: "/etc/letsencrypt/live/mail.example.com/privkey.pem",
138
149
  * watch: true,
139
150
  * });
140
- * // Wire `tls.secureContext` into b.mail.server.mx.create / submission.create
141
- * tls.onReload(function (newCtx) {
142
- * // operator swaps the running listener's SecureContext via the
143
- * // listener's reload hook (when the listener exposes one) or via
144
- * // restart-on-rotation flow
151
+ * // Pass a getter so each connection reads the current context. Listeners
152
+ * // read opts.tlsContext at the point of use, so a reload reaches the next
153
+ * // connection with nothing to swap.
154
+ * var mx = b.mail.server.mx.create({
155
+ * get tlsContext() { return tls.secureContext; },
156
+ * localDomains: ["mail.example.com"]
145
157
  * });
158
+ * // onReload is for observing a rotation — logging it, re-checking expiry.
159
+ * // Delivering it to the listeners is not something it has to do.
160
+ * tls.onReload(function (newCtx) { void newCtx; });
146
161
  *
147
162
  * // ... later, on shutdown:
148
163
  * tls.stop(); // clears the poll timer
@@ -154,6 +169,14 @@ var DEFAULT_POLL_MS = C.TIME.seconds(30);
154
169
  // way to answer the question except by re-deriving the options itself — and a
155
170
  // re-derivation agrees with the build right up until someone changes one.
156
171
  //
172
+ // The SERVER form, `b.network.tls.serverKeyAgreementGroups`, not the outbound
173
+ // one. A flat colon list is an offer order to a client and an accept SET to a
174
+ // server, so the client list applied here refused every group it did not name
175
+ // — secp256r1 among them, which RFC 8446 §9.1 makes mandatory to implement —
176
+ // and collapsed the HelloRetryRequest ordering that pulls a peer up to a
177
+ // hybrid. The server form is tuples, which accepts every registered ECDHE
178
+ // curve while still preferring a hybrid.
179
+ //
157
180
  // The key-agreement preference comes from `b.network.tls.keyAgreementGroups`,
158
181
  // which is where the framework's PQC-first policy lives — the ML-KEM hybrids
159
182
  // with a classical X25519 fallback — along with the reasoning about which key
@@ -176,7 +199,7 @@ var DEFAULT_POLL_MS = C.TIME.seconds(30);
176
199
  // verification posture.
177
200
  function _contextOptions(sourceOpts, certPem, keyPem) {
178
201
  var base = { cert: certPem, key: keyPem };
179
- var groups = networkTls().keyAgreementGroups(
202
+ var groups = networkTls().serverKeyAgreementGroups(
180
203
  sourceOpts ? sourceOpts.ecdhCurve : undefined,
181
204
  "b.mail.server.tls.context: opts.ecdhCurve");
182
205
  if (groups) base.ecdhCurve = groups;
@@ -253,7 +253,7 @@ function _writeReject(req, res, message, reason, onDeny, problemMode) {
253
253
  * protocolResolver: function(req): "http"|"https", // own the HTTPS decision
254
254
  * trustProxy: boolean|number, // legacy; refused unless paired with trustedProxies/protocolResolver (spoofable)
255
255
  * audit: boolean,
256
- * skipStateless: boolean, // default false — skip validation for Authorization-header / cookieless (not-CSRF-able) requests
256
+ * skipStateless: boolean, // default false — skip the token check for cookieless (not-CSRF-able) requests. Turns on the absence of the ambient credential only: an Authorization header is not part of the test, and it never waives `checkOrigin`.
257
257
  * onDeny: function(req, res, info): void, // own the 403; info = { status, reason }
258
258
  * problemDetails: boolean, // default false — emit RFC 9457 application/problem+json instead of the default JSON envelope
259
259
  * }
@@ -363,17 +363,21 @@ function create(opts) {
363
363
  // is opt-in rather than silent.
364
364
  var requireOriginOpt = opts.requireOrigin === true;
365
365
 
366
- // skipStateless — skip token VALIDATION for requests that carry an
367
- // Authorization header (bearer / token auth) or no Cookie header at
368
- // all. Such requests are not CSRF-able: CSRF abuses a victim's ambient
369
- // cookie credential, and a token-authenticated or cookieless request
370
- // has none to abuse. The token is still ISSUED on safe methods so a
371
- // later cookie-authenticated browser flow on the same app works. Default
372
- // false (strict every state-changing request is validated). createApp
373
- // wires its default csrf with this on so mixed browser-form + token-API
374
- // surfaces don't reject legitimate API clients. Cross-site form CSRF is
375
- // unaffected: the browser auto-sends the victim's cookies, so the attack
376
- // request always carries a Cookie header and is validated.
366
+ // skipStateless — skip token VALIDATION for requests that carry no Cookie
367
+ // header at all. Such requests are not CSRF-able: CSRF abuses a victim's
368
+ // ambient cookie credential, and a request that sends none has nothing to
369
+ // abuse. The token is still ISSUED on safe methods so a later
370
+ // cookie-authenticated browser flow on the same app works. Default false
371
+ // (strict every state-changing request is validated). createApp wires its
372
+ // default csrf with this on so mixed browser-form + token-API surfaces don't
373
+ // reject legitimate API clients. Cross-site form CSRF is unaffected: the
374
+ // browser auto-sends the victim's cookies, so the attack request always
375
+ // carries a Cookie header and is validated.
376
+ //
377
+ // An `Authorization` header is NOT part of the test, and a bearer client that
378
+ // also sends an unrelated cookie is validated like anything else. Deciding
379
+ // otherwise needs the auth layer's verdict about which credential
380
+ // authenticated the request, which header presence cannot supply.
377
381
  var skipStateless = opts.skipStateless === true;
378
382
 
379
383
  // Per-path exemption (string-prefix / RegExp / skip predicate), validated at
@@ -548,14 +552,6 @@ function create(opts) {
548
552
 
549
553
  if (methods.indexOf(req.method) === -1) return next();
550
554
 
551
- // Stateless / token-authenticated requests are not CSRF-able — the
552
- // token was still issued above for any later browser flow.
553
- if (skipStateless) {
554
- var hasAuthHeader = !!(req.headers && req.headers.authorization);
555
- var hasCookieHeader = !!(req.headers && req.headers.cookie);
556
- if (hasAuthHeader || !hasCookieHeader) return next();
557
- }
558
-
559
555
  // requireJsonContentType — refuse before the token check.
560
556
  if (requireJsonCt) {
561
557
  var ct = req.headers && req.headers["content-type"];
@@ -578,6 +574,27 @@ function create(opts) {
578
574
  }
579
575
  }
580
576
 
577
+ // Stateless requests are not CSRF-able — the token was still issued above
578
+ // for any later browser flow.
579
+ //
580
+ // The test is the ABSENCE of the ambient credential, and nothing else. CSRF
581
+ // spends a cookie the browser attaches on its own; a request carrying none
582
+ // has nothing to abuse. This used to fire on an `Authorization` header too,
583
+ // which was two mistakes at once. Presence is not authenticity — an
584
+ // attacker composing a cross-site request writes their own headers, so
585
+ // `Authorization: Bearer nonsense` satisfied it by being typed. And the
586
+ // header says nothing about which credential authenticated the request:
587
+ // attachUser with tokenFrom: "both" reads the cookie FIRST, so a request
588
+ // carrying both was authenticated by exactly the ambient credential this
589
+ // gate protects, and skipped the gate on a header nobody had read.
590
+ //
591
+ // It also sits BELOW the origin cross-check now rather than above it. A
592
+ // consumer that asked for checkOrigin asked for something the token compare
593
+ // does not give them, and there is no reading of "stateless" under which a
594
+ // cross-origin state change becomes acceptable — the branch that waived the
595
+ // first line of defence was waiving the second one with it.
596
+ if (skipStateless && !(req.headers && req.headers.cookie)) return next();
597
+
581
598
  if (!cookieCfg) {
582
599
  // Session-stored mode — operator's tokenLookup is the source.
583
600
  expected = opts.tokenLookup(req);
@@ -716,7 +716,21 @@ async function tlsRptSubmit(report, opts) {
716
716
  } else if (/^mailto:/i.test(uri)) {
717
717
  // Operator-side transport. Surface the prepared body so the
718
718
  // operator can hand it to b.mail directly.
719
- var mailtoTarget = uri.slice("mailto:".length);
719
+ var publishedTarget = uri.slice("mailto:".length);
720
+ // The address is published by the RECEIVING domain in its own
721
+ // `_smtp._tls` record. It is not operator input to this sender, and a
722
+ // peer spelling its hostname absolutely is using the form RFC 1035 §3.1
723
+ // defines — a zone file writes it that way routinely.
724
+ //
725
+ // So fold one trailing dot before validating rather than validating the
726
+ // spelling as published. Strictness normally protects the party doing
727
+ // the refusing; here it costs the OTHER party the report they asked for
728
+ // under RFC 8460 and costs this sender nothing — the entry silently
729
+ // never becomes a report. That is the direction that makes the lenient
730
+ // reading correct. The fold cannot turn a valid address into a
731
+ // different valid one, because a trailing dot is not legal in an
732
+ // addr-spec domain, so a name carrying one has exactly one reading.
733
+ var mailtoTarget = publishedTarget.replace(/\.$/, "");
720
734
  // RFC 5322 §3.4.1 addr-spec validation — refuse mailto: rua
721
735
  // entries that aren't valid addresses. Pre-v0.8.32 the
722
736
  // framework would forward whatever string came after
@@ -730,6 +744,9 @@ async function tlsRptSubmit(report, opts) {
730
744
  entry.ok = true;
731
745
  entry.mailto = {
732
746
  to: mailtoTarget,
747
+ // What the peer actually published, kept beside what was used, so
748
+ // an operator reading the result can see the two are different.
749
+ published: publishedTarget,
733
750
  subject: "Report Domain: " + (report["organization-name"] || "") +
734
751
  " Submitter: " + (report["organization-name"] || "") +
735
752
  " Report-ID: <" + (report["report-id"] || "") + ">",
@@ -80,6 +80,10 @@ var STATE = {
80
80
  systemTrust: false,
81
81
  baselineFingerprints: null,
82
82
  tlsKeyShares: ["X25519MLKEM768", "SecP256r1MLKEM768", "SecP384r1MLKEM1024", "X25519"],
83
+ // False until pqc.setKeyShares is called. The list above is the shipped
84
+ // default, and a listener widens THAT for interoperability; a list the
85
+ // operator chose is taken as written.
86
+ tlsKeySharesConfigured: false,
83
87
  };
84
88
 
85
89
  function _normalizePem(pem) {
@@ -566,6 +570,68 @@ function keyAgreementGroups(override, where) {
566
570
  return STATE.tlsKeyShares.length > 0 ? STATE.tlsKeyShares.join(":") : null;
567
571
  }
568
572
 
573
+ // The same policy in the shape a LISTENER needs.
574
+ //
575
+ // keyAgreementGroups above returns a flat colon-separated list, which is an
576
+ // offer order on a client and an accept SET on a server — OpenSSL 3.5 reads
577
+ // only `/`-separated tuples as an order. Handing a listener the client form
578
+ // made it refuse every group the list did not name, secp256r1 included, and
579
+ // collapsed the retry ordering that pulls a peer up to a hybrid. So a server
580
+ // takes this instead, and the difference is not a detail a call site should
581
+ // have to remember.
582
+ //
583
+ // An override is honoured verbatim and a malformed one refused, exactly as on
584
+ // the client side: an operator who names their own groups gets them, and gets
585
+ // told when what they named cannot be parsed rather than being silently given
586
+ // the default.
587
+ // The PREFERENCE half is whatever is active — the same STATE.tlsKeyShares the
588
+ // client form reads, so an operator who called pqc.setKeyShares to opt out of
589
+ // hybrids gets listeners that do not prefer them either. Hardcoding the default
590
+ // here would have let a configured policy apply outbound and be silently
591
+ // ignored inbound, which is worse than not having the helper.
592
+ //
593
+ // The fallback tuple only ADDS acceptance; it never changes what is preferred,
594
+ // and anything already in the preference is not repeated into it.
595
+ function serverKeyAgreementGroups(override, where) {
596
+ if (override !== undefined && override !== null) {
597
+ return _groupPreferenceString(override, where || "tls.serverKeyAgreementGroups");
598
+ }
599
+ var preferred = STATE.tlsKeyShares.length > 0
600
+ ? STATE.tlsKeyShares.slice()
601
+ : C.TLS_GROUP_PREFERENCE.slice();
602
+ if (preferred.length === 0) return null;
603
+
604
+ // EVERY configured rank is its own tuple, in order.
605
+ //
606
+ // Ordering exists only BETWEEN tuples: groups sharing one are a set the
607
+ // server takes in whatever order the handshake reaches them. So any two
608
+ // preferences packed into a single tuple stop being ranked — a hybrid beside
609
+ // X25519 loses to it whenever a peer offers both, and `["secp384r1",
610
+ // "X25519"]` silently negotiates X25519. One tuple per entry is the only
611
+ // shape that means what the configured list says.
612
+ //
613
+ // The acceptance-only curves come last, as a single tuple: they are there so
614
+ // a conforming peer is never refused, and carry no preference between
615
+ // themselves.
616
+ //
617
+ // They are added ONLY to the shipped default. An operator who called
618
+ // setKeyShares narrowed the policy deliberately — the module's own FIPS
619
+ // example drops the X25519-based groups — and adding those back would let a
620
+ // peer negotiate exactly what they excluded, with the restriction holding
621
+ // outbound and silently not holding inbound. A narrowed listener may refuse
622
+ // a conforming peer; that is the operator's choice to make, and it is
623
+ // visible, where the reverse would not be.
624
+ var named = preferred.map(function (g) { return String(g).toLowerCase(); });
625
+ var fallback = STATE.tlsKeySharesConfigured ? [] :
626
+ C.TLS_SERVER_FALLBACK_CURVES.filter(function (g) {
627
+ return named.indexOf(String(g).toLowerCase()) === -1;
628
+ });
629
+
630
+ var tuples = preferred.slice();
631
+ if (fallback.length > 0) tuples.push(fallback.join(":"));
632
+ return tuples.join("/");
633
+ }
634
+
569
635
  function applyToContext(opts) {
570
636
  opts = opts || {};
571
637
  validateOpts(opts, ["base"], "tls.applyToContext");
@@ -699,6 +765,12 @@ function setKeyShares(list) {
699
765
  }
700
766
  for (var i = 0; i < list.length; i += 1) _validateKeyShare(list[i]);
701
767
  STATE.tlsKeyShares = list.slice();
768
+ // Records that this list is a CHOICE rather than the shipped default. A
769
+ // listener adds interoperability curves to the default so it does not refuse
770
+ // a conforming peer out of the box; it must not add them to a list the
771
+ // operator narrowed on purpose, or a restriction would hold outbound and be
772
+ // silently undone inbound.
773
+ STATE.tlsKeySharesConfigured = true;
702
774
  _postureGeneration += 1;
703
775
  return getKeyShares();
704
776
  }
@@ -707,6 +779,11 @@ function getKeyShares() { return STATE.tlsKeyShares.slice(); }
707
779
 
708
780
  function resetKeyShares() {
709
781
  STATE.tlsKeyShares = DEFAULT_PQC_KEY_SHARES.slice();
782
+ // Back to the shipped default in every respect, including whether a listener
783
+ // widens it for interoperability. Restoring the list but keeping the
784
+ // configured flag would leave a reset process serving a different group
785
+ // policy from a fresh one, which is not what a reset means.
786
+ STATE.tlsKeySharesConfigured = false;
710
787
  _postureGeneration += 1;
711
788
  return getKeyShares();
712
789
  }
@@ -1251,6 +1328,7 @@ function _resetForTest() {
1251
1328
  STATE.systemTrust = false;
1252
1329
  STATE.baselineFingerprints = null;
1253
1330
  STATE.tlsKeyShares = DEFAULT_PQC_KEY_SHARES.slice();
1331
+ STATE.tlsKeySharesConfigured = false;
1254
1332
  }
1255
1333
 
1256
1334
  // ---- OCSP / OCSP-stapling wrappers around node:tls ----------------
@@ -4156,6 +4234,7 @@ module.exports = {
4156
4234
  detectBaselineDrift: detectBaselineDrift,
4157
4235
  applyToContext: applyToContext,
4158
4236
  keyAgreementGroups: keyAgreementGroups,
4237
+ serverKeyAgreementGroups: serverKeyAgreementGroups,
4159
4238
  buildOptions: buildOptions,
4160
4239
  getCaPems: getCaPems,
4161
4240
  ocsp: ocsp,
@@ -29,9 +29,12 @@
29
29
  * schema (sidHash PRIMARY KEY, userId, userIdHash, data, createdAt,
30
30
  * expiresAt, lastActivity) plus the indexes session-side queries
31
31
  * need (userIdHash for `destroyAllForUser`, expiresAt for
32
- * `purgeExpired`). Operators typically point `file` at tmpfs (e.g.
33
- * `/dev/shm/blamejs-sessions.db`) so session inserts run RAM-fast
34
- * and don't compete with the main DB's encryption-flush cycle.
32
+ * `purgeExpired`). Operators typically point `file` at tmpfs
33
+ * (`/dev/shm/blamejs-sessions.db` on Linux, an in-memory volume on
34
+ * Windows) so session inserts run RAM-fast and don't compete with
35
+ * the main DB's encryption-flush cycle. The path is yours to name —
36
+ * a leading slash is drive-relative on Windows, where `/dev/shm`
37
+ * means `C:\dev\shm` and is ordinary disk.
35
38
  *
36
39
  * Wire it once at boot, before the first session call:
37
40
  *
@@ -2,7 +2,7 @@
2
2
  "_comment": "Vendored dependencies — no npm runtime packages. Use scripts/vendor-update.sh to update.",
3
3
  "packages": {
4
4
  "@noble/ciphers": {
5
- "version": "2.3.0",
5
+ "version": "2.4.0",
6
6
  "license": "MIT",
7
7
  "author": "Paul Miller",
8
8
  "source": "https://github.com/paulmillr/noble-ciphers",
@@ -15,16 +15,16 @@
15
15
  "browser": "lib/vendor/browser/noble-ciphers.mjs"
16
16
  },
17
17
  "bundler": "esbuild --format=cjs --platform=node (server), esbuild --format=esm --platform=browser (browser)",
18
- "bundledAt": "2026-08-10T00:00:00Z",
19
- "cpe": "cpe:2.3:a:paulmillr:noble-ciphers:2.3.0:*:*:*:*:node.js:*:*",
18
+ "bundledAt": "2026-08-27T00:00:00Z",
19
+ "cpe": "cpe:2.3:a:paulmillr:noble-ciphers:2.4.0:*:*:*:*:node.js:*:*",
20
20
  "hashes": {
21
- "server": "sha256:f3325f480cb8eb814fcb0baaa19336cbbf2b993f48624c6aa9600ffd69d0be5e",
22
- "browser": "sha256:0ffd91540bcb586a29b56e52ee1c29df69097b50776beb4036a07558f7a4e12e"
21
+ "server": "sha256:acdcf1ba4bde8177cbfcfef22b543022284a1529afb68bc8329303837c8c021e",
22
+ "browser": "sha256:fc422d85a65c5dc1208066b0156e100c4790be3c8791f644b80524c25d4bc3ee"
23
23
  },
24
- "refreshedAt": "2026-08-25T10:45:22.083Z"
24
+ "refreshedAt": "2026-08-28T04:49:21.011Z"
25
25
  },
26
26
  "@noble/hashes": {
27
- "version": "2.3.0",
27
+ "version": "2.4.0",
28
28
  "license": "MIT",
29
29
  "author": "Paul Miller",
30
30
  "source": "https://github.com/paulmillr/noble-hashes",
@@ -43,15 +43,15 @@
43
43
  "browser": "lib/vendor/browser/noble-hashes.mjs"
44
44
  },
45
45
  "bundler": "esbuild --format=esm --platform=browser (browser only)",
46
- "bundledAt": "2026-08-10T00:00:00Z",
47
- "cpe": "cpe:2.3:a:paulmillr:noble-hashes:2.3.0:*:*:*:*:node.js:*:*",
46
+ "bundledAt": "2026-08-27T00:00:00Z",
47
+ "cpe": "cpe:2.3:a:paulmillr:noble-hashes:2.4.0:*:*:*:*:node.js:*:*",
48
48
  "hashes": {
49
- "browser": "sha256:dfe4b7ae3c9880e388c8da4b68f44742b229b53afacd1e674179527e33da62b0"
49
+ "browser": "sha256:0e135cbb496a1b7d15bf36b8c611a8feaad49ced60a03bf916bada78b589aa66"
50
50
  },
51
- "refreshedAt": "2026-08-25T10:45:22.083Z"
51
+ "refreshedAt": "2026-08-28T04:49:21.011Z"
52
52
  },
53
53
  "@noble/curves": {
54
- "version": "2.3.0",
54
+ "version": "2.4.0",
55
55
  "license": "MIT",
56
56
  "author": "Paul Miller",
57
57
  "source": "https://github.com/paulmillr/noble-curves",
@@ -65,21 +65,21 @@
65
65
  "server": "lib/vendor/noble-curves.cjs"
66
66
  },
67
67
  "bundler": "esbuild --format=cjs --platform=node",
68
- "bundledAt": "2026-08-21T00:00:00Z",
69
- "cpe": "cpe:2.3:a:paulmillr:noble-curves:2.3.0:*:*:*:*:node.js:*:*",
68
+ "bundledAt": "2026-08-27T00:00:00Z",
69
+ "cpe": "cpe:2.3:a:paulmillr:noble-curves:2.4.0:*:*:*:*:node.js:*:*",
70
70
  "hashes": {
71
- "server": "sha256:b5fe88d1ea780d0581dee6145d666f89d46fc9531b5db35db2e5b16627840890"
71
+ "server": "sha256:477359f445eed241ad4ededb84921483c3c97618a160ef78b74bb1a421c17da7"
72
72
  },
73
- "refreshedAt": "2026-08-25T10:45:22.083Z",
73
+ "refreshedAt": "2026-08-28T04:49:21.011Z",
74
74
  "components": {
75
75
  "@noble/hashes": {
76
76
  "url": "https://github.com/paulmillr/noble-hashes",
77
- "version": "2.3.0"
77
+ "version": "2.4.0"
78
78
  }
79
79
  }
80
80
  },
81
81
  "@noble/post-quantum": {
82
- "version": "0.7.0",
82
+ "version": "0.7.1",
83
83
  "license": "MIT",
84
84
  "author": "Paul Miller",
85
85
  "source": "https://github.com/paulmillr/noble-post-quantum",
@@ -108,25 +108,25 @@
108
108
  "browser": "lib/vendor/browser/noble-post-quantum.mjs"
109
109
  },
110
110
  "bundler": "esbuild --format=cjs --platform=node (server), esbuild --format=esm --platform=browser (browser)",
111
- "bundledAt": "2026-08-10T00:00:00Z",
112
- "cpe": "cpe:2.3:a:paulmillr:noble-post-quantum:0.7.0:*:*:*:*:node.js:*:*",
111
+ "bundledAt": "2026-08-27T00:00:00Z",
112
+ "cpe": "cpe:2.3:a:paulmillr:noble-post-quantum:0.7.1:*:*:*:*:node.js:*:*",
113
113
  "hashes": {
114
- "server": "sha256:fab7ebe5737793862c473444f4ee5912f79dd1edec86683acbb4eecbca0f5892",
115
- "browser": "sha256:cae1d5bbdc7184b202b6ca68df6e1db7b0d0f668c77809ded189ca7f271accc9"
114
+ "server": "sha256:04eaaa4838b43162912c851dd2cab52ab82f56decad8542934171247017a6893",
115
+ "browser": "sha256:30ceef750bc57ae5f59c5aa7454eafb7aad55d30c261a1b5c538a66c569fc031"
116
116
  },
117
- "refreshedAt": "2026-08-25T10:45:22.083Z",
117
+ "refreshedAt": "2026-08-28T04:49:21.011Z",
118
118
  "components": {
119
119
  "@noble/hashes": {
120
120
  "url": "https://github.com/paulmillr/noble-hashes",
121
- "version": "2.3.0"
121
+ "version": "2.4.0"
122
122
  },
123
123
  "@noble/curves": {
124
124
  "url": "https://github.com/paulmillr/noble-curves",
125
- "version": "2.3.0"
125
+ "version": "2.4.0"
126
126
  },
127
127
  "@noble/ciphers": {
128
128
  "url": "https://github.com/paulmillr/noble-ciphers",
129
- "version": "2.3.0"
129
+ "version": "2.4.0"
130
130
  }
131
131
  }
132
132
  },
@@ -148,7 +148,7 @@
148
148
  },
149
149
  "runtime_artifact": "lib/vendor/common-passwords-top-10000.data.js",
150
150
  "integrity_layers": "sha256 + sha3-512 + SLH-DSA-SHAKE-256f signature + in-payload canary (where applicable)",
151
- "refreshedAt": "2026-08-25T10:45:22.083Z"
151
+ "refreshedAt": "2026-08-28T04:49:21.011Z"
152
152
  },
153
153
  "bimi-trust-anchors": {
154
154
  "version": "operator-managed",
@@ -173,7 +173,7 @@
173
173
  },
174
174
  "runtime_artifact": "lib/vendor/bimi-trust-anchors.data.js",
175
175
  "integrity_layers": "sha256 + sha3-512 + SLH-DSA-SHAKE-256f signature + in-payload canary (where applicable)",
176
- "refreshedAt": "2026-08-25T10:45:22.083Z"
176
+ "refreshedAt": "2026-08-28T04:49:21.011Z"
177
177
  },
178
178
  "publicsuffix-list": {
179
179
  "version": "master",
@@ -193,7 +193,7 @@
193
193
  },
194
194
  "runtime_artifact": "lib/vendor/public-suffix-list.data.js",
195
195
  "integrity_layers": "sha256 + sha3-512 + SLH-DSA-SHAKE-256f signature + in-payload canary (where applicable)",
196
- "refreshedAt": "2026-08-25T10:45:22.083Z"
196
+ "refreshedAt": "2026-08-28T04:49:21.011Z"
197
197
  },
198
198
  "@blamejs/pki": {
199
199
  "version": "0.5.31",
@@ -221,7 +221,7 @@
221
221
  "hashes": {
222
222
  "server": "sha256:8cc54fdfb7ac5277fb30f054f57500a9e77ce6b2d2b8f6846a14766d217770db"
223
223
  },
224
- "refreshedAt": "2026-08-25T10:45:22.083Z"
224
+ "refreshedAt": "2026-08-28T04:49:21.011Z"
225
225
  }
226
226
  }
227
227
  }
@@ -1,4 +1,4 @@
1
- // XChaCha20-Poly1305 — vendored from @noble/ciphers v2.3.0 by Paul Miller
1
+ // XChaCha20-Poly1305 — vendored from @noble/ciphers v2.4.0 by Paul Miller
2
2
  // License: MIT — https://github.com/paulmillr/noble-ciphers
3
3
  // Browser build (ESM), bundled with esbuild from the same install as the
4
4
  // server bundle beside it. Exports: xchacha20poly1305
@@ -71,6 +71,17 @@ function byteSwap32(arr) {
71
71
  return arr;
72
72
  }
73
73
  var swap32IfBE = isLE ? (u) => u : byteSwap32;
74
+ function overlapBytes(a, b) {
75
+ if (!a.byteLength || !b.byteLength)
76
+ return false;
77
+ return a.buffer === b.buffer && // best we can do, may fail with an obscure Proxy
78
+ a.byteOffset < b.byteOffset + b.byteLength && // a starts before b end
79
+ b.byteOffset < a.byteOffset + a.byteLength;
80
+ }
81
+ function complexOverlapBytes(input, output) {
82
+ if (overlapBytes(input, output) && input.byteOffset < output.byteOffset)
83
+ throw new Error("complex overlap of input and output is not supported");
84
+ }
74
85
  function checkOpts(defaults, opts) {
75
86
  aobject(defaults, "defaults");
76
87
  aobject(opts, "opts");
@@ -238,7 +249,10 @@ function createCipher(core, opts) {
238
249
  abytes(nonce, void 0, "nonce");
239
250
  abytes(data, void 0, "data");
240
251
  const len = data.length;
252
+ const hasOutput = output !== void 0;
241
253
  output = getOutput(len, output, false);
254
+ if (hasOutput)
255
+ complexOverlapBytes(data, output);
242
256
  anumber(counter);
243
257
  if (counter < 0 || counter >= MAX_COUNTER)
244
258
  throw new Error("arx: counter overflow");
@@ -1,4 +1,4 @@
1
- // @noble/hashes v2.3.0 — vendored from Paul Miller
1
+ // @noble/hashes v2.4.0 — vendored from Paul Miller
2
2
  // License: MIT — https://github.com/paulmillr/noble-hashes
3
3
  // Browser build (ESM), bundled with esbuild. The server side uses node:crypto
4
4
  // for these, so there is no .cjs beside it.
@@ -89,6 +89,14 @@ var aobject = (value, label) => {
89
89
  if (value === null || typeof value !== "object" || Array.isArray(value))
90
90
  throw new TypeError((label === "object" ? "" : `"${label}" `) + "expected object, got type=" + typeof value);
91
91
  };
92
+ var aopts = (value, label) => {
93
+ aobject(value, label);
94
+ const proto = Object.getPrototypeOf(value);
95
+ if (proto !== Object.prototype && proto !== null)
96
+ throw new TypeError(`"${label}" expected plain object`);
97
+ if (Object.hasOwn(value, "__proto__"))
98
+ throw new TypeError(`"${label}.__proto__" is not allowed`);
99
+ };
92
100
  function aexists(instance, checkFinished = true) {
93
101
  if (instance.destroyed)
94
102
  throw new Error("hash was destroyed");
@@ -128,10 +136,10 @@ function byteSwap32(arr) {
128
136
  }
129
137
  var swap32IfBE = isLE ? (u) => u : byteSwap32;
130
138
  function checkOpts(defaults, opts, title = "opts") {
131
- aobject(defaults, "defaults");
139
+ aopts(defaults, "defaults");
132
140
  if (opts !== void 0)
133
- aobject(opts, title);
134
- const merged = Object.assign(defaults, opts);
141
+ aopts(opts, title);
142
+ const merged = Object.assign(/* @__PURE__ */ Object.create(null), defaults, opts);
135
143
  return merged;
136
144
  }
137
145
  function createHasher(hashCons, info = {}) {