@blamejs/blamejs-shop 0.4.69 → 0.4.70
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 +2 -0
- package/lib/admin.js +8 -6
- package/lib/asset-manifest.json +1 -1
- package/lib/security-middleware.js +65 -23
- package/lib/storefront.js +10 -7
- package/lib/vendor/MANIFEST.json +319 -267
- package/lib/vendor/blamejs/CHANGELOG.md +2 -0
- package/lib/vendor/blamejs/api-snapshot.json +58 -5
- package/lib/vendor/blamejs/examples/wiki/README.md +1 -1
- package/lib/vendor/blamejs/examples/wiki/docker-compose.prod.yml +9 -8
- package/lib/vendor/blamejs/examples/wiki/docker-compose.yml +10 -9
- package/lib/vendor/blamejs/examples/wiki/env-snapshot.json +4 -3
- package/lib/vendor/blamejs/examples/wiki/lib/build-app.js +33 -17
- package/lib/vendor/blamejs/examples/wiki/routes/admin.js +7 -10
- package/lib/vendor/blamejs/examples/wiki/server.js +5 -4
- package/lib/vendor/blamejs/examples/wiki/test/e2e.js +5 -0
- package/lib/vendor/blamejs/lib/a2a-tasks.js +38 -6
- package/lib/vendor/blamejs/lib/agent-event-bus.js +13 -0
- package/lib/vendor/blamejs/lib/agent-idempotency.js +5 -1
- package/lib/vendor/blamejs/lib/agent-snapshot.js +32 -2
- package/lib/vendor/blamejs/lib/ai-aedt-bias-audit.js +2 -1
- package/lib/vendor/blamejs/lib/ai-content-detect.js +1 -3
- package/lib/vendor/blamejs/lib/ai-frontier-protocol.js +1 -1
- package/lib/vendor/blamejs/lib/ai-model-manifest.js +1 -1
- package/lib/vendor/blamejs/lib/ai-output.js +16 -7
- package/lib/vendor/blamejs/lib/api-snapshot.js +4 -1
- package/lib/vendor/blamejs/lib/app-shutdown.js +7 -1
- package/lib/vendor/blamejs/lib/archive-gz.js +9 -0
- package/lib/vendor/blamejs/lib/archive-read.js +9 -7
- package/lib/vendor/blamejs/lib/archive-tar-read.js +51 -8
- package/lib/vendor/blamejs/lib/archive.js +4 -2
- package/lib/vendor/blamejs/lib/asn1-der.js +70 -22
- package/lib/vendor/blamejs/lib/atomic-file.js +204 -2
- package/lib/vendor/blamejs/lib/audit-chain.js +54 -5
- package/lib/vendor/blamejs/lib/audit-daily-review.js +12 -2
- package/lib/vendor/blamejs/lib/audit-sign.js +2 -1
- package/lib/vendor/blamejs/lib/audit-tools.js +108 -23
- package/lib/vendor/blamejs/lib/audit.js +7 -2
- package/lib/vendor/blamejs/lib/auth/access-lock.js +2 -2
- package/lib/vendor/blamejs/lib/auth/bot-challenge.js +1 -1
- package/lib/vendor/blamejs/lib/auth/ciba.js +43 -4
- package/lib/vendor/blamejs/lib/auth/dpop.js +6 -1
- package/lib/vendor/blamejs/lib/auth/fido-mds3.js +5 -1
- package/lib/vendor/blamejs/lib/auth/jwt.js +2 -2
- package/lib/vendor/blamejs/lib/auth/lockout.js +18 -2
- package/lib/vendor/blamejs/lib/auth/passkey.js +1 -1
- package/lib/vendor/blamejs/lib/auth/password.js +1 -1
- package/lib/vendor/blamejs/lib/auth/saml.js +33 -13
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc.js +24 -4
- package/lib/vendor/blamejs/lib/auth/status-list.js +14 -2
- package/lib/vendor/blamejs/lib/auth/step-up.js +9 -1
- package/lib/vendor/blamejs/lib/auth-bot-challenge.js +21 -2
- package/lib/vendor/blamejs/lib/backup/bundle.js +7 -2
- package/lib/vendor/blamejs/lib/backup/crypto.js +23 -8
- package/lib/vendor/blamejs/lib/backup/index.js +41 -20
- package/lib/vendor/blamejs/lib/backup/manifest.js +7 -1
- package/lib/vendor/blamejs/lib/break-glass.js +41 -22
- package/lib/vendor/blamejs/lib/cbor.js +34 -11
- package/lib/vendor/blamejs/lib/cdn-cache-control.js +7 -3
- package/lib/vendor/blamejs/lib/cert.js +5 -3
- package/lib/vendor/blamejs/lib/cli.js +5 -1
- package/lib/vendor/blamejs/lib/cloud-events.js +3 -2
- package/lib/vendor/blamejs/lib/cluster-storage.js +7 -3
- package/lib/vendor/blamejs/lib/codepoint-class.js +17 -0
- package/lib/vendor/blamejs/lib/compliance-eaa.js +1 -1
- package/lib/vendor/blamejs/lib/compliance-sanctions.js +9 -7
- package/lib/vendor/blamejs/lib/compliance.js +1 -1
- package/lib/vendor/blamejs/lib/config-drift.js +22 -8
- package/lib/vendor/blamejs/lib/content-credentials.js +11 -8
- package/lib/vendor/blamejs/lib/content-digest.js +11 -4
- package/lib/vendor/blamejs/lib/cookies.js +10 -2
- package/lib/vendor/blamejs/lib/cose.js +20 -0
- package/lib/vendor/blamejs/lib/crdt.js +2 -1
- package/lib/vendor/blamejs/lib/crypto-field.js +29 -23
- package/lib/vendor/blamejs/lib/crypto.js +18 -4
- package/lib/vendor/blamejs/lib/csp.js +4 -0
- package/lib/vendor/blamejs/lib/daemon.js +4 -1
- package/lib/vendor/blamejs/lib/data-act.js +27 -4
- package/lib/vendor/blamejs/lib/db-file-lifecycle.js +14 -6
- package/lib/vendor/blamejs/lib/db-query.js +69 -9
- package/lib/vendor/blamejs/lib/db.js +32 -15
- package/lib/vendor/blamejs/lib/dora.js +43 -13
- package/lib/vendor/blamejs/lib/dr-runbook.js +1 -1
- package/lib/vendor/blamejs/lib/dsa.js +2 -1
- package/lib/vendor/blamejs/lib/dsr.js +22 -8
- package/lib/vendor/blamejs/lib/early-hints.js +19 -0
- package/lib/vendor/blamejs/lib/eat.js +5 -1
- package/lib/vendor/blamejs/lib/external-db.js +60 -4
- package/lib/vendor/blamejs/lib/fda-21cfr11.js +30 -5
- package/lib/vendor/blamejs/lib/flag-providers.js +6 -2
- package/lib/vendor/blamejs/lib/forms.js +1 -1
- package/lib/vendor/blamejs/lib/gate-contract.js +46 -5
- package/lib/vendor/blamejs/lib/gdpr-ropa.js +18 -9
- package/lib/vendor/blamejs/lib/graphql-federation.js +17 -4
- package/lib/vendor/blamejs/lib/guard-all.js +2 -2
- package/lib/vendor/blamejs/lib/guard-dsn.js +1 -1
- package/lib/vendor/blamejs/lib/guard-envelope.js +1 -1
- package/lib/vendor/blamejs/lib/guard-html.js +9 -11
- package/lib/vendor/blamejs/lib/guard-imap-command.js +1 -1
- package/lib/vendor/blamejs/lib/guard-jmap.js +1 -1
- package/lib/vendor/blamejs/lib/guard-json.js +14 -6
- package/lib/vendor/blamejs/lib/guard-mail-move.js +1 -1
- package/lib/vendor/blamejs/lib/guard-managesieve-command.js +1 -1
- package/lib/vendor/blamejs/lib/guard-pop3-command.js +1 -1
- package/lib/vendor/blamejs/lib/guard-smtp-command.js +1 -1
- package/lib/vendor/blamejs/lib/guard-svg.js +8 -9
- package/lib/vendor/blamejs/lib/html-balance.js +7 -3
- package/lib/vendor/blamejs/lib/http-client-cookie-jar.js +33 -12
- package/lib/vendor/blamejs/lib/http-client.js +225 -53
- package/lib/vendor/blamejs/lib/iab-tcf.js +3 -2
- package/lib/vendor/blamejs/lib/importmap-integrity.js +41 -1
- package/lib/vendor/blamejs/lib/incident-report.js +9 -6
- package/lib/vendor/blamejs/lib/json-patch.js +1 -1
- package/lib/vendor/blamejs/lib/json-path.js +24 -3
- package/lib/vendor/blamejs/lib/jtd.js +2 -2
- package/lib/vendor/blamejs/lib/legal-hold.js +24 -8
- package/lib/vendor/blamejs/lib/log.js +2 -2
- package/lib/vendor/blamejs/lib/mail-agent.js +2 -2
- package/lib/vendor/blamejs/lib/mail-arf.js +1 -1
- package/lib/vendor/blamejs/lib/mail-auth.js +3 -3
- package/lib/vendor/blamejs/lib/mail-bimi.js +16 -16
- package/lib/vendor/blamejs/lib/mail-bounce.js +3 -3
- package/lib/vendor/blamejs/lib/mail-crypto-smime.js +71 -6
- package/lib/vendor/blamejs/lib/mail-deploy.js +9 -5
- package/lib/vendor/blamejs/lib/mail-greylist.js +2 -4
- package/lib/vendor/blamejs/lib/mail-helo.js +2 -4
- package/lib/vendor/blamejs/lib/mail-journal.js +11 -8
- package/lib/vendor/blamejs/lib/mail-mdn.js +8 -4
- package/lib/vendor/blamejs/lib/mail-rbl.js +2 -4
- package/lib/vendor/blamejs/lib/mail-scan.js +3 -5
- package/lib/vendor/blamejs/lib/mail-server-jmap.js +4 -1
- package/lib/vendor/blamejs/lib/mail-server-registry.js +1 -1
- package/lib/vendor/blamejs/lib/mail-server-tls.js +9 -2
- package/lib/vendor/blamejs/lib/mail-spam-score.js +2 -4
- package/lib/vendor/blamejs/lib/mail-store-fts.js +1 -1
- package/lib/vendor/blamejs/lib/mail.js +22 -2
- package/lib/vendor/blamejs/lib/markup-tokenizer.js +24 -0
- package/lib/vendor/blamejs/lib/mcp.js +6 -4
- package/lib/vendor/blamejs/lib/mdoc.js +26 -3
- package/lib/vendor/blamejs/lib/metrics.js +14 -3
- package/lib/vendor/blamejs/lib/middleware/api-encrypt.js +2 -2
- package/lib/vendor/blamejs/lib/middleware/body-parser.js +10 -4
- package/lib/vendor/blamejs/lib/middleware/bot-guard.js +26 -18
- package/lib/vendor/blamejs/lib/middleware/clear-site-data.js +5 -1
- package/lib/vendor/blamejs/lib/middleware/compression.js +9 -0
- package/lib/vendor/blamejs/lib/middleware/cors.js +32 -23
- package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +60 -21
- package/lib/vendor/blamejs/lib/middleware/daily-byte-quota.js +6 -4
- package/lib/vendor/blamejs/lib/middleware/fetch-metadata.js +28 -4
- package/lib/vendor/blamejs/lib/middleware/network-allowlist.js +61 -30
- package/lib/vendor/blamejs/lib/middleware/rate-limit.js +25 -16
- package/lib/vendor/blamejs/lib/middleware/scim-server.js +2 -1
- package/lib/vendor/blamejs/lib/middleware/security-headers.js +24 -6
- package/lib/vendor/blamejs/lib/middleware/speculation-rules.js +6 -3
- package/lib/vendor/blamejs/lib/middleware/tus-upload.js +2 -2
- package/lib/vendor/blamejs/lib/money.js +1 -1
- package/lib/vendor/blamejs/lib/mtls-ca.js +10 -6
- package/lib/vendor/blamejs/lib/network-dns-resolver.js +1 -1
- package/lib/vendor/blamejs/lib/network-dns.js +9 -2
- package/lib/vendor/blamejs/lib/network-dnssec.js +2 -1
- package/lib/vendor/blamejs/lib/network-smtp-policy.js +23 -5
- package/lib/vendor/blamejs/lib/network-tls.js +27 -5
- package/lib/vendor/blamejs/lib/network-tsig.js +2 -2
- package/lib/vendor/blamejs/lib/nis2-report.js +1 -1
- package/lib/vendor/blamejs/lib/nist-crosswalk.js +1 -1
- package/lib/vendor/blamejs/lib/ntp-check.js +28 -0
- package/lib/vendor/blamejs/lib/numeric-bounds.js +9 -0
- package/lib/vendor/blamejs/lib/object-store/azure-blob.js +1 -2
- package/lib/vendor/blamejs/lib/object-store/gcs-bucket-ops.js +4 -2
- package/lib/vendor/blamejs/lib/object-store/gcs.js +6 -4
- package/lib/vendor/blamejs/lib/object-store/http-put.js +1 -2
- package/lib/vendor/blamejs/lib/object-store/http-request.js +30 -1
- package/lib/vendor/blamejs/lib/object-store/local.js +37 -17
- package/lib/vendor/blamejs/lib/object-store/sigv4.js +1 -2
- package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +20 -4
- package/lib/vendor/blamejs/lib/outbox.js +11 -4
- package/lib/vendor/blamejs/lib/parsers/safe-xml.js +1 -1
- package/lib/vendor/blamejs/lib/parsers/safe-yaml.js +21 -3
- package/lib/vendor/blamejs/lib/queue-local.js +10 -3
- package/lib/vendor/blamejs/lib/redact.js +7 -3
- package/lib/vendor/blamejs/lib/request-helpers.js +201 -23
- package/lib/vendor/blamejs/lib/resource-access-lock.js +3 -3
- package/lib/vendor/blamejs/lib/restore-bundle.js +46 -18
- package/lib/vendor/blamejs/lib/restore-rollback.js +10 -4
- package/lib/vendor/blamejs/lib/restore.js +19 -0
- package/lib/vendor/blamejs/lib/retention.js +20 -4
- package/lib/vendor/blamejs/lib/router.js +17 -4
- package/lib/vendor/blamejs/lib/safe-ical.js +2 -2
- package/lib/vendor/blamejs/lib/safe-icap.js +1 -1
- package/lib/vendor/blamejs/lib/safe-json.js +44 -0
- package/lib/vendor/blamejs/lib/safe-sieve.js +1 -1
- package/lib/vendor/blamejs/lib/safe-vcard.js +1 -1
- package/lib/vendor/blamejs/lib/sandbox-worker.js +6 -0
- package/lib/vendor/blamejs/lib/sandbox.js +1 -1
- package/lib/vendor/blamejs/lib/scheduler.js +17 -1
- package/lib/vendor/blamejs/lib/self-update-standalone-verifier.js +16 -0
- package/lib/vendor/blamejs/lib/session.js +27 -3
- package/lib/vendor/blamejs/lib/sql.js +3 -3
- package/lib/vendor/blamejs/lib/static.js +65 -13
- package/lib/vendor/blamejs/lib/template.js +7 -5
- package/lib/vendor/blamejs/lib/tenant-quota.js +52 -19
- package/lib/vendor/blamejs/lib/tsa.js +5 -2
- package/lib/vendor/blamejs/lib/vault/index.js +5 -0
- package/lib/vendor/blamejs/lib/vault/passphrase-ops.js +22 -26
- package/lib/vendor/blamejs/lib/vault/passphrase-source.js +8 -3
- package/lib/vendor/blamejs/lib/vault/rotate.js +13 -18
- package/lib/vendor/blamejs/lib/vault/seal-pem-file.js +4 -1
- package/lib/vendor/blamejs/lib/vc.js +1 -1
- package/lib/vendor/blamejs/lib/vendor/MANIFEST.json +10 -10
- package/lib/vendor/blamejs/lib/vendor/public-suffix-list.dat +23 -10
- package/lib/vendor/blamejs/lib/vendor/public-suffix-list.data.js +5498 -5494
- package/lib/vendor/blamejs/lib/webhook.js +16 -1
- package/lib/vendor/blamejs/lib/websocket.js +1 -1
- package/lib/vendor/blamejs/lib/worm.js +1 -1
- package/lib/vendor/blamejs/lib/ws-client.js +57 -46
- package/lib/vendor/blamejs/lib/x509-chain.js +44 -0
- package/lib/vendor/blamejs/package.json +1 -1
- package/lib/vendor/blamejs/release-notes/v0.15.14.json +122 -0
- package/lib/vendor/blamejs/scripts/check-vendor-currency.js +119 -4
- package/lib/vendor/blamejs/test/00-primitives.js +5 -1
- package/lib/vendor/blamejs/test/integration/mail-crypto-smime.test.js +18 -4
- package/lib/vendor/blamejs/test/layer-0-primitives/a2a-tasks.test.js +42 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-event-bus.test.js +36 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-idempotency.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-snapshot.test.js +47 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/archive-gz.test.js +24 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/archive-tar-hardening.test.js +160 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/asn1-der.test.js +45 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file-fd-read.test.js +30 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file-open-nofollow.test.js +79 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file-write-excl.test.js +132 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file-write-stream.test.js +181 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-chain-corrupted-anchor.test.js +65 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-chain-incremental-verify.test.js +83 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-daily-review.test.js +48 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-signing-key-rotation.test.js +13 -3
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-verifybundle-tamper.test.js +122 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-bot-challenge.test.js +37 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-jwt-defenses.test.js +5 -3
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-lockout.test.js +21 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-status-list.test.js +73 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/bot-guard.test.js +17 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/break-glass.test.js +62 -3
- package/lib/vendor/blamejs/test/layer-0-primitives/clear-site-data.test.js +12 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cluster-storage.test.js +40 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +146 -5
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance-sanctions.test.js +14 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compression-range.test.js +115 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/content-credentials.test.js +17 -5
- package/lib/vendor/blamejs/test/layer-0-primitives/cors.test.js +27 -5
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-per-row-key.test.js +83 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csrf-protect.test.js +58 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/data-act.test.js +30 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-raw-residency-gate.test.js +74 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dora.test.js +20 -6
- package/lib/vendor/blamejs/test/layer-0-primitives/dpop-alg-kty.test.js +64 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dsr.test.js +32 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fda-21cfr11.test.js +43 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fetch-metadata.test.js +30 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/graphql-federation.test.js +33 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-html.test.js +34 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-json.test.js +12 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-client-stream.test.js +72 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-client-throttle-transform.test.js +88 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/importmap-integrity.test.js +25 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/legal-hold.test.js +28 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-smime.test.js +20 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-header-injection.test.js +58 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-journal.test.js +63 -9
- package/lib/vendor/blamejs/test/layer-0-primitives/mdoc.test.js +41 -14
- package/lib/vendor/blamejs/test/layer-0-primitives/network-allowlist.test.js +61 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/object-store-range-header.test.js +51 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/otlp-attr-redaction.test.js +35 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/output-header-hardening.test.js +102 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/parser-verify-hardening.test.js +191 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/proto-shadow-allowlist.test.js +120 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/rate-limit-xff-spoofing.test.js +75 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/request-helpers.test.js +128 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/restore-blob-remap.test.js +128 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/retention-sweep-termination.test.js +104 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/router-body-validation.test.js +98 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-json-stringify-for-script.test.js +43 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/saml-subjectconfirmation-notonorafter.test.js +48 -5
- package/lib/vendor/blamejs/test/layer-0-primitives/sandbox.test.js +23 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc.test.js +54 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/session-extensions.test.js +24 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/static.test.js +42 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/step-up.test.js +7 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/template-escape-html.test.js +43 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tenant-quota.test.js +15 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vault-default-store.test.js +48 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vendor-currency-classify.test.js +77 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/webhook.test.js +50 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ws-client.test.js +22 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/x509-chain-ca-enforcement.test.js +149 -0
- package/lib/vendor/blamejs/test/layer-5-integration/external-db-residency.test.js +34 -0
- package/package.json +1 -1
|
@@ -430,6 +430,26 @@ function _validateMessage(message) {
|
|
|
430
430
|
throw new MailError("mail/invalid-subject",
|
|
431
431
|
"message.subject contains forbidden CRLF", true);
|
|
432
432
|
}
|
|
433
|
+
// Reply-To and custom header KEYS go straight onto the wire in _buildRfc822;
|
|
434
|
+
// a CRLF in either smuggles arbitrary headers (Bcc / Reply-To override /
|
|
435
|
+
// Content-Type) — RFC 5322 / CWE-93 header injection. Fail closed.
|
|
436
|
+
if (message.replyTo && safeBuffer.hasCrlf(String(message.replyTo))) {
|
|
437
|
+
throw new MailError("mail/invalid-reply-to",
|
|
438
|
+
"message.replyTo contains forbidden CRLF (header injection)", true);
|
|
439
|
+
}
|
|
440
|
+
if (message.headers && typeof message.headers === "object") {
|
|
441
|
+
for (var _hk in message.headers) {
|
|
442
|
+
if (!Object.prototype.hasOwnProperty.call(message.headers, _hk)) continue;
|
|
443
|
+
if (safeBuffer.hasCrlf(_hk) || _hk.indexOf("\0") !== -1) {
|
|
444
|
+
throw new MailError("mail/invalid-header",
|
|
445
|
+
"message.headers key contains forbidden CRLF/NUL (header injection)", true);
|
|
446
|
+
}
|
|
447
|
+
if (safeBuffer.hasCrlf(String(message.headers[_hk]))) {
|
|
448
|
+
throw new MailError("mail/invalid-header",
|
|
449
|
+
"message.headers value for '" + _hk + "' contains forbidden CRLF (header injection)", true);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
433
453
|
|
|
434
454
|
if (!message.text && !message.html && !message.calendar) {
|
|
435
455
|
throw new MailError("mail/missing-body",
|
|
@@ -711,7 +731,7 @@ function _buildRfc822(message) {
|
|
|
711
731
|
headers.push("From: " + message.from);
|
|
712
732
|
headers.push("To: " + (Array.isArray(message.to) ? message.to.join(", ") : message.to));
|
|
713
733
|
if (message.cc) headers.push("Cc: " + (Array.isArray(message.cc) ? message.cc.join(", ") : message.cc));
|
|
714
|
-
if (message.replyTo) headers.push("Reply-To: " + message.replyTo);
|
|
734
|
+
if (message.replyTo) headers.push("Reply-To: " + safeBuffer.stripCrlf(String(message.replyTo)));
|
|
715
735
|
if (message.subject) headers.push("Subject: " + message.subject);
|
|
716
736
|
headers.push("MIME-Version: 1.0");
|
|
717
737
|
headers.push("Date: " + new Date().toUTCString());
|
|
@@ -721,7 +741,7 @@ function _buildRfc822(message) {
|
|
|
721
741
|
// Strip CRLF defensively even though we already validated the
|
|
722
742
|
// message; custom headers go straight onto the wire.
|
|
723
743
|
var v = safeBuffer.stripCrlf(String(message.headers[k]));
|
|
724
|
-
headers.push(k + ": " + v);
|
|
744
|
+
headers.push(safeBuffer.stripCrlf(String(k)) + ": " + v);
|
|
725
745
|
}
|
|
726
746
|
}
|
|
727
747
|
}
|
|
@@ -48,7 +48,31 @@ function splitTagNameAttrs(inner, tagNameRe) {
|
|
|
48
48
|
};
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
// htmlCommentEnd(s, lt) — given that an HTML comment opens at index `lt`
|
|
52
|
+
// (s.startsWith("<!--", lt)), return the index ONE PAST the comment's
|
|
53
|
+
// terminator per the WHATWG HTML tokenizer, not just the legacy "-->" form.
|
|
54
|
+
// A browser also closes a comment at "--!>" (comment-end-bang state) and
|
|
55
|
+
// ABRUPTLY closes one that begins "<!-->" or "<!--->". A scanner that honours
|
|
56
|
+
// only "-->" therefore disagrees with the browser about where the comment
|
|
57
|
+
// ends, so markup AFTER an early "--!>" / abrupt close is swallowed as inert
|
|
58
|
+
// comment by the sanitizer but parsed as a LIVE element by the browser (mXSS,
|
|
59
|
+
// the comment-parser differential). Returns -1 if the comment is unterminated
|
|
60
|
+
// so each caller keeps its own policy (lenient end-of-input vs. fail-closed
|
|
61
|
+
// throw). NOTE: HTML/SVG-in-HTML only — XML comments do NOT have these forms.
|
|
62
|
+
function htmlCommentEnd(s, lt) {
|
|
63
|
+
var i = lt + 4; // first char after "<!--"
|
|
64
|
+
if (s.charAt(i) === ">") return i + 1; // <!--> abrupt close
|
|
65
|
+
if (s.charAt(i) === "-" && s.charAt(i + 1) === ">") return i + 2; // <!---> abrupt close
|
|
66
|
+
var a = s.indexOf("-->", i);
|
|
67
|
+
var b = s.indexOf("--!>", i);
|
|
68
|
+
if (a === -1 && b === -1) return -1; // unterminated
|
|
69
|
+
if (a === -1) return b + 4;
|
|
70
|
+
if (b === -1) return a + 3;
|
|
71
|
+
return a <= b ? a + 3 : b + 4; // earliest terminator wins
|
|
72
|
+
}
|
|
73
|
+
|
|
51
74
|
module.exports = {
|
|
52
75
|
scanToTagEnd: scanToTagEnd,
|
|
53
76
|
splitTagNameAttrs: splitTagNameAttrs,
|
|
77
|
+
htmlCommentEnd: htmlCommentEnd,
|
|
54
78
|
};
|
|
@@ -636,10 +636,12 @@ function _validateValueAgainstSchema(value, schema, path) {
|
|
|
636
636
|
}
|
|
637
637
|
if (typeof schema.pattern === "string") {
|
|
638
638
|
// Schema-supplied pattern — operator-controlled at registration
|
|
639
|
-
// time, not request-controlled. Cap
|
|
640
|
-
// codebase-patterns regex-bound rule so a
|
|
641
|
-
//
|
|
642
|
-
|
|
639
|
+
// time, not request-controlled. Cap the input LENGTH first per the
|
|
640
|
+
// codebase-patterns regex-bound rule so a huge string can't ReDoS the
|
|
641
|
+
// validator. This is a CHARACTER cap, not a byte cap: regex matching
|
|
642
|
+
// cost scales with the number of code units the engine scans, so 4096
|
|
643
|
+
// chars is the correct ReDoS bound regardless of UTF-8 byte size.
|
|
644
|
+
if (value.length > 4096) return path + ": value exceeds 4096-char cap before regex test"; // ReDoS char cap (not bytes)
|
|
643
645
|
try {
|
|
644
646
|
var pat = new RegExp(schema.pattern); // allow:dynamic-regex — schema.pattern from registered tool author, not request input; bounded above
|
|
645
647
|
if (!pat.test(value)) return path + ": does not match pattern";
|
|
@@ -53,6 +53,7 @@ var C = require("./constants");
|
|
|
53
53
|
var cbor = require("./cbor");
|
|
54
54
|
var cose = require("./cose");
|
|
55
55
|
var bCrypto = require("./crypto");
|
|
56
|
+
var x509Chain = require("./x509-chain");
|
|
56
57
|
var safeBuffer = require("./safe-buffer");
|
|
57
58
|
var validateOpts = require("./validate-opts");
|
|
58
59
|
var { defineClass } = require("./framework-error");
|
|
@@ -128,10 +129,26 @@ function _mapGet(m, k) { return m instanceof Map ? m.get(k) : (m ? m[k] : undefi
|
|
|
128
129
|
*/
|
|
129
130
|
async function verifyIssuerSigned(issuerSigned, opts) {
|
|
130
131
|
validateOpts.requireObject(opts, "mdoc.verifyIssuerSigned", MdocError);
|
|
131
|
-
validateOpts(opts, ["algorithms", "trustAnchorsPem", "expectedDocType", "at", "maxBytes", "maxDepth"], "mdoc.verifyIssuerSigned");
|
|
132
|
+
validateOpts(opts, ["algorithms", "trustAnchorsPem", "allowUntrustedIssuer", "expectedDocType", "at", "maxBytes", "maxDepth"], "mdoc.verifyIssuerSigned");
|
|
132
133
|
if (!Array.isArray(opts.algorithms) || opts.algorithms.length === 0) {
|
|
133
134
|
throw new MdocError("mdoc/algorithms-required", "mdoc.verifyIssuerSigned: opts.algorithms is required");
|
|
134
135
|
}
|
|
136
|
+
// Issuer authentication is the WHOLE point of verifyIssuerSigned. The signer
|
|
137
|
+
// cert rides in the attacker-supplied x5chain, so verifying the COSE_Sign1
|
|
138
|
+
// against that embedded key only proves the MSO is self-consistent — a forged
|
|
139
|
+
// mDL (attacker keypair, self-signed leaf, self-signed MSO) passes unless the
|
|
140
|
+
// chain is anchored to a configured trust root. Require trustAnchorsPem by
|
|
141
|
+
// default; an operator who genuinely wants trust-anchor-free verification must
|
|
142
|
+
// opt in EXPLICITLY (and gets issuerTrusted:false on the result so the
|
|
143
|
+
// unauthenticated posture is visible). Fail closed — never silently accept an
|
|
144
|
+
// unanchored issuer.
|
|
145
|
+
var hasAnchors = opts.trustAnchorsPem !== undefined && opts.trustAnchorsPem !== null;
|
|
146
|
+
if (!hasAnchors && opts.allowUntrustedIssuer !== true) {
|
|
147
|
+
throw new MdocError("mdoc/trust-anchors-required",
|
|
148
|
+
"mdoc.verifyIssuerSigned: opts.trustAnchorsPem is required to authenticate the issuer " +
|
|
149
|
+
"(the x5chain signer cert is attacker-controlled); pass trustAnchorsPem, or set " +
|
|
150
|
+
"allowUntrustedIssuer:true to explicitly accept an unauthenticated issuer");
|
|
151
|
+
}
|
|
135
152
|
validateOpts.optionalDate(opts.at, "mdoc.verifyIssuerSigned: opts.at", MdocError, "mdoc/bad-at");
|
|
136
153
|
var at = (opts.at !== undefined && opts.at !== null) ? opts.at : new Date();
|
|
137
154
|
var decodeOpts = { allowedTags: ALLOWED_TAGS, maxBytes: opts.maxBytes, maxDepth: opts.maxDepth };
|
|
@@ -264,6 +281,10 @@ async function verifyIssuerSigned(issuerSigned, opts) {
|
|
|
264
281
|
deviceKey: deviceKey,
|
|
265
282
|
signerCert: signerCert.toString(),
|
|
266
283
|
alg: verified.alg,
|
|
284
|
+
// true only when the signer chain was validated against a configured trust
|
|
285
|
+
// anchor; false on the explicit allowUntrustedIssuer opt-out so a caller
|
|
286
|
+
// can tell an authenticated issuer from a merely self-consistent MSO.
|
|
287
|
+
issuerTrusted: hasAnchors,
|
|
267
288
|
};
|
|
268
289
|
}
|
|
269
290
|
|
|
@@ -409,8 +430,10 @@ function _verifyChain(chainDer, anchorsPem, at) {
|
|
|
409
430
|
throw new MdocError("mdoc/chain-loop", "mdoc.verifyIssuerSigned: certificate chain did not terminate");
|
|
410
431
|
}
|
|
411
432
|
function _issued(issuer, subject) {
|
|
412
|
-
|
|
413
|
-
|
|
433
|
+
// Enforces basicConstraints cA:TRUE on the issuer alongside the
|
|
434
|
+
// checkIssued + signature linkage — an IACA/DS chain cannot accept a
|
|
435
|
+
// non-CA cert as an issuer (basicConstraints bypass, CVE-2002-0862 class).
|
|
436
|
+
return x509Chain.issuerValidlyIssued(issuer, subject);
|
|
414
437
|
}
|
|
415
438
|
function _assertValidAt(cert, atMs) {
|
|
416
439
|
if (atMs < cert.validFromDate.getTime() || atMs > cert.validToDate.getTime()) {
|
|
@@ -39,7 +39,6 @@
|
|
|
39
39
|
|
|
40
40
|
var C = require("./constants");
|
|
41
41
|
var canonicalJson = require("./canonical-json");
|
|
42
|
-
var nodeFs = require("node:fs");
|
|
43
42
|
var atomicFile = require("./atomic-file");
|
|
44
43
|
var safeJson = require("./safe-json");
|
|
45
44
|
var { defineClass } = require("./framework-error");
|
|
@@ -1067,8 +1066,20 @@ function snapshotRead(p) {
|
|
|
1067
1066
|
MetricsError, "metrics-snapshot/bad-path");
|
|
1068
1067
|
var raw;
|
|
1069
1068
|
try {
|
|
1070
|
-
|
|
1069
|
+
// Capped fd-bound read: the snapshot is parsed AFTER read, so the cap must
|
|
1070
|
+
// precede the alloc — a hostile multi-GB file at the snapshot path would
|
|
1071
|
+
// otherwise OOM the reader before safeJson's 4 MiB parse cap is consulted.
|
|
1072
|
+
raw = atomicFile.fdSafeReadSync(p, {
|
|
1073
|
+
maxBytes: C.BYTES.mib(4), encoding: "utf8", refuseSymlink: true,
|
|
1074
|
+
errorFor: function (kind, detail) {
|
|
1075
|
+
if (kind === "enoent") return new MetricsError("metrics-snapshot/not-found", "metrics.snapshot.read: " + p + " — not found");
|
|
1076
|
+
if (kind === "too-large") return new MetricsError("metrics-snapshot/too-large", "metrics.snapshot.read: " + p + " exceeds " + detail.max + " bytes");
|
|
1077
|
+
if (kind === "symlink") return new MetricsError("metrics-snapshot/symlink-refused", "metrics.snapshot.read: " + p + " is a symlink (refused)");
|
|
1078
|
+
return undefined;
|
|
1079
|
+
},
|
|
1080
|
+
});
|
|
1071
1081
|
} catch (e) {
|
|
1082
|
+
if (e instanceof MetricsError) throw e;
|
|
1072
1083
|
throw new MetricsError("metrics-snapshot/not-found",
|
|
1073
1084
|
"metrics.snapshot.read: " + p + " — " + (e && e.message ? e.message : String(e)));
|
|
1074
1085
|
}
|
|
@@ -1329,7 +1340,7 @@ function shadowRegistry(opts) {
|
|
|
1329
1340
|
var gaugeSet = _shadowSetOf(opts.gauges, "gauges");
|
|
1330
1341
|
var infoSet = _shadowSetOf(opts.info, "info");
|
|
1331
1342
|
var cap = opts.cardinalityCap === undefined ? SHADOW_DEFAULT_CARDINALITY : opts.cardinalityCap;
|
|
1332
|
-
if (
|
|
1343
|
+
if (!numericBounds.isPositiveFiniteInt(cap)) {
|
|
1333
1344
|
throw new MetricsError("metrics-shadow/bad-cap",
|
|
1334
1345
|
"shadowRegistry: cardinalityCap must be a positive integer");
|
|
1335
1346
|
}
|
|
@@ -556,7 +556,7 @@ function create(opts) {
|
|
|
556
556
|
_emitFailure(req, "shape");
|
|
557
557
|
return _writeRejection(res, HTTP_STATUS.BAD_REQUEST, { error: "encrypted-payload-required" });
|
|
558
558
|
}
|
|
559
|
-
if (
|
|
559
|
+
if (!numericBounds.isNonNegativeFiniteInt(ctr)) {
|
|
560
560
|
_emitFailure(req, "shape");
|
|
561
561
|
return _writeRejection(res, HTTP_STATUS.BAD_REQUEST, { error: "encrypted-payload-required" });
|
|
562
562
|
}
|
|
@@ -596,7 +596,7 @@ function create(opts) {
|
|
|
596
596
|
_emitFailure(req, "shape");
|
|
597
597
|
return _writeRejection(res, HTTP_STATUS.BAD_REQUEST, { error: "encrypted-payload-required" });
|
|
598
598
|
}
|
|
599
|
-
if (!
|
|
599
|
+
if (!numericBounds.isNonNegativeFiniteInt(ctr)) {
|
|
600
600
|
_emitFailure(req, "shape");
|
|
601
601
|
return _writeRejection(res, HTTP_STATUS.BAD_REQUEST, { error: "encrypted-payload-required" });
|
|
602
602
|
}
|
|
@@ -269,13 +269,19 @@ function _contentType(req) {
|
|
|
269
269
|
}
|
|
270
270
|
|
|
271
271
|
function _typeMatches(actual, allowed) {
|
|
272
|
+
var ab = actual.split("/");
|
|
272
273
|
for (var i = 0; i < allowed.length; i++) {
|
|
273
274
|
var a = allowed[i].toLowerCase();
|
|
274
|
-
//
|
|
275
|
+
// Exact match, or component-wise wildcard where `*` in either the type or
|
|
276
|
+
// subtype segment matches any value — so "*/*" (the raw() default) matches
|
|
277
|
+
// every type, "type/*" matches a whole type, and "*/json" matches a
|
|
278
|
+
// subtype across types. (A literal indexOf on "*/" never matched a real
|
|
279
|
+
// Content-Type, which made the "*/*" default reject every request.)
|
|
275
280
|
if (a === actual) return true;
|
|
276
|
-
var
|
|
277
|
-
if (
|
|
278
|
-
|
|
281
|
+
var pb = a.split("/");
|
|
282
|
+
if (pb.length === 2 && ab.length === 2 &&
|
|
283
|
+
(pb[0] === "*" || pb[0] === ab[0]) &&
|
|
284
|
+
(pb[1] === "*" || pb[1] === ab[1])) return true;
|
|
279
285
|
}
|
|
280
286
|
return false;
|
|
281
287
|
}
|
|
@@ -71,17 +71,6 @@ function _coerceAgentPattern(r, where) {
|
|
|
71
71
|
"in operator code)");
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
// Bot-guard's "trust the proxy header" semantics for actor.ip — the
|
|
75
|
-
// audit event records the apparent source even when behind a CDN, but
|
|
76
|
-
// only when the operator opts in to trustProxy. Without the opt, we
|
|
77
|
-
// stick to socket.remoteAddress so an attacker-forged XFF can't
|
|
78
|
-
// pollute audit attribution.
|
|
79
|
-
function _xffIpFor(trustProxy) {
|
|
80
|
-
return function (req) {
|
|
81
|
-
return requestHelpers.clientIp(req, { trustProxy: trustProxy });
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
74
|
/**
|
|
86
75
|
* @primitive b.middleware.botGuard
|
|
87
76
|
* @signature b.middleware.botGuard(req, res, next)
|
|
@@ -115,7 +104,10 @@ function _xffIpFor(trustProxy) {
|
|
|
115
104
|
* bodyOnBlock: string,
|
|
116
105
|
* onDeny: function(req, res, info): void, // own the block response; info = { status, reason }
|
|
117
106
|
* problemDetails: boolean, // default false — emit RFC 9457 application/problem+json instead of text/plain
|
|
118
|
-
*
|
|
107
|
+
* trustedProxies: string|string[], // CIDRs of your reverse proxies — peer-gates X-Forwarded-For / -Proto
|
|
108
|
+
* clientIpResolver: function(req): string|null, // own the audit-actor IP
|
|
109
|
+
* protocolResolver: function(req): "http"|"https", // own the secure-context decision
|
|
110
|
+
* trustProxy: boolean|number, // legacy; refused unless paired with trustedProxies/resolver (spoofable)
|
|
119
111
|
* }
|
|
120
112
|
*
|
|
121
113
|
* @example
|
|
@@ -131,11 +123,26 @@ function create(opts) {
|
|
|
131
123
|
opts = opts || {};
|
|
132
124
|
validateOpts(opts, [
|
|
133
125
|
"mode", "onlyForHtml", "allowedAgents", "blockedAgents",
|
|
134
|
-
"skipPaths", "statusOnBlock", "bodyOnBlock", "onDeny", "problemDetails",
|
|
126
|
+
"skipPaths", "statusOnBlock", "bodyOnBlock", "onDeny", "problemDetails",
|
|
127
|
+
"trustProxy", "trustedProxies", "clientIpResolver", "protocolResolver",
|
|
135
128
|
], "middleware.botGuard");
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
129
|
+
// The single trustProxy opt drives two forwarded-header reads: the audit
|
|
130
|
+
// actor.ip (X-Forwarded-For) and the secure-context check (X-Forwarded-Proto,
|
|
131
|
+
// see _isSecureContext). Both are peer-gated — declare your reverse proxies
|
|
132
|
+
// via trustedProxies (CIDRs), or own resolution via clientIpResolver /
|
|
133
|
+
// protocolResolver. A bare trustProxy is refused: it would trust forgeable
|
|
134
|
+
// headers from any caller.
|
|
135
|
+
var _ipResolver, _proto;
|
|
136
|
+
try {
|
|
137
|
+
_ipResolver = requestHelpers.trustedClientIp({ trustedProxies: opts.trustedProxies, clientIpResolver: opts.clientIpResolver });
|
|
138
|
+
_proto = requestHelpers.trustedProtocol({ trustedProxies: opts.trustedProxies, protocolResolver: opts.protocolResolver });
|
|
139
|
+
} catch (e) { throw new BotGuardError("bot-guard/bad-opt", e.message); }
|
|
140
|
+
if ((opts.trustProxy === true || typeof opts.trustProxy === "number") && !_ipResolver.peerGated) {
|
|
141
|
+
throw new BotGuardError("bot-guard/bad-opt",
|
|
142
|
+
"trustProxy is spoofable — a direct caller could forge X-Forwarded-For / -Proto. Declare " +
|
|
143
|
+
"your reverse proxies via trustedProxies: [\"10.0.0.0/8\", …] or supply clientIpResolver / protocolResolver.");
|
|
144
|
+
}
|
|
145
|
+
var _xffIp = _ipResolver.resolve;
|
|
139
146
|
var mode = opts.mode || "block";
|
|
140
147
|
var onlyForHtml = opts.onlyForHtml !== false;
|
|
141
148
|
var allowedAgents = (opts.allowedAgents || []).map(function (r, i) {
|
|
@@ -163,9 +170,10 @@ function create(opts) {
|
|
|
163
170
|
// app, a LAN / *.local reverse-proxy deployment — the browser omits
|
|
164
171
|
// Sec-Fetch-* entirely, so a missing Sec-Fetch-Mode is NORMAL there and
|
|
165
172
|
// must not be read as a bot signal. The effective scheme honours
|
|
166
|
-
// X-Forwarded-Proto only
|
|
173
|
+
// X-Forwarded-Proto only from a trusted proxy peer (peer-gated), else the
|
|
174
|
+
// real TLS socket — a direct caller's forged header is ignored.
|
|
167
175
|
function _isSecureContext(req) {
|
|
168
|
-
if (
|
|
176
|
+
if (_proto.resolve(req) === "https") return true;
|
|
169
177
|
var host = (req.headers && req.headers.host) || "";
|
|
170
178
|
host = String(host).toLowerCase().replace(/:\d+$/, ""); // strip :port
|
|
171
179
|
if (host.charAt(0) === "[") { // [::1] IPv6 literal
|
|
@@ -113,7 +113,11 @@ function headerValue(types, label) {
|
|
|
113
113
|
}
|
|
114
114
|
for (var i = 0; i < types.length; i += 1) {
|
|
115
115
|
var t = types[i];
|
|
116
|
-
|
|
116
|
+
// hasOwnProperty, not `KNOWN_TYPES[t]`: a bracket lookup on the plain-object
|
|
117
|
+
// allowlist resolves inherited members ("toString" / "constructor" /
|
|
118
|
+
// "hasOwnProperty") to truthy functions, so those would pass validation and
|
|
119
|
+
// be emitted as bogus Clear-Site-Data directives (prototype shadowing).
|
|
120
|
+
if (typeof t !== "string" || !Object.prototype.hasOwnProperty.call(KNOWN_TYPES, t)) {
|
|
117
121
|
throw new TypeError(
|
|
118
122
|
label + ": unknown type '" + t +
|
|
119
123
|
"' (expected one of: " + Object.keys(KNOWN_TYPES).join(", ") + ")");
|
|
@@ -328,6 +328,15 @@ function create(opts) {
|
|
|
328
328
|
// Status precludes body? skip.
|
|
329
329
|
if (NO_BODY_STATUS.has(statusCode)) { compress = false; return; }
|
|
330
330
|
|
|
331
|
+
// 206 Partial Content / any Content-Range response must pass through
|
|
332
|
+
// UNtransformed (RFC 7233 §4.1) — compressing it drops Content-Length but
|
|
333
|
+
// leaves Content-Range advertising an uncompressed byte interval over a
|
|
334
|
+
// now-compressed body, corrupting range-assembling clients.
|
|
335
|
+
if (statusCode === 206) { compress = false; return; }
|
|
336
|
+
var crRange = (headersObj && headersObj["content-range"]) ||
|
|
337
|
+
(originalGetHeader && originalGetHeader("Content-Range"));
|
|
338
|
+
if (crRange) { compress = false; return; }
|
|
339
|
+
|
|
331
340
|
// Already compressed by the handler / earlier middleware? skip.
|
|
332
341
|
var existingCE = (headersObj && headersObj["content-encoding"]) ||
|
|
333
342
|
(originalGetHeader && originalGetHeader("Content-Encoding"));
|
|
@@ -58,15 +58,6 @@ var validateOpts = require("../validate-opts");
|
|
|
58
58
|
var denyResponse = require("./deny-response").denyResponse;
|
|
59
59
|
var { defineClass } = require("../framework-error");
|
|
60
60
|
|
|
61
|
-
// CORS audit events use the proxy-aware client IP only when the
|
|
62
|
-
// operator opts in via `trustProxy`. Default refuses forwarded
|
|
63
|
-
// headers — same boundary as the rest of the v0.5.3 trustProxy sweep.
|
|
64
|
-
function _xffIpFor(trustProxy) {
|
|
65
|
-
return function (req) {
|
|
66
|
-
return requestHelpers.clientIp(req, { trustProxy: trustProxy });
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
|
|
70
61
|
var CorsError = defineClass("CorsError", { alwaysPermanent: true });
|
|
71
62
|
|
|
72
63
|
// allowList entries:
|
|
@@ -117,17 +108,18 @@ function _canonicalOrigin(input) {
|
|
|
117
108
|
// supplied. Works for direct deployments (no proxy); operators behind
|
|
118
109
|
// a TLS-terminating proxy that doesn't forward correct Host should set
|
|
119
110
|
// opts.siteOrigin explicitly.
|
|
120
|
-
function _inferRequestOrigin(req,
|
|
111
|
+
function _inferRequestOrigin(req, protoResolve) {
|
|
121
112
|
if (!req || !req.headers) return null;
|
|
122
113
|
var host = req.headers.host;
|
|
123
114
|
if (!host) return null;
|
|
124
|
-
//
|
|
125
|
-
//
|
|
126
|
-
|
|
115
|
+
// Peer-gated protocol resolution — X-Forwarded-Proto is honored only from a
|
|
116
|
+
// trusted proxy (else the TLS socket), so a direct caller can't forge the
|
|
117
|
+
// inferred origin's scheme to slip past the same-origin check.
|
|
118
|
+
var proto = protoResolve(req);
|
|
127
119
|
return _canonicalOrigin(proto + "://" + host);
|
|
128
120
|
}
|
|
129
121
|
|
|
130
|
-
function _isSameOrigin(req, originHeader, configuredSiteOrigins,
|
|
122
|
+
function _isSameOrigin(req, originHeader, configuredSiteOrigins, protoResolve, strictNullOrigin) {
|
|
131
123
|
// Origin: null arrives when a browser opaques the Origin (e.g.
|
|
132
124
|
// Referrer-Policy: no-referrer on the page). Sec-Fetch-Site can
|
|
133
125
|
// distinguish the same-origin case, but non-browser clients can forge
|
|
@@ -150,10 +142,10 @@ function _isSameOrigin(req, originHeader, configuredSiteOrigins, trustProxy, str
|
|
|
150
142
|
}
|
|
151
143
|
return false;
|
|
152
144
|
}
|
|
153
|
-
// Fall back to inferring from the request itself.
|
|
154
|
-
// through so operators behind a TLS terminator
|
|
155
|
-
//
|
|
156
|
-
var reqOrigin = _inferRequestOrigin(req,
|
|
145
|
+
// Fall back to inferring from the request itself. The peer-gated protocol
|
|
146
|
+
// resolver threads through so operators behind a TLS terminator consult
|
|
147
|
+
// X-Forwarded-Proto only from a trusted peer.
|
|
148
|
+
var reqOrigin = _inferRequestOrigin(req, protoResolve);
|
|
157
149
|
return reqOrigin !== null && reqOrigin === canonOrigin;
|
|
158
150
|
}
|
|
159
151
|
|
|
@@ -185,7 +177,10 @@ function _isSameOrigin(req, originHeader, configuredSiteOrigins, trustProxy, str
|
|
|
185
177
|
* maxAgeSeconds: number, // default 600
|
|
186
178
|
* refuseUnknown: boolean, // default true
|
|
187
179
|
* strictNullOrigin: boolean, // default true
|
|
188
|
-
*
|
|
180
|
+
* trustedProxies: string|string[], // CIDRs of your reverse proxies — peer-gates X-Forwarded-Proto for same-origin inference
|
|
181
|
+
* protocolResolver: function(req): "http"|"https", // own the HTTPS decision
|
|
182
|
+
* clientIpResolver: function(req): string|null, // own the audit-actor IP
|
|
183
|
+
* trustProxy: boolean|number, // legacy; refused unless paired with trustedProxies/resolver (spoofable)
|
|
189
184
|
* onDeny: function(req, res, info): void, // own every refusal; info = { status, reason, origin, header? }
|
|
190
185
|
* problemDetails: boolean, // default false — emit RFC 9457 application/problem+json instead of text/plain
|
|
191
186
|
* }
|
|
@@ -205,11 +200,25 @@ function create(opts) {
|
|
|
205
200
|
validateOpts(opts, [
|
|
206
201
|
"origins", "siteOrigin", "methods", "headers", "exposeHeaders",
|
|
207
202
|
"credentials", "maxAgeSeconds", "refuseUnknown", "trustProxy",
|
|
203
|
+
"trustedProxies", "clientIpResolver", "protocolResolver",
|
|
208
204
|
"strictNullOrigin", "allowPrivateNetwork", "onDeny", "problemDetails",
|
|
209
205
|
], "middleware.cors");
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
206
|
+
// The request scheme feeds the same-origin determination, and a bare
|
|
207
|
+
// trustProxy trusts a forgeable X-Forwarded-Proto from any caller. Peer-gate
|
|
208
|
+
// both protocol and the audit-actor IP via trustedProxies (CIDRs) or own them
|
|
209
|
+
// via protocolResolver / clientIpResolver. A bare trustProxy is refused.
|
|
210
|
+
var _proto, _ip;
|
|
211
|
+
try {
|
|
212
|
+
_proto = requestHelpers.trustedProtocol({ trustedProxies: opts.trustedProxies, protocolResolver: opts.protocolResolver });
|
|
213
|
+
_ip = requestHelpers.trustedClientIp({ trustedProxies: opts.trustedProxies, clientIpResolver: opts.clientIpResolver });
|
|
214
|
+
} catch (e) { throw new CorsError("cors/bad-opt", e.message); }
|
|
215
|
+
if ((opts.trustProxy === true || typeof opts.trustProxy === "number") && !_proto.peerGated) {
|
|
216
|
+
throw new CorsError("cors/bad-opt",
|
|
217
|
+
"trustProxy is spoofable — a direct caller could forge X-Forwarded-Proto to alter the " +
|
|
218
|
+
"same-origin decision. Declare your reverse proxies via trustedProxies: [\"10.0.0.0/8\", …] " +
|
|
219
|
+
"or supply protocolResolver(req) / clientIpResolver(req).");
|
|
220
|
+
}
|
|
221
|
+
var _xffIp = _ip.resolve;
|
|
213
222
|
|
|
214
223
|
// Build a canonicalized allowList at create() time. String entries
|
|
215
224
|
// get parsed through _canonicalOrigin so case + default-port
|
|
@@ -296,7 +305,7 @@ function create(opts) {
|
|
|
296
305
|
// Same-origin POST/PUT/etc. carry an Origin header per the Fetch
|
|
297
306
|
// spec but should not be subject to CORS allow-listing — they're
|
|
298
307
|
// the operator's own site talking to itself.
|
|
299
|
-
if (_isSameOrigin(req, origin, siteOrigins,
|
|
308
|
+
if (_isSameOrigin(req, origin, siteOrigins, _proto.resolve, strictNullOrigin)) return next();
|
|
300
309
|
|
|
301
310
|
var matched = _matchOrigin(origin, origins);
|
|
302
311
|
if (!matched) {
|
|
@@ -77,6 +77,9 @@ var audit = lazyRequire(function () { return require("../audit"); });
|
|
|
77
77
|
var DEFAULT_FIELD_NAME = "_csrf";
|
|
78
78
|
var DEFAULT_HEADER_NAME = "X-CSRF-Token";
|
|
79
79
|
var DEFAULT_METHODS = Object.freeze(["POST", "PUT", "DELETE", "PATCH"]);
|
|
80
|
+
// Per-process counter giving each csrfProtect mount a unique idempotency id so
|
|
81
|
+
// a stricter sub-route instance is not silently disabled by an earlier one.
|
|
82
|
+
var _csrfGateSeq = 0;
|
|
80
83
|
|
|
81
84
|
// Default cookie name uses the RFC 6265bis __Host- prefix when the request
|
|
82
85
|
// is over HTTPS. The prefix forces browsers to refuse the cookie unless
|
|
@@ -124,17 +127,6 @@ function _parseCookieHeader(header) {
|
|
|
124
127
|
return Object.assign(Object.create(null), Object.fromEntries(pairs));
|
|
125
128
|
}
|
|
126
129
|
|
|
127
|
-
// `_isHttps` defers to `requestHelpers.requestProtocol` so the
|
|
128
|
-
// per-middleware `trustProxy` opt gates whether X-Forwarded-Proto is
|
|
129
|
-
// consulted. Without trustProxy, an attacker could otherwise forge
|
|
130
|
-
// the header to force the Secure cookie attribute (and inversely,
|
|
131
|
-
// suppress it) on direct-to-server connections.
|
|
132
|
-
function _isHttpsFor(trustProxy) {
|
|
133
|
-
return function (req) {
|
|
134
|
-
return requestHelpers.requestProtocol(req, { trustProxy: trustProxy }) === "https";
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
|
|
138
130
|
function _formatSetCookie(name, value, opts) {
|
|
139
131
|
var parts = [name + "=" + value];
|
|
140
132
|
parts.push("Path=" + (opts.path || "/"));
|
|
@@ -275,7 +267,9 @@ function _writeReject(req, res, message, reason, onDeny, problemMode) {
|
|
|
275
267
|
* allowedOrigins: string[],
|
|
276
268
|
* requireOrigin: boolean,
|
|
277
269
|
* requireJsonContentType: boolean,
|
|
278
|
-
*
|
|
270
|
+
* trustedProxies: string|string[], // CIDRs of your reverse proxies — peer-gates X-Forwarded-Proto for the Secure-cookie decision
|
|
271
|
+
* protocolResolver: function(req): "http"|"https", // own the HTTPS decision
|
|
272
|
+
* trustProxy: boolean|number, // legacy; refused unless paired with trustedProxies/protocolResolver (spoofable)
|
|
279
273
|
* audit: boolean,
|
|
280
274
|
* skipStateless: boolean, // default false — skip validation for Authorization-header / cookieless (not-CSRF-able) requests
|
|
281
275
|
* onDeny: function(req, res, info): void, // own the 403; info = { status, reason }
|
|
@@ -296,14 +290,31 @@ function create(opts) {
|
|
|
296
290
|
|
|
297
291
|
validateOpts(opts, [
|
|
298
292
|
"cookie", "tokenLookup", "fieldName", "headerName", "methods", "audit",
|
|
299
|
-
"trustProxy", "
|
|
293
|
+
"trustProxy", "trustedProxies", "protocolResolver",
|
|
294
|
+
"checkOrigin", "allowedOrigins", "requireJsonContentType",
|
|
300
295
|
"requireOrigin", "skipStateless", "skipPaths", "skip", "onDeny", "problemDetails",
|
|
301
296
|
], "middleware.csrfProtect");
|
|
302
297
|
var onDeny = typeof opts.onDeny === "function" ? opts.onDeny : null;
|
|
303
298
|
var problemMode = opts.problemDetails === true;
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
299
|
+
// The Secure-cookie decision turns on whether the request is HTTPS, which
|
|
300
|
+
// behind a proxy comes from X-Forwarded-Proto. A bare trustProxy trusts that
|
|
301
|
+
// forgeable header from any caller — a direct request could suppress the
|
|
302
|
+
// Secure flag (cookie downgrade) or force it. Peer-gate it: declare your
|
|
303
|
+
// reverse proxies via trustedProxies, or own the decision via
|
|
304
|
+
// protocolResolver. A bare trustProxy is refused at construction.
|
|
305
|
+
var _proto;
|
|
306
|
+
try {
|
|
307
|
+
_proto = requestHelpers.trustedProtocol({
|
|
308
|
+
trustedProxies: opts.trustedProxies,
|
|
309
|
+
protocolResolver: opts.protocolResolver,
|
|
310
|
+
});
|
|
311
|
+
} catch (e) { throw new Error("middleware.csrfProtect: " + e.message); }
|
|
312
|
+
if ((opts.trustProxy === true || typeof opts.trustProxy === "number") && !_proto.peerGated) {
|
|
313
|
+
throw new Error("middleware.csrfProtect: trustProxy is spoofable for the Secure-cookie " +
|
|
314
|
+
"decision — a direct caller could forge X-Forwarded-Proto. Declare your reverse proxies " +
|
|
315
|
+
"via trustedProxies: [\"10.0.0.0/8\", …] or supply protocolResolver(req).");
|
|
316
|
+
}
|
|
317
|
+
function _isHttps(req) { return _proto.resolve(req) === "https"; }
|
|
307
318
|
|
|
308
319
|
// Throw at create() — exactly one issuance source allowed.
|
|
309
320
|
var hasCookie = opts.cookie != null && opts.cookie !== false;
|
|
@@ -318,8 +329,22 @@ function create(opts) {
|
|
|
318
329
|
|
|
319
330
|
var fieldName = opts.fieldName || DEFAULT_FIELD_NAME;
|
|
320
331
|
var headerName = (opts.headerName || DEFAULT_HEADER_NAME).toLowerCase();
|
|
332
|
+
// An empty methods array is truthy → `opts.methods || DEFAULT_METHODS` keeps
|
|
333
|
+
// `[]` and the method-gate matches nothing, silently disabling the primary
|
|
334
|
+
// CSRF check for all state-changing requests. Reject at config time.
|
|
335
|
+
if (opts.methods !== undefined) {
|
|
336
|
+
if (!Array.isArray(opts.methods) || opts.methods.length === 0 ||
|
|
337
|
+
!opts.methods.every(function (m) { return typeof m === "string" && m.length > 0; })) {
|
|
338
|
+
throw new Error("middleware.csrfProtect: opts.methods must be a non-empty array of HTTP method tokens (omit it for the POST/PUT/DELETE/PATCH default)");
|
|
339
|
+
}
|
|
340
|
+
}
|
|
321
341
|
var methods = (opts.methods || DEFAULT_METHODS).map(function (m) { return m.toUpperCase(); });
|
|
322
342
|
var auditOn = opts.audit !== false;
|
|
343
|
+
// Per-instance idempotency id — a stricter sub-route csrf mount must not be
|
|
344
|
+
// silently disabled by an earlier lenient one sharing a global flag (token
|
|
345
|
+
// issue is idempotent and double-validate is safe, so per-instance re-run is
|
|
346
|
+
// sound).
|
|
347
|
+
var GATE_ID = "csrf:" + (_csrfGateSeq++);
|
|
323
348
|
|
|
324
349
|
// Origin / Referer cross-check — second-line defense alongside the
|
|
325
350
|
// double-submit token. If the request's Origin (or Referer when
|
|
@@ -452,6 +477,17 @@ function create(opts) {
|
|
|
452
477
|
req.csrfToken = existing;
|
|
453
478
|
return existing;
|
|
454
479
|
}
|
|
480
|
+
// Cookie issuance is idempotent at the RESPONSE level, keyed by cookie
|
|
481
|
+
// name: a redundant mount (createApp wired csrf AND an operator re-mounted
|
|
482
|
+
// it with the same cookie name) must emit a single Set-Cookie, not one per
|
|
483
|
+
// instance. Enforcement stays per instance (each gate still validates the
|
|
484
|
+
// token below) — only the response-cookie resource is deduped, so a mount
|
|
485
|
+
// with a DIFFERENT cookie name still issues its own.
|
|
486
|
+
if (!req._csrfIssuedCookies) req._csrfIssuedCookies = Object.create(null);
|
|
487
|
+
if (Object.prototype.hasOwnProperty.call(req._csrfIssuedCookies, cookieName)) {
|
|
488
|
+
req.csrfToken = req._csrfIssuedCookies[cookieName];
|
|
489
|
+
return req.csrfToken;
|
|
490
|
+
}
|
|
455
491
|
if (existing && !/^[a-f0-9]{64}$/.test(existing)) {
|
|
456
492
|
// Audit-emit so operators see when a planted/short cookie is
|
|
457
493
|
// refused — surfaces the attack class in compliance logs.
|
|
@@ -472,16 +508,19 @@ function create(opts) {
|
|
|
472
508
|
maxAge: cookieCfg.maxAge,
|
|
473
509
|
});
|
|
474
510
|
_appendSetCookie(res, setCookie);
|
|
511
|
+
req._csrfIssuedCookies[cookieName] = fresh;
|
|
475
512
|
req.csrfToken = fresh;
|
|
476
513
|
return fresh;
|
|
477
514
|
}
|
|
478
515
|
|
|
479
516
|
return function csrfProtect(req, res, next) {
|
|
480
|
-
// Idempotent:
|
|
481
|
-
//
|
|
482
|
-
//
|
|
483
|
-
|
|
484
|
-
req.
|
|
517
|
+
// Idempotent PER INSTANCE: the SAME mount running twice (createApp wired it
|
|
518
|
+
// AND an operator mounted it again) is a no-op, but a distinct stricter
|
|
519
|
+
// sub-route instance still runs — a shared global flag let the first
|
|
520
|
+
// (lenient) mount disable the second.
|
|
521
|
+
if (!req._csrfGates) req._csrfGates = Object.create(null);
|
|
522
|
+
if (req._csrfGates[GATE_ID]) return next();
|
|
523
|
+
req._csrfGates[GATE_ID] = true;
|
|
485
524
|
|
|
486
525
|
// Issue/refresh the token on EVERY request (safe + state-changing)
|
|
487
526
|
// when running in cookie mode — templates rendered after a POST
|
|
@@ -187,13 +187,15 @@ function create(opts) {
|
|
|
187
187
|
// is observed via writableLength as res.write / res.end fire.
|
|
188
188
|
var inboundBytes = 0;
|
|
189
189
|
if (req.headers && typeof req.headers === "object") {
|
|
190
|
-
//
|
|
191
|
-
//
|
|
192
|
-
// byte
|
|
190
|
+
// Each header line is "Name: Value\r\n". Count UTF-8 BYTES (not .length
|
|
191
|
+
// / UTF-16 code units) so a multibyte header value isn't under-accounted
|
|
192
|
+
// against the byte quota — matching the outbound counting below, which
|
|
193
|
+
// already uses Buffer.byteLength.
|
|
193
194
|
var keys = Object.keys(req.headers);
|
|
194
195
|
for (var hi = 0; hi < keys.length; hi++) {
|
|
195
196
|
var v = req.headers[keys[hi]];
|
|
196
|
-
inboundBytes += keys[hi]
|
|
197
|
+
inboundBytes += Buffer.byteLength(keys[hi], "utf8") + 2 +
|
|
198
|
+
(typeof v === "string" ? Buffer.byteLength(v, "utf8") : 0) + 2; // ": " + "\r\n" overhead
|
|
197
199
|
}
|
|
198
200
|
}
|
|
199
201
|
if (req.headers && req.headers["content-length"]) {
|