@blamejs/core 0.15.12 → 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.
- package/CHANGELOG.md +4 -0
- package/index.js +2 -0
- package/lib/a2a-tasks.js +45 -29
- package/lib/acme.js +6 -5
- package/lib/agent-event-bus.js +18 -4
- package/lib/agent-idempotency.js +7 -6
- package/lib/agent-orchestrator.js +2 -5
- package/lib/agent-saga.js +3 -5
- package/lib/agent-snapshot.js +32 -2
- package/lib/agent-tenant.js +2 -5
- package/lib/ai-adverse-decision.js +2 -15
- package/lib/ai-aedt-bias-audit.js +2 -1
- package/lib/ai-capability.js +1 -6
- package/lib/ai-content-detect.js +1 -3
- package/lib/ai-dp.js +1 -5
- package/lib/ai-frontier-protocol.js +1 -1
- package/lib/ai-input.js +2 -2
- package/lib/ai-model-manifest.js +1 -1
- package/lib/ai-output.js +16 -7
- package/lib/ai-pref.js +3 -8
- package/lib/ai-quota.js +3 -14
- package/lib/api-key.js +37 -28
- package/lib/api-snapshot.js +4 -1
- package/lib/app-shutdown.js +7 -1
- package/lib/archive-adapters.js +2 -4
- package/lib/archive-entry-policy.js +32 -0
- package/lib/archive-gz.js +9 -0
- package/lib/archive-read.js +14 -24
- package/lib/archive-tar-read.js +56 -24
- package/lib/archive.js +6 -12
- package/lib/arg-parser.js +7 -6
- package/lib/asn1-der.js +70 -22
- package/lib/asyncapi-traits.js +2 -6
- package/lib/atomic-file.js +312 -33
- package/lib/audit-chain.js +183 -54
- package/lib/audit-daily-review.js +36 -16
- package/lib/audit-emit.js +82 -0
- package/lib/audit-sign.js +258 -0
- package/lib/audit-tools.js +108 -23
- package/lib/audit.js +91 -2
- package/lib/auth/access-lock.js +7 -29
- package/lib/auth/bot-challenge.js +1 -1
- package/lib/auth/ciba.js +43 -4
- package/lib/auth/dpop.js +29 -36
- package/lib/auth/fido-mds3.js +14 -16
- package/lib/auth/jwt-external.js +26 -8
- package/lib/auth/jwt.js +15 -17
- package/lib/auth/lockout.js +24 -27
- package/lib/auth/oauth.js +67 -45
- package/lib/auth/oid4vci.js +55 -32
- package/lib/auth/oid4vp.js +3 -2
- package/lib/auth/openid-federation.js +6 -6
- package/lib/auth/passkey.js +4 -4
- package/lib/auth/password.js +8 -2
- package/lib/auth/saml.js +70 -40
- package/lib/auth/sd-jwt-vc-holder.js +2 -14
- package/lib/auth/sd-jwt-vc-issuer.js +2 -14
- package/lib/auth/sd-jwt-vc.js +25 -5
- package/lib/auth/status-list.js +21 -9
- package/lib/auth/step-up.js +15 -13
- package/lib/auth-bot-challenge.js +27 -39
- package/lib/backup/bundle.js +18 -20
- package/lib/backup/crypto.js +23 -8
- package/lib/backup/index.js +55 -67
- package/lib/backup/manifest.js +7 -1
- package/lib/bounded-map.js +112 -1
- package/lib/breach-deadline.js +1 -11
- package/lib/break-glass.js +41 -22
- package/lib/cache.js +7 -18
- package/lib/cbor.js +36 -12
- package/lib/cdn-cache-control.js +15 -12
- package/lib/cert.js +8 -13
- package/lib/chain-writer.js +162 -47
- package/lib/cli.js +10 -2
- package/lib/client-hints.js +7 -9
- package/lib/cloud-events.js +43 -33
- package/lib/cluster-storage.js +9 -41
- package/lib/cms-codec.js +2 -1
- package/lib/codepoint-class.js +84 -1
- package/lib/compliance-ai-act-logging.js +1 -6
- package/lib/compliance-ai-act-transparency.js +2 -4
- package/lib/compliance-eaa.js +2 -12
- package/lib/compliance-sanctions-fetcher.js +21 -28
- package/lib/compliance-sanctions.js +11 -21
- package/lib/compliance.js +3 -10
- package/lib/config-drift.js +24 -20
- package/lib/content-credentials.js +11 -8
- package/lib/content-digest.js +21 -12
- package/lib/cookies.js +15 -13
- package/lib/cose.js +39 -11
- package/lib/cra-report.js +1 -11
- package/lib/crdt.js +2 -1
- package/lib/crypto-field.js +34 -33
- package/lib/crypto.js +138 -7
- package/lib/csp.js +239 -3
- package/lib/daemon.js +46 -42
- package/lib/data-act.js +46 -13
- package/lib/db-file-lifecycle.js +14 -6
- package/lib/db-query.js +75 -49
- package/lib/db.js +66 -27
- package/lib/dbsc.js +5 -6
- package/lib/ddl-change-control.js +18 -14
- package/lib/deprecate.js +4 -5
- package/lib/did.js +6 -2
- package/lib/dora.js +43 -13
- package/lib/dr-runbook.js +1 -1
- package/lib/dsa.js +2 -1
- package/lib/dsr.js +30 -20
- package/lib/early-hints.js +19 -0
- package/lib/eat.js +5 -1
- package/lib/external-db-migrate.js +21 -22
- package/lib/external-db.js +74 -30
- package/lib/fda-21cfr11.js +42 -13
- package/lib/fdx.js +22 -18
- package/lib/file-upload.js +80 -66
- package/lib/flag-evaluation-context.js +5 -8
- package/lib/flag-providers.js +6 -2
- package/lib/forms.js +1 -1
- package/lib/framework-error.js +12 -0
- package/lib/framework-schema.js +19 -0
- package/lib/fsm.js +7 -12
- package/lib/gate-contract.js +911 -39
- package/lib/gdpr-ropa.js +22 -22
- package/lib/graphql-federation.js +18 -5
- package/lib/guard-agent-registry.js +2 -1
- package/lib/guard-all.js +3 -2
- package/lib/guard-archive.js +9 -30
- package/lib/guard-auth.js +23 -80
- package/lib/guard-cidr.js +20 -96
- package/lib/guard-csv.js +135 -196
- package/lib/guard-domain.js +23 -106
- package/lib/guard-dsn.js +17 -14
- package/lib/guard-email.js +46 -53
- package/lib/guard-envelope.js +2 -2
- package/lib/guard-event-bus-topic.js +2 -1
- package/lib/guard-filename.js +12 -60
- package/lib/guard-graphql.js +28 -75
- package/lib/guard-html-wcag.js +15 -2
- package/lib/guard-html.js +74 -128
- package/lib/guard-idempotency-key.js +2 -1
- package/lib/guard-image.js +280 -77
- package/lib/guard-imap-command.js +9 -10
- package/lib/guard-jmap.js +1 -1
- package/lib/guard-json.js +101 -109
- package/lib/guard-jsonpath.js +20 -88
- package/lib/guard-jwt.js +32 -114
- package/lib/guard-list-id.js +2 -7
- package/lib/guard-list-unsubscribe.js +2 -7
- package/lib/guard-mail-compose.js +5 -6
- package/lib/guard-mail-move.js +3 -2
- package/lib/guard-mail-query.js +5 -3
- package/lib/guard-mail-sieve.js +6 -7
- package/lib/guard-managesieve-command.js +6 -5
- package/lib/guard-markdown.js +76 -110
- package/lib/guard-message-id.js +5 -6
- package/lib/guard-mime.js +20 -99
- package/lib/guard-oauth.js +19 -73
- package/lib/guard-pdf.js +65 -72
- package/lib/guard-pop3-command.js +13 -14
- package/lib/guard-posture-chain.js +2 -1
- package/lib/guard-regex.js +24 -99
- package/lib/guard-saga-config.js +2 -1
- package/lib/guard-shell.js +22 -87
- package/lib/guard-smtp-command.js +9 -12
- package/lib/guard-sql.js +15 -13
- package/lib/guard-stream-args.js +2 -1
- package/lib/guard-svg.js +103 -149
- package/lib/guard-template.js +23 -80
- package/lib/guard-tenant-id.js +2 -1
- package/lib/guard-text.js +592 -0
- package/lib/guard-time.js +27 -95
- package/lib/guard-uuid.js +21 -93
- package/lib/guard-xml.js +76 -106
- package/lib/guard-yaml.js +24 -60
- package/lib/html-balance.js +7 -3
- package/lib/http-client-cache.js +5 -12
- package/lib/http-client-cookie-jar.js +35 -16
- package/lib/http-client.js +233 -74
- package/lib/http-message-signature.js +3 -2
- package/lib/i18n-messageformat.js +5 -7
- package/lib/i18n.js +83 -26
- package/lib/iab-tcf.js +3 -2
- package/lib/importmap-integrity.js +41 -1
- package/lib/inbox.js +8 -8
- package/lib/incident-report.js +12 -27
- package/lib/ip-utils.js +49 -6
- package/lib/jobs.js +3 -2
- package/lib/json-patch.js +1 -1
- package/lib/json-path.js +24 -3
- package/lib/jtd.js +2 -2
- package/lib/keychain.js +6 -18
- package/lib/legal-hold.js +30 -23
- package/lib/log-stream-cloudwatch.js +44 -112
- package/lib/log-stream-otlp-grpc.js +17 -14
- package/lib/log-stream-otlp.js +16 -80
- package/lib/log-stream-webhook.js +20 -92
- package/lib/log.js +2 -2
- package/lib/mail-agent.js +2 -2
- package/lib/mail-arc-sign.js +8 -3
- package/lib/mail-arf.js +4 -3
- package/lib/mail-auth.js +43 -69
- package/lib/mail-bimi.js +35 -55
- package/lib/mail-bounce.js +3 -3
- package/lib/mail-crypto-pgp.js +3 -2
- package/lib/mail-crypto-smime.js +71 -6
- package/lib/mail-dav.js +8 -35
- package/lib/mail-deploy.js +15 -14
- package/lib/mail-dkim.js +19 -38
- package/lib/mail-greylist.js +17 -30
- package/lib/mail-helo.js +4 -7
- package/lib/mail-journal.js +13 -9
- package/lib/mail-mdn.js +12 -7
- package/lib/mail-rbl.js +7 -12
- package/lib/mail-scan.js +5 -7
- package/lib/mail-send-deliver.js +33 -20
- package/lib/mail-server-imap.js +32 -87
- package/lib/mail-server-jmap.js +39 -52
- package/lib/mail-server-managesieve.js +29 -83
- package/lib/mail-server-mx.js +33 -74
- package/lib/mail-server-net.js +177 -0
- package/lib/mail-server-pop3.js +30 -83
- package/lib/mail-server-rate-limit.js +30 -6
- package/lib/mail-server-registry.js +1 -1
- package/lib/mail-server-submission.js +34 -73
- package/lib/mail-server-tls.js +98 -2
- package/lib/mail-spam-score.js +9 -12
- package/lib/mail-store-fts.js +1 -1
- package/lib/mail-store.js +3 -2
- package/lib/mail.js +28 -13
- package/lib/markup-escape.js +31 -0
- package/lib/markup-tokenizer.js +78 -0
- package/lib/mcp-tool-registry.js +26 -23
- package/lib/mcp.js +7 -5
- package/lib/mdoc.js +36 -14
- package/lib/metrics.js +46 -33
- package/lib/middleware/age-gate.js +3 -23
- package/lib/middleware/api-encrypt.js +13 -24
- package/lib/middleware/assetlinks.js +2 -7
- package/lib/middleware/bearer-auth.js +2 -1
- package/lib/middleware/body-parser.js +41 -52
- package/lib/middleware/bot-disclose.js +7 -10
- package/lib/middleware/bot-guard.js +28 -28
- package/lib/middleware/clear-site-data.js +5 -1
- package/lib/middleware/compression.js +9 -0
- package/lib/middleware/cors.js +32 -23
- package/lib/middleware/csrf-protect.js +73 -23
- package/lib/middleware/daily-byte-quota.js +12 -30
- package/lib/middleware/deny-response.js +19 -1
- package/lib/middleware/fetch-metadata.js +35 -4
- package/lib/middleware/idempotency-key.js +4 -20
- package/lib/middleware/network-allowlist.js +61 -30
- package/lib/middleware/rate-limit.js +45 -44
- package/lib/middleware/scim-server.js +5 -3
- package/lib/middleware/security-headers.js +33 -7
- package/lib/middleware/security-txt.js +2 -6
- package/lib/middleware/speculation-rules.js +6 -3
- package/lib/middleware/tus-upload.js +14 -29
- package/lib/middleware/web-app-manifest.js +2 -7
- package/lib/migrations.js +4 -13
- package/lib/mime-parse.js +34 -17
- package/lib/module-loader.js +44 -0
- package/lib/money.js +105 -0
- package/lib/mtls-ca.js +125 -41
- package/lib/network-byte-quota.js +4 -18
- package/lib/network-dane.js +8 -6
- package/lib/network-dns-resolver.js +98 -7
- package/lib/network-dns.js +9 -2
- package/lib/network-dnssec.js +18 -17
- package/lib/network-heartbeat.js +3 -2
- package/lib/network-smtp-policy.js +52 -46
- package/lib/network-tls.js +67 -47
- package/lib/network-tsig.js +2 -2
- package/lib/nis2-report.js +2 -12
- package/lib/nist-crosswalk.js +1 -1
- package/lib/nonce-store.js +81 -3
- package/lib/ntp-check.js +28 -0
- package/lib/numeric-bounds.js +31 -8
- package/lib/object-store/azure-blob-bucket-ops.js +2 -6
- package/lib/object-store/azure-blob.js +6 -47
- package/lib/object-store/gcs-bucket-ops.js +4 -2
- package/lib/object-store/gcs.js +14 -75
- package/lib/object-store/http-put.js +2 -7
- package/lib/object-store/http-request.js +157 -0
- package/lib/object-store/local.js +37 -17
- package/lib/object-store/sigv4-bucket-ops.js +2 -1
- package/lib/object-store/sigv4.js +10 -79
- package/lib/observability-otlp-exporter.js +29 -29
- package/lib/observability.js +95 -0
- package/lib/openapi-paths-builder.js +7 -3
- package/lib/otel-export.js +7 -10
- package/lib/outbox.js +54 -41
- package/lib/pagination.js +11 -15
- package/lib/parsers/safe-env.js +5 -4
- package/lib/parsers/safe-ini.js +10 -4
- package/lib/parsers/safe-toml.js +11 -9
- package/lib/parsers/safe-xml.js +3 -3
- package/lib/parsers/safe-yaml.js +28 -9
- package/lib/pick.js +63 -5
- package/lib/pipl-cn.js +69 -59
- package/lib/privacy-pass.js +8 -6
- package/lib/problem-details.js +2 -5
- package/lib/pubsub.js +4 -8
- package/lib/queue-local.js +10 -3
- package/lib/redact.js +9 -4
- package/lib/render.js +4 -2
- package/lib/request-helpers.js +334 -29
- 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 +24 -22
- package/lib/retention.js +23 -9
- package/lib/router.js +17 -4
- package/lib/safe-async.js +457 -0
- package/lib/safe-buffer.js +137 -6
- package/lib/safe-decompress.js +2 -1
- package/lib/safe-dns.js +2 -1
- package/lib/safe-ical.js +14 -28
- package/lib/safe-icap.js +1 -1
- package/lib/safe-json.js +51 -8
- package/lib/safe-jsonpath.js +6 -5
- package/lib/safe-mime.js +25 -29
- package/lib/safe-redirect.js +2 -5
- package/lib/safe-schema.js +7 -6
- package/lib/safe-sieve.js +1 -1
- package/lib/safe-sql.js +126 -0
- package/lib/safe-vcard.js +13 -27
- package/lib/sandbox-worker.js +15 -2
- package/lib/sandbox.js +4 -3
- package/lib/scheduler.js +22 -13
- package/lib/sec-cyber.js +82 -37
- package/lib/seeders.js +4 -11
- package/lib/self-update-standalone-verifier.js +16 -0
- package/lib/self-update.js +92 -69
- package/lib/session-device-binding.js +112 -66
- package/lib/session.js +219 -7
- package/lib/sql.js +228 -81
- package/lib/sse.js +6 -10
- package/lib/static.js +201 -111
- package/lib/storage.js +4 -2
- package/lib/structured-fields.js +275 -16
- package/lib/template.js +7 -5
- package/lib/tenant-quota.js +53 -38
- package/lib/tsa.js +16 -17
- package/lib/validate-opts.js +154 -8
- 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 +34 -49
- package/lib/vault-aad.js +3 -2
- package/lib/vc.js +3 -8
- 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/vex.js +35 -13
- package/lib/web-push-vapid.js +23 -20
- package/lib/webhook-dispatcher.js +706 -0
- package/lib/webhook.js +59 -19
- package/lib/websocket-channels.js +9 -7
- package/lib/websocket.js +8 -4
- package/lib/worm.js +3 -4
- package/lib/ws-client.js +65 -56
- package/lib/x509-chain.js +44 -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,8 +58,14 @@ 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");
|
|
65
|
+
// b.webhook.dispatcher — durable signed-webhook delivery store. Lives in its
|
|
66
|
+
// own module (distinct domain: persistence) and lazyRequires this file back,
|
|
67
|
+
// so this top-level require is cycle-safe.
|
|
68
|
+
var webhookDispatcher = require("./webhook-dispatcher");
|
|
62
69
|
|
|
63
70
|
var observability = lazyRequire(function () { return require("./observability"); });
|
|
64
71
|
|
|
@@ -424,6 +431,18 @@ function signer(opts) {
|
|
|
424
431
|
allowedProtocols: httpOpts.allowedProtocols || safeUrl.ALLOW_HTTP_TLS,
|
|
425
432
|
errorClass: WebhookError,
|
|
426
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
|
+
}
|
|
427
446
|
_validateBody(body);
|
|
428
447
|
var signed = sign(body, { kid: input.kid });
|
|
429
448
|
var mergedHeaders = Object.assign({}, input.headers || {}, signed.headers);
|
|
@@ -641,7 +660,7 @@ function verifier(opts) {
|
|
|
641
660
|
throw _failure("MISSING_TIMESTAMP", "webhook: t= field missing from signature header", "missing-timestamp", ctxReq);
|
|
642
661
|
}
|
|
643
662
|
var ts = Number(parsed.t);
|
|
644
|
-
if (!
|
|
663
|
+
if (!numericBounds.isNonNegativeFiniteInt(ts)) {
|
|
645
664
|
throw _failure("BAD_TIMESTAMP", "webhook: t= field is not a non-negative integer, got " + JSON.stringify(parsed.t), "bad-timestamp", ctxReq);
|
|
646
665
|
}
|
|
647
666
|
if (parsed.id === null || parsed.id.length === 0) {
|
|
@@ -832,16 +851,28 @@ function _coerceBodyString(body) {
|
|
|
832
851
|
* HMAC-SHA-256 over the literal `<t>.<rawBody>` string using the
|
|
833
852
|
* operator's `whsec_...` secret bytes verbatim (the prefix IS the key).
|
|
834
853
|
* Refuses signatures older than the tolerance window (default 5 min,
|
|
835
|
-
* minimum 30 s). When `nonceStore` is supplied the verifier
|
|
836
|
-
* the accepted v1 signature so a replay within the tolerance window
|
|
837
|
-
* is refused
|
|
854
|
+
* minimum 30 s). When `nonceStore` is supplied the verifier atomically
|
|
855
|
+
* records the accepted v1 signature so a replay within the tolerance window
|
|
856
|
+
* is refused; the store speaks the same `checkAndInsert(nonce, expireAt) →
|
|
857
|
+
* bool` contract as `b.webhook.verifier` and `b.nonceStore.create`, so the
|
|
858
|
+
* framework's own replay store plugs in directly and concurrent redeliveries
|
|
859
|
+
* cannot race. Constant-time compare via `b.crypto.timingSafeEqual`.
|
|
860
|
+
*
|
|
861
|
+
* @opts
|
|
862
|
+
* alg: "hmac-sha256-stripe",
|
|
863
|
+
* secret: string | Buffer, // whsec_... bytes verbatim
|
|
864
|
+
* header: string, // Stripe-Signature value
|
|
865
|
+
* body: string | Buffer, // raw request body
|
|
866
|
+
* toleranceMs: number, // default 5 min, min 30 s
|
|
867
|
+
* nonceStore: { checkAndInsert(nonce, expireAt) }, // optional atomic replay defense
|
|
838
868
|
*
|
|
839
869
|
* @example
|
|
840
|
-
* b.webhook.verify({
|
|
841
|
-
* alg:
|
|
842
|
-
* secret:
|
|
843
|
-
* header:
|
|
844
|
-
* body:
|
|
870
|
+
* await b.webhook.verify({
|
|
871
|
+
* alg: "hmac-sha256-stripe",
|
|
872
|
+
* secret: "whsec_abc...",
|
|
873
|
+
* header: req.headers["stripe-signature"],
|
|
874
|
+
* body: rawBodyBuffer,
|
|
875
|
+
* nonceStore: b.nonceStore.create({ backend: "memory" }),
|
|
845
876
|
* });
|
|
846
877
|
* // → { ok: true, timestamp: 1700000000, scheme: "v1" }
|
|
847
878
|
*/
|
|
@@ -889,24 +920,30 @@ async function verify(input) {
|
|
|
889
920
|
"verify: no v1 signature matched HMAC-SHA-256 of <t>.<body>");
|
|
890
921
|
}
|
|
891
922
|
|
|
892
|
-
// Optional replay defense — the nonceStore
|
|
893
|
-
// (
|
|
894
|
-
//
|
|
895
|
-
//
|
|
896
|
-
//
|
|
923
|
+
// Optional replay defense — the nonceStore speaks the SAME atomic
|
|
924
|
+
// `checkAndInsert(nonce, expireAt) → bool` contract as b.webhook.verifier
|
|
925
|
+
// and b.nonceStore.create, so the framework's own replay store drops in
|
|
926
|
+
// with no adapter. The check + insert is one atomic step: a non-atomic
|
|
927
|
+
// check-then-set would race two concurrent redeliveries of the same event
|
|
928
|
+
// (both observe "unseen", both proceed) — the exact failure replay defense
|
|
929
|
+
// exists to prevent. checkAndInsert MAY return a Promise (Redis / KV /
|
|
930
|
+
// cluster SQL); we always await it. `expireAt` is the absolute epoch-ms at
|
|
931
|
+
// which the signature itself falls outside the tolerance window, so a stored
|
|
932
|
+
// nonce is retained exactly as long as a replay of it could still be fresh.
|
|
897
933
|
if (input.nonceStore) {
|
|
898
934
|
var ns = input.nonceStore;
|
|
899
|
-
if (typeof ns.
|
|
935
|
+
if (typeof ns.checkAndInsert !== "function") {
|
|
900
936
|
throw new WebhookError("webhook/bad-nonce-store",
|
|
901
|
-
"verify: nonceStore must expose
|
|
937
|
+
"verify: nonceStore must expose checkAndInsert(nonce, expireAt) — the " +
|
|
938
|
+
"b.nonceStore contract shared with b.webhook.verifier");
|
|
902
939
|
}
|
|
903
940
|
var nonceKey = "stripe:" + parsed.timestamp + ":" + expectedHex;
|
|
904
|
-
var
|
|
905
|
-
|
|
941
|
+
var expireAt = C.TIME.seconds(parsed.timestamp) + tolerance;
|
|
942
|
+
var fresh = await ns.checkAndInsert(nonceKey, expireAt);
|
|
943
|
+
if (!fresh) {
|
|
906
944
|
throw new WebhookError("webhook/replay",
|
|
907
945
|
"verify: signature already seen within tolerance window (replay)");
|
|
908
946
|
}
|
|
909
|
-
await ns.set(nonceKey, tolerance);
|
|
910
947
|
}
|
|
911
948
|
|
|
912
949
|
return { ok: true, timestamp: parsed.timestamp, scheme: "v1" };
|
|
@@ -974,4 +1011,7 @@ module.exports = {
|
|
|
974
1011
|
// v0.11.25 — Stripe-shaped inbound HMAC-SHA-256 verifier + signer.
|
|
975
1012
|
verify: verify,
|
|
976
1013
|
sign: sign,
|
|
1014
|
+
// Durable signed-webhook delivery store (sign + persist + deliver + retry +
|
|
1015
|
+
// dead-letter + operator replay). Defined in lib/webhook-dispatcher.js.
|
|
1016
|
+
dispatcher: webhookDispatcher.dispatcher,
|
|
977
1017
|
};
|
|
@@ -60,6 +60,7 @@
|
|
|
60
60
|
|
|
61
61
|
var lazyRequire = require("./lazy-require");
|
|
62
62
|
var pubsub = require("./pubsub");
|
|
63
|
+
var boundedMap = require("./bounded-map");
|
|
63
64
|
var validateOpts = require("./validate-opts");
|
|
64
65
|
var { defineClass } = require("./framework-error");
|
|
65
66
|
|
|
@@ -187,17 +188,18 @@ function create(opts) {
|
|
|
187
188
|
if (typeof channel !== "string" || channel.length === 0) {
|
|
188
189
|
throw _err("INVALID_CHANNEL", "subscribe: channel must be a non-empty string");
|
|
189
190
|
}
|
|
190
|
-
|
|
191
|
+
boundedMap.requirePresent(connToChannels, conn, function () {
|
|
191
192
|
throw _err("NOT_ATTACHED", "subscribe: connection must be attach()-ed first");
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
channelToConns.set(channel, new Set());
|
|
193
|
+
});
|
|
194
|
+
var subs = boundedMap.getOrInsert(channelToConns, channel, function () {
|
|
195
195
|
// First local listener for this channel — open a pubsub
|
|
196
|
-
// subscription so cross-node fan-out reaches us
|
|
196
|
+
// subscription so cross-node fan-out reaches us, recording the
|
|
197
|
+
// token under the same key the new Set is stored at.
|
|
197
198
|
var token = ps.subscribe(channel, _onPubsubMessage);
|
|
198
199
|
channelToToken.set(channel, token);
|
|
199
|
-
|
|
200
|
-
|
|
200
|
+
return new Set();
|
|
201
|
+
});
|
|
202
|
+
subs.add(conn);
|
|
201
203
|
connToChannels.get(conn).add(channel);
|
|
202
204
|
}
|
|
203
205
|
|
package/lib/websocket.js
CHANGED
|
@@ -82,6 +82,7 @@ var C = require("./constants");
|
|
|
82
82
|
var requestHelpers = require("./request-helpers");
|
|
83
83
|
var safeAsync = require("./safe-async");
|
|
84
84
|
var safeBuffer = require("./safe-buffer");
|
|
85
|
+
var pick = require("./pick");
|
|
85
86
|
var structuredFields = require("./structured-fields");
|
|
86
87
|
var { FrameworkError } = require("./framework-error");
|
|
87
88
|
var { boot } = require("./log");
|
|
@@ -543,7 +544,7 @@ function _parseExtensionHeader(header) {
|
|
|
543
544
|
var kv = parts[j].split("=");
|
|
544
545
|
var k = kv[0].trim().toLowerCase();
|
|
545
546
|
if (!k) continue;
|
|
546
|
-
if (k
|
|
547
|
+
if (pick.isPoisonedKey(k)) continue;
|
|
547
548
|
var v = kv.length > 1 ? kv.slice(1).join("=").trim() : true;
|
|
548
549
|
// Strip surrounding quotes per the token-or-quoted-string grammar.
|
|
549
550
|
if (typeof v === "string") {
|
|
@@ -572,7 +573,7 @@ function _negotiatePermessageDeflate(reqHeader) {
|
|
|
572
573
|
"server_max_window_bits": true, "client_max_window_bits": true,
|
|
573
574
|
};
|
|
574
575
|
var ok = true;
|
|
575
|
-
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; } }
|
|
576
577
|
if (!ok) continue;
|
|
577
578
|
// Always negotiate WITH no_context_takeover in BOTH directions, so
|
|
578
579
|
// every message uses a fresh zlib state. Echo any client window-
|
|
@@ -1128,7 +1129,7 @@ class WebSocketConnection extends EventEmitter {
|
|
|
1128
1129
|
return this._abort(CLOSE_INVALID_PAYLOAD,
|
|
1129
1130
|
"permessage-deflate inflate failed: " + ((e && e.message) || String(e)));
|
|
1130
1131
|
}
|
|
1131
|
-
if (data
|
|
1132
|
+
if (safeBuffer.byteLengthOf(data) > this.maxMessageBytes) {
|
|
1132
1133
|
return this._abort(CLOSE_MESSAGE_TOO_BIG,
|
|
1133
1134
|
"decompressed message exceeds maxMessageBytes");
|
|
1134
1135
|
}
|
|
@@ -1241,8 +1242,11 @@ class WebSocketConnection extends EventEmitter {
|
|
|
1241
1242
|
} else if (Buffer.isBuffer(data)) {
|
|
1242
1243
|
this._sendDataFrame(OPCODE_BINARY, data);
|
|
1243
1244
|
} else {
|
|
1245
|
+
// WebSocketError's constructor is (code, message, closeCode) — code
|
|
1246
|
+
// first — so the (message, code) errorClass path would swap the two.
|
|
1247
|
+
// errorFactory hands toBuffer a (code, message) constructor instead.
|
|
1244
1248
|
data = safeBuffer.toBuffer(data, {
|
|
1245
|
-
|
|
1249
|
+
errorFactory: function (code, message) { return new WebSocketError(code, message); },
|
|
1246
1250
|
typeCode: "ws/invalid-payload",
|
|
1247
1251
|
typeMessage: "send() requires Buffer, Uint8Array, or string",
|
|
1248
1252
|
});
|
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 + "()");
|
|
@@ -113,10 +113,9 @@ function create(opts) {
|
|
|
113
113
|
throw new WormError("worm/bad-opt", "worm.create: defaultRetentionMs must be a non-negative finite number");
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
var _baseAudit = audit().namespaced("worm");
|
|
116
117
|
function _emit(action, outcome, id, metadata) {
|
|
117
|
-
|
|
118
|
-
audit().safeEmit({ action: "worm." + action, actor: { type: "system" }, outcome: outcome, metadata: Object.assign({ id: id, mode: mode }, metadata || {}) });
|
|
119
|
-
} catch (_e) { /* audit is drop-silent by design */ }
|
|
118
|
+
_baseAudit(action, outcome, Object.assign({ id: id, mode: mode }, metadata || {}), { actor: { type: "system" } });
|
|
120
119
|
}
|
|
121
120
|
|
|
122
121
|
function _resolveRetainUntil(now, putOpts) {
|
package/lib/ws-client.js
CHANGED
|
@@ -60,6 +60,7 @@ var audit = lazyRequire(function () { return require("./audit"); });
|
|
|
60
60
|
var networkTls = lazyRequire(function () { return require("./network-tls"); });
|
|
61
61
|
var safeJson = lazyRequire(function () { return require("./safe-json"); });
|
|
62
62
|
var ssrfGuard = require("./ssrf-guard");
|
|
63
|
+
var structuredFields = require("./structured-fields");
|
|
63
64
|
var C = require("./constants");
|
|
64
65
|
var { defineClass } = require("./framework-error");
|
|
65
66
|
|
|
@@ -247,17 +248,13 @@ function connect(target, opts) {
|
|
|
247
248
|
});
|
|
248
249
|
// SSRF gate — refuse private / loopback / link-local / cloud-metadata /
|
|
249
250
|
// reserved IP destinations by default. Symmetric to b.httpClient. The
|
|
250
|
-
//
|
|
251
|
+
// validated `ips` are pinned through tls.connect / net.connect so the
|
|
251
252
|
// actual TCP connect targets the validated address (closes the DNS-
|
|
252
253
|
// rebinding TOCTOU window). Cloud-metadata IPs are unconditional
|
|
253
|
-
// hard-deny — `allowInternal: true` does not bypass them.
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
allowInternal: opts.allowInternal,
|
|
258
|
-
errorClass: WsClientError,
|
|
259
|
-
}).then(function (result) {
|
|
260
|
-
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 () {
|
|
261
258
|
client._dial();
|
|
262
259
|
}).catch(function (e) {
|
|
263
260
|
setImmediate(function () { client._handleSocketError(e); });
|
|
@@ -314,51 +311,50 @@ class WsClient extends EventEmitter {
|
|
|
314
311
|
get subprotocol() { return this._negotiatedSubprotocol; }
|
|
315
312
|
get url() { return this._opts.target; }
|
|
316
313
|
|
|
317
|
-
|
|
318
|
-
|
|
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() {
|
|
319
324
|
var opts = this._opts;
|
|
320
|
-
this._readyState = "connecting";
|
|
321
|
-
|
|
322
|
-
// Per-dial overrides — urlFor swaps the target URL, tlsOptsFor
|
|
323
|
-
// overrides TLS material. Both fire every dial including reconnects
|
|
324
|
-
// so callers can rotate state. urlFor's result is re-validated
|
|
325
|
-
// through ssrfGuard so a hostile upstream can't direct the client
|
|
326
|
-
// at a private address mid-reconnect.
|
|
327
325
|
var attempt = this._reconnectAttempt || 0;
|
|
328
|
-
var dialTarget = opts.target;
|
|
329
326
|
var dialParsed = opts.parsedUrl;
|
|
330
327
|
if (typeof opts.urlFor === "function") {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
dialParsed = _parseUrl(nextTarget);
|
|
335
|
-
dialTarget = nextTarget;
|
|
336
|
-
var probeProto = dialParsed.protocol === "wss:" ? "https:" : "http:";
|
|
337
|
-
var probeUrl = new nodeUrl.URL(probeProto + "//" + dialParsed.host + dialParsed.pathname + dialParsed.search);
|
|
338
|
-
var probe = ssrfGuard.checkUrl(probeUrl, {
|
|
339
|
-
allowInternal: opts.allowInternal,
|
|
340
|
-
errorClass: WsClientError,
|
|
341
|
-
});
|
|
342
|
-
this._ssrfPinnedIps = probe && probe.ips ? probe.ips : null;
|
|
343
|
-
}
|
|
344
|
-
} catch (e) {
|
|
345
|
-
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);
|
|
346
331
|
}
|
|
347
332
|
}
|
|
348
|
-
|
|
349
333
|
var dialTlsOpts = opts.tlsOpts;
|
|
350
334
|
if (typeof opts.tlsOptsFor === "function") {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
dialTlsOpts = Object.assign({}, opts.tlsOpts || {}, override);
|
|
355
|
-
}
|
|
356
|
-
} catch (e) {
|
|
357
|
-
return self._handleSocketError(e);
|
|
335
|
+
var override = opts.tlsOptsFor(attempt);
|
|
336
|
+
if (override && typeof override === "object") {
|
|
337
|
+
dialTlsOpts = Object.assign({}, opts.tlsOpts || {}, override);
|
|
358
338
|
}
|
|
359
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";
|
|
360
354
|
|
|
361
|
-
|
|
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;
|
|
362
358
|
var port = parsed.port ? parseInt(parsed.port, 10) :
|
|
363
359
|
(parsed.protocol === "wss:" ? 443 : 80); // TLS / HTTP default port
|
|
364
360
|
var host = parsed.hostname;
|
|
@@ -421,8 +417,8 @@ class WsClient extends EventEmitter {
|
|
|
421
417
|
|
|
422
418
|
this._handshakeTimer = setTimeout(function () {
|
|
423
419
|
self._handleSocketError(new WsClientError("ws-client/handshake-timeout",
|
|
424
|
-
"Handshake exceeded " +
|
|
425
|
-
},
|
|
420
|
+
"Handshake exceeded " + self._opts.handshakeTimeoutMs + "ms"));
|
|
421
|
+
}, self._opts.handshakeTimeoutMs);
|
|
426
422
|
if (typeof this._handshakeTimer.unref === "function") this._handshakeTimer.unref();
|
|
427
423
|
}
|
|
428
424
|
|
|
@@ -492,7 +488,7 @@ class WsClient extends EventEmitter {
|
|
|
492
488
|
this._handshakeBuf = Buffer.concat([this._handshakeBuf, chunk]);
|
|
493
489
|
var headerEnd = this._handshakeBuf.indexOf("\r\n\r\n");
|
|
494
490
|
if (headerEnd === -1) {
|
|
495
|
-
if (this._handshakeBuf
|
|
491
|
+
if (safeBuffer.byteLengthOf(this._handshakeBuf) > C.BYTES.kib(64)) {
|
|
496
492
|
this._handleSocketError(new WsClientError("ws-client/handshake-too-large",
|
|
497
493
|
"handshake response exceeded 64 KiB before CRLFCRLF"));
|
|
498
494
|
}
|
|
@@ -526,13 +522,10 @@ class WsClient extends EventEmitter {
|
|
|
526
522
|
}
|
|
527
523
|
|
|
528
524
|
var headers = Object.create(null);
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
var hval = lines[i].slice(idx + 1).trim();
|
|
534
|
-
headers[hkey] = hval;
|
|
535
|
-
}
|
|
525
|
+
var hkvps = structuredFields.parseKeyValuePieces(lines, 1, ":");
|
|
526
|
+
structuredFields.forEachKeyValue(hkvps, function (key, value) {
|
|
527
|
+
headers[key] = value;
|
|
528
|
+
});
|
|
536
529
|
|
|
537
530
|
if ((headers["upgrade"] || "").toLowerCase() !== "websocket" ||
|
|
538
531
|
(headers["connection"] || "").toLowerCase().indexOf("upgrade") === -1) {
|
|
@@ -646,6 +639,17 @@ class WsClient extends EventEmitter {
|
|
|
646
639
|
return;
|
|
647
640
|
}
|
|
648
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
|
+
}
|
|
649
653
|
// Continuation frames MUST NOT carry rsv1 (RFC 7692 §6.1) — only
|
|
650
654
|
// the first frame of a compressed message sets rsv1.
|
|
651
655
|
if (frame.opcode === OPCODE_CONT && frame.rsv1) {
|
|
@@ -698,7 +702,7 @@ class WsClient extends EventEmitter {
|
|
|
698
702
|
}
|
|
699
703
|
if (frame.fin) {
|
|
700
704
|
var fullPayload = Buffer.concat(this._fragmentChunks); // allow:handrolled-buffer-collect — bounded by maxMessageBytes below
|
|
701
|
-
if (fullPayload
|
|
705
|
+
if (safeBuffer.byteLengthOf(fullPayload) > this._opts.maxMessageBytes) {
|
|
702
706
|
this._handleSocketError(new WsClientError("ws-client/message-too-big",
|
|
703
707
|
"incoming message exceeds maxMessageBytes (" + this._opts.maxMessageBytes + ")"));
|
|
704
708
|
return;
|
|
@@ -797,7 +801,7 @@ class WsClient extends EventEmitter {
|
|
|
797
801
|
} else {
|
|
798
802
|
payload = Buffer.from(JSON.stringify(data), "utf8");
|
|
799
803
|
}
|
|
800
|
-
if (payload
|
|
804
|
+
if (safeBuffer.byteLengthOf(payload) > this._opts.maxMessageBytes) {
|
|
801
805
|
throw new WsClientError("ws-client/payload-too-big",
|
|
802
806
|
"send: payload exceeds maxMessageBytes (" + this._opts.maxMessageBytes + ")");
|
|
803
807
|
}
|
|
@@ -928,7 +932,12 @@ class WsClient extends EventEmitter {
|
|
|
928
932
|
this._reconnectTimer = setTimeout(function () {
|
|
929
933
|
self._reconnectTimer = null;
|
|
930
934
|
if (self._closed) return; // operator-cancelled in flight
|
|
931
|
-
|
|
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); });
|
|
932
941
|
}, delay);
|
|
933
942
|
if (typeof this._reconnectTimer.unref === "function") this._reconnectTimer.unref();
|
|
934
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
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:81a205f2-938f-47e5-9426-3245e2ac5859",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-06-
|
|
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.
|
|
22
|
+
"bom-ref": "@blamejs/core@0.15.14",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.15.
|
|
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.
|
|
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.
|
|
57
|
+
"ref": "@blamejs/core@0.15.14",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|