@blamejs/blamejs-shop 0.4.68 → 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 +4 -0
- package/lib/admin.js +8 -6
- package/lib/asset-manifest.json +1 -1
- package/lib/giftcards.js +29 -5
- 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
|
@@ -44,6 +44,11 @@
|
|
|
44
44
|
var structuredFields = require("./structured-fields");
|
|
45
45
|
var pick = require("./pick");
|
|
46
46
|
var codepointClass = require("./codepoint-class");
|
|
47
|
+
var lazyRequire = require("./lazy-require");
|
|
48
|
+
// Lazy — ssrf-guard pulls in the network/DNS stack, and request-helpers is
|
|
49
|
+
// required very early in the boot graph. Only touched at middleware-construction
|
|
50
|
+
// time by trustedClientIp(), never on the hot path.
|
|
51
|
+
var _ssrfGuard = lazyRequire(function () { return require("./ssrf-guard"); });
|
|
47
52
|
|
|
48
53
|
var HTTP_STATUS = Object.freeze({
|
|
49
54
|
OK: 0xC8,
|
|
@@ -214,15 +219,24 @@ function resolveActorWithOverride(callerOpts, baseOverride) {
|
|
|
214
219
|
*
|
|
215
220
|
* Resolve the originating client IP from a request. Default reads
|
|
216
221
|
* only `req.socket.remoteAddress` — `X-Forwarded-For` is ignored
|
|
217
|
-
* because without a sanitizing reverse proxy it's
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
* `trustProxy
|
|
221
|
-
*
|
|
222
|
-
*
|
|
222
|
+
* because without a sanitizing reverse proxy it's attacker-forgeable.
|
|
223
|
+
*
|
|
224
|
+
* For an access-control decision (allowlist, rate-limit key, IP-bound
|
|
225
|
+
* grant), pass `trustProxy` as a PREDICATE `function(addr) => boolean`
|
|
226
|
+
* naming your trusted reverse proxies. The header is then honored only
|
|
227
|
+
* when the immediate TCP peer is itself a trusted proxy, and the client
|
|
228
|
+
* is the first untrusted address walking the chain right-to-left. A
|
|
229
|
+
* direct attacker cannot forge it — this is the only peer-gated form.
|
|
230
|
+
*
|
|
231
|
+
* The legacy `trustProxy: true` (leftmost XFF hop) and `trustProxy: <N>`
|
|
232
|
+
* (Nth-from-rightmost) forms do NOT verify the peer: a client connecting
|
|
233
|
+
* directly can forge any value. They are safe only when an upstream you
|
|
234
|
+
* control terminates and rewrites X-Forwarded-For on every request — never
|
|
235
|
+
* for a security decision on an internet-facing listener. Prefer the
|
|
236
|
+
* predicate form. Returns `null` when no address can be read — never throws.
|
|
223
237
|
*
|
|
224
238
|
* @opts
|
|
225
|
-
* trustProxy: boolean | number // false (default) |
|
|
239
|
+
* trustProxy: boolean | number | function // false (default) | predicate (peer-gated) | legacy true/hop-count
|
|
226
240
|
*
|
|
227
241
|
* @example
|
|
228
242
|
* var req = {
|
|
@@ -232,30 +246,55 @@ function resolveActorWithOverride(callerOpts, baseOverride) {
|
|
|
232
246
|
* b.requestHelpers.clientIp(req);
|
|
233
247
|
* // → "10.0.0.1" (forwarded headers ignored by default)
|
|
234
248
|
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
249
|
+
* var fromTrusted = function (a) { return a.indexOf("10.") === 0; };
|
|
250
|
+
* b.requestHelpers.clientIp(req, { trustProxy: fromTrusted });
|
|
251
|
+
* // → "203.0.113.7" (peer 10.0.0.1 trusted; first untrusted hop)
|
|
237
252
|
*
|
|
238
|
-
*
|
|
239
|
-
*
|
|
253
|
+
* var forged = { socket: { remoteAddress: "198.51.100.66" },
|
|
254
|
+
* headers: { "x-forwarded-for": "203.0.113.7" } };
|
|
255
|
+
* b.requestHelpers.clientIp(forged, { trustProxy: fromTrusted });
|
|
256
|
+
* // → "198.51.100.66" (peer untrusted → forged header ignored)
|
|
240
257
|
*
|
|
241
258
|
* b.requestHelpers.clientIp(undefined);
|
|
242
259
|
* // → null
|
|
243
260
|
*/
|
|
244
261
|
function clientIp(req, opts) {
|
|
245
262
|
if (!req) return null;
|
|
263
|
+
var socketAddr =
|
|
264
|
+
(req.socket && typeof req.socket.remoteAddress === "string" && req.socket.remoteAddress) ? req.socket.remoteAddress
|
|
265
|
+
: (req.connection && typeof req.connection.remoteAddress === "string" && req.connection.remoteAddress) ? req.connection.remoteAddress
|
|
266
|
+
: null;
|
|
246
267
|
var trust = opts && opts.trustProxy;
|
|
247
268
|
if (trust && req.headers) {
|
|
248
269
|
var xff = req.headers["x-forwarded-for"];
|
|
249
270
|
if (xff) {
|
|
250
271
|
var hops = parseListHeader(xff);
|
|
251
|
-
if (
|
|
252
|
-
|
|
253
|
-
|
|
272
|
+
if (hops.length) {
|
|
273
|
+
if (typeof trust === "function") {
|
|
274
|
+
// Peer-gated resolution: `trust(addr)` names the trusted reverse
|
|
275
|
+
// proxies. X-Forwarded-For is honored ONLY when the immediate TCP
|
|
276
|
+
// peer is itself a trusted proxy; the real client is then the first
|
|
277
|
+
// untrusted address walking the chain right-to-left (each hop is
|
|
278
|
+
// appended by the proxy that observed it). A direct attacker — whose
|
|
279
|
+
// socket peer is not a trusted proxy — cannot forge the result: the
|
|
280
|
+
// forgeable header is ignored and we fall through to the socket
|
|
281
|
+
// address. This is the only form safe for an access-control decision.
|
|
282
|
+
if (socketAddr && trust(socketAddr)) {
|
|
283
|
+
for (var i = hops.length - 1; i >= 0; i--) {
|
|
284
|
+
if (!trust(hops[i])) return hops[i];
|
|
285
|
+
}
|
|
286
|
+
return hops[0]; // entire chain trusted — earliest claimed client
|
|
287
|
+
}
|
|
288
|
+
// peer is not a trusted proxy → ignore forgeable XFF, fall through
|
|
289
|
+
} else if (trust === true) {
|
|
290
|
+
return hops[0];
|
|
291
|
+
} else if (typeof trust === "number" && trust >= 1 && hops.length >= trust) {
|
|
292
|
+
return hops[hops.length - trust];
|
|
293
|
+
}
|
|
254
294
|
}
|
|
255
295
|
}
|
|
256
296
|
}
|
|
257
|
-
if (
|
|
258
|
-
if (req.connection && typeof req.connection.remoteAddress === "string") return req.connection.remoteAddress;
|
|
297
|
+
if (socketAddr) return socketAddr;
|
|
259
298
|
// Express-shaped requests expose the resolved client address as `req.ip`
|
|
260
299
|
// (Express derives it from the socket, honoring its own trust-proxy
|
|
261
300
|
// setting) without a `socket.remoteAddress` surface. Fall back to it so a
|
|
@@ -266,6 +305,125 @@ function clientIp(req, opts) {
|
|
|
266
305
|
return null;
|
|
267
306
|
}
|
|
268
307
|
|
|
308
|
+
/**
|
|
309
|
+
* @primitive b.requestHelpers.trustedClientIp
|
|
310
|
+
* @signature b.requestHelpers.trustedClientIp(opts?)
|
|
311
|
+
* @since 0.15.14
|
|
312
|
+
* @related b.requestHelpers.clientIp
|
|
313
|
+
*
|
|
314
|
+
* Build a peer-gated client-IP resolver for an access-control decision
|
|
315
|
+
* (allowlist, rate-limit key, IP-bound grant). The bare `trustProxy`
|
|
316
|
+
* forms of `clientIp` are forgeable; this is the shape every gate shares
|
|
317
|
+
* so the trust model is identical across them. Returns
|
|
318
|
+
* `{ resolve(req), peerGated }`: `resolve` reads the client IP, `peerGated`
|
|
319
|
+
* is true when `trustedProxies` or `clientIpResolver` was supplied — a
|
|
320
|
+
* gate uses it to refuse a bare `trustProxy` at construction (fail closed).
|
|
321
|
+
*
|
|
322
|
+
* With `clientIpResolver(req)` the operator owns resolution entirely. With
|
|
323
|
+
* `trustedProxies` (CIDRs of the reverse proxies), `X-Forwarded-For` is
|
|
324
|
+
* honored only when the immediate peer is one of them. With neither, only
|
|
325
|
+
* the socket address is used and forwarded headers are ignored.
|
|
326
|
+
*
|
|
327
|
+
* @opts
|
|
328
|
+
* trustedProxies: string | string[], // CIDRs — peer-gate X-Forwarded-For
|
|
329
|
+
* clientIpResolver: function(req): string|null, // own resolution entirely
|
|
330
|
+
*
|
|
331
|
+
* @example
|
|
332
|
+
* var tip = b.requestHelpers.trustedClientIp({ trustedProxies: ["10.0.0.0/8"] });
|
|
333
|
+
* var ip = tip.resolve(req); // peer-gated; forged XFF from a direct caller ignored
|
|
334
|
+
*/
|
|
335
|
+
// Build the trusted-proxy predicate shared by trustedClientIp / trustedProtocol.
|
|
336
|
+
// Validates each CIDR (a CIDR is valid iff it contains its own network address,
|
|
337
|
+
// reusing the same matcher the predicate uses so format rules can't diverge) and
|
|
338
|
+
// returns fn(addr)=>boolean, or null when no trustedProxies were given. `where`
|
|
339
|
+
// names the calling helper for the error message.
|
|
340
|
+
function _trustedProxyPredicate(trustedProxies, where) {
|
|
341
|
+
if (!trustedProxies || !trustedProxies.length) return null;
|
|
342
|
+
var ssrfGuard = _ssrfGuard();
|
|
343
|
+
for (var i = 0; i < trustedProxies.length; i++) {
|
|
344
|
+
var cidr = trustedProxies[i];
|
|
345
|
+
var slash = typeof cidr === "string" ? cidr.indexOf("/") : -1;
|
|
346
|
+
if (slash === -1 || !ssrfGuard.cidrContains(cidr, cidr.slice(0, slash))) {
|
|
347
|
+
throw new TypeError(where + ": trustedProxies[" + i + "] is not a valid CIDR, got " + JSON.stringify(cidr));
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return function (addr) {
|
|
351
|
+
// Fold an IPv4-mapped IPv6 peer (::ffff:a.b.c.d, common on a dual-stack
|
|
352
|
+
// listener) to its dotted IPv4 form so it matches an IPv4 trustedProxies
|
|
353
|
+
// CIDR — cidrContains rejects a cross-family compare, so without this a
|
|
354
|
+
// mapped proxy peer reads as untrusted and X-Forwarded-* is ignored. Only
|
|
355
|
+
// the ::ffff:0:0/96 block folds (canonicalizeHost leaves NAT64 / 6to4 as
|
|
356
|
+
// IPv6), so this can't widen the trusted set.
|
|
357
|
+
var canon = ssrfGuard.canonicalizeHost(addr);
|
|
358
|
+
for (var j = 0; j < trustedProxies.length; j++) {
|
|
359
|
+
if (ssrfGuard.cidrContains(trustedProxies[j], canon)) return true;
|
|
360
|
+
}
|
|
361
|
+
return false;
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function _normTrustedProxies(opts) {
|
|
366
|
+
return Array.isArray(opts.trustedProxies) ? opts.trustedProxies.slice()
|
|
367
|
+
: (typeof opts.trustedProxies === "string" && opts.trustedProxies.length ? [opts.trustedProxies] : []);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function trustedClientIp(opts) {
|
|
371
|
+
opts = opts || {};
|
|
372
|
+
var resolver = opts.clientIpResolver;
|
|
373
|
+
if (resolver != null && typeof resolver !== "function") {
|
|
374
|
+
throw new TypeError("trustedClientIp: clientIpResolver must be a function(req) => ip|null");
|
|
375
|
+
}
|
|
376
|
+
var predicate = _trustedProxyPredicate(_normTrustedProxies(opts), "trustedClientIp");
|
|
377
|
+
return {
|
|
378
|
+
peerGated: !!(resolver || predicate),
|
|
379
|
+
resolve: function (req) {
|
|
380
|
+
if (resolver) return resolver(req);
|
|
381
|
+
if (predicate) return clientIp(req, { trustProxy: predicate });
|
|
382
|
+
return clientIp(req, { trustProxy: false });
|
|
383
|
+
},
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* @primitive b.requestHelpers.trustedProtocol
|
|
389
|
+
* @signature b.requestHelpers.trustedProtocol(opts?)
|
|
390
|
+
* @since 0.15.14
|
|
391
|
+
* @related b.requestHelpers.requestProtocol, b.requestHelpers.trustedClientIp
|
|
392
|
+
*
|
|
393
|
+
* Peer-gated companion to trustedClientIp for the request scheme. The
|
|
394
|
+
* Secure-cookie / HSTS / secure-context decisions hinge on whether a request
|
|
395
|
+
* arrived over HTTPS; behind a TLS-terminating proxy that comes from
|
|
396
|
+
* X-Forwarded-Proto, which is forgeable unless the immediate peer is a trusted
|
|
397
|
+
* proxy. Returns `{ resolve(req)=>"http"|"https", peerGated }`. With
|
|
398
|
+
* `trustedProxies` (CIDRs) the header is honored only from a trusted peer; with
|
|
399
|
+
* `protocolResolver(req)` the operator owns the decision; with neither only the
|
|
400
|
+
* real TLS socket is consulted (forwarded headers ignored).
|
|
401
|
+
*
|
|
402
|
+
* @opts
|
|
403
|
+
* trustedProxies: string | string[],
|
|
404
|
+
* protocolResolver: function(req): "http"|"https",
|
|
405
|
+
*
|
|
406
|
+
* @example
|
|
407
|
+
* var tp = b.requestHelpers.trustedProtocol({ trustedProxies: ["10.0.0.0/8"] });
|
|
408
|
+
* tp.resolve(req); // "https" only when X-Forwarded-Proto came via a trusted peer
|
|
409
|
+
*/
|
|
410
|
+
function trustedProtocol(opts) {
|
|
411
|
+
opts = opts || {};
|
|
412
|
+
var resolver = opts.protocolResolver;
|
|
413
|
+
if (resolver != null && typeof resolver !== "function") {
|
|
414
|
+
throw new TypeError("trustedProtocol: protocolResolver must be a function(req) => 'http'|'https'");
|
|
415
|
+
}
|
|
416
|
+
var predicate = _trustedProxyPredicate(_normTrustedProxies(opts), "trustedProtocol");
|
|
417
|
+
return {
|
|
418
|
+
peerGated: !!(resolver || predicate),
|
|
419
|
+
resolve: function (req) {
|
|
420
|
+
if (resolver) return resolver(req);
|
|
421
|
+
if (predicate) return requestProtocol(req, { trustProxy: predicate });
|
|
422
|
+
return requestProtocol(req, { trustProxy: false });
|
|
423
|
+
},
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
|
|
269
427
|
/**
|
|
270
428
|
* @primitive b.requestHelpers.requestProtocol
|
|
271
429
|
* @signature b.requestHelpers.requestProtocol(req, opts?)
|
|
@@ -274,14 +432,17 @@ function clientIp(req, opts) {
|
|
|
274
432
|
*
|
|
275
433
|
* Resolve the inbound transport scheme. Default returns `"https"`
|
|
276
434
|
* when `req.socket.encrypted` is set, otherwise `"http"`. Behind a
|
|
277
|
-
* trusted reverse proxy that terminates TLS,
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
*
|
|
281
|
-
*
|
|
435
|
+
* trusted reverse proxy that terminates TLS, pass `trustProxy` as a
|
|
436
|
+
* PREDICATE `function(addr)=>boolean` naming your proxies:
|
|
437
|
+
* `X-Forwarded-Proto` is then honored only when the immediate peer is
|
|
438
|
+
* a trusted proxy, so a direct caller can't forge it (use
|
|
439
|
+
* `b.requestHelpers.trustedProtocol` to build this). The legacy
|
|
440
|
+
* `trustProxy: true` reads the leftmost hop without checking the peer —
|
|
441
|
+
* forgeable, safe only behind an edge that rewrites the header. Always
|
|
442
|
+
* returns a string; on bad input falls back to `"http"`.
|
|
282
443
|
*
|
|
283
444
|
* @opts
|
|
284
|
-
* trustProxy: boolean // false (default) | true
|
|
445
|
+
* trustProxy: boolean | function // false (default) | predicate (peer-gated) | legacy true
|
|
285
446
|
*
|
|
286
447
|
* @example
|
|
287
448
|
* var req = { socket: { encrypted: true } };
|
|
@@ -305,7 +466,22 @@ function requestProtocol(req, opts) {
|
|
|
305
466
|
var fwd = req.headers["x-forwarded-proto"];
|
|
306
467
|
if (typeof fwd === "string" && fwd.length > 0) {
|
|
307
468
|
var hops = parseListHeader(fwd, { lowercase: true });
|
|
308
|
-
if (hops.length > 0)
|
|
469
|
+
if (hops.length > 0) {
|
|
470
|
+
if (typeof trust === "function") {
|
|
471
|
+
// Peer-gated: honor X-Forwarded-Proto only when the immediate TCP
|
|
472
|
+
// peer is a trusted proxy. A direct caller's forged header is
|
|
473
|
+
// ignored — fall through to the real TLS socket. The only form safe
|
|
474
|
+
// for a Secure-cookie / HSTS / secure-context decision.
|
|
475
|
+
var peer =
|
|
476
|
+
(req.socket && typeof req.socket.remoteAddress === "string" && req.socket.remoteAddress) ? req.socket.remoteAddress
|
|
477
|
+
: (req.connection && typeof req.connection.remoteAddress === "string" && req.connection.remoteAddress) ? req.connection.remoteAddress
|
|
478
|
+
: null;
|
|
479
|
+
if (peer && trust(peer)) return hops[0];
|
|
480
|
+
// peer not a trusted proxy → ignore forgeable header, fall through
|
|
481
|
+
} else {
|
|
482
|
+
return hops[0]; // legacy true/number — spoofable, see docstring
|
|
483
|
+
}
|
|
484
|
+
}
|
|
309
485
|
}
|
|
310
486
|
}
|
|
311
487
|
if (req.socket && req.socket.encrypted) return "https";
|
|
@@ -890,7 +1066,9 @@ module.exports = {
|
|
|
890
1066
|
parseListHeader: parseListHeader,
|
|
891
1067
|
// proxy-trust primitives (default refuses forwarded headers)
|
|
892
1068
|
clientIp: clientIp,
|
|
1069
|
+
trustedClientIp: trustedClientIp,
|
|
893
1070
|
requestProtocol: requestProtocol,
|
|
1071
|
+
trustedProtocol: trustedProtocol,
|
|
894
1072
|
appendVary: appendVary,
|
|
895
1073
|
// CVE-2026-21710 wrap — safe alternative to req.headersDistinct
|
|
896
1074
|
safeHeadersDistinct: safeHeadersDistinct,
|
|
@@ -49,7 +49,7 @@ function create(opts) {
|
|
|
49
49
|
validateOpts.requireNonEmptyString(opts.resource, "resource",
|
|
50
50
|
ResourceAccessLockError, "resource-access-lock/no-resource");
|
|
51
51
|
var startMode = opts.startMode || "open";
|
|
52
|
-
if (!VALID_MODES
|
|
52
|
+
if (!Object.prototype.hasOwnProperty.call(VALID_MODES, startMode)) {
|
|
53
53
|
throw new ResourceAccessLockError(
|
|
54
54
|
"resource-access-lock/bad-start-mode",
|
|
55
55
|
"startMode must be one of: " + Object.keys(VALID_MODES).join(" / "));
|
|
@@ -71,12 +71,12 @@ function create(opts) {
|
|
|
71
71
|
function permits(action) {
|
|
72
72
|
if (mode === "open") return true;
|
|
73
73
|
if (mode === "locked") return false;
|
|
74
|
-
return !!READ_ACTIONS
|
|
74
|
+
return !!Object.prototype.hasOwnProperty.call(READ_ACTIONS, action);
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
function set(newMode, ctx) {
|
|
78
78
|
ctx = ctx || {};
|
|
79
|
-
if (!VALID_MODES
|
|
79
|
+
if (!Object.prototype.hasOwnProperty.call(VALID_MODES, newMode)) {
|
|
80
80
|
throw new ResourceAccessLockError(
|
|
81
81
|
"resource-access-lock/bad-mode",
|
|
82
82
|
"set: mode must be one of: " + Object.keys(VALID_MODES).join(" / "));
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
var nodeFs = require("node:fs");
|
|
49
49
|
var nodePath = require("node:path");
|
|
50
50
|
var atomicFile = require("./atomic-file");
|
|
51
|
+
var C = require("./constants");
|
|
51
52
|
var backupCrypto = require("./backup/crypto");
|
|
52
53
|
var backupManifest = require("./backup/manifest");
|
|
53
54
|
var validateOpts = require("./validate-opts");
|
|
@@ -146,13 +147,21 @@ async function extract(opts) {
|
|
|
146
147
|
// 1. Read + parse + validate manifest
|
|
147
148
|
_emit(progress, { phase: "read_manifest" });
|
|
148
149
|
var manifestPath = nodePath.join(bundleDir, "manifest.json");
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
150
|
+
// Capped fd-bound read OUTSIDE the parse try/catch, so a missing / oversized
|
|
151
|
+
// bundle manifest surfaces the precise restore-bundle code (not a generic
|
|
152
|
+
// parse error). A backup manifest is small JSON; 4 MiB bounds a hostile one
|
|
153
|
+
// before backupManifest.parse materializes it.
|
|
154
|
+
var manifestRaw = atomicFile.fdSafeReadSync(manifestPath, {
|
|
155
|
+
maxBytes: C.BYTES.mib(4), encoding: "utf8",
|
|
156
|
+
errorFor: function (kind, detail) {
|
|
157
|
+
if (kind === "enoent") return new RestoreBundleError("restore-bundle/missing-manifest", "extract: bundleDir has no manifest.json — bundle is incomplete or not a blamejs backup");
|
|
158
|
+
if (kind === "too-large") return new RestoreBundleError("restore-bundle/bad-manifest", "extract: manifest.json exceeds " + detail.max + " bytes");
|
|
159
|
+
return new RestoreBundleError("restore-bundle/bad-manifest", "extract: manifest unreadable: " + kind);
|
|
160
|
+
},
|
|
161
|
+
});
|
|
153
162
|
var manifest;
|
|
154
163
|
try {
|
|
155
|
-
manifest = backupManifest.parse(
|
|
164
|
+
manifest = backupManifest.parse(manifestRaw);
|
|
156
165
|
} catch (e) {
|
|
157
166
|
if (e && e.isBackupManifestError) throw e;
|
|
158
167
|
throw new RestoreBundleError("restore-bundle/bad-manifest",
|
|
@@ -216,12 +225,20 @@ async function extract(opts) {
|
|
|
216
225
|
}
|
|
217
226
|
|
|
218
227
|
var blobPath = nodePath.join(bundleDir, entry.encryptedPath);
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
228
|
+
// Cap the read to the manifest's declared encryptedSize so an oversize-on-
|
|
229
|
+
// disk blob is refused BEFORE it is read into memory (was: read fully, then
|
|
230
|
+
// compare → an OOM window for a huge swapped blob). A valid blob is exactly
|
|
231
|
+
// encryptedSize; the post-read compare still catches the under-size case.
|
|
232
|
+
var blobCap = (typeof entry.encryptedSize === "number" && entry.encryptedSize > 0)
|
|
233
|
+
? entry.encryptedSize : C.BYTES.gib(8);
|
|
234
|
+
var blob = atomicFile.fdSafeReadSync(blobPath, {
|
|
235
|
+
maxBytes: blobCap,
|
|
236
|
+
errorFor: function (kind, detail) {
|
|
237
|
+
if (kind === "enoent") return new RestoreBundleError("restore-bundle/missing-blob", "extract: manifest references '" + entry.encryptedPath + "' but the bundle has no such file");
|
|
238
|
+
if (kind === "too-large") return new RestoreBundleError("restore-bundle/size-mismatch", "extract: blob '" + entry.encryptedPath + "' has size " + detail.size + " but manifest expected " + entry.encryptedSize);
|
|
239
|
+
return new RestoreBundleError("restore-bundle/missing-blob", "extract: blob '" + entry.encryptedPath + "' unreadable: " + kind);
|
|
240
|
+
},
|
|
241
|
+
});
|
|
225
242
|
if (blob.length !== entry.encryptedSize) {
|
|
226
243
|
throw new RestoreBundleError("restore-bundle/size-mismatch",
|
|
227
244
|
"extract: blob '" + entry.encryptedPath + "' has size " + blob.length +
|
|
@@ -235,12 +252,18 @@ async function extract(opts) {
|
|
|
235
252
|
|
|
236
253
|
var plaintext;
|
|
237
254
|
try {
|
|
238
|
-
|
|
255
|
+
// Bundles written with manifest.aadBound sealed each blob with its
|
|
256
|
+
// relativePath as AEAD associated data — pass the SAME path so a blob
|
|
257
|
+
// remapped to a different manifest entry fails the tag here (the
|
|
258
|
+
// blob-remap / restore-corruption defense). Legacy bundles without
|
|
259
|
+
// the flag decrypt with no AAD (backward compatible).
|
|
260
|
+
var blobAad = manifest.aadBound === true ? Buffer.from(entry.relativePath, "utf8") : undefined;
|
|
261
|
+
plaintext = await backupCrypto.decryptWithPassphrase(blob, passphrase, entry.salt, blobAad);
|
|
239
262
|
} catch (e) {
|
|
240
263
|
if (e && e.isBackupCryptoError && e.code === "backup-crypto/decrypt-failed") {
|
|
241
264
|
throw new RestoreBundleError("restore-bundle/decrypt-failed",
|
|
242
265
|
"extract: blob '" + entry.encryptedPath + "' did not decrypt — " +
|
|
243
|
-
"passphrase rejected or
|
|
266
|
+
"passphrase rejected, ciphertext tampered, or blob remapped to a different path");
|
|
244
267
|
}
|
|
245
268
|
throw e;
|
|
246
269
|
}
|
|
@@ -326,11 +349,16 @@ function inspect(opts) {
|
|
|
326
349
|
"inspect: opts.bundleDir is required and must exist");
|
|
327
350
|
}
|
|
328
351
|
var manifestPath = nodePath.join(opts.bundleDir, "manifest.json");
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
352
|
+
// Capped fd-bound read (no-passphrase preview path, reachable with only read
|
|
353
|
+
// access to the bundle): bound a hostile manifest before parse.
|
|
354
|
+
return backupManifest.parse(atomicFile.fdSafeReadSync(manifestPath, {
|
|
355
|
+
maxBytes: C.BYTES.mib(4), encoding: "utf8",
|
|
356
|
+
errorFor: function (kind) {
|
|
357
|
+
if (kind === "enoent") return new RestoreBundleError("restore-bundle/missing-manifest", "inspect: bundleDir has no manifest.json");
|
|
358
|
+
if (kind === "too-large") return new RestoreBundleError("restore-bundle/bad-manifest", "inspect: manifest.json too large");
|
|
359
|
+
return new RestoreBundleError("restore-bundle/missing-manifest", "inspect: manifest unreadable: " + kind);
|
|
360
|
+
},
|
|
361
|
+
}));
|
|
334
362
|
}
|
|
335
363
|
|
|
336
364
|
module.exports = {
|
|
@@ -283,10 +283,16 @@ function list(opts) {
|
|
|
283
283
|
var p = nodePath.join(rollbackRoot, name);
|
|
284
284
|
var markerPath = p + ".marker.json";
|
|
285
285
|
var marker = null;
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
286
|
+
// Capped fd-bound read (no existsSync check-then-read window): the fs read is
|
|
287
|
+
// now bounded to 64 KiB too, so a tampered multi-GB marker.json is refused
|
|
288
|
+
// BEFORE allocation (the parse-only cap let readFileSync slurp the whole file
|
|
289
|
+
// first). refuseSymlink: the marker lives under the operator's rollbackRoot,
|
|
290
|
+
// never a secret-mount. Any failure → marker:null, the best-effort behavior.
|
|
291
|
+
try {
|
|
292
|
+
marker = safeJson.parse(
|
|
293
|
+
atomicFile.fdSafeReadSync(markerPath, { maxBytes: C.BYTES.kib(64), encoding: "utf8", refuseSymlink: true }),
|
|
294
|
+
{ maxBytes: C.BYTES.kib(64) });
|
|
295
|
+
} catch (_e) { marker = null; }
|
|
290
296
|
var stat;
|
|
291
297
|
try { stat = nodeFs.statSync(p); } catch (_e) { continue; }
|
|
292
298
|
out.push({
|
|
@@ -83,6 +83,7 @@ function create(opts) {
|
|
|
83
83
|
validateOpts(opts, [
|
|
84
84
|
"dataDir", "storage", "passphrase", "rollbackRoot", "audit",
|
|
85
85
|
"maxPulledBytes", "maxPulledFiles",
|
|
86
|
+
"requireSignature", "expectedFingerprint", "verifySignature",
|
|
86
87
|
], "restore");
|
|
87
88
|
validateOpts.requireNonEmptyString(opts.dataDir, "create: opts.dataDir", RestoreError, "restore/no-datadir");
|
|
88
89
|
_validateStorage(opts.storage);
|
|
@@ -96,6 +97,19 @@ function create(opts) {
|
|
|
96
97
|
var passphrase = opts.passphrase;
|
|
97
98
|
var rollbackRoot = opts.rollbackRoot || (dataDir + ".rollbacks");
|
|
98
99
|
var auditOn = opts.audit !== false;
|
|
100
|
+
// Manifest-signature policy. The framework signs bundles best-effort (an
|
|
101
|
+
// unsigned bundle is the documented CLI / standalone / worker case), so the
|
|
102
|
+
// non-opt-in integrity default is the per-blob AEAD path-binding below
|
|
103
|
+
// (which defeats the blob-remap attack on EVERY bundle, signed or not);
|
|
104
|
+
// requireSignature is the additional provenance policy operators under
|
|
105
|
+
// HIPAA/PCI opt into to mandate a verified signer. expectedFingerprint pins
|
|
106
|
+
// a signer; verifySignature:false allows a cold/cross-org restore. All
|
|
107
|
+
// three are threaded into restoreBundle.extract on every run — previously
|
|
108
|
+
// omitted entirely, so a present signature was never even verified and a
|
|
109
|
+
// requireSignature policy could not be enforced (CWE-347).
|
|
110
|
+
var requireSignature = opts.requireSignature === true;
|
|
111
|
+
var expectedFingerprint = opts.expectedFingerprint;
|
|
112
|
+
var verifySignature = opts.verifySignature;
|
|
99
113
|
|
|
100
114
|
// Preflight footprint caps. Defended against storage that returns a
|
|
101
115
|
// tampered or oversized bundle: we cap both the storage-reported size
|
|
@@ -271,6 +285,9 @@ function create(opts) {
|
|
|
271
285
|
passphrase: passphrase,
|
|
272
286
|
filter: runOpts.filter,
|
|
273
287
|
progressCallback: runOpts.progressCallback,
|
|
288
|
+
requireSignature: requireSignature,
|
|
289
|
+
expectedFingerprint: expectedFingerprint,
|
|
290
|
+
verifySignature: verifySignature,
|
|
274
291
|
});
|
|
275
292
|
} catch (e) {
|
|
276
293
|
_cleanupTmp();
|
|
@@ -283,6 +300,8 @@ function create(opts) {
|
|
|
283
300
|
else if (code === "restore-bundle/missing-manifest") mappedCode = "restore/missing-manifest";
|
|
284
301
|
else if (code === "restore-bundle/missing-blob") mappedCode = "restore/missing-blob";
|
|
285
302
|
else if (code === "restore-bundle/size-mismatch") mappedCode = "restore/size-mismatch";
|
|
303
|
+
else if (code === "restore-bundle/missing-signature") mappedCode = "restore/missing-signature";
|
|
304
|
+
else if (code === "restore-bundle/bad-signature") mappedCode = "restore/bad-signature";
|
|
286
305
|
_emitAudit("restore.failure",
|
|
287
306
|
{ bundleId: bundleId, reason: (e && e.message) || String(e) }, "failure");
|
|
288
307
|
throw new RestoreError(mappedCode,
|
|
@@ -431,17 +431,29 @@ function create(opts) {
|
|
|
431
431
|
|
|
432
432
|
try {
|
|
433
433
|
var moreRows = true;
|
|
434
|
+
// Keyset pagination cursor. Without it the loop re-selects the SAME batch
|
|
435
|
+
// forever whenever a full batch of rows is NOT removed from the candidate
|
|
436
|
+
// set by its action — legal-hold-skipped, "warn"-stage, errored, and
|
|
437
|
+
// (critically) EVERY row under dryRun mutate nothing, so a LIMIT-from-the-
|
|
438
|
+
// top query returns the identical rows each pass and `rows.length ===
|
|
439
|
+
// batchSize` never goes false. Ordering by _id and advancing past the last
|
|
440
|
+
// row seen guarantees forward progress regardless of whether a row was
|
|
441
|
+
// actioned. (Deleted / anonymized rows are also simply skipped past.)
|
|
442
|
+
var lastId = null;
|
|
434
443
|
while (moreRows) {
|
|
435
444
|
var rows;
|
|
436
445
|
// The candidate WHERE-clause: age + not-already-erased + not-on-legal-hold +
|
|
437
|
-
// (when soft-delete is configured) not-already-soft-deleted.
|
|
438
|
-
// through b.sql so the operator-supplied table / ageField /
|
|
439
|
-
// identifiers are quoted by construction and every value
|
|
440
|
-
// placeholder (the '' empty-string compare included — no
|
|
446
|
+
// (when soft-delete is configured) not-already-soft-deleted + keyset cursor.
|
|
447
|
+
// Built through b.sql so the operator-supplied table / ageField /
|
|
448
|
+
// softDeleteField identifiers are quoted by construction and every value
|
|
449
|
+
// binds as a placeholder (the '' empty-string compare included — no
|
|
450
|
+
// embedded literal).
|
|
441
451
|
function _candidateBase() {
|
|
442
452
|
var qb = sql.select(rule.table, SQL_OPTS)
|
|
443
453
|
.where(rule.ageField, "<=", cutoff);
|
|
444
454
|
if (rule.softDeleteField) qb.whereNull(rule.softDeleteField);
|
|
455
|
+
if (lastId !== null) qb.where("_id", ">", lastId);
|
|
456
|
+
qb.orderBy("_id", "asc");
|
|
445
457
|
return qb;
|
|
446
458
|
}
|
|
447
459
|
var selStmt;
|
|
@@ -504,6 +516,10 @@ function create(opts) {
|
|
|
504
516
|
reason: (e && e.message) || String(e) });
|
|
505
517
|
}
|
|
506
518
|
}
|
|
519
|
+
// Advance the keyset cursor past this batch so already-seen rows
|
|
520
|
+
// (including skipped / warned / errored / dry-run rows that stay in the
|
|
521
|
+
// candidate set) are never re-selected — the loop-termination guarantee.
|
|
522
|
+
lastId = rows[rows.length - 1]._id;
|
|
507
523
|
if (rows.length < rule.batchSize) moreRows = false;
|
|
508
524
|
}
|
|
509
525
|
} catch (e) {
|
|
@@ -36,6 +36,7 @@ var http = require("node:http");
|
|
|
36
36
|
var http2 = require("node:http2");
|
|
37
37
|
var nodeFs = require("node:fs");
|
|
38
38
|
var nodePath = require("node:path");
|
|
39
|
+
var atomicFile = require("./atomic-file");
|
|
39
40
|
var C = require("./constants");
|
|
40
41
|
var requestHelpers = require("./request-helpers");
|
|
41
42
|
var lazyRequire = require("./lazy-require");
|
|
@@ -147,7 +148,11 @@ function _makeSchemaValidator(spec) {
|
|
|
147
148
|
if (!qq.ok) return _writeValidationError(req, res, "query", qq.errors);
|
|
148
149
|
req.query = qq.value;
|
|
149
150
|
}
|
|
150
|
-
if (spec.body
|
|
151
|
+
if (spec.body) {
|
|
152
|
+
// Validate even when req.body is undefined (no body parsed / empty POST):
|
|
153
|
+
// a required body schema must report the violation, not be silently
|
|
154
|
+
// skipped. safeParse(undefined) fails a non-optional schema and passes an
|
|
155
|
+
// optional()/default() one. Mirrors the always-run params/query checks.
|
|
151
156
|
var bb = spec.body.safeParse(req.body);
|
|
152
157
|
if (!bb.ok) return _writeValidationError(req, res, "body", bb.errors);
|
|
153
158
|
req.body = bb.value;
|
|
@@ -1425,11 +1430,19 @@ function serveStatic(dir) {
|
|
|
1425
1430
|
// that shares the root's name as a prefix — e.g. root `/srv/public`
|
|
1426
1431
|
// vs `/srv/public-evil` — cannot satisfy the containment check.
|
|
1427
1432
|
if (filePath !== root && !filePath.startsWith(root + nodePath.sep)) return next();
|
|
1428
|
-
|
|
1433
|
+
// Open the (lexically-confined) path ONCE with O_NOFOLLOW and bind existence,
|
|
1434
|
+
// stat, and streaming to that single fd. The previous existsSync → statSync →
|
|
1435
|
+
// statSync → createReadStream sequence was a triple check-then-read TOCTOU
|
|
1436
|
+
// (CWE-367) on a request-derived pathname, and the plain createReadStream
|
|
1437
|
+
// would follow a symlink swapped in after the lexical check (CWE-22).
|
|
1438
|
+
// Opening before writeHead lets a refusal still fall through to next().
|
|
1439
|
+
var fd;
|
|
1440
|
+
try { fd = atomicFile.openNoFollowSync(filePath); } catch (_e) { return next(); }
|
|
1441
|
+
var stat = nodeFs.fstatSync(fd);
|
|
1442
|
+
if (stat.isDirectory()) { nodeFs.closeSync(fd); return next(); }
|
|
1429
1443
|
|
|
1430
1444
|
var ext = nodePath.extname(filePath).toLowerCase();
|
|
1431
1445
|
var mime = MIME_TYPES[ext] || "application/octet-stream";
|
|
1432
|
-
var stat = nodeFs.statSync(filePath);
|
|
1433
1446
|
var hasVersion = req.url && req.url.includes("?v=");
|
|
1434
1447
|
var cacheControl = hasVersion
|
|
1435
1448
|
? "public, max-age=31536000, immutable"
|
|
@@ -1439,7 +1452,7 @@ function serveStatic(dir) {
|
|
|
1439
1452
|
"Content-Length": stat.size,
|
|
1440
1453
|
"Cache-Control": cacheControl,
|
|
1441
1454
|
});
|
|
1442
|
-
nodeFs.createReadStream(filePath).pipe(res);
|
|
1455
|
+
nodeFs.createReadStream(filePath, { fd: fd }).pipe(res);
|
|
1443
1456
|
};
|
|
1444
1457
|
}
|
|
1445
1458
|
|
|
@@ -444,7 +444,7 @@ function _parseComponent(lines, startIdx, ctx, depth) {
|
|
|
444
444
|
"safeIcal.parse: expected BEGIN, got '" + begin.name + "'");
|
|
445
445
|
}
|
|
446
446
|
var compName = begin.value.toUpperCase();
|
|
447
|
-
if (!KNOWN_COMPONENTS
|
|
447
|
+
if (!Object.prototype.hasOwnProperty.call(KNOWN_COMPONENTS, compName) && !ctx.extraComps[compName] &&
|
|
448
448
|
compName.indexOf("X-") !== 0) {
|
|
449
449
|
throw new SafeIcalError("safe-ical/unknown-component",
|
|
450
450
|
"safeIcal.parse: unknown component '" + compName +
|
|
@@ -475,7 +475,7 @@ function _parseComponent(lines, startIdx, ctx, depth) {
|
|
|
475
475
|
}
|
|
476
476
|
// Validate property name.
|
|
477
477
|
var pn = ln.name;
|
|
478
|
-
if (!KNOWN_PROPERTIES
|
|
478
|
+
if (!Object.prototype.hasOwnProperty.call(KNOWN_PROPERTIES, pn) && !ctx.extraProps[pn] && pn.indexOf("X-") !== 0) {
|
|
479
479
|
throw new SafeIcalError("safe-ical/unknown-property",
|
|
480
480
|
"safeIcal.parse: unknown property '" + pn +
|
|
481
481
|
"' (extend via opts.extraProperties or use X- prefix)");
|
|
@@ -426,7 +426,7 @@ function _parseEncapsulated(value) {
|
|
|
426
426
|
}
|
|
427
427
|
var part = token.slice(0, eq);
|
|
428
428
|
var offStr = token.slice(eq + 1);
|
|
429
|
-
if (!ENCAPSULATED_PARTS
|
|
429
|
+
if (!Object.prototype.hasOwnProperty.call(ENCAPSULATED_PARTS, part)) {
|
|
430
430
|
throw new SafeIcapError("safe-icap/bad-encapsulated",
|
|
431
431
|
"safeIcap.parse: Encapsulated part '" + part + "' is not one of " +
|
|
432
432
|
Object.keys(ENCAPSULATED_PARTS).join(", "));
|