@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
|
@@ -181,6 +181,13 @@ async function run() {
|
|
|
181
181
|
check("parseChallenge: maxAge", rt.maxAge === 300);
|
|
182
182
|
check("parseChallenge: non-Bearer → null", b.auth.stepUp.parseChallenge("Basic realm=x") === null);
|
|
183
183
|
|
|
184
|
+
// A malformed max_age from the server must not land as NaN (a downstream
|
|
185
|
+
// `age > maxAge` against NaN is always false) — it's omitted so the caller
|
|
186
|
+
// falls back to its own default.
|
|
187
|
+
var badMa = b.auth.stepUp.parseChallenge('Bearer error="insufficient_user_authentication", max_age="not-a-number"');
|
|
188
|
+
check("parseChallenge: non-numeric max_age stays null (not NaN)",
|
|
189
|
+
badMa && badMa.maxAge === null);
|
|
190
|
+
|
|
184
191
|
// ---- parseAuthorizationDetails (RFC 9396) ----
|
|
185
192
|
var rar = b.auth.stepUp.parseAuthorizationDetails(JSON.stringify([
|
|
186
193
|
{ type: "payment_initiation", actions: ["initiate"], amount: { currency: "USD", value: 100 } },
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// b.template.escapeHtml — the {{ expr }} interpolation escaper (XSS boundary).
|
|
3
|
+
// Now delegates to the centralized markup-escape so the five-character HTML set
|
|
4
|
+
// is defined in ONE place; a divergence between this and the shared escaper is
|
|
5
|
+
// an XSS / XML-injection surface. This pins the contract (all five chars, &
|
|
6
|
+
// first so emitted entities aren't double-escaped, null/undefined → "",
|
|
7
|
+
// non-string coercion) so the delegation can't silently regress.
|
|
8
|
+
|
|
9
|
+
var helpers = require("../helpers");
|
|
10
|
+
var b = helpers.b;
|
|
11
|
+
var check = helpers.check;
|
|
12
|
+
|
|
13
|
+
function run() {
|
|
14
|
+
var esc = b.template.escapeHtml;
|
|
15
|
+
|
|
16
|
+
check("escapes <", esc("<script>") === "<script>");
|
|
17
|
+
check("escapes & first (no double-escape of emitted entities)", esc("a&b") === "a&b");
|
|
18
|
+
check("escapes double-quote", esc('say "hi"') === "say "hi"");
|
|
19
|
+
check("escapes apostrophe to numeric form (single-quoted attr safety)",
|
|
20
|
+
esc("it's") === "it's");
|
|
21
|
+
check("all five together", esc("<a href='x' title=\"y\">&</a>") ===
|
|
22
|
+
"<a href='x' title="y">&</a>");
|
|
23
|
+
|
|
24
|
+
// & is escaped first so a literal "<" in the input becomes "&lt;",
|
|
25
|
+
// not "<" (which would let an attacker inject a pre-formed entity).
|
|
26
|
+
check("pre-formed entity in input is neutralized (& escaped first)",
|
|
27
|
+
esc("<") === "&lt;");
|
|
28
|
+
|
|
29
|
+
check("null → empty string", esc(null) === "");
|
|
30
|
+
check("undefined → empty string", esc(undefined) === "");
|
|
31
|
+
check("number coerced + returned", esc(42) === "42");
|
|
32
|
+
check("boolean coerced", esc(true) === "true");
|
|
33
|
+
check("plain string unchanged", esc("hello world") === "hello world");
|
|
34
|
+
check("empty string → empty string", esc("") === "");
|
|
35
|
+
|
|
36
|
+
console.log("OK — template.escapeHtml (" + helpers.getChecks() + " checks)");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = { run: run };
|
|
40
|
+
if (require.main === module) {
|
|
41
|
+
try { run(); process.exit(0); }
|
|
42
|
+
catch (e) { console.error("FAIL: " + (e && e.message)); process.exit(1); }
|
|
43
|
+
}
|
|
@@ -41,6 +41,21 @@ async function run() {
|
|
|
41
41
|
var snapReset = budget.snapshot("tenant-acme");
|
|
42
42
|
check("reset clears counters", snapReset.calls === 0);
|
|
43
43
|
|
|
44
|
+
// ---- budget — TRUE sliding window (no fixed-window boundary doubling) ----
|
|
45
|
+
// maxCalls = floor(qpsCap * window/1s) = 5. Fill 5 near the window's tail,
|
|
46
|
+
// then 1 more just past the nominal reset: a fixed window would admit it
|
|
47
|
+
// (~2x burst), a sliding window refuses (trailing window still covers them).
|
|
48
|
+
var sw = b.tenantQuota.budget({ tenantField: "tenantId", perTenantQpsCap: 5, window: 1000, audit: false });
|
|
49
|
+
for (var i = 0; i < 5; i++) sw.observe("t-burst", { now: 900 });
|
|
50
|
+
var threwBoundary = null;
|
|
51
|
+
try { sw.observe("t-burst", { now: 1001 }); } catch (e) { threwBoundary = e; }
|
|
52
|
+
check("sliding window refuses the boundary-straddling burst (not a fixed window)",
|
|
53
|
+
threwBoundary && threwBoundary.code === "tenant-quota/budget-exceeded");
|
|
54
|
+
// After a full window has elapsed, the old calls scroll out → admitted again.
|
|
55
|
+
var allowedAfterGap = true;
|
|
56
|
+
try { sw.observe("t-burst", { now: 2600 }); } catch (_e) { allowedAfterGap = false; }
|
|
57
|
+
check("sliding window admits again once the prior calls age out", allowedAfterGap === true);
|
|
58
|
+
|
|
44
59
|
// ---- instrumentQuery — tenant-isolation breach ----
|
|
45
60
|
var goodCheck = b.tenantQuota.instrumentQuery({
|
|
46
61
|
rows: [
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* b.vault.getDefaultStore — the default sealed-storage Store ({ seal, unseal })
|
|
4
|
+
* that b.cert.create and other sealed-disk consumers resolve when no explicit
|
|
5
|
+
* vault is passed. It previously did not exist, so the documented cert default
|
|
6
|
+
* path threw `TypeError: vault(...).getDefaultStore is not a function`.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
var b = require("../..");
|
|
10
|
+
var check = require("../helpers/check").check;
|
|
11
|
+
var fs = require("fs");
|
|
12
|
+
var path = require("path");
|
|
13
|
+
var os = require("os");
|
|
14
|
+
|
|
15
|
+
async function run() {
|
|
16
|
+
var tmp = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-vault-defstore-"));
|
|
17
|
+
try {
|
|
18
|
+
await b.vault.init({ dataDir: tmp, mode: "plaintext" });
|
|
19
|
+
|
|
20
|
+
check("vault.getDefaultStore is a function", typeof b.vault.getDefaultStore === "function");
|
|
21
|
+
var store = b.vault.getDefaultStore();
|
|
22
|
+
check("getDefaultStore() exposes seal + unseal",
|
|
23
|
+
store && typeof store.seal === "function" && typeof store.unseal === "function");
|
|
24
|
+
|
|
25
|
+
// Round-trip a payload through the default store (the load-bearing
|
|
26
|
+
// guarantee; at-rest encryption strength is mode-dependent and covered by
|
|
27
|
+
// the wrapped-mode vault tests — here the vault is plaintext for speed).
|
|
28
|
+
var plain = "default-store-secret-not-real";
|
|
29
|
+
var sealed = store.seal(plain);
|
|
30
|
+
check("default store seal emits a vault-prefixed value",
|
|
31
|
+
typeof sealed === "string" && sealed.indexOf("vault:") === 0);
|
|
32
|
+
check("default store unseals back to the original", store.unseal(sealed) === plain);
|
|
33
|
+
|
|
34
|
+
check("vault.Store exposes the same seal/unseal pair",
|
|
35
|
+
b.vault.Store && typeof b.vault.Store.seal === "function" && typeof b.vault.Store.unseal === "function");
|
|
36
|
+
|
|
37
|
+
console.log("OK — vault.getDefaultStore (" + (require("../helpers").getChecks ? require("../helpers").getChecks() : "?") + " checks)");
|
|
38
|
+
} finally {
|
|
39
|
+
try { fs.rmSync(tmp, { recursive: true, force: true }); } catch (_e) { /* best-effort */ }
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = { run: run };
|
|
44
|
+
|
|
45
|
+
if (require.main === module) {
|
|
46
|
+
run().then(function () { process.exit(0); })
|
|
47
|
+
.catch(function (e) { console.error(e); process.exit(1); });
|
|
48
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Vendor-currency content classifier — the logic that makes Public Suffix
|
|
4
|
+
* List drift visible to the release gate. The PSL (and other bare-URL data
|
|
5
|
+
* vendors) carry no npm semver, so the currency checker compares an
|
|
6
|
+
* embedded `// COMMIT:` sha (primary) or `// VERSION:` timestamp (fallback)
|
|
7
|
+
* between the live upstream and our bundled copy. This drift was previously
|
|
8
|
+
* invisible: the checker queried npm, got a 404, and still printed "OK".
|
|
9
|
+
*
|
|
10
|
+
* Pure unit test (no network) — drives _classifyContentCurrency directly.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
var helpers = require("../helpers");
|
|
14
|
+
var check = helpers.check;
|
|
15
|
+
|
|
16
|
+
var cur = require("../../scripts/check-vendor-currency.js");
|
|
17
|
+
|
|
18
|
+
var PSL = cur.SPECIAL_MAP["publicsuffix-list"];
|
|
19
|
+
|
|
20
|
+
function _doc(commit, version) {
|
|
21
|
+
var head = "// This Source Code Form is subject to the terms of the MPL 2.0.\n";
|
|
22
|
+
if (version) head += "// VERSION: " + version + "\n";
|
|
23
|
+
if (commit) head += "// COMMIT: " + commit + "\n";
|
|
24
|
+
return head + "com\nnet\norg\n";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function testCommitMatchIsCurrent() {
|
|
28
|
+
var v = cur._classifyContentCurrency(_doc("abc1234", "2026-06-13_x"), _doc("abc1234", "2026-06-13_x"), PSL);
|
|
29
|
+
check("matching COMMIT -> not stale", v.stale === false && v.basis === "commit");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function testCommitDifferIsStale() {
|
|
33
|
+
// Upstream moved (new commit) but our bundled copy is pinned to the old
|
|
34
|
+
// sha — this is exactly the PSL-rot case the gate must now catch.
|
|
35
|
+
var v = cur._classifyContentCurrency(_doc("9186eee", "2026-06-13_x"), _doc("ee780bc", "2026-05-07_y"), PSL);
|
|
36
|
+
check("differing COMMIT -> stale", v.stale === true && v.basis === "commit");
|
|
37
|
+
check("stale verdict carries both ids", v.upstreamId === "9186eee" && v.localId === "ee780bc");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function testVersionFallbackWhenNoCommit() {
|
|
41
|
+
// Older list snapshots predate the COMMIT header — fall back to VERSION.
|
|
42
|
+
var up = cur._classifyContentCurrency(_doc(null, "2026-06-13_x"), _doc(null, "2026-05-07_y"), PSL);
|
|
43
|
+
check("no COMMIT, differing VERSION -> stale via version", up.stale === true && up.basis === "version");
|
|
44
|
+
var same = cur._classifyContentCurrency(_doc(null, "2026-06-13_x"), _doc(null, "2026-06-13_x"), PSL);
|
|
45
|
+
check("no COMMIT, matching VERSION -> current via version", same.stale === false && same.basis === "version");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function testNoIdentifierThrows() {
|
|
49
|
+
// Neither side yields a comparable id (e.g. upstream format changed) —
|
|
50
|
+
// must throw so the caller reports registry-error, never a silent pass.
|
|
51
|
+
var threw = false;
|
|
52
|
+
try { cur._classifyContentCurrency(_doc(null, null), _doc(null, null), PSL); }
|
|
53
|
+
catch (_e) { threw = true; }
|
|
54
|
+
check("no comparable identifier -> throws (loud, not silent pass)", threw === true);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function testGateConfigLockedIn() {
|
|
58
|
+
// The whole point: PSL is content-tracked, BIMI is an intentional skip.
|
|
59
|
+
check("publicsuffix-list mapped to http-content", PSL && PSL.type === "http-content");
|
|
60
|
+
check("publicsuffix-list has an upstream URL", typeof PSL.url === "string" && PSL.url.indexOf("publicsuffix.org") !== -1);
|
|
61
|
+
var bimi = cur.SPECIAL_MAP["bimi-trust-anchors"];
|
|
62
|
+
check("bimi-trust-anchors is a documented skip", bimi && bimi.type === "skip" && typeof bimi.reason === "string");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function run() {
|
|
66
|
+
testCommitMatchIsCurrent();
|
|
67
|
+
testCommitDifferIsStale();
|
|
68
|
+
testVersionFallbackWhenNoCommit();
|
|
69
|
+
testNoIdentifierThrows();
|
|
70
|
+
testGateConfigLockedIn();
|
|
71
|
+
console.log("[vendor-currency-classify] OK — " + helpers.getChecks() + " checks passed");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
module.exports = { run: run };
|
|
75
|
+
if (require.main === module) {
|
|
76
|
+
run();
|
|
77
|
+
}
|
|
@@ -256,6 +256,54 @@ async function testNonceStoreErrorPropagates() {
|
|
|
256
256
|
threw && /redis down/.test(threw.message));
|
|
257
257
|
}
|
|
258
258
|
|
|
259
|
+
async function testStripeVerifyReplayAtomic() {
|
|
260
|
+
// #328: b.webhook.verify (Stripe HMAC-SHA-256) speaks the SAME atomic
|
|
261
|
+
// checkAndInsert nonce contract as b.webhook.verifier / b.nonceStore — so the
|
|
262
|
+
// framework's own replay store drops in, and concurrent redeliveries can't
|
|
263
|
+
// race a non-atomic check-then-set.
|
|
264
|
+
var secret = "whsec_test_328";
|
|
265
|
+
var body = '{"id":"evt_328"}';
|
|
266
|
+
var header = b.webhook.sign({ alg: "hmac-sha256-stripe", secret: secret, body: body });
|
|
267
|
+
var seen = new Map();
|
|
268
|
+
var ns = { checkAndInsert: function (nonce, expireAt) { if (seen.has(nonce)) return false; seen.set(nonce, expireAt); return true; } };
|
|
269
|
+
var base = { alg: "hmac-sha256-stripe", secret: secret, body: body, header: header, nonceStore: ns };
|
|
270
|
+
|
|
271
|
+
var first = await b.webhook.verify(base);
|
|
272
|
+
check("Stripe verify: first delivery accepted", first && first.ok === true);
|
|
273
|
+
|
|
274
|
+
var threw = null;
|
|
275
|
+
try { await b.webhook.verify(base); } catch (e) { threw = e; }
|
|
276
|
+
check("Stripe verify: replay → webhook/replay", threw && threw.code === "webhook/replay");
|
|
277
|
+
|
|
278
|
+
// The framework's own b.nonceStore plugs in directly, no adapter.
|
|
279
|
+
var fwStore = b.nonceStore.create({ backend: "memory" });
|
|
280
|
+
var body2 = '{"id":"evt_328b"}';
|
|
281
|
+
var header2 = b.webhook.sign({ alg: "hmac-sha256-stripe", secret: secret, body: body2 });
|
|
282
|
+
var okFw = await b.webhook.verify({ alg: "hmac-sha256-stripe", secret: secret, body: body2, header: header2, nonceStore: fwStore });
|
|
283
|
+
check("Stripe verify: b.nonceStore drops in (no adapter)", okFw && okFw.ok === true);
|
|
284
|
+
|
|
285
|
+
// A legacy { has, set } store (no checkAndInsert) is refused at construction.
|
|
286
|
+
var threw2 = null;
|
|
287
|
+
try {
|
|
288
|
+
await b.webhook.verify({ alg: "hmac-sha256-stripe", secret: secret, body: body, header: header,
|
|
289
|
+
nonceStore: { has: function () {}, set: function () {} } });
|
|
290
|
+
} catch (e) { threw2 = e; }
|
|
291
|
+
check("Stripe verify: non-checkAndInsert store → bad-nonce-store",
|
|
292
|
+
threw2 && threw2.code === "webhook/bad-nonce-store");
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async function testSignerSendRefusesSsrf() {
|
|
296
|
+
// signer.send refuses a private / cloud-metadata destination (SSRF) up front,
|
|
297
|
+
// before the retry loop and any network — webhook-layer gate + the transport's.
|
|
298
|
+
var s = b.webhook.signer({ algo: "hmac-sha3-512", keys: { v1: "secret" } });
|
|
299
|
+
var threw = null;
|
|
300
|
+
try { await s.send({ url: "https://169.254.169.254/hook", body: "{}", kid: "v1" }); }
|
|
301
|
+
catch (e) { threw = e; }
|
|
302
|
+
check("signer.send refuses cloud-metadata IP (SSRF)",
|
|
303
|
+
threw && (threw.code === "SSRF_REFUSED" ||
|
|
304
|
+
/ssrf|metadata|blocked|internal|private/i.test((threw.code || "") + " " + (threw.message || ""))));
|
|
305
|
+
}
|
|
306
|
+
|
|
259
307
|
// ---- Middleware ----
|
|
260
308
|
|
|
261
309
|
async function testMiddlewareSuccess() {
|
|
@@ -704,6 +752,8 @@ async function run() {
|
|
|
704
752
|
await testHeaderCaseInsensitive();
|
|
705
753
|
await testNonceStoreReplay();
|
|
706
754
|
await testNonceStoreErrorPropagates();
|
|
755
|
+
await testStripeVerifyReplayAtomic();
|
|
756
|
+
await testSignerSendRefusesSsrf();
|
|
707
757
|
await testMiddlewareSuccess();
|
|
708
758
|
await testMiddlewareMissingRawBody();
|
|
709
759
|
await testMiddlewareBadSignature();
|
|
@@ -342,6 +342,28 @@ async function run() {
|
|
|
342
342
|
await _sleep(150);
|
|
343
343
|
check("wsClient: custom handshakeGuid accepted at config time", true);
|
|
344
344
|
|
|
345
|
+
// ---- urlFor swap is SSRF re-validated (awaited) before connect ----
|
|
346
|
+
// checkUrl is async; the dial used to call it synchronously and discard the
|
|
347
|
+
// Promise, so a urlFor that pointed at a private / cloud-metadata address was
|
|
348
|
+
// connected anyway (the rejection surfaced only as an unhandled rejection).
|
|
349
|
+
// A swap to the metadata IP must now be refused with an SSRF error, and the
|
|
350
|
+
// dial must NOT reach the metadata host.
|
|
351
|
+
var sawUnhandled = null;
|
|
352
|
+
function _onUnhandled(e) { sawUnhandled = e; }
|
|
353
|
+
process.on("unhandledRejection", _onUnhandled);
|
|
354
|
+
var ssrfErr = null;
|
|
355
|
+
var cSwap = b.wsClient.connect("ws://127.0.0.1:1", {
|
|
356
|
+
urlFor: function () { return "ws://169.254.169.254:1/"; }, // cloud-metadata — hard-deny
|
|
357
|
+
reconnect: false, audit: false, allowInternal: true, // allowInternal must NOT bypass metadata
|
|
358
|
+
});
|
|
359
|
+
cSwap.on("error", function (e) { if (!ssrfErr) ssrfErr = e; });
|
|
360
|
+
await helpers.waitUntil(function () { return ssrfErr !== null; },
|
|
361
|
+
{ timeoutMs: 5000, label: "ws-client: urlFor-swap SSRF refusal emitted" });
|
|
362
|
+
check("urlFor swap to metadata IP refused with SSRF error",
|
|
363
|
+
ssrfErr && /metadata|ssrf|blocked|private|internal/i.test((ssrfErr.code || "") + " " + (ssrfErr.message || "")));
|
|
364
|
+
check("urlFor swap did not surface an unhandled rejection", sawUnhandled === null);
|
|
365
|
+
process.removeListener("unhandledRejection", _onUnhandled);
|
|
366
|
+
|
|
345
367
|
console.log("OK — ws-client tests");
|
|
346
368
|
}
|
|
347
369
|
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* X.509 basicConstraints cA enforcement across the framework's cert-chain
|
|
4
|
+
* walkers (b.tsa.verifyToken, b.mail.bimi, b.mail.crypto.smime.verify).
|
|
5
|
+
*
|
|
6
|
+
* node:crypto X509Certificate.checkIssued() does NOT enforce basicConstraints
|
|
7
|
+
* cA:TRUE, so a leaf / end-entity cert (cA:FALSE) that omits keyUsage is
|
|
8
|
+
* wrongly accepted as a signing CA — the classic basicConstraints bypass
|
|
9
|
+
* (CVE-2002-0862 class). All three walkers route their issuer test through
|
|
10
|
+
* lib/x509-chain.js, which adds the cA check. This proves:
|
|
11
|
+
* 1. the shared primitive (isCaCert / issuerValidlyIssued) — the exact
|
|
12
|
+
* logic every walker now depends on — rejects a non-CA issuer; and
|
|
13
|
+
* 2. an end-to-end consumer path (b.mail.bimi.fetchAndVerifyMark) refuses
|
|
14
|
+
* a forged mark whose chain hangs off a cA:FALSE intermediate.
|
|
15
|
+
*
|
|
16
|
+
* The intermediate deliberately OMITS keyUsage: checkIssued already rejects
|
|
17
|
+
* keyUsage-without-keyCertSign, so a keyUsage-bearing intermediate would pass
|
|
18
|
+
* even on the buggy code — only the missing basicConstraints check is under
|
|
19
|
+
* test here.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
var helpers = require("../helpers");
|
|
23
|
+
var b = helpers.b;
|
|
24
|
+
var check = helpers.check;
|
|
25
|
+
|
|
26
|
+
var nodeCrypto = require("crypto");
|
|
27
|
+
var pki = require("../../lib/vendor/pki.cjs");
|
|
28
|
+
var x509 = pki.x509;
|
|
29
|
+
var x509Chain = require("../../lib/x509-chain");
|
|
30
|
+
|
|
31
|
+
var BIMI_EKU_OID = "1.3.6.1.5.5.7.3.31";
|
|
32
|
+
|
|
33
|
+
// Mint root(CA:TRUE) -> intermediate(cA per opts) -> leaf, returning PEMs and
|
|
34
|
+
// node X509Certificate objects. The intermediate omits keyUsage so the cA
|
|
35
|
+
// check is the only thing that can reject it as an issuer.
|
|
36
|
+
async function _mintChain(opts) {
|
|
37
|
+
opts = opts || {};
|
|
38
|
+
var interCa = opts.interCa === true; // default cA:FALSE (the attack)
|
|
39
|
+
var leafSan = opts.leafSan || "victim.example";
|
|
40
|
+
var now = new Date();
|
|
41
|
+
var notAfter = new Date(now.getTime() + 365 * 24 * 60 * 60 * 1000);
|
|
42
|
+
var alg = { name: "ECDSA", namedCurve: "P-256" };
|
|
43
|
+
var sigAlg = { name: "ECDSA", hash: "SHA-256" };
|
|
44
|
+
|
|
45
|
+
var rootKeys = await pki.crypto.subtle.generateKey(alg, true, ["sign", "verify"]);
|
|
46
|
+
var interKeys = await pki.crypto.subtle.generateKey(alg, true, ["sign", "verify"]);
|
|
47
|
+
var leafKeys = await pki.crypto.subtle.generateKey(alg, true, ["sign", "verify"]);
|
|
48
|
+
|
|
49
|
+
var root = await x509.X509CertificateGenerator.createSelfSigned({
|
|
50
|
+
serialNumber: "01", name: "CN=Test Root CA", notBefore: now, notAfter: notAfter,
|
|
51
|
+
signingAlgorithm: sigAlg, keys: rootKeys,
|
|
52
|
+
extensions: [
|
|
53
|
+
new x509.BasicConstraintsExtension(true, 2, true),
|
|
54
|
+
new x509.KeyUsagesExtension(x509.KeyUsageFlags.keyCertSign | x509.KeyUsageFlags.cRLSign, true),
|
|
55
|
+
],
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// The intermediate: cA per opts, and NO keyUsage extension at all.
|
|
59
|
+
var inter = await x509.X509CertificateGenerator.create({
|
|
60
|
+
serialNumber: "02", issuer: root.subject, subject: "CN=Test Intermediate",
|
|
61
|
+
notBefore: now, notAfter: notAfter, signingAlgorithm: sigAlg,
|
|
62
|
+
publicKey: interKeys.publicKey, signingKey: rootKeys.privateKey,
|
|
63
|
+
extensions: [ new x509.BasicConstraintsExtension(interCa, interCa ? 0 : undefined, true) ],
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
var leaf = await x509.X509CertificateGenerator.create({
|
|
67
|
+
serialNumber: "03", issuer: inter.subject, subject: "CN=" + leafSan,
|
|
68
|
+
notBefore: now, notAfter: notAfter, signingAlgorithm: sigAlg,
|
|
69
|
+
publicKey: leafKeys.publicKey, signingKey: interKeys.privateKey,
|
|
70
|
+
extensions: [
|
|
71
|
+
new x509.BasicConstraintsExtension(false, undefined, true),
|
|
72
|
+
new x509.KeyUsagesExtension(x509.KeyUsageFlags.digitalSignature, true),
|
|
73
|
+
new x509.SubjectAlternativeNameExtension([{ type: "dns", value: leafSan }]),
|
|
74
|
+
new x509.ExtendedKeyUsageExtension([BIMI_EKU_OID], false),
|
|
75
|
+
],
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
rootPem: root.toString("pem"),
|
|
80
|
+
interPem: inter.toString("pem"),
|
|
81
|
+
leafPem: leaf.toString("pem"),
|
|
82
|
+
root: new nodeCrypto.X509Certificate(root.toString("pem")),
|
|
83
|
+
inter: new nodeCrypto.X509Certificate(inter.toString("pem")),
|
|
84
|
+
leaf: new nodeCrypto.X509Certificate(leaf.toString("pem")),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function _stubHttpClient(body) {
|
|
89
|
+
return {
|
|
90
|
+
request: function () {
|
|
91
|
+
return Promise.resolve({
|
|
92
|
+
statusCode: 200, headers: {},
|
|
93
|
+
body: Buffer.isBuffer(body) ? body : Buffer.from(String(body), "utf8"),
|
|
94
|
+
});
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function run() {
|
|
100
|
+
// ---- 1. Shared primitive: the cA enforcement every walker routes through.
|
|
101
|
+
var c = await _mintChain({ interCa: false }); // intermediate is cA:FALSE
|
|
102
|
+
check("isCaCert: root (cA:TRUE) is a CA", x509Chain.isCaCert(c.root) === true);
|
|
103
|
+
check("isCaCert: intermediate (cA:FALSE) is NOT a CA", x509Chain.isCaCert(c.inter) === false);
|
|
104
|
+
check("isCaCert: leaf (cA:FALSE) is NOT a CA", x509Chain.isCaCert(c.leaf) === false);
|
|
105
|
+
check("issuerValidlyIssued: root validly issued the intermediate",
|
|
106
|
+
x509Chain.issuerValidlyIssued(c.root, c.inter) === true);
|
|
107
|
+
// THE FIX: the intermediate genuinely issued + signed the leaf, but it is
|
|
108
|
+
// not a CA, so it must be refused as a chain issuer.
|
|
109
|
+
check("issuerValidlyIssued: cA:FALSE intermediate is REFUSED as the leaf's issuer",
|
|
110
|
+
x509Chain.issuerValidlyIssued(c.inter, c.leaf) === false);
|
|
111
|
+
|
|
112
|
+
// Control: when the intermediate IS a CA, the same call accepts it.
|
|
113
|
+
var ok = await _mintChain({ interCa: true });
|
|
114
|
+
check("issuerValidlyIssued: cA:TRUE intermediate is ACCEPTED as the leaf's issuer",
|
|
115
|
+
x509Chain.issuerValidlyIssued(ok.inter, ok.leaf) === true);
|
|
116
|
+
|
|
117
|
+
// ---- 2. Consumer path: b.mail.bimi.fetchAndVerifyMark must refuse a mark
|
|
118
|
+
// whose chain hangs off the cA:FALSE intermediate (leaf + intermediate
|
|
119
|
+
// served from the VMC URL, root as the trust anchor).
|
|
120
|
+
var threw = null;
|
|
121
|
+
try {
|
|
122
|
+
await b.mail.bimi.fetchAndVerifyMark({
|
|
123
|
+
domain: "victim.example",
|
|
124
|
+
vmcUrl: "https://victim.example/cert.pem",
|
|
125
|
+
trustAnchorsPem: c.rootPem,
|
|
126
|
+
httpClient: _stubHttpClient(c.leafPem + "\n" + c.interPem),
|
|
127
|
+
});
|
|
128
|
+
} catch (e) { threw = e; }
|
|
129
|
+
check("bimi.fetchAndVerifyMark: forged mark via cA:FALSE intermediate is REJECTED",
|
|
130
|
+
threw && threw.code === "bimi/vmc-chain-invalid");
|
|
131
|
+
|
|
132
|
+
// Control: the same wiring with a cA:TRUE intermediate verifies.
|
|
133
|
+
var rv = await b.mail.bimi.fetchAndVerifyMark({
|
|
134
|
+
domain: "victim.example",
|
|
135
|
+
vmcUrl: "https://victim.example/cert.pem",
|
|
136
|
+
trustAnchorsPem: ok.rootPem,
|
|
137
|
+
httpClient: _stubHttpClient(ok.leafPem + "\n" + ok.interPem),
|
|
138
|
+
});
|
|
139
|
+
check("bimi.fetchAndVerifyMark: valid CA chain still verifies", rv.ok === true);
|
|
140
|
+
|
|
141
|
+
console.log("OK — x509 cA enforcement (" + helpers.getChecks() + " checks)");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
module.exports = { run: run };
|
|
145
|
+
|
|
146
|
+
if (require.main === module) {
|
|
147
|
+
run().then(function () { process.exit(0); })
|
|
148
|
+
.catch(function (err) { process.exitCode = 1; throw err; });
|
|
149
|
+
}
|
|
@@ -520,6 +520,40 @@ async function run() {
|
|
|
520
520
|
check("omitted-tag read → us-replica wire NOT reached (gate fail-closed)",
|
|
521
521
|
usReplicaOmit.seen.every(function (s) { return !/SELECT id FROM orders/.test(s); }));
|
|
522
522
|
|
|
523
|
+
// ---- read-replica gate must NOT over-reject an UNRESTRICTED replica ----
|
|
524
|
+
// A replica with no residencyTag defaults to "unrestricted" (no residency
|
|
525
|
+
// constraint — it may serve any region's rows). Under a cross-border-regulated
|
|
526
|
+
// posture a read to it WITHOUT opts.rowResidencyTag must still pass: there is
|
|
527
|
+
// no constraint to enforce. (Was: the fail-closed tag-required gate fired on
|
|
528
|
+
// any truthy replica.residencyTag, and "unrestricted" is truthy → it refused
|
|
529
|
+
// every regulated read to an untagged replica that omitted rowResidencyTag.)
|
|
530
|
+
b.externalDb._resetForTest();
|
|
531
|
+
var roPrimaryFree = _trackingDriver("ro-primary-free");
|
|
532
|
+
var freeReplica = _trackingDriver("free-replica");
|
|
533
|
+
b.externalDb.init({
|
|
534
|
+
backends: {
|
|
535
|
+
main: {
|
|
536
|
+
connect: roPrimaryFree.connect, query: roPrimaryFree.query,
|
|
537
|
+
close: roPrimaryFree.close, // primary unrestricted
|
|
538
|
+
replicas: [
|
|
539
|
+
{
|
|
540
|
+
connect: freeReplica.connect, query: freeReplica.query,
|
|
541
|
+
close: freeReplica.close, // no residencyTag → "unrestricted"
|
|
542
|
+
allowCrossBorder: false,
|
|
543
|
+
},
|
|
544
|
+
],
|
|
545
|
+
replicaFallbackToPrimary: false,
|
|
546
|
+
},
|
|
547
|
+
},
|
|
548
|
+
});
|
|
549
|
+
await _underGdpr(async function () {
|
|
550
|
+
var res = await b.externalDb.read.query("SELECT id FROM orders WHERE id = $1", ["o-1"]);
|
|
551
|
+
check("gdpr + unrestricted replica + omitted tag → passes (no over-reject)",
|
|
552
|
+
res && res.rowCount === 1);
|
|
553
|
+
});
|
|
554
|
+
check("unrestricted-replica untagged read → replica wire reached",
|
|
555
|
+
_saw(freeReplica, /SELECT id FROM orders/));
|
|
556
|
+
|
|
523
557
|
// ---- write verbs that wear a harmless leading keyword must still be
|
|
524
558
|
// gated: WITH (CTE) / COPY FROM / EXPLAIN ANALYZE / CALL / EXECUTE /
|
|
525
559
|
// REPLACE / DO all PLACE rows, so under gdpr + eu backend they require
|
package/package.json
CHANGED