@blamejs/core 0.15.13 → 0.15.15

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.
Files changed (208) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/index.js +2 -0
  3. package/lib/a2a-tasks.js +38 -6
  4. package/lib/agent-event-bus.js +13 -0
  5. package/lib/agent-idempotency.js +5 -1
  6. package/lib/agent-snapshot.js +32 -2
  7. package/lib/ai-aedt-bias-audit.js +2 -1
  8. package/lib/ai-content-detect.js +1 -3
  9. package/lib/ai-frontier-protocol.js +1 -1
  10. package/lib/ai-model-manifest.js +1 -1
  11. package/lib/ai-output.js +16 -7
  12. package/lib/api-snapshot.js +4 -1
  13. package/lib/app-shutdown.js +7 -1
  14. package/lib/archive-gz.js +9 -0
  15. package/lib/archive-read.js +9 -7
  16. package/lib/archive-tar-read.js +51 -8
  17. package/lib/archive.js +4 -2
  18. package/lib/asn1-der.js +70 -22
  19. package/lib/atomic-file.js +204 -2
  20. package/lib/audit-chain.js +54 -5
  21. package/lib/audit-daily-review.js +12 -2
  22. package/lib/audit-sign.js +2 -1
  23. package/lib/audit-tools.js +108 -23
  24. package/lib/audit.js +7 -2
  25. package/lib/auth/access-lock.js +2 -2
  26. package/lib/auth/bot-challenge.js +1 -1
  27. package/lib/auth/ciba.js +71 -8
  28. package/lib/auth/dpop.js +15 -1
  29. package/lib/auth/fido-mds3.js +30 -13
  30. package/lib/auth/jwt.js +21 -5
  31. package/lib/auth/lockout.js +18 -2
  32. package/lib/auth/oauth.js +8 -2
  33. package/lib/auth/passkey.js +1 -1
  34. package/lib/auth/password.js +1 -1
  35. package/lib/auth/saml.js +42 -12
  36. package/lib/auth/sd-jwt-vc.js +24 -4
  37. package/lib/auth/status-list.js +14 -2
  38. package/lib/auth/step-up.js +9 -1
  39. package/lib/auth-bot-challenge.js +21 -2
  40. package/lib/backup/bundle.js +7 -2
  41. package/lib/backup/crypto.js +23 -8
  42. package/lib/backup/index.js +41 -20
  43. package/lib/backup/manifest.js +7 -1
  44. package/lib/break-glass.js +41 -22
  45. package/lib/cbor.js +34 -11
  46. package/lib/cdn-cache-control.js +7 -3
  47. package/lib/cert.js +5 -3
  48. package/lib/cli.js +5 -1
  49. package/lib/cloud-events.js +3 -2
  50. package/lib/cluster-storage.js +7 -3
  51. package/lib/codepoint-class.js +17 -0
  52. package/lib/compliance-eaa.js +1 -1
  53. package/lib/compliance-sanctions.js +9 -7
  54. package/lib/compliance.js +1 -1
  55. package/lib/config-drift.js +22 -8
  56. package/lib/content-credentials.js +11 -8
  57. package/lib/content-digest.js +11 -4
  58. package/lib/cookies.js +10 -2
  59. package/lib/cose.js +20 -0
  60. package/lib/crdt.js +2 -1
  61. package/lib/crypto-field.js +48 -24
  62. package/lib/crypto.js +18 -4
  63. package/lib/csp.js +13 -0
  64. package/lib/daemon.js +4 -1
  65. package/lib/data-act.js +27 -4
  66. package/lib/db-file-lifecycle.js +14 -6
  67. package/lib/db-query.js +102 -11
  68. package/lib/db.js +32 -15
  69. package/lib/dora.js +43 -13
  70. package/lib/dr-runbook.js +1 -1
  71. package/lib/dsa.js +2 -1
  72. package/lib/dsr.js +22 -8
  73. package/lib/early-hints.js +19 -0
  74. package/lib/eat.js +5 -1
  75. package/lib/external-db.js +60 -4
  76. package/lib/fda-21cfr11.js +30 -5
  77. package/lib/flag-providers.js +6 -2
  78. package/lib/forms.js +1 -1
  79. package/lib/gate-contract.js +46 -5
  80. package/lib/gdpr-ropa.js +18 -9
  81. package/lib/graphql-federation.js +17 -4
  82. package/lib/guard-all.js +2 -2
  83. package/lib/guard-dsn.js +1 -1
  84. package/lib/guard-envelope.js +1 -1
  85. package/lib/guard-html.js +9 -11
  86. package/lib/guard-imap-command.js +1 -1
  87. package/lib/guard-jmap.js +1 -1
  88. package/lib/guard-json.js +14 -6
  89. package/lib/guard-mail-move.js +1 -1
  90. package/lib/guard-managesieve-command.js +1 -1
  91. package/lib/guard-pop3-command.js +1 -1
  92. package/lib/guard-smtp-command.js +1 -1
  93. package/lib/guard-svg.js +8 -9
  94. package/lib/html-balance.js +7 -3
  95. package/lib/http-client-cookie-jar.js +33 -12
  96. package/lib/http-client.js +225 -53
  97. package/lib/iab-tcf.js +3 -2
  98. package/lib/importmap-integrity.js +41 -1
  99. package/lib/incident-report.js +9 -6
  100. package/lib/json-patch.js +1 -1
  101. package/lib/json-path.js +24 -3
  102. package/lib/jtd.js +2 -2
  103. package/lib/legal-hold.js +24 -8
  104. package/lib/log.js +2 -2
  105. package/lib/mail-agent.js +2 -2
  106. package/lib/mail-arf.js +1 -1
  107. package/lib/mail-auth.js +27 -4
  108. package/lib/mail-bimi.js +16 -16
  109. package/lib/mail-bounce.js +3 -3
  110. package/lib/mail-crypto-smime.js +71 -6
  111. package/lib/mail-deploy.js +9 -5
  112. package/lib/mail-dkim.js +20 -7
  113. package/lib/mail-greylist.js +2 -4
  114. package/lib/mail-helo.js +2 -4
  115. package/lib/mail-journal.js +11 -8
  116. package/lib/mail-mdn.js +8 -4
  117. package/lib/mail-rbl.js +2 -4
  118. package/lib/mail-scan.js +3 -5
  119. package/lib/mail-server-jmap.js +4 -1
  120. package/lib/mail-server-registry.js +1 -1
  121. package/lib/mail-server-tls.js +9 -2
  122. package/lib/mail-spam-score.js +2 -4
  123. package/lib/mail-store-fts.js +1 -1
  124. package/lib/mail.js +22 -2
  125. package/lib/markup-tokenizer.js +24 -0
  126. package/lib/mcp.js +6 -4
  127. package/lib/mdoc.js +26 -3
  128. package/lib/metrics.js +14 -3
  129. package/lib/middleware/api-encrypt.js +2 -2
  130. package/lib/middleware/body-parser.js +10 -4
  131. package/lib/middleware/bot-guard.js +26 -18
  132. package/lib/middleware/clear-site-data.js +5 -1
  133. package/lib/middleware/compose-pipeline.js +39 -5
  134. package/lib/middleware/compression.js +9 -0
  135. package/lib/middleware/cors.js +32 -23
  136. package/lib/middleware/csrf-protect.js +60 -21
  137. package/lib/middleware/daily-byte-quota.js +6 -4
  138. package/lib/middleware/fetch-metadata.js +28 -4
  139. package/lib/middleware/network-allowlist.js +61 -30
  140. package/lib/middleware/rate-limit.js +25 -16
  141. package/lib/middleware/scim-server.js +2 -1
  142. package/lib/middleware/security-headers.js +24 -6
  143. package/lib/middleware/speculation-rules.js +6 -3
  144. package/lib/middleware/tus-upload.js +2 -2
  145. package/lib/money.js +1 -1
  146. package/lib/mtls-ca.js +10 -6
  147. package/lib/network-dns-resolver.js +1 -1
  148. package/lib/network-dns.js +9 -2
  149. package/lib/network-dnssec.js +2 -1
  150. package/lib/network-smtp-policy.js +23 -5
  151. package/lib/network-tls.js +27 -5
  152. package/lib/network-tsig.js +2 -2
  153. package/lib/nis2-report.js +1 -1
  154. package/lib/nist-crosswalk.js +1 -1
  155. package/lib/ntp-check.js +28 -0
  156. package/lib/numeric-bounds.js +9 -0
  157. package/lib/object-store/azure-blob.js +1 -2
  158. package/lib/object-store/gcs-bucket-ops.js +4 -2
  159. package/lib/object-store/gcs.js +6 -4
  160. package/lib/object-store/http-put.js +1 -2
  161. package/lib/object-store/http-request.js +30 -1
  162. package/lib/object-store/local.js +37 -17
  163. package/lib/object-store/sigv4.js +1 -2
  164. package/lib/observability-otlp-exporter.js +20 -4
  165. package/lib/outbox.js +11 -4
  166. package/lib/parsers/safe-xml.js +1 -1
  167. package/lib/parsers/safe-yaml.js +21 -3
  168. package/lib/pipl-cn.js +11 -8
  169. package/lib/queue-local.js +10 -3
  170. package/lib/redact.js +7 -3
  171. package/lib/request-helpers.js +347 -36
  172. package/lib/resource-access-lock.js +3 -3
  173. package/lib/restore-bundle.js +46 -18
  174. package/lib/restore-rollback.js +10 -4
  175. package/lib/restore.js +19 -0
  176. package/lib/retention.js +20 -4
  177. package/lib/router.js +17 -4
  178. package/lib/safe-ical.js +2 -2
  179. package/lib/safe-icap.js +1 -1
  180. package/lib/safe-json.js +70 -0
  181. package/lib/safe-sieve.js +1 -1
  182. package/lib/safe-vcard.js +1 -1
  183. package/lib/sandbox-worker.js +6 -0
  184. package/lib/sandbox.js +1 -1
  185. package/lib/scheduler.js +17 -1
  186. package/lib/self-update-standalone-verifier.js +16 -0
  187. package/lib/session.js +62 -120
  188. package/lib/sql.js +25 -3
  189. package/lib/static.js +65 -13
  190. package/lib/template.js +7 -5
  191. package/lib/tenant-quota.js +52 -19
  192. package/lib/tsa.js +5 -2
  193. package/lib/vault/index.js +5 -0
  194. package/lib/vault/passphrase-ops.js +22 -26
  195. package/lib/vault/passphrase-source.js +8 -3
  196. package/lib/vault/rotate.js +13 -18
  197. package/lib/vault/seal-pem-file.js +4 -1
  198. package/lib/vc.js +1 -1
  199. package/lib/vendor/MANIFEST.json +10 -10
  200. package/lib/vendor/public-suffix-list.dat +23 -10
  201. package/lib/vendor/public-suffix-list.data.js +5498 -5494
  202. package/lib/webhook.js +16 -1
  203. package/lib/websocket.js +1 -1
  204. package/lib/worm.js +1 -1
  205. package/lib/ws-client.js +83 -46
  206. package/lib/x509-chain.js +91 -0
  207. package/package.json +1 -1
  208. package/sbom.cdx.json +6 -6
package/lib/webhook.js CHANGED
@@ -48,6 +48,7 @@
48
48
  */
49
49
 
50
50
  var nodeCrypto = require("node:crypto");
51
+ var numericBounds = require("./numeric-bounds");
51
52
  var bCrypto = require("./crypto");
52
53
  var httpClient = require("./http-client");
53
54
  var safeBuffer = require("./safe-buffer");
@@ -57,6 +58,8 @@ var C = require("./constants");
57
58
  var lazyRequire = require("./lazy-require");
58
59
  var numericChecks = require("./numeric-checks");
59
60
  var requestHelpers = require("./request-helpers");
61
+ // Lazy — ssrf-guard pulls in the DNS/network stack; only touched on send().
62
+ var ssrfGuard = lazyRequire(function () { return require("./ssrf-guard"); });
60
63
  var validateOpts = require("./validate-opts");
61
64
  var { WebhookError } = require("./framework-error");
62
65
  // b.webhook.dispatcher — durable signed-webhook delivery store. Lives in its
@@ -428,6 +431,18 @@ function signer(opts) {
428
431
  allowedProtocols: httpOpts.allowedProtocols || safeUrl.ALLOW_HTTP_TLS,
429
432
  errorClass: WebhookError,
430
433
  });
434
+ // SSRF gate at the webhook layer: refuse private / loopback / link-local /
435
+ // cloud-metadata destinations up front, before the retry loop. httpClient
436
+ // also enforces this on every request, but checking here fails fast (no
437
+ // wasted retries) and keeps the SSRF boundary explicit at the send() API
438
+ // rather than relying on the transport's internal gate. allowInternal
439
+ // mirrors the operator's http opts (its affirmative waiver).
440
+ try {
441
+ await ssrfGuard().checkUrl(url, { allowInternal: httpOpts.allowInternal === true });
442
+ } catch (e) {
443
+ if (e && e.isSsrfError) throw _err("SSRF_REFUSED", "webhook.signer.send: " + e.message);
444
+ throw e;
445
+ }
431
446
  _validateBody(body);
432
447
  var signed = sign(body, { kid: input.kid });
433
448
  var mergedHeaders = Object.assign({}, input.headers || {}, signed.headers);
@@ -645,7 +660,7 @@ function verifier(opts) {
645
660
  throw _failure("MISSING_TIMESTAMP", "webhook: t= field missing from signature header", "missing-timestamp", ctxReq);
646
661
  }
647
662
  var ts = Number(parsed.t);
648
- if (!isFinite(ts) || ts < 0 || Math.floor(ts) !== ts) {
663
+ if (!numericBounds.isNonNegativeFiniteInt(ts)) {
649
664
  throw _failure("BAD_TIMESTAMP", "webhook: t= field is not a non-negative integer, got " + JSON.stringify(parsed.t), "bad-timestamp", ctxReq);
650
665
  }
651
666
  if (parsed.id === null || parsed.id.length === 0) {
package/lib/websocket.js CHANGED
@@ -573,7 +573,7 @@ function _negotiatePermessageDeflate(reqHeader) {
573
573
  "server_max_window_bits": true, "client_max_window_bits": true,
574
574
  };
575
575
  var ok = true;
576
- for (var k in p) { if (Object.prototype.hasOwnProperty.call(p, k) && !KNOWN[k]) { ok = false; break; } }
576
+ for (var k in p) { if (Object.prototype.hasOwnProperty.call(p, k) && !Object.prototype.hasOwnProperty.call(KNOWN, k)) { ok = false; break; } }
577
577
  if (!ok) continue;
578
578
  // Always negotiate WITH no_context_takeover in BOTH directions, so
579
579
  // every message uses a fresh zlib state. Echo any client window-
package/lib/worm.js CHANGED
@@ -102,7 +102,7 @@ function create(opts) {
102
102
  opts = opts || {};
103
103
  validateOpts(opts, ["store", "mode", "defaultRetentionMs", "clock"], "worm.create");
104
104
  var mode = opts.mode || "compliance";
105
- if (!MODES[mode]) throw new WormError("worm/bad-mode", "worm.create: mode must be 'compliance' or 'governance'");
105
+ if (!Object.prototype.hasOwnProperty.call(MODES, mode)) throw new WormError("worm/bad-mode", "worm.create: mode must be 'compliance' or 'governance'");
106
106
  var store = opts.store || _memStore();
107
107
  ["get", "set", "delete", "has", "keys"].forEach(function (m) {
108
108
  if (typeof store[m] !== "function") throw new WormError("worm/bad-store", "worm.create: store adapter must implement " + m + "()");
package/lib/ws-client.js CHANGED
@@ -248,17 +248,13 @@ function connect(target, opts) {
248
248
  });
249
249
  // SSRF gate — refuse private / loopback / link-local / cloud-metadata /
250
250
  // reserved IP destinations by default. Symmetric to b.httpClient. The
251
- // returned `ips` are pinned through tls.connect / net.connect so the
251
+ // validated `ips` are pinned through tls.connect / net.connect so the
252
252
  // actual TCP connect targets the validated address (closes the DNS-
253
253
  // rebinding TOCTOU window). Cloud-metadata IPs are unconditional
254
- // hard-deny — `allowInternal: true` does not bypass them.
255
- var hostnameForUrl = parsed.protocol === "wss:" ? "https:" : "http:";
256
- var probeUrl = new nodeUrl.URL(hostnameForUrl + "//" + parsed.host + parsed.pathname + parsed.search);
257
- ssrfGuard.checkUrl(probeUrl, {
258
- allowInternal: opts.allowInternal,
259
- errorClass: WsClientError,
260
- }).then(function (result) {
261
- client._ssrfPinnedIps = result && result.ips;
254
+ // hard-deny — `allowInternal: true` does not bypass them. _prepareDial
255
+ // performs the (async) check + pinning before the dial and reruns it on
256
+ // every reconnect, so a urlFor-swapped target is validated too.
257
+ client._prepareDial().then(function () {
262
258
  client._dial();
263
259
  }).catch(function (e) {
264
260
  setImmediate(function () { client._handleSocketError(e); });
@@ -315,51 +311,50 @@ class WsClient extends EventEmitter {
315
311
  get subprotocol() { return this._negotiatedSubprotocol; }
316
312
  get url() { return this._opts.target; }
317
313
 
318
- _dial() {
319
- var self = this;
314
+ // Resolve the dial target and re-validate it BEFORE any socket opens.
315
+ // urlFor may swap the URL and tlsOptsFor may rotate TLS material every
316
+ // dial (including reconnects). The resolved target is re-checked through
317
+ // ssrfGuard — AWAITED, because checkUrl is async: the prior code called it
318
+ // synchronously inside _dial and discarded the Promise, so a urlFor that
319
+ // pointed at a private / cloud-metadata address mid-reconnect was connected
320
+ // anyway (the SSRF rejection surfaced only as an unhandled rejection) and
321
+ // the connect was never pinned. Returns a Promise; sync throws from
322
+ // urlFor / tlsOptsFor become rejections (async fn) handled by the caller.
323
+ async _prepareDial() {
320
324
  var opts = this._opts;
321
- this._readyState = "connecting";
322
-
323
- // Per-dial overrides — urlFor swaps the target URL, tlsOptsFor
324
- // overrides TLS material. Both fire every dial including reconnects
325
- // so callers can rotate state. urlFor's result is re-validated
326
- // through ssrfGuard so a hostile upstream can't direct the client
327
- // at a private address mid-reconnect.
328
325
  var attempt = this._reconnectAttempt || 0;
329
- var dialTarget = opts.target;
330
326
  var dialParsed = opts.parsedUrl;
331
327
  if (typeof opts.urlFor === "function") {
332
- try {
333
- var nextTarget = opts.urlFor(attempt);
334
- if (typeof nextTarget === "string" && nextTarget.length > 0 && nextTarget !== dialTarget) {
335
- dialParsed = _parseUrl(nextTarget);
336
- dialTarget = nextTarget;
337
- var probeProto = dialParsed.protocol === "wss:" ? "https:" : "http:";
338
- var probeUrl = new nodeUrl.URL(probeProto + "//" + dialParsed.host + dialParsed.pathname + dialParsed.search);
339
- var probe = ssrfGuard.checkUrl(probeUrl, {
340
- allowInternal: opts.allowInternal,
341
- errorClass: WsClientError,
342
- });
343
- this._ssrfPinnedIps = probe && probe.ips ? probe.ips : null;
344
- }
345
- } catch (e) {
346
- return self._handleSocketError(e);
328
+ var nextTarget = opts.urlFor(attempt);
329
+ if (typeof nextTarget === "string" && nextTarget.length > 0 && nextTarget !== opts.target) {
330
+ dialParsed = _parseUrl(nextTarget);
347
331
  }
348
332
  }
349
-
350
333
  var dialTlsOpts = opts.tlsOpts;
351
334
  if (typeof opts.tlsOptsFor === "function") {
352
- try {
353
- var override = opts.tlsOptsFor(attempt);
354
- if (override && typeof override === "object") {
355
- dialTlsOpts = Object.assign({}, opts.tlsOpts || {}, override);
356
- }
357
- } catch (e) {
358
- return self._handleSocketError(e);
335
+ var override = opts.tlsOptsFor(attempt);
336
+ if (override && typeof override === "object") {
337
+ dialTlsOpts = Object.assign({}, opts.tlsOpts || {}, override);
359
338
  }
360
339
  }
340
+ var probeProto = dialParsed.protocol === "wss:" ? "https:" : "http:";
341
+ var probeUrl = new nodeUrl.URL(probeProto + "//" + dialParsed.host + dialParsed.pathname + dialParsed.search);
342
+ var probe = await ssrfGuard.checkUrl(probeUrl, {
343
+ allowInternal: opts.allowInternal,
344
+ errorClass: WsClientError,
345
+ });
346
+ this._ssrfPinnedIps = probe && probe.ips ? probe.ips : null;
347
+ this._dialParsed = dialParsed;
348
+ this._dialTlsOpts = dialTlsOpts;
349
+ }
350
+
351
+ _dial() {
352
+ var self = this;
353
+ this._readyState = "connecting";
361
354
 
362
- var parsed = dialParsed;
355
+ // Target + TLS material were resolved and SSRF-validated by _prepareDial.
356
+ var parsed = this._dialParsed || this._opts.parsedUrl;
357
+ var dialTlsOpts = this._dialTlsOpts || this._opts.tlsOpts;
363
358
  var port = parsed.port ? parseInt(parsed.port, 10) :
364
359
  (parsed.protocol === "wss:" ? 443 : 80); // TLS / HTTP default port
365
360
  var host = parsed.hostname;
@@ -422,8 +417,8 @@ class WsClient extends EventEmitter {
422
417
 
423
418
  this._handshakeTimer = setTimeout(function () {
424
419
  self._handleSocketError(new WsClientError("ws-client/handshake-timeout",
425
- "Handshake exceeded " + opts.handshakeTimeoutMs + "ms"));
426
- }, opts.handshakeTimeoutMs);
420
+ "Handshake exceeded " + self._opts.handshakeTimeoutMs + "ms"));
421
+ }, self._opts.handshakeTimeoutMs);
427
422
  if (typeof this._handshakeTimer.unref === "function") this._handshakeTimer.unref();
428
423
  }
429
424
 
@@ -590,6 +585,7 @@ class WsClient extends EventEmitter {
590
585
  this._reconnectAttempt = 0;
591
586
  this._fragmentChunks = [];
592
587
  this._fragmentOpcode = null;
588
+ this._fragmentBytes = 0;
593
589
 
594
590
  this._startHeartbeat();
595
591
  if (this._opts.auditOn) {
@@ -628,6 +624,14 @@ class WsClient extends EventEmitter {
628
624
  }
629
625
 
630
626
  _handleFrame(frame) {
627
+ // Once the connection has been torn down (e.g. a prior frame in the same
628
+ // parsed batch tripped maxMessageBytes and called _teardown, which sets
629
+ // _closed synchronously), drop any remaining buffered frames — processing
630
+ // them would emit a spurious cascade of protocol errors (a stray
631
+ // continuation after the fragment state reset). A graceful close keeps
632
+ // _closed false until the handshake completes, so the peer's CLOSE frame
633
+ // is still processed and the normal close code surfaces.
634
+ if (this._closed) return;
631
635
  // RFC 6455 §5.5: control frames MUST be <= 125 bytes AND non-fragmented.
632
636
  var isControl = frame.opcode === OPCODE_PING ||
633
637
  frame.opcode === OPCODE_PONG ||
@@ -644,6 +648,17 @@ class WsClient extends EventEmitter {
644
648
  return;
645
649
  }
646
650
  }
651
+ // Fail the connection on any opcode outside the six RFC 6455 §5.2-defined
652
+ // values (CONT/TEXT/BINARY/CLOSE/PING/PONG). Without this, a reserved
653
+ // opcode (0x3-0x7, 0xB-0xF) from a malicious server fell through every
654
+ // branch to the FIN block and emitted a (stale/empty) message — a
655
+ // fragmented-message desync / frame-injection lever.
656
+ if (frame.opcode !== OPCODE_CONT && frame.opcode !== OPCODE_TEXT &&
657
+ frame.opcode !== OPCODE_BINARY && !isControl) {
658
+ this._handleSocketError(new WsClientError("ws-client/reserved-opcode",
659
+ "reserved/unknown WebSocket opcode 0x" + frame.opcode.toString(16) + " (RFC 6455 §5.2)"));
660
+ return;
661
+ }
647
662
  // Continuation frames MUST NOT carry rsv1 (RFC 7692 §6.1) — only
648
663
  // the first frame of a compressed message sets rsv1.
649
664
  if (frame.opcode === OPCODE_CONT && frame.rsv1) {
@@ -686,6 +701,7 @@ class WsClient extends EventEmitter {
686
701
  this._fragmentOpcode = frame.opcode;
687
702
  this._fragmentRsv1 = frame.rsv1 === true;
688
703
  this._fragmentChunks = [frame.payload];
704
+ this._fragmentBytes = safeBuffer.byteLengthOf(frame.payload);
689
705
  } else if (frame.opcode === OPCODE_CONT) {
690
706
  if (this._fragmentOpcode == null) {
691
707
  this._handleSocketError(new WsClientError("ws-client/protocol-error",
@@ -693,6 +709,21 @@ class WsClient extends EventEmitter {
693
709
  return;
694
710
  }
695
711
  this._fragmentChunks.push(frame.payload);
712
+ this._fragmentBytes += safeBuffer.byteLengthOf(frame.payload);
713
+ }
714
+ // Enforce maxMessageBytes on the RUNNING fragment total, not only at FIN:
715
+ // a peer that streams continuation frames and never sets FIN would
716
+ // otherwise grow _fragmentChunks without bound, one maxFrameBytes-sized
717
+ // frame at a time (CWE-770 / CWE-400). The per-frame parser cap bounds a
718
+ // single frame, never the sum.
719
+ if (this._fragmentOpcode != null && this._fragmentBytes > this._opts.maxMessageBytes) {
720
+ this._fragmentChunks = [];
721
+ this._fragmentOpcode = null;
722
+ this._fragmentRsv1 = false;
723
+ this._fragmentBytes = 0;
724
+ this._handleSocketError(new WsClientError("ws-client/message-too-big",
725
+ "incoming message exceeds maxMessageBytes (" + this._opts.maxMessageBytes + ")"));
726
+ return;
696
727
  }
697
728
  if (frame.fin) {
698
729
  var fullPayload = Buffer.concat(this._fragmentChunks); // allow:handrolled-buffer-collect — bounded by maxMessageBytes below
@@ -706,6 +737,7 @@ class WsClient extends EventEmitter {
706
737
  this._fragmentChunks = [];
707
738
  this._fragmentOpcode = null;
708
739
  this._fragmentRsv1 = false;
740
+ this._fragmentBytes = 0;
709
741
  if (this._negotiatedDeflate && firstFrameRsv1) {
710
742
  try {
711
743
  var zlib = require("node:zlib"); // allow:inline-require — zlib only on deflate-negotiated path
@@ -926,7 +958,12 @@ class WsClient extends EventEmitter {
926
958
  this._reconnectTimer = setTimeout(function () {
927
959
  self._reconnectTimer = null;
928
960
  if (self._closed) return; // operator-cancelled in flight
929
- self._dial();
961
+ // Re-resolve + re-validate the target (urlFor swap, DNS rebind) before
962
+ // reconnecting — awaited, so a now-private/metadata address is refused.
963
+ self._prepareDial().then(function () {
964
+ if (self._closed) return;
965
+ self._dial();
966
+ }).catch(function (e) { self._handleSocketError(e); });
930
967
  }, delay);
931
968
  if (typeof this._reconnectTimer.unref === "function") this._reconnectTimer.unref();
932
969
  this.emit("reconnecting", { attempt: this._reconnectAttempt, delayMs: delay });
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ /**
3
+ * @module b.x509Chain
4
+ * @nav Crypto
5
+ * @title X.509 chain (CA-bit issuer test)
6
+ *
7
+ * @intro
8
+ * The basicConstraints-enforcing issuer test the framework's own
9
+ * certificate-chain walkers route through (<code>b.tsa.verifyToken</code>,
10
+ * <code>b.mail.bimi</code> VMC/CMC, <code>b.mail.crypto.smime</code>,
11
+ * <code>b.mdoc</code>, <code>b.contentCredentials</code>,
12
+ * <code>b.auth.fido</code>). It exists because node:crypto's
13
+ * <code>X509Certificate.checkIssued()</code> validates the issuer/subject
14
+ * DN match, the AKI/SKI linkage, and — only when a keyUsage extension is
15
+ * present — keyCertSign, but it does <strong>not</strong> enforce
16
+ * basicConstraints cA:TRUE. A leaf / end-entity certificate (cA:FALSE)
17
+ * that omits keyUsage is therefore wrongly accepted as a signing CA for
18
+ * the next certificate in the chain — the classic basicConstraints bypass
19
+ * (CVE-2002-0862 class). Every in-tree walker routes its issuer test
20
+ * through these helpers so the cA enforcement can never be forgotten in
21
+ * one walker but present in another.
22
+ *
23
+ * Exposed so a consumer validating an X.509 chain <em>outside</em> a TLS
24
+ * handshake — an operator-uploaded CA bundle, a non-handshake PQ-signed
25
+ * certificate — has the same hardened, fail-closed test instead of being
26
+ * pushed toward the raw <code>checkIssued()</code> path this module
27
+ * exists to prevent. Both helpers fail closed: any malformed input or
28
+ * unsupported key type returns false rather than throwing.
29
+ *
30
+ * @card
31
+ * basicConstraints cA:TRUE-enforcing X.509 issuer test, fail-closed —
32
+ * the hardened alternative to node's checkIssued() for chains built
33
+ * outside a TLS handshake.
34
+ */
35
+
36
+ /**
37
+ * @primitive b.x509Chain.isCaCert
38
+ * @signature b.x509Chain.isCaCert(cert)
39
+ * @since 0.15.15
40
+ * @status stable
41
+ * @related b.x509Chain.issuerValidlyIssued
42
+ *
43
+ * True only when <code>cert</code> asserts basicConstraints cA:TRUE.
44
+ * node's <code>X509Certificate</code> exposes <code>.ca</code> (a boolean);
45
+ * a certificate with no basicConstraints extension or with cA:FALSE
46
+ * returns false. A missing cert or a non-boolean <code>.ca</code> (parse
47
+ * failure / unsupported runtime) fails closed to false.
48
+ *
49
+ * @example
50
+ * var crypto = require("crypto");
51
+ * var ca = new crypto.X509Certificate(caPem);
52
+ * b.x509Chain.isCaCert(ca); // → true only if basicConstraints cA:TRUE
53
+ */
54
+ function isCaCert(cert) {
55
+ return !!cert && cert.ca === true;
56
+ }
57
+
58
+ /**
59
+ * @primitive b.x509Chain.issuerValidlyIssued
60
+ * @signature b.x509Chain.issuerValidlyIssued(issuer, subject)
61
+ * @since 0.15.15
62
+ * @status stable
63
+ * @related b.x509Chain.isCaCert
64
+ *
65
+ * True when <code>issuer</code> validly issued <code>subject</code> AND is
66
+ * itself a CA: the DN / AKI-SKI / keyUsage linkage (checkIssued), the
67
+ * cryptographic signature (verify), and basicConstraints cA:TRUE
68
+ * (isCaCert). The cA check runs first so a non-CA certificate is rejected
69
+ * before the expensive signature verification. Any exception (malformed
70
+ * cert, unsupported key type) fails closed to false.
71
+ *
72
+ * @example
73
+ * var crypto = require("crypto");
74
+ * var issuer = new crypto.X509Certificate(issuerPem);
75
+ * var subject = new crypto.X509Certificate(leafPem);
76
+ * b.x509Chain.issuerValidlyIssued(issuer, subject); // → boolean
77
+ */
78
+ function issuerValidlyIssued(issuer, subject) {
79
+ try {
80
+ return isCaCert(issuer) &&
81
+ subject.checkIssued(issuer) &&
82
+ subject.verify(issuer.publicKey);
83
+ } catch (_e) {
84
+ return false;
85
+ }
86
+ }
87
+
88
+ module.exports = {
89
+ isCaCert: isCaCert,
90
+ issuerValidlyIssued: issuerValidlyIssued,
91
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/core",
3
- "version": "0.15.13",
3
+ "version": "0.15.15",
4
4
  "description": "The Node framework that owns its stack.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "blamejs contributors",
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:4df0908b-006e-466d-b6f7-6426f710546e",
5
+ "serialNumber": "urn:uuid:b3337d85-2941-4510-a9ff-2246e428aef5",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-06-20T02:02:17.152Z",
8
+ "timestamp": "2026-06-22T03:24:08.024Z",
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.13",
22
+ "bom-ref": "@blamejs/core@0.15.15",
23
23
  "type": "application",
24
24
  "name": "blamejs",
25
- "version": "0.15.13",
25
+ "version": "0.15.15",
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.13",
29
+ "purl": "pkg:npm/%40blamejs/core@0.15.15",
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.13",
57
+ "ref": "@blamejs/core@0.15.15",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]