@blamejs/core 0.15.13 → 0.15.14

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 (203) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/lib/a2a-tasks.js +38 -6
  3. package/lib/agent-event-bus.js +13 -0
  4. package/lib/agent-idempotency.js +5 -1
  5. package/lib/agent-snapshot.js +32 -2
  6. package/lib/ai-aedt-bias-audit.js +2 -1
  7. package/lib/ai-content-detect.js +1 -3
  8. package/lib/ai-frontier-protocol.js +1 -1
  9. package/lib/ai-model-manifest.js +1 -1
  10. package/lib/ai-output.js +16 -7
  11. package/lib/api-snapshot.js +4 -1
  12. package/lib/app-shutdown.js +7 -1
  13. package/lib/archive-gz.js +9 -0
  14. package/lib/archive-read.js +9 -7
  15. package/lib/archive-tar-read.js +51 -8
  16. package/lib/archive.js +4 -2
  17. package/lib/asn1-der.js +70 -22
  18. package/lib/atomic-file.js +204 -2
  19. package/lib/audit-chain.js +54 -5
  20. package/lib/audit-daily-review.js +12 -2
  21. package/lib/audit-sign.js +2 -1
  22. package/lib/audit-tools.js +108 -23
  23. package/lib/audit.js +7 -2
  24. package/lib/auth/access-lock.js +2 -2
  25. package/lib/auth/bot-challenge.js +1 -1
  26. package/lib/auth/ciba.js +43 -4
  27. package/lib/auth/dpop.js +6 -1
  28. package/lib/auth/fido-mds3.js +5 -1
  29. package/lib/auth/jwt.js +2 -2
  30. package/lib/auth/lockout.js +18 -2
  31. package/lib/auth/passkey.js +1 -1
  32. package/lib/auth/password.js +1 -1
  33. package/lib/auth/saml.js +33 -13
  34. package/lib/auth/sd-jwt-vc.js +24 -4
  35. package/lib/auth/status-list.js +14 -2
  36. package/lib/auth/step-up.js +9 -1
  37. package/lib/auth-bot-challenge.js +21 -2
  38. package/lib/backup/bundle.js +7 -2
  39. package/lib/backup/crypto.js +23 -8
  40. package/lib/backup/index.js +41 -20
  41. package/lib/backup/manifest.js +7 -1
  42. package/lib/break-glass.js +41 -22
  43. package/lib/cbor.js +34 -11
  44. package/lib/cdn-cache-control.js +7 -3
  45. package/lib/cert.js +5 -3
  46. package/lib/cli.js +5 -1
  47. package/lib/cloud-events.js +3 -2
  48. package/lib/cluster-storage.js +7 -3
  49. package/lib/codepoint-class.js +17 -0
  50. package/lib/compliance-eaa.js +1 -1
  51. package/lib/compliance-sanctions.js +9 -7
  52. package/lib/compliance.js +1 -1
  53. package/lib/config-drift.js +22 -8
  54. package/lib/content-credentials.js +11 -8
  55. package/lib/content-digest.js +11 -4
  56. package/lib/cookies.js +10 -2
  57. package/lib/cose.js +20 -0
  58. package/lib/crdt.js +2 -1
  59. package/lib/crypto-field.js +29 -23
  60. package/lib/crypto.js +18 -4
  61. package/lib/csp.js +4 -0
  62. package/lib/daemon.js +4 -1
  63. package/lib/data-act.js +27 -4
  64. package/lib/db-file-lifecycle.js +14 -6
  65. package/lib/db-query.js +69 -9
  66. package/lib/db.js +32 -15
  67. package/lib/dora.js +43 -13
  68. package/lib/dr-runbook.js +1 -1
  69. package/lib/dsa.js +2 -1
  70. package/lib/dsr.js +22 -8
  71. package/lib/early-hints.js +19 -0
  72. package/lib/eat.js +5 -1
  73. package/lib/external-db.js +60 -4
  74. package/lib/fda-21cfr11.js +30 -5
  75. package/lib/flag-providers.js +6 -2
  76. package/lib/forms.js +1 -1
  77. package/lib/gate-contract.js +46 -5
  78. package/lib/gdpr-ropa.js +18 -9
  79. package/lib/graphql-federation.js +17 -4
  80. package/lib/guard-all.js +2 -2
  81. package/lib/guard-dsn.js +1 -1
  82. package/lib/guard-envelope.js +1 -1
  83. package/lib/guard-html.js +9 -11
  84. package/lib/guard-imap-command.js +1 -1
  85. package/lib/guard-jmap.js +1 -1
  86. package/lib/guard-json.js +14 -6
  87. package/lib/guard-mail-move.js +1 -1
  88. package/lib/guard-managesieve-command.js +1 -1
  89. package/lib/guard-pop3-command.js +1 -1
  90. package/lib/guard-smtp-command.js +1 -1
  91. package/lib/guard-svg.js +8 -9
  92. package/lib/html-balance.js +7 -3
  93. package/lib/http-client-cookie-jar.js +33 -12
  94. package/lib/http-client.js +225 -53
  95. package/lib/iab-tcf.js +3 -2
  96. package/lib/importmap-integrity.js +41 -1
  97. package/lib/incident-report.js +9 -6
  98. package/lib/json-patch.js +1 -1
  99. package/lib/json-path.js +24 -3
  100. package/lib/jtd.js +2 -2
  101. package/lib/legal-hold.js +24 -8
  102. package/lib/log.js +2 -2
  103. package/lib/mail-agent.js +2 -2
  104. package/lib/mail-arf.js +1 -1
  105. package/lib/mail-auth.js +3 -3
  106. package/lib/mail-bimi.js +16 -16
  107. package/lib/mail-bounce.js +3 -3
  108. package/lib/mail-crypto-smime.js +71 -6
  109. package/lib/mail-deploy.js +9 -5
  110. package/lib/mail-greylist.js +2 -4
  111. package/lib/mail-helo.js +2 -4
  112. package/lib/mail-journal.js +11 -8
  113. package/lib/mail-mdn.js +8 -4
  114. package/lib/mail-rbl.js +2 -4
  115. package/lib/mail-scan.js +3 -5
  116. package/lib/mail-server-jmap.js +4 -1
  117. package/lib/mail-server-registry.js +1 -1
  118. package/lib/mail-server-tls.js +9 -2
  119. package/lib/mail-spam-score.js +2 -4
  120. package/lib/mail-store-fts.js +1 -1
  121. package/lib/mail.js +22 -2
  122. package/lib/markup-tokenizer.js +24 -0
  123. package/lib/mcp.js +6 -4
  124. package/lib/mdoc.js +26 -3
  125. package/lib/metrics.js +14 -3
  126. package/lib/middleware/api-encrypt.js +2 -2
  127. package/lib/middleware/body-parser.js +10 -4
  128. package/lib/middleware/bot-guard.js +26 -18
  129. package/lib/middleware/clear-site-data.js +5 -1
  130. package/lib/middleware/compression.js +9 -0
  131. package/lib/middleware/cors.js +32 -23
  132. package/lib/middleware/csrf-protect.js +60 -21
  133. package/lib/middleware/daily-byte-quota.js +6 -4
  134. package/lib/middleware/fetch-metadata.js +28 -4
  135. package/lib/middleware/network-allowlist.js +61 -30
  136. package/lib/middleware/rate-limit.js +25 -16
  137. package/lib/middleware/scim-server.js +2 -1
  138. package/lib/middleware/security-headers.js +24 -6
  139. package/lib/middleware/speculation-rules.js +6 -3
  140. package/lib/middleware/tus-upload.js +2 -2
  141. package/lib/money.js +1 -1
  142. package/lib/mtls-ca.js +10 -6
  143. package/lib/network-dns-resolver.js +1 -1
  144. package/lib/network-dns.js +9 -2
  145. package/lib/network-dnssec.js +2 -1
  146. package/lib/network-smtp-policy.js +23 -5
  147. package/lib/network-tls.js +27 -5
  148. package/lib/network-tsig.js +2 -2
  149. package/lib/nis2-report.js +1 -1
  150. package/lib/nist-crosswalk.js +1 -1
  151. package/lib/ntp-check.js +28 -0
  152. package/lib/numeric-bounds.js +9 -0
  153. package/lib/object-store/azure-blob.js +1 -2
  154. package/lib/object-store/gcs-bucket-ops.js +4 -2
  155. package/lib/object-store/gcs.js +6 -4
  156. package/lib/object-store/http-put.js +1 -2
  157. package/lib/object-store/http-request.js +30 -1
  158. package/lib/object-store/local.js +37 -17
  159. package/lib/object-store/sigv4.js +1 -2
  160. package/lib/observability-otlp-exporter.js +20 -4
  161. package/lib/outbox.js +11 -4
  162. package/lib/parsers/safe-xml.js +1 -1
  163. package/lib/parsers/safe-yaml.js +21 -3
  164. package/lib/queue-local.js +10 -3
  165. package/lib/redact.js +7 -3
  166. package/lib/request-helpers.js +201 -23
  167. package/lib/resource-access-lock.js +3 -3
  168. package/lib/restore-bundle.js +46 -18
  169. package/lib/restore-rollback.js +10 -4
  170. package/lib/restore.js +19 -0
  171. package/lib/retention.js +20 -4
  172. package/lib/router.js +17 -4
  173. package/lib/safe-ical.js +2 -2
  174. package/lib/safe-icap.js +1 -1
  175. package/lib/safe-json.js +44 -0
  176. package/lib/safe-sieve.js +1 -1
  177. package/lib/safe-vcard.js +1 -1
  178. package/lib/sandbox-worker.js +6 -0
  179. package/lib/sandbox.js +1 -1
  180. package/lib/scheduler.js +17 -1
  181. package/lib/self-update-standalone-verifier.js +16 -0
  182. package/lib/session.js +27 -3
  183. package/lib/sql.js +3 -3
  184. package/lib/static.js +65 -13
  185. package/lib/template.js +7 -5
  186. package/lib/tenant-quota.js +52 -19
  187. package/lib/tsa.js +5 -2
  188. package/lib/vault/index.js +5 -0
  189. package/lib/vault/passphrase-ops.js +22 -26
  190. package/lib/vault/passphrase-source.js +8 -3
  191. package/lib/vault/rotate.js +13 -18
  192. package/lib/vault/seal-pem-file.js +4 -1
  193. package/lib/vc.js +1 -1
  194. package/lib/vendor/MANIFEST.json +10 -10
  195. package/lib/vendor/public-suffix-list.dat +23 -10
  196. package/lib/vendor/public-suffix-list.data.js +5498 -5494
  197. package/lib/webhook.js +16 -1
  198. package/lib/websocket.js +1 -1
  199. package/lib/worm.js +1 -1
  200. package/lib/ws-client.js +57 -46
  201. package/lib/x509-chain.js +44 -0
  202. package/package.json +1 -1
  203. 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
 
@@ -644,6 +639,17 @@ class WsClient extends EventEmitter {
644
639
  return;
645
640
  }
646
641
  }
642
+ // Fail the connection on any opcode outside the six RFC 6455 §5.2-defined
643
+ // values (CONT/TEXT/BINARY/CLOSE/PING/PONG). Without this, a reserved
644
+ // opcode (0x3-0x7, 0xB-0xF) from a malicious server fell through every
645
+ // branch to the FIN block and emitted a (stale/empty) message — a
646
+ // fragmented-message desync / frame-injection lever.
647
+ if (frame.opcode !== OPCODE_CONT && frame.opcode !== OPCODE_TEXT &&
648
+ frame.opcode !== OPCODE_BINARY && !isControl) {
649
+ this._handleSocketError(new WsClientError("ws-client/reserved-opcode",
650
+ "reserved/unknown WebSocket opcode 0x" + frame.opcode.toString(16) + " (RFC 6455 §5.2)"));
651
+ return;
652
+ }
647
653
  // Continuation frames MUST NOT carry rsv1 (RFC 7692 §6.1) — only
648
654
  // the first frame of a compressed message sets rsv1.
649
655
  if (frame.opcode === OPCODE_CONT && frame.rsv1) {
@@ -926,7 +932,12 @@ class WsClient extends EventEmitter {
926
932
  this._reconnectTimer = setTimeout(function () {
927
933
  self._reconnectTimer = null;
928
934
  if (self._closed) return; // operator-cancelled in flight
929
- self._dial();
935
+ // Re-resolve + re-validate the target (urlFor swap, DNS rebind) before
936
+ // reconnecting — awaited, so a now-private/metadata address is refused.
937
+ self._prepareDial().then(function () {
938
+ if (self._closed) return;
939
+ self._dial();
940
+ }).catch(function (e) { self._handleSocketError(e); });
930
941
  }, delay);
931
942
  if (typeof this._reconnectTimer.unref === "function") this._reconnectTimer.unref();
932
943
  this.emit("reconnecting", { attempt: this._reconnectAttempt, delayMs: delay });
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+
3
+ // Internal X.509 path-validation helpers shared by the framework's
4
+ // certificate-chain walkers (b.tsa.verifyToken, b.mail.bimi VMC/CMC,
5
+ // b.mail.crypto.smime.verify). They exist because node:crypto's
6
+ // X509Certificate.checkIssued() validates the issuer/subject DN match,
7
+ // the AKI/SKI linkage, and — when a keyUsage extension is present —
8
+ // keyCertSign, but it does NOT enforce basicConstraints cA:TRUE. A
9
+ // leaf / end-entity certificate (cA:FALSE) that omits keyUsage is
10
+ // therefore wrongly accepted as a signing CA for the next cert in the
11
+ // chain — the classic basicConstraints bypass (CVE-2002-0862 class).
12
+ // Every chain walker routes its issuer test through these helpers so the
13
+ // cA enforcement can never be forgotten in one walker but present in
14
+ // another.
15
+
16
+ // True only when `cert` asserts basicConstraints cA:TRUE. node's
17
+ // X509Certificate exposes `.ca` (a boolean); a cert with no
18
+ // basicConstraints extension or with cA:FALSE returns false. A missing
19
+ // cert or a non-boolean `.ca` (parse failure / unsupported runtime)
20
+ // fails closed.
21
+ function isCaCert(cert) {
22
+ return !!cert && cert.ca === true;
23
+ }
24
+
25
+ // True when `issuer` validly issued `subject` AND is itself a CA: the
26
+ // DN / AKI-SKI / keyUsage linkage (checkIssued), the cryptographic
27
+ // signature (verify), and basicConstraints cA:TRUE (isCaCert). The cA
28
+ // check runs first so a non-CA cert is rejected before the expensive
29
+ // signature verification. Any exception (malformed cert, unsupported
30
+ // key type) fails closed to false.
31
+ function issuerValidlyIssued(issuer, subject) {
32
+ try {
33
+ return isCaCert(issuer) &&
34
+ subject.checkIssued(issuer) &&
35
+ subject.verify(issuer.publicKey);
36
+ } catch (_e) {
37
+ return false;
38
+ }
39
+ }
40
+
41
+ module.exports = {
42
+ isCaCert: isCaCert,
43
+ issuerValidlyIssued: issuerValidlyIssued,
44
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/core",
3
- "version": "0.15.13",
3
+ "version": "0.15.14",
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:81a205f2-938f-47e5-9426-3245e2ac5859",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-06-20T02:02:17.152Z",
8
+ "timestamp": "2026-06-21T13:24:58.132Z",
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.14",
23
23
  "type": "application",
24
24
  "name": "blamejs",
25
- "version": "0.15.13",
25
+ "version": "0.15.14",
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.14",
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.14",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]