@blamejs/blamejs-shop 0.4.69 → 0.4.71
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/security-middleware.js +65 -23
- package/lib/shipping-insurance.js +29 -4
- package/lib/sms-dispatcher.js +17 -4
- 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
|
@@ -343,7 +343,7 @@ function create(opts) {
|
|
|
343
343
|
var secret = opts.secret;
|
|
344
344
|
|
|
345
345
|
var providerKey = opts.provider !== undefined ? opts.provider : DEFAULT_PROVIDER;
|
|
346
|
-
if (typeof providerKey !== "string" || !PROVIDERS
|
|
346
|
+
if (typeof providerKey !== "string" || !Object.prototype.hasOwnProperty.call(PROVIDERS, providerKey)) {
|
|
347
347
|
var supported = Object.keys(PROVIDERS).join(", ");
|
|
348
348
|
throw new BotChallengeError("bot-challenge/bad-opt",
|
|
349
349
|
"provider: expected one of [" + supported + "], got " + JSON.stringify(providerKey));
|
|
@@ -452,6 +452,23 @@ function create(opts) {
|
|
|
452
452
|
});
|
|
453
453
|
}
|
|
454
454
|
|
|
455
|
+
// Verify an OIDC ID token (if present) via the composed inner OAuth client,
|
|
456
|
+
// returning its verified claims, or null when no id_token was supplied. A
|
|
457
|
+
// present-but-invalid id_token throws (fail-closed) so a forged token's
|
|
458
|
+
// sub/acr/amr can never be returned as trusted.
|
|
459
|
+
async function _verifyIdTokenIfPresent(idToken, auditKey, op) {
|
|
460
|
+
if (!idToken) return null;
|
|
461
|
+
try {
|
|
462
|
+
return await inner.verifyIdToken(idToken);
|
|
463
|
+
} catch (e) {
|
|
464
|
+
_emitAudit("id_token_verify_fail", "failure",
|
|
465
|
+
auditKey ? { authReqIdHash: sha3Hash(auditKey) } : {});
|
|
466
|
+
throw new AuthError("auth-ciba/id-token-invalid",
|
|
467
|
+
"ciba." + op + ": id_token failed verification: " +
|
|
468
|
+
((e && e.code) || (e && e.message) || String(e)));
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
455
472
|
async function pollToken(popts) {
|
|
456
473
|
popts = popts || {};
|
|
457
474
|
if (typeof popts.authReqId !== "string" || popts.authReqId.length === 0) {
|
|
@@ -514,6 +531,12 @@ function create(opts) {
|
|
|
514
531
|
}
|
|
515
532
|
// Token issued — clear interval tracking for this authReqId.
|
|
516
533
|
_intervalState.delete(popts.authReqId);
|
|
534
|
+
// Verify the ID token (OIDC Core §3.1.3.7) via the composed inner OAuth
|
|
535
|
+
// client — it applies the create()-level issuer / clientId / JWKS /
|
|
536
|
+
// accepted-algorithms and enforces signature + iss/aud/exp. Returning an
|
|
537
|
+
// unverified id_token let a forged token's sub/acr/amr be trusted.
|
|
538
|
+
var pollClaims = await _verifyIdTokenIfPresent(rv.id_token,
|
|
539
|
+
"auth-ciba:" + popts.authReqId, "pollToken");
|
|
517
540
|
_emitAudit("token_received", "success", {
|
|
518
541
|
authReqIdHash: sha3Hash("auth-ciba:" + popts.authReqId),
|
|
519
542
|
});
|
|
@@ -521,6 +544,7 @@ function create(opts) {
|
|
|
521
544
|
return {
|
|
522
545
|
accessToken: rv.access_token || null,
|
|
523
546
|
idToken: rv.id_token || null,
|
|
547
|
+
claims: pollClaims,
|
|
524
548
|
refreshToken: rv.refresh_token || null,
|
|
525
549
|
tokenType: rv.token_type || null,
|
|
526
550
|
scope: rv.scope || null,
|
|
@@ -545,17 +569,23 @@ function create(opts) {
|
|
|
545
569
|
* - In **push** mode the body carries the full token-response
|
|
546
570
|
* object; no follow-up call needed.
|
|
547
571
|
*
|
|
572
|
+
* Async because a pushed `id_token` is verified (signature + iss/aud/exp)
|
|
573
|
+
* via the composed inner OAuth client before it is returned — the
|
|
574
|
+
* verified claims are surfaced as `claims`. A present-but-invalid
|
|
575
|
+
* id_token throws `auth-ciba/id-token-invalid` (the notification-token
|
|
576
|
+
* bearer authenticates the caller, never the token itself).
|
|
577
|
+
*
|
|
548
578
|
* @opts
|
|
549
579
|
* { body?: object } // pre-parsed body; defaults to req.body
|
|
550
580
|
*
|
|
551
581
|
* @example
|
|
552
|
-
* app.post("/ciba/notify", function (req, res) {
|
|
553
|
-
* var info = ciba.parseNotification(req, { body: req.body });
|
|
554
|
-
* // → { authReqId, accessToken, idToken, ... }
|
|
582
|
+
* app.post("/ciba/notify", async function (req, res) {
|
|
583
|
+
* var info = await ciba.parseNotification(req, { body: req.body });
|
|
584
|
+
* // → { authReqId, accessToken, idToken, claims, ... }
|
|
555
585
|
* res.statusCode = 204; res.end();
|
|
556
586
|
* });
|
|
557
587
|
*/
|
|
558
|
-
function parseNotification(req, popts) {
|
|
588
|
+
async function parseNotification(req, popts) {
|
|
559
589
|
popts = popts || {};
|
|
560
590
|
if (!req || !req.headers) {
|
|
561
591
|
throw new AuthError("auth-ciba/bad-notification-req",
|
|
@@ -605,6 +635,14 @@ function create(opts) {
|
|
|
605
635
|
throw new AuthError("auth-ciba/no-auth-req-id-in-body",
|
|
606
636
|
"ciba.parseNotification: body missing auth_req_id");
|
|
607
637
|
}
|
|
638
|
+
// Verify the pushed ID token (CIBA §10.2 / OIDC Core MUST) via the
|
|
639
|
+
// composed inner OAuth client before returning it as trusted. The
|
|
640
|
+
// notification-token bearer authenticates the CALLER, not the token; a
|
|
641
|
+
// leaked/observed notification token previously let an attacker inject
|
|
642
|
+
// arbitrary id_token claims the RP would trust on the documented
|
|
643
|
+
// "no follow-up call" push path.
|
|
644
|
+
var pushClaims = await _verifyIdTokenIfPresent(body.id_token,
|
|
645
|
+
"auth-ciba:" + body.auth_req_id, "parseNotification");
|
|
608
646
|
_emitAudit("notification_received", "success", {
|
|
609
647
|
authReqIdHash: sha3Hash("auth-ciba:" + body.auth_req_id),
|
|
610
648
|
mode: deliveryMode,
|
|
@@ -614,6 +652,7 @@ function create(opts) {
|
|
|
614
652
|
authReqId: body.auth_req_id,
|
|
615
653
|
accessToken: body.access_token || null,
|
|
616
654
|
idToken: body.id_token || null,
|
|
655
|
+
claims: pushClaims,
|
|
617
656
|
refreshToken: body.refresh_token || null,
|
|
618
657
|
tokenType: body.token_type || null,
|
|
619
658
|
scope: body.scope || null,
|
|
@@ -92,7 +92,7 @@ function thumbprint(key) {
|
|
|
92
92
|
if (!key || typeof key !== "object" || typeof key.kty !== "string" || key.kty.length === 0) {
|
|
93
93
|
throw new AuthError("auth-dpop/bad-jwk", "jwk must be an object with a kty");
|
|
94
94
|
}
|
|
95
|
-
if (!DPOP_KTY
|
|
95
|
+
if (!Object.prototype.hasOwnProperty.call(DPOP_KTY, key.kty)) {
|
|
96
96
|
throw new AuthError("auth-dpop/refused-kty", "jwk.kty='" + key.kty + "' is not allowed (DPoP requires asymmetric kty)");
|
|
97
97
|
}
|
|
98
98
|
// The RFC 7638 thumbprint itself is computed by b.jwk.
|
|
@@ -348,6 +348,11 @@ async function verify(proof, opts) {
|
|
|
348
348
|
"DPoP proof declares 'crit' header — refused");
|
|
349
349
|
}
|
|
350
350
|
|
|
351
|
+
// Bind the declared alg to the embedded JWK's kty/curve before handing
|
|
352
|
+
// the self-asserted key to node:crypto — every other JWS verifier in the
|
|
353
|
+
// framework enforces this (alg-confusion family, CVE-2026-22817 class).
|
|
354
|
+
jwtExternal._assertAlgKtyMatch(header.alg, header.jwk);
|
|
355
|
+
|
|
351
356
|
// Verify signature against the embedded jwk
|
|
352
357
|
var key = _jwkToKeyObject(header.jwk);
|
|
353
358
|
var params = _signParamsForAlg(header.alg);
|
|
@@ -43,6 +43,7 @@ var safeJson = require("../safe-json");
|
|
|
43
43
|
var safeBuffer = require("../safe-buffer");
|
|
44
44
|
var lazyRequire = require("../lazy-require");
|
|
45
45
|
var validateOpts = require("../validate-opts");
|
|
46
|
+
var x509Chain = require("../x509-chain");
|
|
46
47
|
var jwtExternal = require("./jwt-external");
|
|
47
48
|
var _wa = require("../vendor/simplewebauthn-server.cjs");
|
|
48
49
|
var { FidoMds3Error } = require("../framework-error");
|
|
@@ -239,7 +240,10 @@ function _validateChain(x5c, rootPems) {
|
|
|
239
240
|
var root;
|
|
240
241
|
try { root = new nodeCrypto.X509Certificate(rootPems[r]); }
|
|
241
242
|
catch (_e) { continue; }
|
|
242
|
-
|
|
243
|
+
// issuerValidlyIssued enforces basicConstraints cA:TRUE on the root in
|
|
244
|
+
// addition to the issuance + signature linkage — a non-CA cert cannot
|
|
245
|
+
// anchor the x5c chain (basicConstraints bypass, CVE-2002-0862 class).
|
|
246
|
+
if (x509Chain.issuerValidlyIssued(root, tail)) {
|
|
243
247
|
anchored = true;
|
|
244
248
|
break;
|
|
245
249
|
}
|
|
@@ -117,7 +117,7 @@ function _toKeyObject(pemOrKey, kind) {
|
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
function _resolveAlgorithm(alg) {
|
|
120
|
-
if (typeof alg !== "string" || !ALGORITHM_TO_NODE
|
|
120
|
+
if (typeof alg !== "string" || !Object.prototype.hasOwnProperty.call(ALGORITHM_TO_NODE, alg)) {
|
|
121
121
|
throw new AuthError("auth-jwt/unsupported-algorithm",
|
|
122
122
|
"algorithm must be one of " + SUPPORTED_ALGORITHMS.join(", ") + " (got: " + alg + ")");
|
|
123
123
|
}
|
|
@@ -222,7 +222,7 @@ async function verify(token, opts) {
|
|
|
222
222
|
// Validate the allowlist itself — typoed entries should surface here,
|
|
223
223
|
// not as silent "every token rejected."
|
|
224
224
|
for (var i = 0; i < allowed.length; i++) {
|
|
225
|
-
if (!ALGORITHM_TO_NODE
|
|
225
|
+
if (!Object.prototype.hasOwnProperty.call(ALGORITHM_TO_NODE, allowed[i])) {
|
|
226
226
|
throw new AuthError("auth-jwt/unsupported-algorithm",
|
|
227
227
|
"opts.algorithms[" + i + "] = '" + allowed[i] + "' is not in the supported list (" +
|
|
228
228
|
SUPPORTED_ALGORITHMS.join(", ") + ")");
|
|
@@ -80,6 +80,7 @@
|
|
|
80
80
|
*/
|
|
81
81
|
|
|
82
82
|
var C = require("../constants");
|
|
83
|
+
var numericBounds = require("../numeric-bounds");
|
|
83
84
|
var lazyRequire = require("../lazy-require");
|
|
84
85
|
var requestHelpers = require("../request-helpers");
|
|
85
86
|
var validateOpts = require("../validate-opts");
|
|
@@ -119,7 +120,7 @@ function _requireString(name, val) {
|
|
|
119
120
|
}
|
|
120
121
|
|
|
121
122
|
function _requirePositiveInt(name, val) {
|
|
122
|
-
if (
|
|
123
|
+
if (!numericBounds.isPositiveFiniteInt(val)) {
|
|
123
124
|
throw _err("BAD_OPT", name + ": expected positive integer, got " +
|
|
124
125
|
typeof val + " " + JSON.stringify(val));
|
|
125
126
|
}
|
|
@@ -261,7 +262,22 @@ function create(opts) {
|
|
|
261
262
|
|
|
262
263
|
// ---- Public surface ----
|
|
263
264
|
|
|
264
|
-
|
|
265
|
+
// Per-key serialization of the failure counter (read→increment→write on an
|
|
266
|
+
// async store): concurrent recordFailure calls for the same key would lose
|
|
267
|
+
// updates, letting parallel failures stay under the lockout threshold. A
|
|
268
|
+
// per-key promise chain applies them sequentially in-process.
|
|
269
|
+
var _recordChains = new Map();
|
|
270
|
+
function recordFailure(key, callOpts) {
|
|
271
|
+
var prev = _recordChains.get(key) || Promise.resolve();
|
|
272
|
+
var run = prev.then(function () { return _doRecordFailure(key, callOpts); },
|
|
273
|
+
function () { return _doRecordFailure(key, callOpts); });
|
|
274
|
+
var tail = run.then(function () {}, function () {});
|
|
275
|
+
_recordChains.set(key, tail);
|
|
276
|
+
tail.then(function () { if (_recordChains.get(key) === tail) _recordChains.delete(key); });
|
|
277
|
+
return run;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async function _doRecordFailure(key, callOpts) {
|
|
265
281
|
_requireKey(key);
|
|
266
282
|
callOpts = callOpts || {};
|
|
267
283
|
var now = clock();
|
|
@@ -385,7 +385,7 @@ function _largeBlobExt(args) {
|
|
|
385
385
|
var SUPPORT = { preferred: 1, required: 1 };
|
|
386
386
|
var modes = 0;
|
|
387
387
|
if (args.support !== undefined) {
|
|
388
|
-
if (!SUPPORT
|
|
388
|
+
if (!Object.prototype.hasOwnProperty.call(SUPPORT, args.support)) {
|
|
389
389
|
throw new AuthError("auth-passkey/bad-largeblob-support",
|
|
390
390
|
"extensions.largeBlob support must be 'preferred' or 'required'");
|
|
391
391
|
}
|
|
@@ -282,7 +282,7 @@ function policy(opts) {
|
|
|
282
282
|
// Apply named profile FIRST, then operator opts on top so the
|
|
283
283
|
// operator can override profile defaults per-field.
|
|
284
284
|
if (typeof opts.profile === "string" && opts.profile.length > 0) {
|
|
285
|
-
if (!POLICY_PROFILES
|
|
285
|
+
if (!Object.prototype.hasOwnProperty.call(POLICY_PROFILES, opts.profile)) {
|
|
286
286
|
throw new AuthError("auth-password/bad-policy",
|
|
287
287
|
"policy.profile must be one of " + Object.keys(POLICY_PROFILES).join("/") +
|
|
288
288
|
", got " + JSON.stringify(opts.profile));
|
|
@@ -170,7 +170,7 @@ function _verifyXmldsig(envelope, signatureNode, certPem) {
|
|
|
170
170
|
}
|
|
171
171
|
var sigMethodNode = _findChild(signedInfo, "SignatureMethod");
|
|
172
172
|
var sigAlgo = sigMethodNode && _attr(sigMethodNode, "Algorithm");
|
|
173
|
-
if (!SUPPORTED_SIG
|
|
173
|
+
if (!Object.prototype.hasOwnProperty.call(SUPPORTED_SIG, sigAlgo)) {
|
|
174
174
|
throw new AuthError("auth-saml/unsupported-sig-alg",
|
|
175
175
|
"Unsupported SignatureMethod: " + sigAlgo);
|
|
176
176
|
}
|
|
@@ -184,7 +184,7 @@ function _verifyXmldsig(envelope, signatureNode, certPem) {
|
|
|
184
184
|
var refId = refUri.substring(1);
|
|
185
185
|
var digestMethodNode = _findChild(refNode, "DigestMethod");
|
|
186
186
|
var digestAlgo = digestMethodNode && _attr(digestMethodNode, "Algorithm");
|
|
187
|
-
if (!SUPPORTED_DIGEST
|
|
187
|
+
if (!Object.prototype.hasOwnProperty.call(SUPPORTED_DIGEST, digestAlgo)) {
|
|
188
188
|
throw new AuthError("auth-saml/unsupported-digest",
|
|
189
189
|
"Unsupported DigestMethod: " + digestAlgo);
|
|
190
190
|
}
|
|
@@ -745,26 +745,46 @@ function create(opts) {
|
|
|
745
745
|
var cNotOnOrAfter = _attr(conditions, "NotOnOrAfter");
|
|
746
746
|
if (cNotBefore) {
|
|
747
747
|
var cnb = Date.parse(cNotBefore) / 1000; // ms→s
|
|
748
|
-
|
|
748
|
+
// Fail CLOSED on a present-but-unparseable bound (mirrors the Bearer
|
|
749
|
+
// SCD path at line ~708) instead of skipping the window via isFinite().
|
|
750
|
+
if (!isFinite(cnb)) {
|
|
751
|
+
throw new AuthError("auth-saml/conditions-bad-timestamp",
|
|
752
|
+
"Conditions NotBefore is present but unparseable");
|
|
753
|
+
}
|
|
754
|
+
if (cnb > nowSec + clockSkewSec) {
|
|
749
755
|
throw new AuthError("auth-saml/conditions-not-yet-valid",
|
|
750
756
|
"Conditions NotBefore is in the future");
|
|
751
757
|
}
|
|
752
758
|
}
|
|
753
759
|
if (cNotOnOrAfter) {
|
|
754
760
|
var cnoa = Date.parse(cNotOnOrAfter) / 1000; // ms→s
|
|
755
|
-
if (isFinite(cnoa)
|
|
761
|
+
if (!isFinite(cnoa)) {
|
|
762
|
+
throw new AuthError("auth-saml/conditions-bad-timestamp",
|
|
763
|
+
"Conditions NotOnOrAfter is present but unparseable");
|
|
764
|
+
}
|
|
765
|
+
if (cnoa < nowSec - clockSkewSec) {
|
|
756
766
|
throw new AuthError("auth-saml/conditions-expired",
|
|
757
767
|
"Conditions NotOnOrAfter has passed");
|
|
758
768
|
}
|
|
759
769
|
}
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
770
|
+
}
|
|
771
|
+
// Audience binding — a signed assertion is bound to THIS SP via
|
|
772
|
+
// AudienceRestriction. A missing Conditions or AudienceRestriction means
|
|
773
|
+
// it is not bound here (audience-confusion: an assertion minted for another
|
|
774
|
+
// SP). Fail closed when an audience is configured; opt out only via
|
|
775
|
+
// vopts.requireAudienceRestriction === false.
|
|
776
|
+
if (audience && vopts.requireAudienceRestriction !== false) {
|
|
777
|
+
var ar = conditions && _findChild(conditions, "AudienceRestriction", SAML_NS.assertion);
|
|
778
|
+
if (!ar) {
|
|
779
|
+
throw new AuthError("auth-saml/no-audience-restriction",
|
|
780
|
+
"verifyResponse: assertion has no AudienceRestriction binding it to \"" +
|
|
781
|
+
audience + "\" (audience-confusion defense; set requireAudienceRestriction:false to opt out)");
|
|
782
|
+
}
|
|
783
|
+
var audiences = _findAllChildren(ar, "Audience", SAML_NS.assertion).map(_textContent);
|
|
784
|
+
if (audiences.indexOf(audience) === -1) {
|
|
785
|
+
throw new AuthError("auth-saml/wrong-audience",
|
|
786
|
+
"Audience \"" + audience + "\" not in assertion's AudienceRestriction (got " +
|
|
787
|
+
JSON.stringify(audiences) + ")");
|
|
768
788
|
}
|
|
769
789
|
}
|
|
770
790
|
|
|
@@ -1897,7 +1917,7 @@ function _verifyEmbeddedXmlDsig(xml, idpVerifyKey, idpVerifyAlg, expectedRootLoc
|
|
|
1897
1917
|
// Allow either sha3-512 (framework default) or the SHA-2 family.
|
|
1898
1918
|
var digestAlgName;
|
|
1899
1919
|
if (digestUri === "http://www.w3.org/2007/05/xmldsig-more#sha3-512") digestAlgName = "sha3-512";
|
|
1900
|
-
else if (SUPPORTED_DIGEST
|
|
1920
|
+
else if (Object.prototype.hasOwnProperty.call(SUPPORTED_DIGEST, digestUri)) digestAlgName = SUPPORTED_DIGEST[digestUri];
|
|
1901
1921
|
else {
|
|
1902
1922
|
throw new AuthError("auth-saml/unsupported-digest",
|
|
1903
1923
|
"_verifyEmbeddedXmlDsig: DigestMethod " + digestUri + " not supported");
|
|
@@ -212,7 +212,7 @@ function issue(opts) {
|
|
|
212
212
|
"issue: algorithm must be one of " + SUPPORTED_ALGS.join(", "));
|
|
213
213
|
}
|
|
214
214
|
var hashAlg = opts.hashAlg || DEFAULT_HASH_ALG;
|
|
215
|
-
if (!SUPPORTED_HASH_ALGS
|
|
215
|
+
if (!Object.prototype.hasOwnProperty.call(SUPPORTED_HASH_ALGS, hashAlg)) {
|
|
216
216
|
throw new AuthError("auth-sd-jwt-vc/bad-hash",
|
|
217
217
|
"issue: hashAlg must be one of " + Object.keys(SUPPORTED_HASH_ALGS).join(", "));
|
|
218
218
|
}
|
|
@@ -413,8 +413,8 @@ async function verify(presentation, opts) {
|
|
|
413
413
|
validateOpts.requireObject(opts, "auth.sdJwtVc.verify", AuthError);
|
|
414
414
|
validateOpts(opts, [
|
|
415
415
|
"issuerKeyResolver", "audience", "nonce",
|
|
416
|
-
"now", "expectedVct", "maxClockSkewSec",
|
|
417
|
-
"requireKeyBinding",
|
|
416
|
+
"now", "expectedVct", "expectedIssuer", "maxClockSkewSec",
|
|
417
|
+
"requireKeyBinding", "requireExp",
|
|
418
418
|
"keyAttestationVerifier", "requireKeyAttestation",
|
|
419
419
|
], "auth.sdJwtVc.verify");
|
|
420
420
|
|
|
@@ -528,6 +528,15 @@ async function verify(presentation, opts) {
|
|
|
528
528
|
throw new AuthError("auth-sd-jwt-vc/iat-future",
|
|
529
529
|
"verify: iat is in the future (clock skew?)");
|
|
530
530
|
}
|
|
531
|
+
// SD-JWT-VC makes `exp` OPTIONAL, so the default only checks it when present.
|
|
532
|
+
// Operators with a time-bounded trust expectation (esp. for FOREIGN issuers
|
|
533
|
+
// resolved via issuerKeyResolver) pass requireExp:true to fail closed on a
|
|
534
|
+
// missing / non-numeric exp — mirroring jwt-external's unconditional exp
|
|
535
|
+
// requirement (a credential that never expires is accepted otherwise).
|
|
536
|
+
if (opts.requireExp === true && typeof jwtParsed.payload.exp !== "number") {
|
|
537
|
+
throw new AuthError("auth-sd-jwt-vc/missing-exp",
|
|
538
|
+
"verify: requireExp is set but the credential has no numeric exp claim");
|
|
539
|
+
}
|
|
531
540
|
if (typeof jwtParsed.payload.exp === "number" && jwtParsed.payload.exp < nowSec - skew) {
|
|
532
541
|
throw new AuthError("auth-sd-jwt-vc/expired",
|
|
533
542
|
"verify: token is expired");
|
|
@@ -556,7 +565,7 @@ async function verify(presentation, opts) {
|
|
|
556
565
|
// to its own DEFAULT_HASH_ALG (`sha3-512`) which broke verification
|
|
557
566
|
// against spec-conformant issuers when `_sd_alg` was omitted.
|
|
558
567
|
var hashAlg = jwtParsed.payload._sd_alg || "sha-256";
|
|
559
|
-
if (!SUPPORTED_HASH_ALGS
|
|
568
|
+
if (!Object.prototype.hasOwnProperty.call(SUPPORTED_HASH_ALGS, hashAlg)) {
|
|
560
569
|
throw new AuthError("auth-sd-jwt-vc/bad-hash",
|
|
561
570
|
"verify: _sd_alg \"" + hashAlg + "\" not supported");
|
|
562
571
|
}
|
|
@@ -638,6 +647,17 @@ async function verify(presentation, opts) {
|
|
|
638
647
|
jwtExternal._assertAlgKtyMatch(kbAlg, holderKey);
|
|
639
648
|
var holderKeyObj = bCrypto.importPublicJwk(holderKey);
|
|
640
649
|
var kbParsed = _verifyJwt(maybeKbJwt, holderKeyObj, kbAlg);
|
|
650
|
+
// A KB-JWT proves holder possession of THIS presentation's bytes (sd_hash),
|
|
651
|
+
// but binds it to a verifier + a fresh challenge only via aud + nonce. If
|
|
652
|
+
// the caller processes a KB-JWT without supplying both, the aud/nonce
|
|
653
|
+
// compares below would silently skip — making the presentation replayable
|
|
654
|
+
// and acceptable at any verifier. Fail CLOSED: require both (oid4vp always
|
|
655
|
+
// supplies them). RFC: SD-JWT-VC §KB-JWT / OIDC4VP nonce binding.
|
|
656
|
+
if (typeof opts.audience !== "string" || opts.audience.length === 0 ||
|
|
657
|
+
typeof opts.nonce !== "string" || opts.nonce.length === 0) {
|
|
658
|
+
throw new AuthError("auth-sd-jwt-vc/missing-replay-binding",
|
|
659
|
+
"verify: a KB-JWT requires opts.audience + opts.nonce to bind the presentation to this verifier and a fresh challenge (replay / audience-redirection defense)");
|
|
660
|
+
}
|
|
641
661
|
// Constant-time compares: the nonce is a verifier-issued replay-defense
|
|
642
662
|
// value, so a short-circuiting !== leaks a matching-prefix timing oracle.
|
|
643
663
|
// Matches the sd_hash check below (the framework's hash/token discipline).
|
|
@@ -78,7 +78,7 @@ var _fromB64url = bCrypto.makeBase64UrlDecoder({
|
|
|
78
78
|
});
|
|
79
79
|
|
|
80
80
|
function _validateBits(bits) {
|
|
81
|
-
if (!SUPPORTED_BIT_SIZES
|
|
81
|
+
if (!Object.prototype.hasOwnProperty.call(SUPPORTED_BIT_SIZES, bits)) {
|
|
82
82
|
throw new StatusListError("status-list/bad-bits",
|
|
83
83
|
"statusList: bits must be 1, 2, 4, or 8 (draft §6.1.1) — got " + bits);
|
|
84
84
|
}
|
|
@@ -249,7 +249,19 @@ async function fromJwt(token, opts) {
|
|
|
249
249
|
list: {
|
|
250
250
|
size: size,
|
|
251
251
|
bits: bits,
|
|
252
|
-
|
|
252
|
+
// Bounds-check the relying-party status read, mirroring create().get. An
|
|
253
|
+
// out-of-range index must FAIL CLOSED (throw): _getAt over-reads the buffer
|
|
254
|
+
// and returns 0 for an out-of-bounds index, and status 0 = VALID — so a
|
|
255
|
+
// credential whose status_list index points past the list would otherwise
|
|
256
|
+
// read as "not revoked", a revocation bypass.
|
|
257
|
+
get: function (idx) {
|
|
258
|
+
if (typeof idx !== "number" || idx < 0 || idx >= size || (idx >> 0) !== idx) {
|
|
259
|
+
throw new StatusListError("status-list/bad-index",
|
|
260
|
+
"statusList.fromJwt get: idx out of range — got " + idx + ", size=" + size +
|
|
261
|
+
" (an out-of-range status index fails closed, never reads as status 0/valid)");
|
|
262
|
+
}
|
|
263
|
+
return _getAt(inflated, bits, idx);
|
|
264
|
+
},
|
|
253
265
|
snapshot: function () { return { size: size, bits: bits, bytes: Buffer.from(inflated) }; },
|
|
254
266
|
},
|
|
255
267
|
claims: claims,
|
|
@@ -386,7 +386,15 @@ function parseChallenge(headerValue) {
|
|
|
386
386
|
if (key === "error") out.error = val;
|
|
387
387
|
else if (key === "scope") out.scope = val;
|
|
388
388
|
else if (key === "acr_values") out.acrValues = val.split(/\s+/);
|
|
389
|
-
else if (key === "max_age")
|
|
389
|
+
else if (key === "max_age") {
|
|
390
|
+
// Defensive: a malformed max_age (non-numeric / negative) from the
|
|
391
|
+
// server's challenge must not land as NaN — a downstream `age > maxAge`
|
|
392
|
+
// comparison against NaN is always false and would silently mis-handle
|
|
393
|
+
// the freshness requirement. Omit it unless it parses to a non-negative
|
|
394
|
+
// integer, so callers fall back to their own default.
|
|
395
|
+
var ma = parseInt(val, 10);
|
|
396
|
+
if (isFinite(ma) && ma >= 0) out.maxAge = ma;
|
|
397
|
+
}
|
|
390
398
|
});
|
|
391
399
|
return out;
|
|
392
400
|
}
|
|
@@ -40,6 +40,7 @@ var C = require("./constants");
|
|
|
40
40
|
var lazyRequire = require("./lazy-require");
|
|
41
41
|
var requestHelpers = require("./request-helpers");
|
|
42
42
|
var validateOpts = require("./validate-opts");
|
|
43
|
+
var numericBounds = require("./numeric-bounds");
|
|
43
44
|
var { AuthBotChallengeError } = require("./framework-error");
|
|
44
45
|
|
|
45
46
|
var observability = lazyRequire(function () { return require("./observability"); });
|
|
@@ -67,7 +68,7 @@ function _requireFunction(name, val) {
|
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
function _requirePositiveInt(name, val) {
|
|
70
|
-
if (
|
|
71
|
+
if (!numericBounds.isPositiveFiniteInt(val)) {
|
|
71
72
|
throw new AuthBotChallengeError("auth-bot-challenge/bad-opt",
|
|
72
73
|
name + ": expected positive integer, got " + JSON.stringify(val));
|
|
73
74
|
}
|
|
@@ -272,7 +273,25 @@ function create(opts) {
|
|
|
272
273
|
|
|
273
274
|
// ---- Internal staircase advance ----
|
|
274
275
|
|
|
275
|
-
|
|
276
|
+
// Per-key serialization: the failure staircase is a read→increment→write on
|
|
277
|
+
// an async store, so concurrent recordFailure calls for the SAME key would
|
|
278
|
+
// both read N and both write N+1 (lost update), letting an attacker fire
|
|
279
|
+
// parallel failures to stay under the challenge / lockout thresholds. A
|
|
280
|
+
// per-key promise chain applies advances for a key sequentially. (Cross-node
|
|
281
|
+
// atomicity additionally needs an atomic store; this fixes the in-process
|
|
282
|
+
// race the gate actually depends on.)
|
|
283
|
+
var _advanceChains = new Map();
|
|
284
|
+
function _advanceFailure(key, req) {
|
|
285
|
+
var prev = _advanceChains.get(key) || Promise.resolve();
|
|
286
|
+
var run = prev.then(function () { return _doAdvanceFailure(key, req); },
|
|
287
|
+
function () { return _doAdvanceFailure(key, req); });
|
|
288
|
+
var tail = run.then(function () {}, function () {});
|
|
289
|
+
_advanceChains.set(key, tail);
|
|
290
|
+
tail.then(function () { if (_advanceChains.get(key) === tail) _advanceChains.delete(key); });
|
|
291
|
+
return run;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async function _doAdvanceFailure(key, req) {
|
|
276
295
|
var now = clock();
|
|
277
296
|
var state = await _readState(key) || {
|
|
278
297
|
stage: STATE_NEW, failures: 0, challengedAt: null, passedAt: null,
|
|
@@ -160,14 +160,18 @@ async function create(opts) {
|
|
|
160
160
|
},
|
|
161
161
|
});
|
|
162
162
|
var checksum = bCrypto.checksum(plain);
|
|
163
|
-
|
|
163
|
+
// Bind the ciphertext to this blob's canonical relativePath as AEAD
|
|
164
|
+
// associated data. A blob copied to a different manifest entry (the
|
|
165
|
+
// restore-corruption / blob-remap attack) then fails the Poly1305 tag on
|
|
166
|
+
// restore — tamper-evident even on an unsigned bundle (manifest.aadBound).
|
|
167
|
+
var encResult = await bCrypto.encryptWithFreshSalt(plain, passphrase, entry.relativePath);
|
|
164
168
|
var encPath = _encryptedPathFor(entry.relativePath);
|
|
165
169
|
var destFull = nodePath.join(outDir, encPath);
|
|
166
170
|
atomicFile.ensureDir(nodePath.dirname(destFull));
|
|
167
171
|
atomicFile.writeSync(destFull, encResult.encrypted, { fileMode: 0o600 });
|
|
168
172
|
|
|
169
173
|
var kind = entry.kind || "raw";
|
|
170
|
-
if (!backupManifest.VALID_KINDS
|
|
174
|
+
if (!Object.prototype.hasOwnProperty.call(backupManifest.VALID_KINDS, kind)) {
|
|
171
175
|
throw new BackupBundleError("backup-bundle/bad-kind",
|
|
172
176
|
"create: files[" + i + "].kind must be one of raw, vault-sealed, plaintext (got '" + kind + "')");
|
|
173
177
|
}
|
|
@@ -205,6 +209,7 @@ async function create(opts) {
|
|
|
205
209
|
vaultKeyEnc: wrappedVk.encrypted.toString("base64"),
|
|
206
210
|
files: fileEntries,
|
|
207
211
|
metadata: opts.metadata || undefined,
|
|
212
|
+
aadBound: true, // every blob above was sealed with its relativePath as AEAD AAD
|
|
208
213
|
});
|
|
209
214
|
// Sign the manifest with the audit-sign keypair so a tampered
|
|
210
215
|
// manifest fails verification on restore. The signer is best-
|
|
@@ -117,7 +117,21 @@ async function deriveKey(passphrase, saltHex, opts) {
|
|
|
117
117
|
return hash;
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
|
|
120
|
+
// Normalize optional associated-authenticated-data (AAD) into the
|
|
121
|
+
// Uint8Array the AEAD expects, or undefined when none. AAD is NOT encrypted
|
|
122
|
+
// but IS authenticated: ciphertext sealed under one AAD fails the Poly1305
|
|
123
|
+
// tag when decrypted under a different AAD. Backup file blobs pass their
|
|
124
|
+
// canonical relativePath so a blob remapped to a different manifest entry is
|
|
125
|
+
// cryptographically rejected (the blob-remap / restore-corruption defense).
|
|
126
|
+
function _aadBytes(aad) {
|
|
127
|
+
if (aad === undefined || aad === null) return undefined;
|
|
128
|
+
if (Buffer.isBuffer(aad)) return new Uint8Array(aad);
|
|
129
|
+
if (typeof aad === "string") return new Uint8Array(Buffer.from(aad, "utf8"));
|
|
130
|
+
throw new BackupCryptoError("backup-crypto/bad-aad",
|
|
131
|
+
"associated data must be a Buffer or string");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function encryptWithPassphrase(plaintext, passphrase, saltHex, aad) {
|
|
121
135
|
if (!Buffer.isBuffer(plaintext) && typeof plaintext !== "string") {
|
|
122
136
|
throw new BackupCryptoError("backup-crypto/bad-plaintext",
|
|
123
137
|
"encryptWithPassphrase: plaintext must be a Buffer or string");
|
|
@@ -125,11 +139,11 @@ async function encryptWithPassphrase(plaintext, passphrase, saltHex) {
|
|
|
125
139
|
var plainBuf = Buffer.isBuffer(plaintext) ? plaintext : Buffer.from(plaintext, "utf8");
|
|
126
140
|
var key = await deriveKey(passphrase, saltHex);
|
|
127
141
|
var nonce = nodeCrypto.randomBytes(NONCE_BYTES);
|
|
128
|
-
var ct = xchacha20poly1305(new Uint8Array(key), nonce).encrypt(new Uint8Array(plainBuf));
|
|
142
|
+
var ct = xchacha20poly1305(new Uint8Array(key), nonce, _aadBytes(aad)).encrypt(new Uint8Array(plainBuf));
|
|
129
143
|
return Buffer.concat([nonce, Buffer.from(ct)]);
|
|
130
144
|
}
|
|
131
145
|
|
|
132
|
-
async function decryptWithPassphrase(encrypted, passphrase, saltHex) {
|
|
146
|
+
async function decryptWithPassphrase(encrypted, passphrase, saltHex, aad) {
|
|
133
147
|
if (!Buffer.isBuffer(encrypted)) {
|
|
134
148
|
throw new BackupCryptoError("backup-crypto/bad-input",
|
|
135
149
|
"decryptWithPassphrase: encrypted must be a Buffer");
|
|
@@ -143,11 +157,11 @@ async function decryptWithPassphrase(encrypted, passphrase, saltHex) {
|
|
|
143
157
|
var ct = encrypted.subarray(NONCE_BYTES);
|
|
144
158
|
var plain;
|
|
145
159
|
try {
|
|
146
|
-
plain = xchacha20poly1305(new Uint8Array(key), new Uint8Array(nonce))
|
|
160
|
+
plain = xchacha20poly1305(new Uint8Array(key), new Uint8Array(nonce), _aadBytes(aad))
|
|
147
161
|
.decrypt(new Uint8Array(ct));
|
|
148
162
|
} catch (e) {
|
|
149
163
|
throw new BackupCryptoError("backup-crypto/decrypt-failed",
|
|
150
|
-
"XChaCha20-Poly1305 decryption failed (wrong passphrase or
|
|
164
|
+
"XChaCha20-Poly1305 decryption failed (wrong passphrase, tampered ciphertext, or blob remapped to a different path): " +
|
|
151
165
|
((e && e.message) || String(e)));
|
|
152
166
|
}
|
|
153
167
|
return Buffer.from(plain);
|
|
@@ -155,11 +169,12 @@ async function decryptWithPassphrase(encrypted, passphrase, saltHex) {
|
|
|
155
169
|
|
|
156
170
|
// Convenience for the common "encrypt this with a fresh salt" pattern.
|
|
157
171
|
// Returns the salt as hex so callers can store it alongside the
|
|
158
|
-
// ciphertext in the bundle manifest.
|
|
159
|
-
|
|
172
|
+
// ciphertext in the bundle manifest. `aad` (optional) is bound as AEAD
|
|
173
|
+
// associated data (see _aadBytes).
|
|
174
|
+
async function encryptWithFreshSalt(plaintext, passphrase, aad) {
|
|
160
175
|
var salt = nodeCrypto.randomBytes(SALT_BYTES);
|
|
161
176
|
var saltHex = salt.toString("hex");
|
|
162
|
-
var encrypted = await encryptWithPassphrase(plaintext, passphrase, saltHex);
|
|
177
|
+
var encrypted = await encryptWithPassphrase(plaintext, passphrase, saltHex, aad);
|
|
163
178
|
return { encrypted: encrypted, salt: saltHex };
|
|
164
179
|
}
|
|
165
180
|
|