@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.
- package/CHANGELOG.md +4 -0
- package/index.js +2 -0
- package/lib/a2a-tasks.js +38 -6
- package/lib/agent-event-bus.js +13 -0
- package/lib/agent-idempotency.js +5 -1
- package/lib/agent-snapshot.js +32 -2
- package/lib/ai-aedt-bias-audit.js +2 -1
- package/lib/ai-content-detect.js +1 -3
- package/lib/ai-frontier-protocol.js +1 -1
- package/lib/ai-model-manifest.js +1 -1
- package/lib/ai-output.js +16 -7
- package/lib/api-snapshot.js +4 -1
- package/lib/app-shutdown.js +7 -1
- package/lib/archive-gz.js +9 -0
- package/lib/archive-read.js +9 -7
- package/lib/archive-tar-read.js +51 -8
- package/lib/archive.js +4 -2
- package/lib/asn1-der.js +70 -22
- package/lib/atomic-file.js +204 -2
- package/lib/audit-chain.js +54 -5
- package/lib/audit-daily-review.js +12 -2
- package/lib/audit-sign.js +2 -1
- package/lib/audit-tools.js +108 -23
- package/lib/audit.js +7 -2
- package/lib/auth/access-lock.js +2 -2
- package/lib/auth/bot-challenge.js +1 -1
- package/lib/auth/ciba.js +71 -8
- package/lib/auth/dpop.js +15 -1
- package/lib/auth/fido-mds3.js +30 -13
- package/lib/auth/jwt.js +21 -5
- package/lib/auth/lockout.js +18 -2
- package/lib/auth/oauth.js +8 -2
- package/lib/auth/passkey.js +1 -1
- package/lib/auth/password.js +1 -1
- package/lib/auth/saml.js +42 -12
- package/lib/auth/sd-jwt-vc.js +24 -4
- package/lib/auth/status-list.js +14 -2
- package/lib/auth/step-up.js +9 -1
- package/lib/auth-bot-challenge.js +21 -2
- package/lib/backup/bundle.js +7 -2
- package/lib/backup/crypto.js +23 -8
- package/lib/backup/index.js +41 -20
- package/lib/backup/manifest.js +7 -1
- package/lib/break-glass.js +41 -22
- package/lib/cbor.js +34 -11
- package/lib/cdn-cache-control.js +7 -3
- package/lib/cert.js +5 -3
- package/lib/cli.js +5 -1
- package/lib/cloud-events.js +3 -2
- package/lib/cluster-storage.js +7 -3
- package/lib/codepoint-class.js +17 -0
- package/lib/compliance-eaa.js +1 -1
- package/lib/compliance-sanctions.js +9 -7
- package/lib/compliance.js +1 -1
- package/lib/config-drift.js +22 -8
- package/lib/content-credentials.js +11 -8
- package/lib/content-digest.js +11 -4
- package/lib/cookies.js +10 -2
- package/lib/cose.js +20 -0
- package/lib/crdt.js +2 -1
- package/lib/crypto-field.js +48 -24
- package/lib/crypto.js +18 -4
- package/lib/csp.js +13 -0
- package/lib/daemon.js +4 -1
- package/lib/data-act.js +27 -4
- package/lib/db-file-lifecycle.js +14 -6
- package/lib/db-query.js +102 -11
- package/lib/db.js +32 -15
- package/lib/dora.js +43 -13
- package/lib/dr-runbook.js +1 -1
- package/lib/dsa.js +2 -1
- package/lib/dsr.js +22 -8
- package/lib/early-hints.js +19 -0
- package/lib/eat.js +5 -1
- package/lib/external-db.js +60 -4
- package/lib/fda-21cfr11.js +30 -5
- package/lib/flag-providers.js +6 -2
- package/lib/forms.js +1 -1
- package/lib/gate-contract.js +46 -5
- package/lib/gdpr-ropa.js +18 -9
- package/lib/graphql-federation.js +17 -4
- package/lib/guard-all.js +2 -2
- package/lib/guard-dsn.js +1 -1
- package/lib/guard-envelope.js +1 -1
- package/lib/guard-html.js +9 -11
- package/lib/guard-imap-command.js +1 -1
- package/lib/guard-jmap.js +1 -1
- package/lib/guard-json.js +14 -6
- package/lib/guard-mail-move.js +1 -1
- package/lib/guard-managesieve-command.js +1 -1
- package/lib/guard-pop3-command.js +1 -1
- package/lib/guard-smtp-command.js +1 -1
- package/lib/guard-svg.js +8 -9
- package/lib/html-balance.js +7 -3
- package/lib/http-client-cookie-jar.js +33 -12
- package/lib/http-client.js +225 -53
- package/lib/iab-tcf.js +3 -2
- package/lib/importmap-integrity.js +41 -1
- package/lib/incident-report.js +9 -6
- package/lib/json-patch.js +1 -1
- package/lib/json-path.js +24 -3
- package/lib/jtd.js +2 -2
- package/lib/legal-hold.js +24 -8
- package/lib/log.js +2 -2
- package/lib/mail-agent.js +2 -2
- package/lib/mail-arf.js +1 -1
- package/lib/mail-auth.js +27 -4
- package/lib/mail-bimi.js +16 -16
- package/lib/mail-bounce.js +3 -3
- package/lib/mail-crypto-smime.js +71 -6
- package/lib/mail-deploy.js +9 -5
- package/lib/mail-dkim.js +20 -7
- package/lib/mail-greylist.js +2 -4
- package/lib/mail-helo.js +2 -4
- package/lib/mail-journal.js +11 -8
- package/lib/mail-mdn.js +8 -4
- package/lib/mail-rbl.js +2 -4
- package/lib/mail-scan.js +3 -5
- package/lib/mail-server-jmap.js +4 -1
- package/lib/mail-server-registry.js +1 -1
- package/lib/mail-server-tls.js +9 -2
- package/lib/mail-spam-score.js +2 -4
- package/lib/mail-store-fts.js +1 -1
- package/lib/mail.js +22 -2
- package/lib/markup-tokenizer.js +24 -0
- package/lib/mcp.js +6 -4
- package/lib/mdoc.js +26 -3
- package/lib/metrics.js +14 -3
- package/lib/middleware/api-encrypt.js +2 -2
- package/lib/middleware/body-parser.js +10 -4
- package/lib/middleware/bot-guard.js +26 -18
- package/lib/middleware/clear-site-data.js +5 -1
- package/lib/middleware/compose-pipeline.js +39 -5
- package/lib/middleware/compression.js +9 -0
- package/lib/middleware/cors.js +32 -23
- package/lib/middleware/csrf-protect.js +60 -21
- package/lib/middleware/daily-byte-quota.js +6 -4
- package/lib/middleware/fetch-metadata.js +28 -4
- package/lib/middleware/network-allowlist.js +61 -30
- package/lib/middleware/rate-limit.js +25 -16
- package/lib/middleware/scim-server.js +2 -1
- package/lib/middleware/security-headers.js +24 -6
- package/lib/middleware/speculation-rules.js +6 -3
- package/lib/middleware/tus-upload.js +2 -2
- package/lib/money.js +1 -1
- package/lib/mtls-ca.js +10 -6
- package/lib/network-dns-resolver.js +1 -1
- package/lib/network-dns.js +9 -2
- package/lib/network-dnssec.js +2 -1
- package/lib/network-smtp-policy.js +23 -5
- package/lib/network-tls.js +27 -5
- package/lib/network-tsig.js +2 -2
- package/lib/nis2-report.js +1 -1
- package/lib/nist-crosswalk.js +1 -1
- package/lib/ntp-check.js +28 -0
- package/lib/numeric-bounds.js +9 -0
- package/lib/object-store/azure-blob.js +1 -2
- package/lib/object-store/gcs-bucket-ops.js +4 -2
- package/lib/object-store/gcs.js +6 -4
- package/lib/object-store/http-put.js +1 -2
- package/lib/object-store/http-request.js +30 -1
- package/lib/object-store/local.js +37 -17
- package/lib/object-store/sigv4.js +1 -2
- package/lib/observability-otlp-exporter.js +20 -4
- package/lib/outbox.js +11 -4
- package/lib/parsers/safe-xml.js +1 -1
- package/lib/parsers/safe-yaml.js +21 -3
- package/lib/pipl-cn.js +11 -8
- package/lib/queue-local.js +10 -3
- package/lib/redact.js +7 -3
- package/lib/request-helpers.js +347 -36
- package/lib/resource-access-lock.js +3 -3
- package/lib/restore-bundle.js +46 -18
- package/lib/restore-rollback.js +10 -4
- package/lib/restore.js +19 -0
- package/lib/retention.js +20 -4
- package/lib/router.js +17 -4
- package/lib/safe-ical.js +2 -2
- package/lib/safe-icap.js +1 -1
- package/lib/safe-json.js +70 -0
- package/lib/safe-sieve.js +1 -1
- package/lib/safe-vcard.js +1 -1
- package/lib/sandbox-worker.js +6 -0
- package/lib/sandbox.js +1 -1
- package/lib/scheduler.js +17 -1
- package/lib/self-update-standalone-verifier.js +16 -0
- package/lib/session.js +62 -120
- package/lib/sql.js +25 -3
- package/lib/static.js +65 -13
- package/lib/template.js +7 -5
- package/lib/tenant-quota.js +52 -19
- package/lib/tsa.js +5 -2
- package/lib/vault/index.js +5 -0
- package/lib/vault/passphrase-ops.js +22 -26
- package/lib/vault/passphrase-source.js +8 -3
- package/lib/vault/rotate.js +13 -18
- package/lib/vault/seal-pem-file.js +4 -1
- package/lib/vc.js +1 -1
- package/lib/vendor/MANIFEST.json +10 -10
- package/lib/vendor/public-suffix-list.dat +23 -10
- package/lib/vendor/public-suffix-list.data.js +5498 -5494
- package/lib/webhook.js +16 -1
- package/lib/websocket.js +1 -1
- package/lib/worm.js +1 -1
- package/lib/ws-client.js +83 -46
- package/lib/x509-chain.js +91 -0
- package/package.json +1 -1
- 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 (!
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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
|
-
|
|
319
|
-
|
|
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
|
-
|
|
333
|
-
|
|
334
|
-
|
|
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
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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
|
-
|
|
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 " +
|
|
426
|
-
},
|
|
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
|
-
|
|
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
package/sbom.cdx.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
|
|
3
3
|
"bomFormat": "CycloneDX",
|
|
4
4
|
"specVersion": "1.5",
|
|
5
|
-
"serialNumber": "urn:uuid:
|
|
5
|
+
"serialNumber": "urn:uuid:b3337d85-2941-4510-a9ff-2246e428aef5",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-06-
|
|
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.
|
|
22
|
+
"bom-ref": "@blamejs/core@0.15.15",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.15.
|
|
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.
|
|
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.
|
|
57
|
+
"ref": "@blamejs/core@0.15.15",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|