@blamejs/blamejs-shop 0.4.68 → 0.4.70
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/lib/admin.js +8 -6
- package/lib/asset-manifest.json +1 -1
- package/lib/giftcards.js +29 -5
- package/lib/security-middleware.js +65 -23
- package/lib/storefront.js +10 -7
- package/lib/vendor/MANIFEST.json +319 -267
- package/lib/vendor/blamejs/CHANGELOG.md +2 -0
- package/lib/vendor/blamejs/api-snapshot.json +58 -5
- package/lib/vendor/blamejs/examples/wiki/README.md +1 -1
- package/lib/vendor/blamejs/examples/wiki/docker-compose.prod.yml +9 -8
- package/lib/vendor/blamejs/examples/wiki/docker-compose.yml +10 -9
- package/lib/vendor/blamejs/examples/wiki/env-snapshot.json +4 -3
- package/lib/vendor/blamejs/examples/wiki/lib/build-app.js +33 -17
- package/lib/vendor/blamejs/examples/wiki/routes/admin.js +7 -10
- package/lib/vendor/blamejs/examples/wiki/server.js +5 -4
- package/lib/vendor/blamejs/examples/wiki/test/e2e.js +5 -0
- package/lib/vendor/blamejs/lib/a2a-tasks.js +38 -6
- package/lib/vendor/blamejs/lib/agent-event-bus.js +13 -0
- package/lib/vendor/blamejs/lib/agent-idempotency.js +5 -1
- package/lib/vendor/blamejs/lib/agent-snapshot.js +32 -2
- package/lib/vendor/blamejs/lib/ai-aedt-bias-audit.js +2 -1
- package/lib/vendor/blamejs/lib/ai-content-detect.js +1 -3
- package/lib/vendor/blamejs/lib/ai-frontier-protocol.js +1 -1
- package/lib/vendor/blamejs/lib/ai-model-manifest.js +1 -1
- package/lib/vendor/blamejs/lib/ai-output.js +16 -7
- package/lib/vendor/blamejs/lib/api-snapshot.js +4 -1
- package/lib/vendor/blamejs/lib/app-shutdown.js +7 -1
- package/lib/vendor/blamejs/lib/archive-gz.js +9 -0
- package/lib/vendor/blamejs/lib/archive-read.js +9 -7
- package/lib/vendor/blamejs/lib/archive-tar-read.js +51 -8
- package/lib/vendor/blamejs/lib/archive.js +4 -2
- package/lib/vendor/blamejs/lib/asn1-der.js +70 -22
- package/lib/vendor/blamejs/lib/atomic-file.js +204 -2
- package/lib/vendor/blamejs/lib/audit-chain.js +54 -5
- package/lib/vendor/blamejs/lib/audit-daily-review.js +12 -2
- package/lib/vendor/blamejs/lib/audit-sign.js +2 -1
- package/lib/vendor/blamejs/lib/audit-tools.js +108 -23
- package/lib/vendor/blamejs/lib/audit.js +7 -2
- package/lib/vendor/blamejs/lib/auth/access-lock.js +2 -2
- package/lib/vendor/blamejs/lib/auth/bot-challenge.js +1 -1
- package/lib/vendor/blamejs/lib/auth/ciba.js +43 -4
- package/lib/vendor/blamejs/lib/auth/dpop.js +6 -1
- package/lib/vendor/blamejs/lib/auth/fido-mds3.js +5 -1
- package/lib/vendor/blamejs/lib/auth/jwt.js +2 -2
- package/lib/vendor/blamejs/lib/auth/lockout.js +18 -2
- package/lib/vendor/blamejs/lib/auth/passkey.js +1 -1
- package/lib/vendor/blamejs/lib/auth/password.js +1 -1
- package/lib/vendor/blamejs/lib/auth/saml.js +33 -13
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc.js +24 -4
- package/lib/vendor/blamejs/lib/auth/status-list.js +14 -2
- package/lib/vendor/blamejs/lib/auth/step-up.js +9 -1
- package/lib/vendor/blamejs/lib/auth-bot-challenge.js +21 -2
- package/lib/vendor/blamejs/lib/backup/bundle.js +7 -2
- package/lib/vendor/blamejs/lib/backup/crypto.js +23 -8
- package/lib/vendor/blamejs/lib/backup/index.js +41 -20
- package/lib/vendor/blamejs/lib/backup/manifest.js +7 -1
- package/lib/vendor/blamejs/lib/break-glass.js +41 -22
- package/lib/vendor/blamejs/lib/cbor.js +34 -11
- package/lib/vendor/blamejs/lib/cdn-cache-control.js +7 -3
- package/lib/vendor/blamejs/lib/cert.js +5 -3
- package/lib/vendor/blamejs/lib/cli.js +5 -1
- package/lib/vendor/blamejs/lib/cloud-events.js +3 -2
- package/lib/vendor/blamejs/lib/cluster-storage.js +7 -3
- package/lib/vendor/blamejs/lib/codepoint-class.js +17 -0
- package/lib/vendor/blamejs/lib/compliance-eaa.js +1 -1
- package/lib/vendor/blamejs/lib/compliance-sanctions.js +9 -7
- package/lib/vendor/blamejs/lib/compliance.js +1 -1
- package/lib/vendor/blamejs/lib/config-drift.js +22 -8
- package/lib/vendor/blamejs/lib/content-credentials.js +11 -8
- package/lib/vendor/blamejs/lib/content-digest.js +11 -4
- package/lib/vendor/blamejs/lib/cookies.js +10 -2
- package/lib/vendor/blamejs/lib/cose.js +20 -0
- package/lib/vendor/blamejs/lib/crdt.js +2 -1
- package/lib/vendor/blamejs/lib/crypto-field.js +29 -23
- package/lib/vendor/blamejs/lib/crypto.js +18 -4
- package/lib/vendor/blamejs/lib/csp.js +4 -0
- package/lib/vendor/blamejs/lib/daemon.js +4 -1
- package/lib/vendor/blamejs/lib/data-act.js +27 -4
- package/lib/vendor/blamejs/lib/db-file-lifecycle.js +14 -6
- package/lib/vendor/blamejs/lib/db-query.js +69 -9
- package/lib/vendor/blamejs/lib/db.js +32 -15
- package/lib/vendor/blamejs/lib/dora.js +43 -13
- package/lib/vendor/blamejs/lib/dr-runbook.js +1 -1
- package/lib/vendor/blamejs/lib/dsa.js +2 -1
- package/lib/vendor/blamejs/lib/dsr.js +22 -8
- package/lib/vendor/blamejs/lib/early-hints.js +19 -0
- package/lib/vendor/blamejs/lib/eat.js +5 -1
- package/lib/vendor/blamejs/lib/external-db.js +60 -4
- package/lib/vendor/blamejs/lib/fda-21cfr11.js +30 -5
- package/lib/vendor/blamejs/lib/flag-providers.js +6 -2
- package/lib/vendor/blamejs/lib/forms.js +1 -1
- package/lib/vendor/blamejs/lib/gate-contract.js +46 -5
- package/lib/vendor/blamejs/lib/gdpr-ropa.js +18 -9
- package/lib/vendor/blamejs/lib/graphql-federation.js +17 -4
- package/lib/vendor/blamejs/lib/guard-all.js +2 -2
- package/lib/vendor/blamejs/lib/guard-dsn.js +1 -1
- package/lib/vendor/blamejs/lib/guard-envelope.js +1 -1
- package/lib/vendor/blamejs/lib/guard-html.js +9 -11
- package/lib/vendor/blamejs/lib/guard-imap-command.js +1 -1
- package/lib/vendor/blamejs/lib/guard-jmap.js +1 -1
- package/lib/vendor/blamejs/lib/guard-json.js +14 -6
- package/lib/vendor/blamejs/lib/guard-mail-move.js +1 -1
- package/lib/vendor/blamejs/lib/guard-managesieve-command.js +1 -1
- package/lib/vendor/blamejs/lib/guard-pop3-command.js +1 -1
- package/lib/vendor/blamejs/lib/guard-smtp-command.js +1 -1
- package/lib/vendor/blamejs/lib/guard-svg.js +8 -9
- package/lib/vendor/blamejs/lib/html-balance.js +7 -3
- package/lib/vendor/blamejs/lib/http-client-cookie-jar.js +33 -12
- package/lib/vendor/blamejs/lib/http-client.js +225 -53
- package/lib/vendor/blamejs/lib/iab-tcf.js +3 -2
- package/lib/vendor/blamejs/lib/importmap-integrity.js +41 -1
- package/lib/vendor/blamejs/lib/incident-report.js +9 -6
- package/lib/vendor/blamejs/lib/json-patch.js +1 -1
- package/lib/vendor/blamejs/lib/json-path.js +24 -3
- package/lib/vendor/blamejs/lib/jtd.js +2 -2
- package/lib/vendor/blamejs/lib/legal-hold.js +24 -8
- package/lib/vendor/blamejs/lib/log.js +2 -2
- package/lib/vendor/blamejs/lib/mail-agent.js +2 -2
- package/lib/vendor/blamejs/lib/mail-arf.js +1 -1
- package/lib/vendor/blamejs/lib/mail-auth.js +3 -3
- package/lib/vendor/blamejs/lib/mail-bimi.js +16 -16
- package/lib/vendor/blamejs/lib/mail-bounce.js +3 -3
- package/lib/vendor/blamejs/lib/mail-crypto-smime.js +71 -6
- package/lib/vendor/blamejs/lib/mail-deploy.js +9 -5
- package/lib/vendor/blamejs/lib/mail-greylist.js +2 -4
- package/lib/vendor/blamejs/lib/mail-helo.js +2 -4
- package/lib/vendor/blamejs/lib/mail-journal.js +11 -8
- package/lib/vendor/blamejs/lib/mail-mdn.js +8 -4
- package/lib/vendor/blamejs/lib/mail-rbl.js +2 -4
- package/lib/vendor/blamejs/lib/mail-scan.js +3 -5
- package/lib/vendor/blamejs/lib/mail-server-jmap.js +4 -1
- package/lib/vendor/blamejs/lib/mail-server-registry.js +1 -1
- package/lib/vendor/blamejs/lib/mail-server-tls.js +9 -2
- package/lib/vendor/blamejs/lib/mail-spam-score.js +2 -4
- package/lib/vendor/blamejs/lib/mail-store-fts.js +1 -1
- package/lib/vendor/blamejs/lib/mail.js +22 -2
- package/lib/vendor/blamejs/lib/markup-tokenizer.js +24 -0
- package/lib/vendor/blamejs/lib/mcp.js +6 -4
- package/lib/vendor/blamejs/lib/mdoc.js +26 -3
- package/lib/vendor/blamejs/lib/metrics.js +14 -3
- package/lib/vendor/blamejs/lib/middleware/api-encrypt.js +2 -2
- package/lib/vendor/blamejs/lib/middleware/body-parser.js +10 -4
- package/lib/vendor/blamejs/lib/middleware/bot-guard.js +26 -18
- package/lib/vendor/blamejs/lib/middleware/clear-site-data.js +5 -1
- package/lib/vendor/blamejs/lib/middleware/compression.js +9 -0
- package/lib/vendor/blamejs/lib/middleware/cors.js +32 -23
- package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +60 -21
- package/lib/vendor/blamejs/lib/middleware/daily-byte-quota.js +6 -4
- package/lib/vendor/blamejs/lib/middleware/fetch-metadata.js +28 -4
- package/lib/vendor/blamejs/lib/middleware/network-allowlist.js +61 -30
- package/lib/vendor/blamejs/lib/middleware/rate-limit.js +25 -16
- package/lib/vendor/blamejs/lib/middleware/scim-server.js +2 -1
- package/lib/vendor/blamejs/lib/middleware/security-headers.js +24 -6
- package/lib/vendor/blamejs/lib/middleware/speculation-rules.js +6 -3
- package/lib/vendor/blamejs/lib/middleware/tus-upload.js +2 -2
- package/lib/vendor/blamejs/lib/money.js +1 -1
- package/lib/vendor/blamejs/lib/mtls-ca.js +10 -6
- package/lib/vendor/blamejs/lib/network-dns-resolver.js +1 -1
- package/lib/vendor/blamejs/lib/network-dns.js +9 -2
- package/lib/vendor/blamejs/lib/network-dnssec.js +2 -1
- package/lib/vendor/blamejs/lib/network-smtp-policy.js +23 -5
- package/lib/vendor/blamejs/lib/network-tls.js +27 -5
- package/lib/vendor/blamejs/lib/network-tsig.js +2 -2
- package/lib/vendor/blamejs/lib/nis2-report.js +1 -1
- package/lib/vendor/blamejs/lib/nist-crosswalk.js +1 -1
- package/lib/vendor/blamejs/lib/ntp-check.js +28 -0
- package/lib/vendor/blamejs/lib/numeric-bounds.js +9 -0
- package/lib/vendor/blamejs/lib/object-store/azure-blob.js +1 -2
- package/lib/vendor/blamejs/lib/object-store/gcs-bucket-ops.js +4 -2
- package/lib/vendor/blamejs/lib/object-store/gcs.js +6 -4
- package/lib/vendor/blamejs/lib/object-store/http-put.js +1 -2
- package/lib/vendor/blamejs/lib/object-store/http-request.js +30 -1
- package/lib/vendor/blamejs/lib/object-store/local.js +37 -17
- package/lib/vendor/blamejs/lib/object-store/sigv4.js +1 -2
- package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +20 -4
- package/lib/vendor/blamejs/lib/outbox.js +11 -4
- package/lib/vendor/blamejs/lib/parsers/safe-xml.js +1 -1
- package/lib/vendor/blamejs/lib/parsers/safe-yaml.js +21 -3
- package/lib/vendor/blamejs/lib/queue-local.js +10 -3
- package/lib/vendor/blamejs/lib/redact.js +7 -3
- package/lib/vendor/blamejs/lib/request-helpers.js +201 -23
- package/lib/vendor/blamejs/lib/resource-access-lock.js +3 -3
- package/lib/vendor/blamejs/lib/restore-bundle.js +46 -18
- package/lib/vendor/blamejs/lib/restore-rollback.js +10 -4
- package/lib/vendor/blamejs/lib/restore.js +19 -0
- package/lib/vendor/blamejs/lib/retention.js +20 -4
- package/lib/vendor/blamejs/lib/router.js +17 -4
- package/lib/vendor/blamejs/lib/safe-ical.js +2 -2
- package/lib/vendor/blamejs/lib/safe-icap.js +1 -1
- package/lib/vendor/blamejs/lib/safe-json.js +44 -0
- package/lib/vendor/blamejs/lib/safe-sieve.js +1 -1
- package/lib/vendor/blamejs/lib/safe-vcard.js +1 -1
- package/lib/vendor/blamejs/lib/sandbox-worker.js +6 -0
- package/lib/vendor/blamejs/lib/sandbox.js +1 -1
- package/lib/vendor/blamejs/lib/scheduler.js +17 -1
- package/lib/vendor/blamejs/lib/self-update-standalone-verifier.js +16 -0
- package/lib/vendor/blamejs/lib/session.js +27 -3
- package/lib/vendor/blamejs/lib/sql.js +3 -3
- package/lib/vendor/blamejs/lib/static.js +65 -13
- package/lib/vendor/blamejs/lib/template.js +7 -5
- package/lib/vendor/blamejs/lib/tenant-quota.js +52 -19
- package/lib/vendor/blamejs/lib/tsa.js +5 -2
- package/lib/vendor/blamejs/lib/vault/index.js +5 -0
- package/lib/vendor/blamejs/lib/vault/passphrase-ops.js +22 -26
- package/lib/vendor/blamejs/lib/vault/passphrase-source.js +8 -3
- package/lib/vendor/blamejs/lib/vault/rotate.js +13 -18
- package/lib/vendor/blamejs/lib/vault/seal-pem-file.js +4 -1
- package/lib/vendor/blamejs/lib/vc.js +1 -1
- package/lib/vendor/blamejs/lib/vendor/MANIFEST.json +10 -10
- package/lib/vendor/blamejs/lib/vendor/public-suffix-list.dat +23 -10
- package/lib/vendor/blamejs/lib/vendor/public-suffix-list.data.js +5498 -5494
- package/lib/vendor/blamejs/lib/webhook.js +16 -1
- package/lib/vendor/blamejs/lib/websocket.js +1 -1
- package/lib/vendor/blamejs/lib/worm.js +1 -1
- package/lib/vendor/blamejs/lib/ws-client.js +57 -46
- package/lib/vendor/blamejs/lib/x509-chain.js +44 -0
- package/lib/vendor/blamejs/package.json +1 -1
- package/lib/vendor/blamejs/release-notes/v0.15.14.json +122 -0
- package/lib/vendor/blamejs/scripts/check-vendor-currency.js +119 -4
- package/lib/vendor/blamejs/test/00-primitives.js +5 -1
- package/lib/vendor/blamejs/test/integration/mail-crypto-smime.test.js +18 -4
- package/lib/vendor/blamejs/test/layer-0-primitives/a2a-tasks.test.js +42 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-event-bus.test.js +36 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-idempotency.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-snapshot.test.js +47 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/archive-gz.test.js +24 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/archive-tar-hardening.test.js +160 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/asn1-der.test.js +45 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file-fd-read.test.js +30 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file-open-nofollow.test.js +79 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file-write-excl.test.js +132 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file-write-stream.test.js +181 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-chain-corrupted-anchor.test.js +65 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-chain-incremental-verify.test.js +83 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-daily-review.test.js +48 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-signing-key-rotation.test.js +13 -3
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-verifybundle-tamper.test.js +122 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-bot-challenge.test.js +37 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-jwt-defenses.test.js +5 -3
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-lockout.test.js +21 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-status-list.test.js +73 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/bot-guard.test.js +17 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/break-glass.test.js +62 -3
- package/lib/vendor/blamejs/test/layer-0-primitives/clear-site-data.test.js +12 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cluster-storage.test.js +40 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +146 -5
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance-sanctions.test.js +14 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compression-range.test.js +115 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/content-credentials.test.js +17 -5
- package/lib/vendor/blamejs/test/layer-0-primitives/cors.test.js +27 -5
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-per-row-key.test.js +83 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csrf-protect.test.js +58 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/data-act.test.js +30 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-raw-residency-gate.test.js +74 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dora.test.js +20 -6
- package/lib/vendor/blamejs/test/layer-0-primitives/dpop-alg-kty.test.js +64 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dsr.test.js +32 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fda-21cfr11.test.js +43 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fetch-metadata.test.js +30 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/graphql-federation.test.js +33 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-html.test.js +34 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-json.test.js +12 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-client-stream.test.js +72 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-client-throttle-transform.test.js +88 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/importmap-integrity.test.js +25 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/legal-hold.test.js +28 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-smime.test.js +20 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-header-injection.test.js +58 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-journal.test.js +63 -9
- package/lib/vendor/blamejs/test/layer-0-primitives/mdoc.test.js +41 -14
- package/lib/vendor/blamejs/test/layer-0-primitives/network-allowlist.test.js +61 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/object-store-range-header.test.js +51 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/otlp-attr-redaction.test.js +35 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/output-header-hardening.test.js +102 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/parser-verify-hardening.test.js +191 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/proto-shadow-allowlist.test.js +120 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/rate-limit-xff-spoofing.test.js +75 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/request-helpers.test.js +128 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/restore-blob-remap.test.js +128 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/retention-sweep-termination.test.js +104 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/router-body-validation.test.js +98 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-json-stringify-for-script.test.js +43 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/saml-subjectconfirmation-notonorafter.test.js +48 -5
- package/lib/vendor/blamejs/test/layer-0-primitives/sandbox.test.js +23 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc.test.js +54 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/session-extensions.test.js +24 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/static.test.js +42 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/step-up.test.js +7 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/template-escape-html.test.js +43 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tenant-quota.test.js +15 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vault-default-store.test.js +48 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vendor-currency-classify.test.js +77 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/webhook.test.js +50 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ws-client.test.js +22 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/x509-chain-ca-enforcement.test.js +149 -0
- package/lib/vendor/blamejs/test/layer-5-integration/external-db-residency.test.js +34 -0
- package/package.json +1 -1
|
@@ -42,11 +42,36 @@ function testImportmapErrorClass() {
|
|
|
42
42
|
check("ImportmapError carries code", e.code === "importmap/test");
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
function testScriptTagEscapesScriptBreakout() {
|
|
46
|
+
// A module url containing "</script>" must NOT close the injected
|
|
47
|
+
// <script type="importmap"> element. scriptTag uses the <script>-safe
|
|
48
|
+
// serializer; raw JSON.stringify would leave the "</script>" literal.
|
|
49
|
+
var im = b.importmapIntegrity.build({
|
|
50
|
+
modules: { "x": { url: "/a</script><script>alert(1)</script>", body: "code" } },
|
|
51
|
+
});
|
|
52
|
+
var tag = b.importmapIntegrity.scriptTag(im);
|
|
53
|
+
// Exactly one "</script>" — the tag's own close. The url's "</script>"
|
|
54
|
+
// must be escaped.
|
|
55
|
+
check("scriptTag: only the tag's own </script> is present (url breakout escaped)",
|
|
56
|
+
tag.split("</script>").length - 1 === 1);
|
|
57
|
+
check("scriptTag: the url's < is escaped to \\u003c",
|
|
58
|
+
tag.indexOf("u003c/script") !== -1 && tag.indexOf("/a</script>") === -1);
|
|
59
|
+
// The embedded JSON still parses and yields the original url.
|
|
60
|
+
var jsonText = tag.replace(/^<script[^>]*>/, "").replace(/<\/script>$/, "");
|
|
61
|
+
check("scriptTag: embedded JSON round-trips to the original url",
|
|
62
|
+
JSON.parse(jsonText).imports.x === "/a</script><script>alert(1)</script>");
|
|
63
|
+
// nonce validation: a malformed nonce is refused, not escaped into the tag.
|
|
64
|
+
var threw = null;
|
|
65
|
+
try { b.importmapIntegrity.scriptTag(im, { nonce: '" onload="x' }); } catch (e) { threw = e; }
|
|
66
|
+
check("scriptTag: malformed nonce is refused", threw && threw.code === "importmap/bad-nonce");
|
|
67
|
+
}
|
|
68
|
+
|
|
45
69
|
function run() {
|
|
46
70
|
testBuildShape();
|
|
47
71
|
testBuildSha256();
|
|
48
72
|
testRefusesBadHash();
|
|
49
73
|
testImportmapErrorClass();
|
|
74
|
+
testScriptTagEscapesScriptBreakout();
|
|
50
75
|
}
|
|
51
76
|
|
|
52
77
|
if (require.main === module) {
|
|
@@ -137,6 +137,34 @@ async function run() {
|
|
|
137
137
|
var resRaw = JSON.stringify(b.db.prepare("SELECT reason FROM \"_blamejs_subject_restrictions\"").all());
|
|
138
138
|
check("#114 subject-restriction reason is sealed at rest (not plaintext)", resRaw.indexOf(RES_SECRET) === -1);
|
|
139
139
|
|
|
140
|
+
// ---- B8b: a LAPSED hold must be renewable via place() ----
|
|
141
|
+
// place() rejected "already-held" whenever ANY row existed — including a hold
|
|
142
|
+
// whose retainUntil sunset had passed (isHeld already false). That left a
|
|
143
|
+
// subject both unprotected (isHeld false) AND unable to receive a fresh hold
|
|
144
|
+
// when new litigation arose. A lapsed hold must be replaceable; an ACTIVE one
|
|
145
|
+
// still rejects.
|
|
146
|
+
var lapsedPlace = holds.place("lapse-user", {
|
|
147
|
+
reason: "original matter (sunset already passed)",
|
|
148
|
+
retainUntil: Date.now() - b.constants.TIME.days(1), // born lapsed
|
|
149
|
+
});
|
|
150
|
+
check("B8b: placing a hold with a past retainUntil succeeds", lapsedPlace.placed === true);
|
|
151
|
+
check("B8b: a lapsed hold reads as NOT held", holds.isHeld("lapse-user") === false);
|
|
152
|
+
|
|
153
|
+
var renew = holds.place("lapse-user", {
|
|
154
|
+
reason: "fresh litigation — renewed hold",
|
|
155
|
+
citation: "FRCP-26",
|
|
156
|
+
});
|
|
157
|
+
check("B8b: place() RENEWS a lapsed hold (not rejected already-held)", renew.placed === true);
|
|
158
|
+
check("B8b: renewal is flagged renewedFromLapsed", renew.renewedFromLapsed === true);
|
|
159
|
+
check("B8b: subject is held again after renewal", holds.isHeld("lapse-user") === true);
|
|
160
|
+
check("B8b: the renewed hold carries the new reason",
|
|
161
|
+
holds.get("lapse-user").reason === "fresh litigation — renewed hold");
|
|
162
|
+
|
|
163
|
+
// Control: an ACTIVE (non-lapsed) hold still rejects a second place().
|
|
164
|
+
var activeReplace = holds.place("lapse-user", { reason: "duplicate attempt" });
|
|
165
|
+
check("B8b: re-placing an ACTIVE hold is still rejected already-held",
|
|
166
|
+
activeReplace.error === "already-held");
|
|
167
|
+
|
|
140
168
|
await dbHelper.teardownTestDb(tmpDir);
|
|
141
169
|
}
|
|
142
170
|
|
|
@@ -299,8 +299,28 @@ function testSmimeCertKeyBinding() {
|
|
|
299
299
|
smime._certKeyMatches({ publicKey: { export: function () { throw new Error("nope"); } } }, spkiA) === false);
|
|
300
300
|
}
|
|
301
301
|
|
|
302
|
+
function testSmimeContentTypeAttrExtraction() {
|
|
303
|
+
// RFC 5652 §11.1: verify() binds the contentType signed-attr to the
|
|
304
|
+
// eContentType. _extractContentTypeOid must surface a present contentType
|
|
305
|
+
// OID and return null when the attribute is absent (which verify rejects).
|
|
306
|
+
var asn1 = require("../../lib/asn1-der");
|
|
307
|
+
var OID_CT_ATTR = "1.2.840.113549.1.9.3";
|
|
308
|
+
var OID_MD_ATTR = "1.2.840.113549.1.9.4";
|
|
309
|
+
var OID_ID_DATA = "1.2.840.113549.1.7.1";
|
|
310
|
+
function setOf(buf) { return asn1.writeNode(0x31, buf); } // SET
|
|
311
|
+
var ctAttr = asn1.writeSequence([asn1.writeOid(OID_CT_ATTR), setOf(asn1.writeOid(OID_ID_DATA))]);
|
|
312
|
+
var mdAttr = asn1.writeSequence([asn1.writeOid(OID_MD_ATTR), setOf(asn1.writeOctetString(Buffer.alloc(32)))]);
|
|
313
|
+
var withCt = setOf(Buffer.concat([mdAttr, ctAttr]));
|
|
314
|
+
var withoutCt = setOf(mdAttr);
|
|
315
|
+
check("smime contentType: present attribute extracts its eContentType OID",
|
|
316
|
+
smime._extractContentTypeOid(withCt) === OID_ID_DATA);
|
|
317
|
+
check("smime contentType: absent attribute yields null (verify then refuses)",
|
|
318
|
+
smime._extractContentTypeOid(withoutCt) === null);
|
|
319
|
+
}
|
|
320
|
+
|
|
302
321
|
function run() {
|
|
303
322
|
testSmimeSurface();
|
|
323
|
+
testSmimeContentTypeAttrExtraction();
|
|
304
324
|
testSmimeCertKeyBinding();
|
|
305
325
|
testSmimeSignVerifyRoundtrip();
|
|
306
326
|
testSmimeVerifyTamperRefused();
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* b.mail — outbound header injection (CWE-93 / RFC 5322). Reply-To and custom
|
|
4
|
+
* header KEYS reach the wire in _buildRfc822; a CRLF in either smuggles
|
|
5
|
+
* arbitrary headers (Bcc / Reply-To override / Content-Type). _validateMessage
|
|
6
|
+
* must fail closed on CRLF in replyTo, header keys, and header values.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
var helpers = require("../helpers");
|
|
10
|
+
var b = helpers.b;
|
|
11
|
+
var check = helpers.check;
|
|
12
|
+
|
|
13
|
+
function _mailer() {
|
|
14
|
+
return b.mail.create({
|
|
15
|
+
transport: function () { return Promise.resolve({ ok: true }); },
|
|
16
|
+
audit: false,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
function _base() {
|
|
20
|
+
return { from: "sender@example.com", to: "rcpt@example.com", subject: "hi", text: "hello" };
|
|
21
|
+
}
|
|
22
|
+
async function _expectThrow(label, message, codeMatch) {
|
|
23
|
+
var m = _mailer();
|
|
24
|
+
try { await m.send(message); check(label + " (did not throw)", false); }
|
|
25
|
+
catch (e) { check(label, (e && e.code || "").indexOf(codeMatch) !== -1); }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function testCleanSends() {
|
|
29
|
+
var m = _mailer();
|
|
30
|
+
var sent = true;
|
|
31
|
+
try { await m.send(_base()); } catch (_e) { sent = false; }
|
|
32
|
+
check("clean message sends", sent === true);
|
|
33
|
+
}
|
|
34
|
+
async function testReplyToCrlfRefused() {
|
|
35
|
+
await _expectThrow("Reply-To CRLF refused (header injection)",
|
|
36
|
+
Object.assign(_base(), { replyTo: "a@b.com\r\nBcc: evil@x.com" }), "mail/invalid-reply-to");
|
|
37
|
+
}
|
|
38
|
+
async function testHeaderKeyCrlfRefused() {
|
|
39
|
+
await _expectThrow("header KEY CRLF refused (header injection)",
|
|
40
|
+
Object.assign(_base(), { headers: { "X-Foo\r\nBcc: evil@x.com": "v" } }), "mail/invalid-header");
|
|
41
|
+
}
|
|
42
|
+
async function testHeaderValueCrlfRefused() {
|
|
43
|
+
await _expectThrow("header VALUE CRLF refused (header injection)",
|
|
44
|
+
Object.assign(_base(), { headers: { "X-Foo": "v\r\nBcc: evil@x.com" } }), "mail/invalid-header");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function run() {
|
|
48
|
+
await testCleanSends();
|
|
49
|
+
await testReplyToCrlfRefused();
|
|
50
|
+
await testHeaderKeyCrlfRefused();
|
|
51
|
+
await testHeaderValueCrlfRefused();
|
|
52
|
+
console.log("[mail-header-injection] OK — " + helpers.getChecks() + " checks passed");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = { run: run };
|
|
56
|
+
if (require.main === module) {
|
|
57
|
+
run().then(function () {}, function (e) { console.error("FAIL: " + helpers.formatErr(e)); process.exit(1); });
|
|
58
|
+
}
|
|
@@ -32,14 +32,14 @@ function testBadInput() {
|
|
|
32
32
|
function () { b.mail.journal.create({
|
|
33
33
|
storage: { putObject: function () {} },
|
|
34
34
|
vault: { seal: function () {} },
|
|
35
|
-
legalHold: {
|
|
35
|
+
legalHold: { isHeld: function () {} },
|
|
36
36
|
db: { runSql: function () {} },
|
|
37
37
|
}); });
|
|
38
38
|
expectThrow("refuses unknown regime",
|
|
39
39
|
function () { b.mail.journal.create({
|
|
40
40
|
storage: { putObject: function () {} },
|
|
41
41
|
vault: { seal: function () {} },
|
|
42
|
-
legalHold: {
|
|
42
|
+
legalHold: { isHeld: function () {} },
|
|
43
43
|
db: { runSql: function () {} },
|
|
44
44
|
regimes: ["bogus-regime"],
|
|
45
45
|
}); });
|
|
@@ -47,7 +47,7 @@ function testBadInput() {
|
|
|
47
47
|
function () { b.mail.journal.create({
|
|
48
48
|
storage: { putObject: function () {} },
|
|
49
49
|
vault: { seal: function () {} },
|
|
50
|
-
legalHold: {
|
|
50
|
+
legalHold: { isHeld: function () {} },
|
|
51
51
|
db: { runSql: function () {} },
|
|
52
52
|
regimes: ["hipaa"],
|
|
53
53
|
namespace: "bad/slash",
|
|
@@ -64,7 +64,7 @@ function testRegimeFloorMath() {
|
|
|
64
64
|
var calls = [];
|
|
65
65
|
var db = { runSql: function (sql, args) { calls.push({ sql: sql.slice(0, 40), args: args }); return []; } };
|
|
66
66
|
var vault = { seal: function (x) { return x; } };
|
|
67
|
-
var legalHold = {
|
|
67
|
+
var legalHold = { isHeld: function () { return false; } };
|
|
68
68
|
|
|
69
69
|
var j = b.mail.journal.create({
|
|
70
70
|
storage: storage, vault: vault, legalHold: legalHold, db: db,
|
|
@@ -94,7 +94,7 @@ function testCreatesIndexTable() {
|
|
|
94
94
|
b.mail.journal.create({
|
|
95
95
|
storage: { putObject: async function () {} },
|
|
96
96
|
vault: { seal: function () {} },
|
|
97
|
-
legalHold: {
|
|
97
|
+
legalHold: { isHeld: function () { return false; } },
|
|
98
98
|
db: db,
|
|
99
99
|
regimes: ["hipaa"],
|
|
100
100
|
namespace: "compliance",
|
|
@@ -120,7 +120,7 @@ function testIndexNamesValidSql() {
|
|
|
120
120
|
b.mail.journal.create({
|
|
121
121
|
storage: { putObject: async function () {} },
|
|
122
122
|
vault: { seal: function () {} },
|
|
123
|
-
legalHold: {
|
|
123
|
+
legalHold: { isHeld: function () { return false; } },
|
|
124
124
|
db: db,
|
|
125
125
|
regimes: ["hipaa"],
|
|
126
126
|
namespace: "compliance",
|
|
@@ -154,11 +154,14 @@ function testSealUnsealRoundTrip() {
|
|
|
154
154
|
var db = {
|
|
155
155
|
runSql: function (sql, args) {
|
|
156
156
|
if (/INSERT INTO/.test(sql)) {
|
|
157
|
+
// INSERT value order (b.sql binds in object-key order): journal_id,
|
|
158
|
+
// direction, actor_id, message_id, archived_at, size_bytes, regimes,
|
|
159
|
+
// floor_until, legal_hold, storage_key, sealed_payload.
|
|
157
160
|
insertedRow = {
|
|
158
161
|
journal_id: args[0], direction: args[1], actor_id: args[2],
|
|
159
162
|
message_id: args[3], archived_at: args[4], size_bytes: args[5],
|
|
160
|
-
regimes: args[6], floor_until: args[7], legal_hold:
|
|
161
|
-
storage_key: args[
|
|
163
|
+
regimes: args[6], floor_until: args[7], legal_hold: args[8],
|
|
164
|
+
storage_key: args[9], sealed_payload: args[10],
|
|
162
165
|
};
|
|
163
166
|
return [];
|
|
164
167
|
}
|
|
@@ -172,7 +175,7 @@ function testSealUnsealRoundTrip() {
|
|
|
172
175
|
};
|
|
173
176
|
var j = b.mail.journal.create({
|
|
174
177
|
storage: storage, vault: fakeVault,
|
|
175
|
-
legalHold: {
|
|
178
|
+
legalHold: { isHeld: function () { return false; } },
|
|
176
179
|
db: db, regimes: ["hipaa"], namespace: "rt",
|
|
177
180
|
});
|
|
178
181
|
return j.record({
|
|
@@ -200,6 +203,56 @@ function testSealUnsealRoundTrip() {
|
|
|
200
203
|
});
|
|
201
204
|
}
|
|
202
205
|
|
|
206
|
+
// B8c: the real b.legalHold handle exposes isHeld, NOT isOnHold. create()
|
|
207
|
+
// required isOnHold, so the documented `legalHold: b.legalHold` wiring threw
|
|
208
|
+
// mail-journal/bad-legal-hold and the handle could never be used; the legal_hold
|
|
209
|
+
// flag was also hardcoded 0, never derived from the registry. Fix: require
|
|
210
|
+
// isHeld and auto-flag an entry whose actor is under an active hold. The prior
|
|
211
|
+
// tests stubbed { isOnHold } — fabricating the broken contract — so they never
|
|
212
|
+
// exercised the real handle's shape; this drives the actual consumer contract.
|
|
213
|
+
async function testRealLegalHoldContractAndAutoFlag() {
|
|
214
|
+
var fakeDb = { prepare: function () {
|
|
215
|
+
return { get: function () { return null; }, run: function () {}, all: function () { return []; } };
|
|
216
|
+
} };
|
|
217
|
+
var realHandle = b.legalHold.create({ db: fakeDb, audit: false });
|
|
218
|
+
check("a real b.legalHold handle exposes isHeld (the method create() consumes)",
|
|
219
|
+
typeof realHandle.isHeld === "function");
|
|
220
|
+
check("a real b.legalHold handle does NOT expose the old isOnHold name",
|
|
221
|
+
typeof realHandle.isOnHold === "undefined");
|
|
222
|
+
|
|
223
|
+
var heldActor = "held-subject";
|
|
224
|
+
var legalHold = { isHeld: function (sid) { return sid === heldActor; } };
|
|
225
|
+
var inserted = [];
|
|
226
|
+
var fakeVault = { seal: function (s) { return "vault:" + s; }, unseal: function (s) { return s.slice(6); } };
|
|
227
|
+
var storage = { putObject: async function () {} };
|
|
228
|
+
var db = { runSql: function (sqlText, args) { if (/INSERT INTO/.test(sqlText)) inserted.push(args); return []; } };
|
|
229
|
+
|
|
230
|
+
var j = b.mail.journal.create({
|
|
231
|
+
storage: storage, vault: fakeVault, legalHold: legalHold, db: db,
|
|
232
|
+
regimes: ["hipaa"], namespace: "hold",
|
|
233
|
+
});
|
|
234
|
+
// legal_hold is the 9th bound value (index 8) — see the INSERT order above.
|
|
235
|
+
await j.record({ direction: "inbound", actorId: heldActor, messageId: "<h@x>",
|
|
236
|
+
headers: {}, envelope: {}, bodyBytes: Buffer.from("x") });
|
|
237
|
+
check("entry whose actor is on legal hold is auto-flagged (legal_hold = 1)",
|
|
238
|
+
inserted.length === 1 && inserted[0][8] === 1);
|
|
239
|
+
await j.record({ direction: "inbound", actorId: "free-subject", messageId: "<f@x>",
|
|
240
|
+
headers: {}, envelope: {}, bodyBytes: Buffer.from("y") });
|
|
241
|
+
check("entry whose actor is NOT held is legal_hold = 0",
|
|
242
|
+
inserted.length === 2 && inserted[1][8] === 0);
|
|
243
|
+
|
|
244
|
+
// create() must REJECT a legacy { isOnHold } handle (the wrong contract).
|
|
245
|
+
var threw = null;
|
|
246
|
+
try {
|
|
247
|
+
b.mail.journal.create({
|
|
248
|
+
storage: storage, vault: fakeVault, db: db, regimes: ["hipaa"], namespace: "legacy",
|
|
249
|
+
legalHold: { isOnHold: function () { return false; } },
|
|
250
|
+
});
|
|
251
|
+
} catch (e) { threw = e; }
|
|
252
|
+
check("create() rejects a handle exposing only the legacy isOnHold",
|
|
253
|
+
threw && (threw.code || "").indexOf("mail-journal/bad-legal-hold") === 0);
|
|
254
|
+
}
|
|
255
|
+
|
|
203
256
|
async function run() {
|
|
204
257
|
testSurface();
|
|
205
258
|
testBadInput();
|
|
@@ -207,6 +260,7 @@ async function run() {
|
|
|
207
260
|
testCreatesIndexTable();
|
|
208
261
|
testIndexNamesValidSql();
|
|
209
262
|
await testSealUnsealRoundTrip();
|
|
263
|
+
await testRealLegalHoldContractAndAutoFlag();
|
|
210
264
|
}
|
|
211
265
|
|
|
212
266
|
module.exports = { run: run };
|
|
@@ -99,7 +99,7 @@ function testSurface() {
|
|
|
99
99
|
async function testRoundTrip() {
|
|
100
100
|
var cert = _makeCert();
|
|
101
101
|
var mdoc = await _makeMdoc(cert);
|
|
102
|
-
var out = await b.mdoc.verifyIssuerSigned(mdoc, { algorithms: ["ES256"], expectedDocType: DOCTYPE });
|
|
102
|
+
var out = await b.mdoc.verifyIssuerSigned(mdoc, { algorithms: ["ES256"], expectedDocType: DOCTYPE, trustAnchorsPem: cert.pem });
|
|
103
103
|
check("verify: docType", out.docType === DOCTYPE);
|
|
104
104
|
check("verify: alg reported", out.alg === "ES256");
|
|
105
105
|
check("verify: digestAlgorithm", out.digestAlgorithm === "SHA-256");
|
|
@@ -125,7 +125,7 @@ async function testDigestAndSignatureRefusals() {
|
|
|
125
125
|
items[0] = new cbor.Tag(24, cbor.encode(inner0));
|
|
126
126
|
var tampered = cbor.encode(top);
|
|
127
127
|
var e1 = null;
|
|
128
|
-
try { await b.mdoc.verifyIssuerSigned(tampered, { algorithms: ["ES256"] }); } catch (e) { e1 = e; }
|
|
128
|
+
try { await b.mdoc.verifyIssuerSigned(tampered, { algorithms: ["ES256"], trustAnchorsPem: cert.pem }); } catch (e) { e1 = e; }
|
|
129
129
|
check("verify: tampered element refused (digest-mismatch)", e1 && e1.code === "mdoc/digest-mismatch");
|
|
130
130
|
|
|
131
131
|
// Tamper the COSE signature deterministically (flip a byte in the
|
|
@@ -135,19 +135,19 @@ async function testDigestAndSignatureRefusals() {
|
|
|
135
135
|
ia[3] = Buffer.from(ia[3]); ia[3][0] ^= 0xff;
|
|
136
136
|
var bad = cbor.encode(top2);
|
|
137
137
|
var e2 = null;
|
|
138
|
-
try { await b.mdoc.verifyIssuerSigned(bad, { algorithms: ["ES256"] }); } catch (e) { e2 = e; }
|
|
138
|
+
try { await b.mdoc.verifyIssuerSigned(bad, { algorithms: ["ES256"], trustAnchorsPem: cert.pem }); } catch (e) { e2 = e; }
|
|
139
139
|
check("verify: tampered COSE signature refused", e2 && e2.code === "cose/bad-signature");
|
|
140
140
|
|
|
141
141
|
// A malformed x5chain certificate surfaces a clean error (not raw OpenSSL).
|
|
142
142
|
var top3 = cbor.decode(mdoc, { allowedTags: [0, 1, 24, 1004] });
|
|
143
143
|
top3.get("issuerAuth")[1].set(33, Buffer.from([0x30, 0x03, 0x02, 0x01, 0x01]));
|
|
144
144
|
var e6 = null;
|
|
145
|
-
try { await b.mdoc.verifyIssuerSigned(cbor.encode(top3), { algorithms: ["ES256"] }); } catch (e) { e6 = e; }
|
|
145
|
+
try { await b.mdoc.verifyIssuerSigned(cbor.encode(top3), { algorithms: ["ES256"], trustAnchorsPem: cert.pem }); } catch (e) { e6 = e; }
|
|
146
146
|
check("verify: malformed x5chain cert refused cleanly", e6 && e6.code === "mdoc/bad-cert");
|
|
147
147
|
|
|
148
148
|
// alg outside allowlist
|
|
149
149
|
var e3 = null;
|
|
150
|
-
try { await b.mdoc.verifyIssuerSigned(mdoc, { algorithms: ["EdDSA"] }); } catch (e) { e3 = e; }
|
|
150
|
+
try { await b.mdoc.verifyIssuerSigned(mdoc, { algorithms: ["EdDSA"], trustAnchorsPem: cert.pem }); } catch (e) { e3 = e; }
|
|
151
151
|
check("verify: alg outside allowlist refused", e3 && (e3.code === "cose/alg-not-allowed"));
|
|
152
152
|
}
|
|
153
153
|
|
|
@@ -157,40 +157,43 @@ async function testValidityAndDocType() {
|
|
|
157
157
|
// expired (valid window 10 days ago .. 1 day ago)
|
|
158
158
|
var expired = await _makeMdoc(cert, { validFrom: Date.now() - 86400000 * 10, validUntil: Date.now() - 86400000 });
|
|
159
159
|
var e1 = null;
|
|
160
|
-
try { await b.mdoc.verifyIssuerSigned(expired, { algorithms: ["ES256"] }); } catch (e) { e1 = e; }
|
|
160
|
+
try { await b.mdoc.verifyIssuerSigned(expired, { algorithms: ["ES256"], trustAnchorsPem: cert.pem }); } catch (e) { e1 = e; }
|
|
161
161
|
check("verify: expired credential refused", e1 && e1.code === "mdoc/expired");
|
|
162
162
|
|
|
163
163
|
// not yet valid
|
|
164
164
|
var future = await _makeMdoc(cert, { validFrom: Date.now() + 86400000 * 30 });
|
|
165
165
|
var e2 = null;
|
|
166
|
-
try { await b.mdoc.verifyIssuerSigned(future, { algorithms: ["ES256"] }); } catch (e) { e2 = e; }
|
|
166
|
+
try { await b.mdoc.verifyIssuerSigned(future, { algorithms: ["ES256"], trustAnchorsPem: cert.pem }); } catch (e) { e2 = e; }
|
|
167
167
|
check("verify: not-yet-valid credential refused", e2 && e2.code === "mdoc/not-yet-valid");
|
|
168
168
|
|
|
169
169
|
// opts.at within window accepts an otherwise-expired credential
|
|
170
|
-
|
|
170
|
+
// allowUntrustedIssuer (not trustAnchorsPem): this asserts the MSO validity
|
|
171
|
+
// window honors opts.at — anchoring would instead chain-validate the cert at
|
|
172
|
+
// the past `at`, where the (created-now) cert isn't yet valid.
|
|
173
|
+
var ok = await b.mdoc.verifyIssuerSigned(expired, { algorithms: ["ES256"], at: new Date(Date.now() - 86400000 * 2), allowUntrustedIssuer: true });
|
|
171
174
|
check("verify: opts.at within window accepts", ok.docType === DOCTYPE);
|
|
172
175
|
|
|
173
176
|
// docType mismatch
|
|
174
177
|
var e3 = null;
|
|
175
|
-
try { await b.mdoc.verifyIssuerSigned(await _makeMdoc(cert), { algorithms: ["ES256"], expectedDocType: "org.iso.18013.5.1.photoID" }); } catch (e) { e3 = e; }
|
|
178
|
+
try { await b.mdoc.verifyIssuerSigned(await _makeMdoc(cert), { algorithms: ["ES256"], expectedDocType: "org.iso.18013.5.1.photoID", trustAnchorsPem: cert.pem }); } catch (e) { e3 = e; }
|
|
176
179
|
check("verify: docType mismatch refused", e3 && e3.code === "mdoc/doctype-mismatch");
|
|
177
180
|
|
|
178
181
|
// malformed validUntil (a non-date) fails closed
|
|
179
182
|
var badValidity = await _makeMdoc(cert, { validUntilRaw: new cbor.Tag(0, "not-a-date") });
|
|
180
183
|
var e4 = null;
|
|
181
|
-
try { await b.mdoc.verifyIssuerSigned(badValidity, { algorithms: ["ES256"] }); } catch (e) { e4 = e; }
|
|
184
|
+
try { await b.mdoc.verifyIssuerSigned(badValidity, { algorithms: ["ES256"], trustAnchorsPem: cert.pem }); } catch (e) { e4 = e; }
|
|
182
185
|
check("verify: malformed validUntil refused (fail closed)", e4 && e4.code === "mdoc/bad-validity");
|
|
183
186
|
|
|
184
187
|
// invalid opts.at refused (lesson carried from b.tsa / b.vc)
|
|
185
188
|
var e5 = null;
|
|
186
|
-
try { await b.mdoc.verifyIssuerSigned(await _makeMdoc(cert), { algorithms: ["ES256"], at: new Date("nope") }); } catch (e) { e5 = e; }
|
|
189
|
+
try { await b.mdoc.verifyIssuerSigned(await _makeMdoc(cert), { algorithms: ["ES256"], at: new Date("nope"), trustAnchorsPem: cert.pem }); } catch (e) { e5 = e; }
|
|
187
190
|
check("verify: invalid opts.at refused", e5 && e5.code === "mdoc/bad-at");
|
|
188
191
|
|
|
189
192
|
// Two signed IssuerSignedItems with the same elementIdentifier (each
|
|
190
193
|
// with a valid MSO digest) is ambiguous → fail closed, not last-wins.
|
|
191
194
|
var dup = await _makeMdoc(cert, { elements: [["family_name", "Doe"], ["family_name", "Roe"]] });
|
|
192
195
|
var e6 = null;
|
|
193
|
-
try { await b.mdoc.verifyIssuerSigned(dup, { algorithms: ["ES256"] }); } catch (e) { e6 = e; }
|
|
196
|
+
try { await b.mdoc.verifyIssuerSigned(dup, { algorithms: ["ES256"], trustAnchorsPem: cert.pem }); } catch (e) { e6 = e; }
|
|
194
197
|
check("verify: duplicate elementIdentifier refused", e6 && e6.code === "mdoc/duplicate-element");
|
|
195
198
|
}
|
|
196
199
|
|
|
@@ -211,7 +214,7 @@ async function testChainAndInputGuards() {
|
|
|
211
214
|
|
|
212
215
|
// garbage input → not CBOR / malformed
|
|
213
216
|
var e3 = null;
|
|
214
|
-
try { await b.mdoc.verifyIssuerSigned(Buffer.from([0x00, 0x01]), { algorithms: ["ES256"] }); } catch (e) { e3 = e; }
|
|
217
|
+
try { await b.mdoc.verifyIssuerSigned(Buffer.from([0x00, 0x01]), { algorithms: ["ES256"], trustAnchorsPem: cert.pem }); } catch (e) { e3 = e; }
|
|
215
218
|
check("verify: garbage input refused", e3 && (e3.code === "mdoc/malformed" || e3.code === "mdoc/bad-input" || /cbor/.test(e3.code || "")));
|
|
216
219
|
|
|
217
220
|
// missing algorithms
|
|
@@ -226,7 +229,7 @@ async function testDeviceAuth() {
|
|
|
226
229
|
var deviceJwk = device.publicKey.export({ format: "jwk" });
|
|
227
230
|
// issuer-signed mdoc carrying the device key in the MSO
|
|
228
231
|
var mdoc = await _makeMdoc(cert, { deviceJwk: deviceJwk });
|
|
229
|
-
var issuer = await b.mdoc.verifyIssuerSigned(mdoc, { algorithms: ["ES256"] });
|
|
232
|
+
var issuer = await b.mdoc.verifyIssuerSigned(mdoc, { algorithms: ["ES256"], trustAnchorsPem: cert.pem });
|
|
230
233
|
check("verifyIssuerSigned: returns the deviceKey", issuer.deviceKey instanceof Map || (issuer.deviceKey && typeof issuer.deviceKey === "object"));
|
|
231
234
|
|
|
232
235
|
// build a DeviceSigned: detached COSE_Sign1 over DeviceAuthentication
|
|
@@ -263,12 +266,36 @@ async function testDeviceAuth() {
|
|
|
263
266
|
check("verifyDeviceAuth: missing sessionTranscript refused", e4 && e4.code === "mdoc/no-session-transcript");
|
|
264
267
|
}
|
|
265
268
|
|
|
269
|
+
async function testTrustAnchorFailClosedByDefault() {
|
|
270
|
+
// The signer cert rides in the attacker-supplied x5chain, so verifying the
|
|
271
|
+
// COSE_Sign1 against that embedded key only proves the MSO is self-consistent.
|
|
272
|
+
// A forged mDL (attacker keypair + self-signed leaf + self-signed MSO) must be
|
|
273
|
+
// REFUSED by default — issuer trust is not opt-in.
|
|
274
|
+
var forged = _makeCert(); // an arbitrary self-signed issuer
|
|
275
|
+
var mdoc = await _makeMdoc(forged);
|
|
276
|
+
|
|
277
|
+
var e1 = null;
|
|
278
|
+
try { await b.mdoc.verifyIssuerSigned(mdoc, { algorithms: ["ES256"] }); } catch (e) { e1 = e; }
|
|
279
|
+
check("verify: refuses a self-signed mDL with no trust anchor (no fail-open)",
|
|
280
|
+
e1 && e1.code === "mdoc/trust-anchors-required");
|
|
281
|
+
|
|
282
|
+
// The explicit, audited opt-out accepts it but marks it unauthenticated.
|
|
283
|
+
var out = await b.mdoc.verifyIssuerSigned(mdoc, { algorithms: ["ES256"], allowUntrustedIssuer: true });
|
|
284
|
+
check("verify: allowUntrustedIssuer opt-out accepts but flags issuerTrusted:false",
|
|
285
|
+
out.docType === DOCTYPE && out.issuerTrusted === false);
|
|
286
|
+
|
|
287
|
+
// Anchored to its own cert → trusted.
|
|
288
|
+
var anchored = await b.mdoc.verifyIssuerSigned(mdoc, { algorithms: ["ES256"], trustAnchorsPem: forged.pem });
|
|
289
|
+
check("verify: anchored verification reports issuerTrusted:true", anchored.issuerTrusted === true);
|
|
290
|
+
}
|
|
291
|
+
|
|
266
292
|
async function run() {
|
|
267
293
|
testSurface();
|
|
268
294
|
await testRoundTrip();
|
|
269
295
|
await testDigestAndSignatureRefusals();
|
|
270
296
|
await testValidityAndDocType();
|
|
271
297
|
await testChainAndInputGuards();
|
|
298
|
+
await testTrustAnchorFailClosedByDefault();
|
|
272
299
|
await testDeviceAuth();
|
|
273
300
|
}
|
|
274
301
|
|
|
@@ -94,6 +94,67 @@ async function run() {
|
|
|
94
94
|
req5.socket = { remoteAddress: "192.168.1.1" };
|
|
95
95
|
var r5 = await _runMw(fence403, req5);
|
|
96
96
|
check("custom denyStatus = 403", r5.res._captured().status === 403);
|
|
97
|
+
|
|
98
|
+
// ---- X-Forwarded-For spoofing (CWE-290/348) ----
|
|
99
|
+
// A bare trustProxy honored the leftmost XFF hop, which a direct caller
|
|
100
|
+
// fully controls — forging an allowed IP walked through the gate. The gate
|
|
101
|
+
// now refuses that spoofable config at construction and peer-gates XFF.
|
|
102
|
+
|
|
103
|
+
// Construction fails closed: trustProxy without trustedProxies/resolver.
|
|
104
|
+
var threwBareTrust = null;
|
|
105
|
+
try { b.middleware.networkAllowlist({ paths: ["/admin"], allowedCidrs: ["203.0.113.0/24"], trustProxy: true }); }
|
|
106
|
+
catch (e) { threwBareTrust = e; }
|
|
107
|
+
check("create: bare trustProxy refused (spoofable gate)", threwBareTrust !== null);
|
|
108
|
+
var threwBareNum = null;
|
|
109
|
+
try { b.middleware.networkAllowlist({ paths: ["/admin"], allowedCidrs: ["203.0.113.0/24"], trustProxy: 1 }); }
|
|
110
|
+
catch (e) { threwBareNum = e; }
|
|
111
|
+
check("create: bare trustProxy:N refused too", threwBareNum !== null);
|
|
112
|
+
var threwBadResolver = null;
|
|
113
|
+
try { b.middleware.networkAllowlist({ paths: ["/admin"], allowedCidrs: ["203.0.113.0/24"], clientIpResolver: 123 }); }
|
|
114
|
+
catch (e) { threwBadResolver = e; }
|
|
115
|
+
check("create: non-function clientIpResolver refused", threwBadResolver !== null);
|
|
116
|
+
|
|
117
|
+
function _xffReq(sock, xff) {
|
|
118
|
+
var rq = _mockReq({ url: "/admin/x", method: "GET" });
|
|
119
|
+
rq.pathname = "/admin/x";
|
|
120
|
+
rq.socket = { remoteAddress: sock };
|
|
121
|
+
rq.headers["x-forwarded-for"] = xff;
|
|
122
|
+
return rq;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Peer-gated via trustedProxies.
|
|
126
|
+
var pgFence = b.middleware.networkAllowlist({
|
|
127
|
+
paths: ["/admin"],
|
|
128
|
+
allowedCidrs: ["203.0.113.0/24"],
|
|
129
|
+
trustedProxies: ["10.0.0.0/8"],
|
|
130
|
+
});
|
|
131
|
+
// Direct attacker (untrusted peer) forging an allowed IP → DENIED.
|
|
132
|
+
var rSpoof = await _runMw(pgFence, _xffReq("198.51.100.66", "203.0.113.7"));
|
|
133
|
+
check("spoofed XFF from untrusted peer → blocked", rSpoof.next === false);
|
|
134
|
+
// Trusted proxy forwarding a real allowed client → allowed.
|
|
135
|
+
var rTrusted = await _runMw(pgFence, _xffReq("10.0.0.9", "203.0.113.7"));
|
|
136
|
+
check("trusted proxy + allowed client → next()", rTrusted.next === true);
|
|
137
|
+
// Trusted proxy forwarding a non-allowed client → blocked (real IP honored).
|
|
138
|
+
var rTrustedBad = await _runMw(pgFence, _xffReq("10.0.0.9", "198.51.100.66"));
|
|
139
|
+
check("trusted proxy + non-allowed client → blocked", rTrustedBad.next === false);
|
|
140
|
+
|
|
141
|
+
// clientIpResolver: operator owns resolution entirely.
|
|
142
|
+
var resFence = b.middleware.networkAllowlist({
|
|
143
|
+
paths: ["/admin"],
|
|
144
|
+
allowedCidrs: ["203.0.113.0/24"],
|
|
145
|
+
clientIpResolver: function (rq) { return rq.headers["true-client-ip"]; },
|
|
146
|
+
});
|
|
147
|
+
var resReq = _mockReq({ url: "/admin/x", method: "GET" });
|
|
148
|
+
resReq.pathname = "/admin/x";
|
|
149
|
+
resReq.socket = { remoteAddress: "1.2.3.4" };
|
|
150
|
+
resReq.headers["true-client-ip"] = "203.0.113.50";
|
|
151
|
+
var rRes = await _runMw(resFence, resReq);
|
|
152
|
+
check("clientIpResolver resolves allowed client → next()", rRes.next === true);
|
|
153
|
+
|
|
154
|
+
// Default (no trust config): socket address governs, forged XFF ignored.
|
|
155
|
+
var defFence = b.middleware.networkAllowlist({ paths: ["/admin"], allowedCidrs: ["203.0.113.0/24"] });
|
|
156
|
+
var rDefSpoof = await _runMw(defFence, _xffReq("198.51.100.66", "203.0.113.7"));
|
|
157
|
+
check("default: forged XFF ignored, attacker socket blocked", rDefSpoof.next === false);
|
|
97
158
|
}
|
|
98
159
|
|
|
99
160
|
module.exports = { run: run };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* b.objectStore conditional-GET Range header (shared across sigv4 / gcs / azure).
|
|
4
|
+
* The documented + shipped contract is `range: [start, end]` (array) — see
|
|
5
|
+
* b.archive.adapters.objectStore and b.backup. Reading .start/.end off an array
|
|
6
|
+
* yielded "bytes=undefined-undefined", which every store IGNORES, silently
|
|
7
|
+
* returning the FULL object instead of the requested byte range.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
var helpers = require("../helpers");
|
|
11
|
+
var check = helpers.check;
|
|
12
|
+
|
|
13
|
+
var httpReq = require("../../lib/object-store/http-request");
|
|
14
|
+
var apply = httpReq.applyConditionalGetHeaders;
|
|
15
|
+
|
|
16
|
+
function run() {
|
|
17
|
+
// Array contract [start, end] → correct bytes= header (the real caller shape).
|
|
18
|
+
var t1 = apply({}, { range: [0, 1023] }, "Range");
|
|
19
|
+
check("array range [0,1023] → bytes=0-1023", t1.Range === "bytes=0-1023");
|
|
20
|
+
|
|
21
|
+
// Honors the backend-specific range header name (Azure x-ms-range).
|
|
22
|
+
var t2 = apply({}, { range: [100, 199] }, "x-ms-range");
|
|
23
|
+
check("array range honors azure header name", t2["x-ms-range"] === "bytes=100-199");
|
|
24
|
+
|
|
25
|
+
// { start, end } object form still accepted (compatibility).
|
|
26
|
+
var t3 = apply({}, { range: { start: 5, end: 9 } }, "Range");
|
|
27
|
+
check("object {start,end} compat → bytes=5-9", t3.Range === "bytes=5-9");
|
|
28
|
+
|
|
29
|
+
// No range → no Range header at all.
|
|
30
|
+
var t4 = apply({}, {}, "Range");
|
|
31
|
+
check("no range → no Range header emitted", !("Range" in t4));
|
|
32
|
+
|
|
33
|
+
// Malformed range must throw, not emit a garbage header that over-fetches.
|
|
34
|
+
var threw = null;
|
|
35
|
+
try { apply({}, { range: [undefined, 10] }, "Range"); } catch (e) { threw = e; }
|
|
36
|
+
check("malformed range ([undefined,10]) throws, not bytes=undefined",
|
|
37
|
+
threw && /range must be|INVALID_RANGE/.test((threw.message || "") + " " + (threw.code || "")));
|
|
38
|
+
var threwInv = null;
|
|
39
|
+
try { apply({}, { range: [10, 5] }, "Range"); } catch (e) { threwInv = e; }
|
|
40
|
+
check("inverted range (end < start) throws", threwInv !== null);
|
|
41
|
+
var threwNeg = null;
|
|
42
|
+
try { apply({}, { range: [-1, 5] }, "Range"); } catch (e) { threwNeg = e; }
|
|
43
|
+
check("negative start throws", threwNeg !== null);
|
|
44
|
+
|
|
45
|
+
console.log("[object-store-range-header] OK — " + helpers.getChecks() + " checks passed");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = { run: run };
|
|
49
|
+
if (require.main === module) {
|
|
50
|
+
try { run(); } catch (e) { console.error("FAIL: " + helpers.formatErr(e)); process.exit(1); }
|
|
51
|
+
}
|
|
@@ -78,6 +78,24 @@ async function run() {
|
|
|
78
78
|
check("redactAttrs scrubs sensitive keys", ra.authorization === "[REDACTED]" && ra.password === "[REDACTED]");
|
|
79
79
|
check("redactAttrs keeps non-sensitive keys", ra["http.method"] === CONTROL_VAL);
|
|
80
80
|
|
|
81
|
+
// A sensitive KEY whose VALUE is an array or object must STILL be scrubbed —
|
|
82
|
+
// the field-name rule has to fire before descending into the composite, or an
|
|
83
|
+
// opaque token reaches the collector verbatim (CWE-532). OTLP serializes
|
|
84
|
+
// arrays/objects natively, so these are real wire shapes.
|
|
85
|
+
var raComposite = b.observability.redactAttrs({
|
|
86
|
+
authorization: ["Bearer " + SECRET_TOKEN], password: { nested: SECRET_PW },
|
|
87
|
+
session: [SECRET_API], "http.methods": ["GET", "POST"],
|
|
88
|
+
});
|
|
89
|
+
check("redactAttrs scrubs a sensitive key with an ARRAY value", raComposite.authorization === "[REDACTED]");
|
|
90
|
+
check("redactAttrs scrubs a sensitive key with an OBJECT value", raComposite.password === "[REDACTED]");
|
|
91
|
+
check("redactAttrs scrubs a sensitive key with a single-elem array", raComposite.session === "[REDACTED]");
|
|
92
|
+
check("redactAttrs preserves a NON-sensitive array value",
|
|
93
|
+
Array.isArray(raComposite["http.methods"]) && raComposite["http.methods"][1] === "POST");
|
|
94
|
+
// Value-shape detectors still run inside a non-sensitive composite.
|
|
95
|
+
var raCc = b.observability.redactAttrs({ cards: ["4111 1111 1111 1111"] });
|
|
96
|
+
check("redactAttrs still detects a CC inside a non-sensitive array",
|
|
97
|
+
Array.isArray(raCc.cards) && raCc.cards[0] === "[REDACTED-CC]");
|
|
98
|
+
|
|
81
99
|
var jsonWire = await captureBody("json");
|
|
82
100
|
check("json: bearer token redacted on the OTLP wire", jsonWire.indexOf(SECRET_TOKEN) === -1);
|
|
83
101
|
check("json: password redacted on the OTLP wire", jsonWire.indexOf(SECRET_PW) === -1);
|
|
@@ -91,6 +109,23 @@ async function run() {
|
|
|
91
109
|
check("protobuf: api key redacted on the OTLP wire", protoWire.indexOf(SECRET_API) === -1);
|
|
92
110
|
check("protobuf: non-sensitive control attribute survives", protoWire.indexOf(CONTROL_VAL) !== -1);
|
|
93
111
|
|
|
112
|
+
// span status.message + name are EGRESS too — the tracer's canonical
|
|
113
|
+
// setStatus("error", e.message) routinely carries a connection string. Both
|
|
114
|
+
// the message AND the name now route through the telemetry redactor, so a
|
|
115
|
+
// value-shape secret (connection string) in either is scrubbed on the wire.
|
|
116
|
+
var CONN = "connect failed: postgres://app:" + SECRET_PW + "@db:5432/prod";
|
|
117
|
+
var spanJson = JSON.stringify(otlp._spanToOtlp({
|
|
118
|
+
traceId: "t", spanId: "s", name: "span " + CONN,
|
|
119
|
+
status: { code: 2, message: CONN }, attributes: {},
|
|
120
|
+
}));
|
|
121
|
+
check("json: span status.message connection-string redacted",
|
|
122
|
+
spanJson.indexOf(SECRET_PW) === -1 && spanJson.indexOf("[REDACTED-CONN-STRING]") !== -1);
|
|
123
|
+
var spanProto = Buffer.from(otlp._spanToProto({
|
|
124
|
+
traceId: "t", spanId: "s", name: "span " + CONN,
|
|
125
|
+
status: { code: 2, message: CONN }, attributes: {},
|
|
126
|
+
})).toString("latin1");
|
|
127
|
+
check("protobuf: span status.message + name connection-string redacted", spanProto.indexOf(SECRET_PW) === -1);
|
|
128
|
+
|
|
94
129
|
// ---- OTLP LOG sinks are EGRESS too: record-meta + resource attrs must redact ----
|
|
95
130
|
// The span/metric exporters redact; the log sinks (HTTP-JSON + gRPC) shipped
|
|
96
131
|
// the same attribute maps to the collector UNREDACTED — a log line carrying a
|