@blamejs/blamejs-shop 0.4.69 → 0.4.70
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2 -0
- package/lib/admin.js +8 -6
- package/lib/asset-manifest.json +1 -1
- package/lib/security-middleware.js +65 -23
- package/lib/storefront.js +10 -7
- package/lib/vendor/MANIFEST.json +319 -267
- package/lib/vendor/blamejs/CHANGELOG.md +2 -0
- package/lib/vendor/blamejs/api-snapshot.json +58 -5
- package/lib/vendor/blamejs/examples/wiki/README.md +1 -1
- package/lib/vendor/blamejs/examples/wiki/docker-compose.prod.yml +9 -8
- package/lib/vendor/blamejs/examples/wiki/docker-compose.yml +10 -9
- package/lib/vendor/blamejs/examples/wiki/env-snapshot.json +4 -3
- package/lib/vendor/blamejs/examples/wiki/lib/build-app.js +33 -17
- package/lib/vendor/blamejs/examples/wiki/routes/admin.js +7 -10
- package/lib/vendor/blamejs/examples/wiki/server.js +5 -4
- package/lib/vendor/blamejs/examples/wiki/test/e2e.js +5 -0
- package/lib/vendor/blamejs/lib/a2a-tasks.js +38 -6
- package/lib/vendor/blamejs/lib/agent-event-bus.js +13 -0
- package/lib/vendor/blamejs/lib/agent-idempotency.js +5 -1
- package/lib/vendor/blamejs/lib/agent-snapshot.js +32 -2
- package/lib/vendor/blamejs/lib/ai-aedt-bias-audit.js +2 -1
- package/lib/vendor/blamejs/lib/ai-content-detect.js +1 -3
- package/lib/vendor/blamejs/lib/ai-frontier-protocol.js +1 -1
- package/lib/vendor/blamejs/lib/ai-model-manifest.js +1 -1
- package/lib/vendor/blamejs/lib/ai-output.js +16 -7
- package/lib/vendor/blamejs/lib/api-snapshot.js +4 -1
- package/lib/vendor/blamejs/lib/app-shutdown.js +7 -1
- package/lib/vendor/blamejs/lib/archive-gz.js +9 -0
- package/lib/vendor/blamejs/lib/archive-read.js +9 -7
- package/lib/vendor/blamejs/lib/archive-tar-read.js +51 -8
- package/lib/vendor/blamejs/lib/archive.js +4 -2
- package/lib/vendor/blamejs/lib/asn1-der.js +70 -22
- package/lib/vendor/blamejs/lib/atomic-file.js +204 -2
- package/lib/vendor/blamejs/lib/audit-chain.js +54 -5
- package/lib/vendor/blamejs/lib/audit-daily-review.js +12 -2
- package/lib/vendor/blamejs/lib/audit-sign.js +2 -1
- package/lib/vendor/blamejs/lib/audit-tools.js +108 -23
- package/lib/vendor/blamejs/lib/audit.js +7 -2
- package/lib/vendor/blamejs/lib/auth/access-lock.js +2 -2
- package/lib/vendor/blamejs/lib/auth/bot-challenge.js +1 -1
- package/lib/vendor/blamejs/lib/auth/ciba.js +43 -4
- package/lib/vendor/blamejs/lib/auth/dpop.js +6 -1
- package/lib/vendor/blamejs/lib/auth/fido-mds3.js +5 -1
- package/lib/vendor/blamejs/lib/auth/jwt.js +2 -2
- package/lib/vendor/blamejs/lib/auth/lockout.js +18 -2
- package/lib/vendor/blamejs/lib/auth/passkey.js +1 -1
- package/lib/vendor/blamejs/lib/auth/password.js +1 -1
- package/lib/vendor/blamejs/lib/auth/saml.js +33 -13
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc.js +24 -4
- package/lib/vendor/blamejs/lib/auth/status-list.js +14 -2
- package/lib/vendor/blamejs/lib/auth/step-up.js +9 -1
- package/lib/vendor/blamejs/lib/auth-bot-challenge.js +21 -2
- package/lib/vendor/blamejs/lib/backup/bundle.js +7 -2
- package/lib/vendor/blamejs/lib/backup/crypto.js +23 -8
- package/lib/vendor/blamejs/lib/backup/index.js +41 -20
- package/lib/vendor/blamejs/lib/backup/manifest.js +7 -1
- package/lib/vendor/blamejs/lib/break-glass.js +41 -22
- package/lib/vendor/blamejs/lib/cbor.js +34 -11
- package/lib/vendor/blamejs/lib/cdn-cache-control.js +7 -3
- package/lib/vendor/blamejs/lib/cert.js +5 -3
- package/lib/vendor/blamejs/lib/cli.js +5 -1
- package/lib/vendor/blamejs/lib/cloud-events.js +3 -2
- package/lib/vendor/blamejs/lib/cluster-storage.js +7 -3
- package/lib/vendor/blamejs/lib/codepoint-class.js +17 -0
- package/lib/vendor/blamejs/lib/compliance-eaa.js +1 -1
- package/lib/vendor/blamejs/lib/compliance-sanctions.js +9 -7
- package/lib/vendor/blamejs/lib/compliance.js +1 -1
- package/lib/vendor/blamejs/lib/config-drift.js +22 -8
- package/lib/vendor/blamejs/lib/content-credentials.js +11 -8
- package/lib/vendor/blamejs/lib/content-digest.js +11 -4
- package/lib/vendor/blamejs/lib/cookies.js +10 -2
- package/lib/vendor/blamejs/lib/cose.js +20 -0
- package/lib/vendor/blamejs/lib/crdt.js +2 -1
- package/lib/vendor/blamejs/lib/crypto-field.js +29 -23
- package/lib/vendor/blamejs/lib/crypto.js +18 -4
- package/lib/vendor/blamejs/lib/csp.js +4 -0
- package/lib/vendor/blamejs/lib/daemon.js +4 -1
- package/lib/vendor/blamejs/lib/data-act.js +27 -4
- package/lib/vendor/blamejs/lib/db-file-lifecycle.js +14 -6
- package/lib/vendor/blamejs/lib/db-query.js +69 -9
- package/lib/vendor/blamejs/lib/db.js +32 -15
- package/lib/vendor/blamejs/lib/dora.js +43 -13
- package/lib/vendor/blamejs/lib/dr-runbook.js +1 -1
- package/lib/vendor/blamejs/lib/dsa.js +2 -1
- package/lib/vendor/blamejs/lib/dsr.js +22 -8
- package/lib/vendor/blamejs/lib/early-hints.js +19 -0
- package/lib/vendor/blamejs/lib/eat.js +5 -1
- package/lib/vendor/blamejs/lib/external-db.js +60 -4
- package/lib/vendor/blamejs/lib/fda-21cfr11.js +30 -5
- package/lib/vendor/blamejs/lib/flag-providers.js +6 -2
- package/lib/vendor/blamejs/lib/forms.js +1 -1
- package/lib/vendor/blamejs/lib/gate-contract.js +46 -5
- package/lib/vendor/blamejs/lib/gdpr-ropa.js +18 -9
- package/lib/vendor/blamejs/lib/graphql-federation.js +17 -4
- package/lib/vendor/blamejs/lib/guard-all.js +2 -2
- package/lib/vendor/blamejs/lib/guard-dsn.js +1 -1
- package/lib/vendor/blamejs/lib/guard-envelope.js +1 -1
- package/lib/vendor/blamejs/lib/guard-html.js +9 -11
- package/lib/vendor/blamejs/lib/guard-imap-command.js +1 -1
- package/lib/vendor/blamejs/lib/guard-jmap.js +1 -1
- package/lib/vendor/blamejs/lib/guard-json.js +14 -6
- package/lib/vendor/blamejs/lib/guard-mail-move.js +1 -1
- package/lib/vendor/blamejs/lib/guard-managesieve-command.js +1 -1
- package/lib/vendor/blamejs/lib/guard-pop3-command.js +1 -1
- package/lib/vendor/blamejs/lib/guard-smtp-command.js +1 -1
- package/lib/vendor/blamejs/lib/guard-svg.js +8 -9
- package/lib/vendor/blamejs/lib/html-balance.js +7 -3
- package/lib/vendor/blamejs/lib/http-client-cookie-jar.js +33 -12
- package/lib/vendor/blamejs/lib/http-client.js +225 -53
- package/lib/vendor/blamejs/lib/iab-tcf.js +3 -2
- package/lib/vendor/blamejs/lib/importmap-integrity.js +41 -1
- package/lib/vendor/blamejs/lib/incident-report.js +9 -6
- package/lib/vendor/blamejs/lib/json-patch.js +1 -1
- package/lib/vendor/blamejs/lib/json-path.js +24 -3
- package/lib/vendor/blamejs/lib/jtd.js +2 -2
- package/lib/vendor/blamejs/lib/legal-hold.js +24 -8
- package/lib/vendor/blamejs/lib/log.js +2 -2
- package/lib/vendor/blamejs/lib/mail-agent.js +2 -2
- package/lib/vendor/blamejs/lib/mail-arf.js +1 -1
- package/lib/vendor/blamejs/lib/mail-auth.js +3 -3
- package/lib/vendor/blamejs/lib/mail-bimi.js +16 -16
- package/lib/vendor/blamejs/lib/mail-bounce.js +3 -3
- package/lib/vendor/blamejs/lib/mail-crypto-smime.js +71 -6
- package/lib/vendor/blamejs/lib/mail-deploy.js +9 -5
- package/lib/vendor/blamejs/lib/mail-greylist.js +2 -4
- package/lib/vendor/blamejs/lib/mail-helo.js +2 -4
- package/lib/vendor/blamejs/lib/mail-journal.js +11 -8
- package/lib/vendor/blamejs/lib/mail-mdn.js +8 -4
- package/lib/vendor/blamejs/lib/mail-rbl.js +2 -4
- package/lib/vendor/blamejs/lib/mail-scan.js +3 -5
- package/lib/vendor/blamejs/lib/mail-server-jmap.js +4 -1
- package/lib/vendor/blamejs/lib/mail-server-registry.js +1 -1
- package/lib/vendor/blamejs/lib/mail-server-tls.js +9 -2
- package/lib/vendor/blamejs/lib/mail-spam-score.js +2 -4
- package/lib/vendor/blamejs/lib/mail-store-fts.js +1 -1
- package/lib/vendor/blamejs/lib/mail.js +22 -2
- package/lib/vendor/blamejs/lib/markup-tokenizer.js +24 -0
- package/lib/vendor/blamejs/lib/mcp.js +6 -4
- package/lib/vendor/blamejs/lib/mdoc.js +26 -3
- package/lib/vendor/blamejs/lib/metrics.js +14 -3
- package/lib/vendor/blamejs/lib/middleware/api-encrypt.js +2 -2
- package/lib/vendor/blamejs/lib/middleware/body-parser.js +10 -4
- package/lib/vendor/blamejs/lib/middleware/bot-guard.js +26 -18
- package/lib/vendor/blamejs/lib/middleware/clear-site-data.js +5 -1
- package/lib/vendor/blamejs/lib/middleware/compression.js +9 -0
- package/lib/vendor/blamejs/lib/middleware/cors.js +32 -23
- package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +60 -21
- package/lib/vendor/blamejs/lib/middleware/daily-byte-quota.js +6 -4
- package/lib/vendor/blamejs/lib/middleware/fetch-metadata.js +28 -4
- package/lib/vendor/blamejs/lib/middleware/network-allowlist.js +61 -30
- package/lib/vendor/blamejs/lib/middleware/rate-limit.js +25 -16
- package/lib/vendor/blamejs/lib/middleware/scim-server.js +2 -1
- package/lib/vendor/blamejs/lib/middleware/security-headers.js +24 -6
- package/lib/vendor/blamejs/lib/middleware/speculation-rules.js +6 -3
- package/lib/vendor/blamejs/lib/middleware/tus-upload.js +2 -2
- package/lib/vendor/blamejs/lib/money.js +1 -1
- package/lib/vendor/blamejs/lib/mtls-ca.js +10 -6
- package/lib/vendor/blamejs/lib/network-dns-resolver.js +1 -1
- package/lib/vendor/blamejs/lib/network-dns.js +9 -2
- package/lib/vendor/blamejs/lib/network-dnssec.js +2 -1
- package/lib/vendor/blamejs/lib/network-smtp-policy.js +23 -5
- package/lib/vendor/blamejs/lib/network-tls.js +27 -5
- package/lib/vendor/blamejs/lib/network-tsig.js +2 -2
- package/lib/vendor/blamejs/lib/nis2-report.js +1 -1
- package/lib/vendor/blamejs/lib/nist-crosswalk.js +1 -1
- package/lib/vendor/blamejs/lib/ntp-check.js +28 -0
- package/lib/vendor/blamejs/lib/numeric-bounds.js +9 -0
- package/lib/vendor/blamejs/lib/object-store/azure-blob.js +1 -2
- package/lib/vendor/blamejs/lib/object-store/gcs-bucket-ops.js +4 -2
- package/lib/vendor/blamejs/lib/object-store/gcs.js +6 -4
- package/lib/vendor/blamejs/lib/object-store/http-put.js +1 -2
- package/lib/vendor/blamejs/lib/object-store/http-request.js +30 -1
- package/lib/vendor/blamejs/lib/object-store/local.js +37 -17
- package/lib/vendor/blamejs/lib/object-store/sigv4.js +1 -2
- package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +20 -4
- package/lib/vendor/blamejs/lib/outbox.js +11 -4
- package/lib/vendor/blamejs/lib/parsers/safe-xml.js +1 -1
- package/lib/vendor/blamejs/lib/parsers/safe-yaml.js +21 -3
- package/lib/vendor/blamejs/lib/queue-local.js +10 -3
- package/lib/vendor/blamejs/lib/redact.js +7 -3
- package/lib/vendor/blamejs/lib/request-helpers.js +201 -23
- package/lib/vendor/blamejs/lib/resource-access-lock.js +3 -3
- package/lib/vendor/blamejs/lib/restore-bundle.js +46 -18
- package/lib/vendor/blamejs/lib/restore-rollback.js +10 -4
- package/lib/vendor/blamejs/lib/restore.js +19 -0
- package/lib/vendor/blamejs/lib/retention.js +20 -4
- package/lib/vendor/blamejs/lib/router.js +17 -4
- package/lib/vendor/blamejs/lib/safe-ical.js +2 -2
- package/lib/vendor/blamejs/lib/safe-icap.js +1 -1
- package/lib/vendor/blamejs/lib/safe-json.js +44 -0
- package/lib/vendor/blamejs/lib/safe-sieve.js +1 -1
- package/lib/vendor/blamejs/lib/safe-vcard.js +1 -1
- package/lib/vendor/blamejs/lib/sandbox-worker.js +6 -0
- package/lib/vendor/blamejs/lib/sandbox.js +1 -1
- package/lib/vendor/blamejs/lib/scheduler.js +17 -1
- package/lib/vendor/blamejs/lib/self-update-standalone-verifier.js +16 -0
- package/lib/vendor/blamejs/lib/session.js +27 -3
- package/lib/vendor/blamejs/lib/sql.js +3 -3
- package/lib/vendor/blamejs/lib/static.js +65 -13
- package/lib/vendor/blamejs/lib/template.js +7 -5
- package/lib/vendor/blamejs/lib/tenant-quota.js +52 -19
- package/lib/vendor/blamejs/lib/tsa.js +5 -2
- package/lib/vendor/blamejs/lib/vault/index.js +5 -0
- package/lib/vendor/blamejs/lib/vault/passphrase-ops.js +22 -26
- package/lib/vendor/blamejs/lib/vault/passphrase-source.js +8 -3
- package/lib/vendor/blamejs/lib/vault/rotate.js +13 -18
- package/lib/vendor/blamejs/lib/vault/seal-pem-file.js +4 -1
- package/lib/vendor/blamejs/lib/vc.js +1 -1
- package/lib/vendor/blamejs/lib/vendor/MANIFEST.json +10 -10
- package/lib/vendor/blamejs/lib/vendor/public-suffix-list.dat +23 -10
- package/lib/vendor/blamejs/lib/vendor/public-suffix-list.data.js +5498 -5494
- package/lib/vendor/blamejs/lib/webhook.js +16 -1
- package/lib/vendor/blamejs/lib/websocket.js +1 -1
- package/lib/vendor/blamejs/lib/worm.js +1 -1
- package/lib/vendor/blamejs/lib/ws-client.js +57 -46
- package/lib/vendor/blamejs/lib/x509-chain.js +44 -0
- package/lib/vendor/blamejs/package.json +1 -1
- package/lib/vendor/blamejs/release-notes/v0.15.14.json +122 -0
- package/lib/vendor/blamejs/scripts/check-vendor-currency.js +119 -4
- package/lib/vendor/blamejs/test/00-primitives.js +5 -1
- package/lib/vendor/blamejs/test/integration/mail-crypto-smime.test.js +18 -4
- package/lib/vendor/blamejs/test/layer-0-primitives/a2a-tasks.test.js +42 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-event-bus.test.js +36 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-idempotency.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-snapshot.test.js +47 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/archive-gz.test.js +24 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/archive-tar-hardening.test.js +160 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/asn1-der.test.js +45 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file-fd-read.test.js +30 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file-open-nofollow.test.js +79 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file-write-excl.test.js +132 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file-write-stream.test.js +181 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-chain-corrupted-anchor.test.js +65 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-chain-incremental-verify.test.js +83 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-daily-review.test.js +48 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-signing-key-rotation.test.js +13 -3
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-verifybundle-tamper.test.js +122 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-bot-challenge.test.js +37 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-jwt-defenses.test.js +5 -3
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-lockout.test.js +21 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-status-list.test.js +73 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/bot-guard.test.js +17 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/break-glass.test.js +62 -3
- package/lib/vendor/blamejs/test/layer-0-primitives/clear-site-data.test.js +12 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cluster-storage.test.js +40 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +146 -5
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance-sanctions.test.js +14 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compression-range.test.js +115 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/content-credentials.test.js +17 -5
- package/lib/vendor/blamejs/test/layer-0-primitives/cors.test.js +27 -5
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-per-row-key.test.js +83 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csrf-protect.test.js +58 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/data-act.test.js +30 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-raw-residency-gate.test.js +74 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dora.test.js +20 -6
- package/lib/vendor/blamejs/test/layer-0-primitives/dpop-alg-kty.test.js +64 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dsr.test.js +32 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fda-21cfr11.test.js +43 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fetch-metadata.test.js +30 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/graphql-federation.test.js +33 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-html.test.js +34 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-json.test.js +12 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-client-stream.test.js +72 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-client-throttle-transform.test.js +88 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/importmap-integrity.test.js +25 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/legal-hold.test.js +28 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-smime.test.js +20 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-header-injection.test.js +58 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-journal.test.js +63 -9
- package/lib/vendor/blamejs/test/layer-0-primitives/mdoc.test.js +41 -14
- package/lib/vendor/blamejs/test/layer-0-primitives/network-allowlist.test.js +61 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/object-store-range-header.test.js +51 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/otlp-attr-redaction.test.js +35 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/output-header-hardening.test.js +102 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/parser-verify-hardening.test.js +191 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/proto-shadow-allowlist.test.js +120 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/rate-limit-xff-spoofing.test.js +75 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/request-helpers.test.js +128 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/restore-blob-remap.test.js +128 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/retention-sweep-termination.test.js +104 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/router-body-validation.test.js +98 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-json-stringify-for-script.test.js +43 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/saml-subjectconfirmation-notonorafter.test.js +48 -5
- package/lib/vendor/blamejs/test/layer-0-primitives/sandbox.test.js +23 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc.test.js +54 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/session-extensions.test.js +24 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/static.test.js +42 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/step-up.test.js +7 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/template-escape-html.test.js +43 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tenant-quota.test.js +15 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vault-default-store.test.js +48 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vendor-currency-classify.test.js +77 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/webhook.test.js +50 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ws-client.test.js +22 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/x509-chain-ca-enforcement.test.js +149 -0
- package/lib/vendor/blamejs/test/layer-5-integration/external-db-residency.test.js +34 -0
- package/package.json +1 -1
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* b.restore — blob-remap / signature-enforcement integrity (CWE-347).
|
|
4
|
+
*
|
|
5
|
+
* The documented attack: an attacker with write access to a bundle in storage
|
|
6
|
+
* copies one valid blob over another manifest entry (reusing its
|
|
7
|
+
* salt/checksum/size), strips manifest.signature, and restore.run() applies
|
|
8
|
+
* it with NO integrity error — writing one file's plaintext into another's
|
|
9
|
+
* slot (e.g. db.enc's bytes into db.key.enc). Two defenses:
|
|
10
|
+
* 1. each blob is sealed with its relativePath as AEAD associated data, so a
|
|
11
|
+
* remapped blob fails the Poly1305 tag on restore (manifest.aadBound);
|
|
12
|
+
* 2. restore.create accepts a requireSignature policy (threaded into
|
|
13
|
+
* extract) operators opt into to mandate a verified signer.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
var helpers = require("../helpers");
|
|
17
|
+
var b = helpers.b;
|
|
18
|
+
var check = helpers.check;
|
|
19
|
+
var fs = helpers.fs;
|
|
20
|
+
var os = helpers.os;
|
|
21
|
+
var path = helpers.path;
|
|
22
|
+
|
|
23
|
+
function _seed() {
|
|
24
|
+
var root = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-remap-"));
|
|
25
|
+
var dataDir = path.join(root, "data");
|
|
26
|
+
fs.mkdirSync(dataDir);
|
|
27
|
+
fs.writeFileSync(path.join(dataDir, "db.enc"), "ORIG-DB-CONTENT");
|
|
28
|
+
fs.writeFileSync(path.join(dataDir, "db.key.enc"), "SEALED-DEK");
|
|
29
|
+
return { root: root, dataDir: dataDir, storageRoot: path.join(root, "store") };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function run() {
|
|
33
|
+
var pp = Buffer.from("operator-passphrase-not-secret");
|
|
34
|
+
|
|
35
|
+
// --- Build a bundle. ---
|
|
36
|
+
var fx = _seed();
|
|
37
|
+
try {
|
|
38
|
+
function storage() { return b.backup.diskStorage({ root: fx.storageRoot }); }
|
|
39
|
+
var r = await b.backup.create({
|
|
40
|
+
dataDir: fx.dataDir, storage: storage(), passphrase: pp,
|
|
41
|
+
files: [
|
|
42
|
+
{ relativePath: "db.enc", kind: "raw", required: true },
|
|
43
|
+
{ relativePath: "db.key.enc", kind: "raw", required: true },
|
|
44
|
+
],
|
|
45
|
+
vaultKeyJson: '{"vault":"orig"}', audit: false,
|
|
46
|
+
}).run();
|
|
47
|
+
var bdir = path.join(fx.storageRoot, r.bundleId);
|
|
48
|
+
var manifest = JSON.parse(fs.readFileSync(path.join(bdir, "manifest.json"), "utf8"));
|
|
49
|
+
check("backup: new bundle is marked aadBound", manifest.aadBound === true);
|
|
50
|
+
|
|
51
|
+
// --- Clean round-trip still restores (no regression). ---
|
|
52
|
+
fs.writeFileSync(path.join(fx.dataDir, "db.enc"), "MUTATED");
|
|
53
|
+
var rr = await b.restore.create({
|
|
54
|
+
dataDir: fx.dataDir, storage: storage(), passphrase: pp,
|
|
55
|
+
rollbackRoot: path.join(fx.root, "rb"), audit: false,
|
|
56
|
+
}).run({ bundleId: r.bundleId });
|
|
57
|
+
check("restore: clean round-trip succeeds", rr.fileCount === 2);
|
|
58
|
+
check("restore: db.enc restored to bundle bytes",
|
|
59
|
+
fs.readFileSync(path.join(fx.dataDir, "db.enc")).toString() === "ORIG-DB-CONTENT");
|
|
60
|
+
|
|
61
|
+
// --- Blob-remap attack: point db.key.enc's entry at db.enc's blob
|
|
62
|
+
// (reuse its salt/checksum/size), strip the signature, restore. ---
|
|
63
|
+
var dbEnc = manifest.files.filter(function (f) { return f.relativePath === "db.enc"; })[0];
|
|
64
|
+
var dbKey = manifest.files.filter(function (f) { return f.relativePath === "db.key.enc"; })[0];
|
|
65
|
+
dbKey.encryptedPath = dbEnc.encryptedPath;
|
|
66
|
+
dbKey.salt = dbEnc.salt;
|
|
67
|
+
dbKey.checksum = dbEnc.checksum;
|
|
68
|
+
dbKey.size = dbEnc.size;
|
|
69
|
+
dbKey.encryptedSize = dbEnc.encryptedSize;
|
|
70
|
+
delete manifest.signature;
|
|
71
|
+
fs.writeFileSync(path.join(bdir, "manifest.json"), JSON.stringify(manifest));
|
|
72
|
+
|
|
73
|
+
var threw = null;
|
|
74
|
+
try {
|
|
75
|
+
await b.restore.create({
|
|
76
|
+
dataDir: fx.dataDir, storage: storage(), passphrase: pp,
|
|
77
|
+
rollbackRoot: path.join(fx.root, "rb2"), audit: false,
|
|
78
|
+
}).run({ bundleId: r.bundleId });
|
|
79
|
+
} catch (e) { threw = e; }
|
|
80
|
+
check("restore: blob-remap attack is REFUSED (AEAD path-binding)",
|
|
81
|
+
threw && /^restore\/(decrypt-failed|extract-failed)$/.test(threw.code || ""));
|
|
82
|
+
} finally {
|
|
83
|
+
fs.rmSync(fx.root, { recursive: true, force: true });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// --- requireSignature policy: an unsigned bundle is refused when the
|
|
87
|
+
// operator mandates a signature (HIPAA/PCI), and restores when not. ---
|
|
88
|
+
var fx2 = _seed();
|
|
89
|
+
try {
|
|
90
|
+
function storage2() { return b.backup.diskStorage({ root: fx2.storageRoot }); }
|
|
91
|
+
var r2 = await b.backup.create({
|
|
92
|
+
dataDir: fx2.dataDir, storage: storage2(), passphrase: pp,
|
|
93
|
+
files: [{ relativePath: "db.enc", kind: "raw", required: true }],
|
|
94
|
+
vaultKeyJson: '{"vault":"orig"}', audit: false,
|
|
95
|
+
}).run();
|
|
96
|
+
var m2 = JSON.parse(fs.readFileSync(path.join(fx2.storageRoot, r2.bundleId, "manifest.json"), "utf8"));
|
|
97
|
+
check("backup: best-effort bundle is unsigned (no audit-sign)", !m2.signature);
|
|
98
|
+
|
|
99
|
+
var threwReq = null;
|
|
100
|
+
try {
|
|
101
|
+
await b.restore.create({
|
|
102
|
+
dataDir: fx2.dataDir, storage: storage2(), passphrase: pp,
|
|
103
|
+
rollbackRoot: path.join(fx2.root, "rb"), audit: false, requireSignature: true,
|
|
104
|
+
}).run({ bundleId: r2.bundleId });
|
|
105
|
+
} catch (e) { threwReq = e; }
|
|
106
|
+
check("restore: requireSignature:true refuses an unsigned bundle",
|
|
107
|
+
threwReq && threwReq.code === "restore/missing-signature");
|
|
108
|
+
|
|
109
|
+
// Default (requireSignature not set) restores the unsigned best-effort
|
|
110
|
+
// bundle — the framework's documented CLI/standalone case.
|
|
111
|
+
var okRun = await b.restore.create({
|
|
112
|
+
dataDir: fx2.dataDir, storage: storage2(), passphrase: pp,
|
|
113
|
+
rollbackRoot: path.join(fx2.root, "rb3"), audit: false,
|
|
114
|
+
}).run({ bundleId: r2.bundleId });
|
|
115
|
+
check("restore: default restores an unsigned best-effort bundle", okRun.fileCount === 1);
|
|
116
|
+
} finally {
|
|
117
|
+
fs.rmSync(fx2.root, { recursive: true, force: true });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
console.log("OK — restore blob-remap + signature policy (" + helpers.getChecks() + " checks)");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
module.exports = { run: run };
|
|
124
|
+
|
|
125
|
+
if (require.main === module) {
|
|
126
|
+
run().then(function () { process.exit(0); })
|
|
127
|
+
.catch(function (err) { process.exitCode = 1; throw err; });
|
|
128
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// B8a: the retention sweep loop must TERMINATE even when a full batch of rows is
|
|
3
|
+
// not removed from the candidate set by its action. The loop paged LIMIT-from-
|
|
4
|
+
// the-top and exited only when `rows.length < batchSize`, so any full batch that
|
|
5
|
+
// mutated nothing was re-selected forever:
|
|
6
|
+
// - dryRun (preview) mutates NOTHING → every table with > batchSize past-TTL
|
|
7
|
+
// rows looped infinitely (a preview that never returns — a DoS);
|
|
8
|
+
// - a full batch all on legal-hold (skipped) → re-selected forever;
|
|
9
|
+
// - (same for "warn"-stage and errored rows).
|
|
10
|
+
// The fix adds keyset pagination (ORDER BY _id, _id > cursor) so the loop always
|
|
11
|
+
// advances past rows it has already seen, regardless of whether they were
|
|
12
|
+
// actioned.
|
|
13
|
+
//
|
|
14
|
+
// RED on the buggy tree: run() never returns (the harness times out).
|
|
15
|
+
// GREEN after the fix: each scenario terminates and scans every row exactly once.
|
|
16
|
+
|
|
17
|
+
var fs = require("fs");
|
|
18
|
+
var path = require("path");
|
|
19
|
+
var os = require("os");
|
|
20
|
+
var helpers = require("../helpers");
|
|
21
|
+
var dbHelper = require("../helpers/db");
|
|
22
|
+
var b = helpers.b;
|
|
23
|
+
var check = helpers.check;
|
|
24
|
+
|
|
25
|
+
var N = 5; // rows past TTL
|
|
26
|
+
var BATCH = 2; // < N so the loop must iterate (and, when buggy, re-select)
|
|
27
|
+
|
|
28
|
+
function _seedRows(table, withHold) {
|
|
29
|
+
var cols = "\"_id\" TEXT PRIMARY KEY, \"createdAt\" INTEGER, \"payload\" TEXT, \"__erasedAt\" INTEGER" +
|
|
30
|
+
(withHold ? ", \"onHold\" INTEGER" : "");
|
|
31
|
+
b.db.prepare("CREATE TABLE \"" + table + "\" (" + cols + ")").run();
|
|
32
|
+
var longAgo = Date.now() - b.constants.TIME.days(400);
|
|
33
|
+
for (var i = 0; i < N; i++) {
|
|
34
|
+
if (withHold) {
|
|
35
|
+
b.db.prepare("INSERT INTO \"" + table + "\" (\"_id\", \"createdAt\", \"payload\", \"__erasedAt\", \"onHold\") " +
|
|
36
|
+
"VALUES (?, ?, ?, NULL, 1)").run("r-" + i, longAgo, "p-" + i);
|
|
37
|
+
} else {
|
|
38
|
+
b.db.prepare("INSERT INTO \"" + table + "\" (\"_id\", \"createdAt\", \"payload\", \"__erasedAt\") " +
|
|
39
|
+
"VALUES (?, ?, ?, NULL)").run("r-" + i, longAgo, "p-" + i);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function run() {
|
|
45
|
+
var tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-retention-term-"));
|
|
46
|
+
await dbHelper.setupTestDb(tmpDir);
|
|
47
|
+
try {
|
|
48
|
+
// ---- dryRun: mutates nothing → must still terminate, scanning each row once ----
|
|
49
|
+
b.cryptoField.registerTable("retention_dry", { sealedFields: ["payload"], rowIdField: "_id" });
|
|
50
|
+
_seedRows("retention_dry", false);
|
|
51
|
+
var ret1 = b.retention.create({ db: b.db, audit: false });
|
|
52
|
+
ret1.declare({
|
|
53
|
+
name: "dry-ttl", table: "retention_dry", ageField: "createdAt",
|
|
54
|
+
ttlMs: b.constants.TIME.days(90), action: "erase", batchSize: BATCH,
|
|
55
|
+
});
|
|
56
|
+
var preview = await ret1.run("dry-ttl", { dryRun: true });
|
|
57
|
+
check("dryRun sweep TERMINATES (did not infinite-loop)", !!preview);
|
|
58
|
+
check("dryRun scans each row exactly once (no re-selection)", preview.scanned === N);
|
|
59
|
+
check("dryRun would-process every past-TTL row", preview.processed === N);
|
|
60
|
+
// Nothing was actually erased (preview).
|
|
61
|
+
var stillThere = b.db.prepare("SELECT COUNT(*) AS n FROM \"retention_dry\" WHERE \"payload\" IS NOT NULL").get();
|
|
62
|
+
check("dryRun left every row intact", stillThere.n === N);
|
|
63
|
+
|
|
64
|
+
// ---- all-legal-hold: every candidate skipped → must still terminate ----
|
|
65
|
+
b.cryptoField.registerTable("retention_held", { sealedFields: ["payload"], rowIdField: "_id" });
|
|
66
|
+
_seedRows("retention_held", true);
|
|
67
|
+
var ret2 = b.retention.create({ db: b.db, audit: false });
|
|
68
|
+
ret2.declare({
|
|
69
|
+
name: "held-ttl", table: "retention_held", ageField: "createdAt",
|
|
70
|
+
ttlMs: b.constants.TIME.days(90), action: "erase", batchSize: BATCH,
|
|
71
|
+
legalHoldField: "onHold",
|
|
72
|
+
});
|
|
73
|
+
var heldSweep = await ret2.run("held-ttl", { dryRun: false });
|
|
74
|
+
check("all-legal-hold sweep TERMINATES (did not infinite-loop)", !!heldSweep);
|
|
75
|
+
check("all-legal-hold scans each row exactly once", heldSweep.scanned === N);
|
|
76
|
+
check("all-legal-hold honors the hold on every row", heldSweep.legalHoldsHonored === N);
|
|
77
|
+
check("all-legal-hold processed none (all skipped)", heldSweep.processed === 0);
|
|
78
|
+
var heldIntact = b.db.prepare("SELECT COUNT(*) AS n FROM \"retention_held\" WHERE \"payload\" IS NOT NULL").get();
|
|
79
|
+
check("all-legal-hold left every held row intact", heldIntact.n === N);
|
|
80
|
+
|
|
81
|
+
// ---- real erase still works + terminates (no over/under-scan) ----
|
|
82
|
+
b.cryptoField.registerTable("retention_real", { sealedFields: ["payload"], rowIdField: "_id" });
|
|
83
|
+
_seedRows("retention_real", false);
|
|
84
|
+
var ret3 = b.retention.create({ db: b.db, audit: false });
|
|
85
|
+
ret3.declare({
|
|
86
|
+
name: "real-ttl", table: "retention_real", ageField: "createdAt",
|
|
87
|
+
ttlMs: b.constants.TIME.days(90), action: "erase", batchSize: BATCH,
|
|
88
|
+
});
|
|
89
|
+
var realSweep = await ret3.run("real-ttl", { dryRun: false });
|
|
90
|
+
check("real erase sweep terminates + scans each row once", realSweep.scanned === N);
|
|
91
|
+
check("real erase processed every past-TTL row", realSweep.processed === N);
|
|
92
|
+
var erased = b.db.prepare("SELECT COUNT(*) AS n FROM \"retention_real\" WHERE \"payload\" IS NULL").get();
|
|
93
|
+
check("real erase NULLed every sealed payload", erased.n === N);
|
|
94
|
+
} finally {
|
|
95
|
+
await dbHelper.teardownTestDb(tmpDir);
|
|
96
|
+
}
|
|
97
|
+
console.log("OK — retention sweep termination (" + helpers.getChecks() + " checks)");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
module.exports = { run: run };
|
|
101
|
+
if (require.main === module) {
|
|
102
|
+
run().then(function () { process.exit(0); })
|
|
103
|
+
.catch(function (err) { process.exitCode = 1; throw err; });
|
|
104
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* b.router — request-body schema validation must run even when no body was
|
|
4
|
+
* parsed. A route declaring `spec.body` is asserting the body is part of the
|
|
5
|
+
* contract; skipping the check when `req.body === undefined` (no body sent /
|
|
6
|
+
* bodyParser absent / empty POST) silently admits a request that omits a
|
|
7
|
+
* required body straight to the handler. Mirrors the always-run params/query
|
|
8
|
+
* checks.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
var helpers = require("../helpers");
|
|
12
|
+
var b = helpers.b;
|
|
13
|
+
var check = helpers.check;
|
|
14
|
+
var s = b.safeSchema;
|
|
15
|
+
|
|
16
|
+
function _req(method, url) {
|
|
17
|
+
return { method: method, url: url, headers: { host: "localhost" } };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function _res() {
|
|
21
|
+
var res = {
|
|
22
|
+
statusCode: 0,
|
|
23
|
+
headersSent: false,
|
|
24
|
+
writableEnded: false,
|
|
25
|
+
_body: "",
|
|
26
|
+
writeHead: function (status, headers) {
|
|
27
|
+
res.statusCode = status;
|
|
28
|
+
res._headers = headers || {};
|
|
29
|
+
res.headersSent = true;
|
|
30
|
+
},
|
|
31
|
+
end: function (chunk) {
|
|
32
|
+
if (chunk !== undefined) res._body += chunk;
|
|
33
|
+
res.writableEnded = true;
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
return res;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function testMissingRequiredBodyRejected() {
|
|
40
|
+
var r = b.router.create();
|
|
41
|
+
var handlerRan = false;
|
|
42
|
+
r.post("/items", { body: s.object({ name: s.string() }) }, function (req, res) {
|
|
43
|
+
handlerRan = true;
|
|
44
|
+
res.writeHead(200); res.end("created");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// No body was parsed onto the request (req.body === undefined).
|
|
48
|
+
var res = _res();
|
|
49
|
+
await r.handle(_req("POST", "/items"), res);
|
|
50
|
+
check("missing required body → 400", res.statusCode === 400);
|
|
51
|
+
check("missing required body → handler did not run", handlerRan === false);
|
|
52
|
+
check("missing required body → validation error names the body location",
|
|
53
|
+
/"where":"body"/.test(res._body));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function testValidBodyAccepted() {
|
|
57
|
+
var r = b.router.create();
|
|
58
|
+
var seen = null;
|
|
59
|
+
r.post("/items", { body: s.object({ name: s.string() }) }, function (req, res) {
|
|
60
|
+
seen = req.body;
|
|
61
|
+
res.writeHead(200); res.end("created");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
var req = _req("POST", "/items");
|
|
65
|
+
req.body = { name: "widget" };
|
|
66
|
+
var res = _res();
|
|
67
|
+
await r.handle(req, res);
|
|
68
|
+
check("valid body → 200", res.statusCode === 200);
|
|
69
|
+
check("valid body → handler sees parsed body", seen && seen.name === "widget");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function testOptionalBodyAbsentAccepted() {
|
|
73
|
+
// An explicitly-optional body schema must still pass when no body is sent —
|
|
74
|
+
// the fix validates, it does not require a body unconditionally.
|
|
75
|
+
var r = b.router.create();
|
|
76
|
+
var handlerRan = false;
|
|
77
|
+
r.post("/items", { body: s.object({ name: s.string() }).optional() }, function (req, res) {
|
|
78
|
+
handlerRan = true;
|
|
79
|
+
res.writeHead(200); res.end("ok");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
var res = _res();
|
|
83
|
+
await r.handle(_req("POST", "/items"), res);
|
|
84
|
+
check("optional body absent → 200", res.statusCode === 200 && handlerRan === true);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function run() {
|
|
88
|
+
await testMissingRequiredBodyRejected();
|
|
89
|
+
await testValidBodyAccepted();
|
|
90
|
+
await testOptionalBodyAbsentAccepted();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
module.exports = { run: run };
|
|
94
|
+
|
|
95
|
+
if (require.main === module) {
|
|
96
|
+
run().then(function () { console.log("OK router-body-validation — " + helpers.getChecks() + " checks"); })
|
|
97
|
+
.catch(function (e) { console.error("FAIL:", e && e.stack || e); process.exit(1); });
|
|
98
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* b.safeJson.stringifyForScript — JSON safe to embed verbatim inside an
|
|
4
|
+
* inline <script>. Raw JSON.stringify leaves <, >, & literal, so a value
|
|
5
|
+
* containing "</script>" closes the surrounding element (XSS); the
|
|
6
|
+
* U+2028 / U+2029 separators are also illegal unescaped in a script on
|
|
7
|
+
* older parsers. This escapes all of them to \uXXXX — the parsed value is
|
|
8
|
+
* byte-identical.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
var helpers = require("../helpers");
|
|
12
|
+
var b = helpers.b;
|
|
13
|
+
var check = helpers.check;
|
|
14
|
+
|
|
15
|
+
function run() {
|
|
16
|
+
// </script> in a value is neutralised but round-trips.
|
|
17
|
+
var payload = { url: "/a</script><script>alert(1)</script>", amp: "a&b", lt: "x<y>z" };
|
|
18
|
+
var out = b.safeJson.stringifyForScript(payload);
|
|
19
|
+
check("no raw </script> survives", out.indexOf("</script>") === -1);
|
|
20
|
+
check("< is escaped to \\u003c", out.indexOf("\\u003c") !== -1 && out.indexOf("<") === -1);
|
|
21
|
+
check("> is escaped to \\u003e", out.indexOf(">") === -1);
|
|
22
|
+
check("& is escaped to \\u0026", out.indexOf("&") === -1);
|
|
23
|
+
check("round-trips to the identical value", JSON.parse(out).url === payload.url &&
|
|
24
|
+
JSON.parse(out).amp === "a&b" && JSON.parse(out).lt === "x<y>z");
|
|
25
|
+
|
|
26
|
+
// U+2028 / U+2029 (built via code point so this source stays ASCII).
|
|
27
|
+
var sep = { s: "a" + String.fromCharCode(0x2028) + "b" + String.fromCharCode(0x2029) + "c" };
|
|
28
|
+
var outSep = b.safeJson.stringifyForScript(sep);
|
|
29
|
+
check("U+2028 escaped to \\u2028", outSep.indexOf("\\u2028") !== -1 &&
|
|
30
|
+
outSep.indexOf(String.fromCharCode(0x2028)) === -1);
|
|
31
|
+
check("U+2029 escaped to \\u2029", outSep.indexOf("\\u2029") !== -1 &&
|
|
32
|
+
outSep.indexOf(String.fromCharCode(0x2029)) === -1);
|
|
33
|
+
check("separators round-trip to the identical value",
|
|
34
|
+
JSON.parse(outSep).s === sep.s);
|
|
35
|
+
|
|
36
|
+
console.log("OK — safeJson.stringifyForScript (" + helpers.getChecks() + " checks)");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = { run: run };
|
|
40
|
+
|
|
41
|
+
if (require.main === module) {
|
|
42
|
+
try { run(); } catch (e) { console.error(e); process.exit(1); }
|
|
43
|
+
}
|
package/lib/vendor/blamejs/test/layer-0-primitives/saml-subjectconfirmation-notonorafter.test.js
CHANGED
|
@@ -85,11 +85,12 @@ function _buildSignedResponse(idp, parts) {
|
|
|
85
85
|
parts.nameId + "</saml:NameID>" +
|
|
86
86
|
subjectConfirmation +
|
|
87
87
|
"</saml:Subject>" +
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
88
|
+
(parts.conditions !== undefined ? parts.conditions :
|
|
89
|
+
"<saml:Conditions NotBefore=\"" + _isoFromNow(-5 * 60 * 1000) + // allow:raw-time-literal — 5m skew window
|
|
90
|
+
"\" NotOnOrAfter=\"" + _isoFromNow(5 * 60 * 1000) + "\">" + // allow:raw-time-literal — 5m skew window
|
|
91
|
+
"<saml:AudienceRestriction><saml:Audience>" + SP_ENTITY_ID +
|
|
92
|
+
"</saml:Audience></saml:AudienceRestriction>" +
|
|
93
|
+
"</saml:Conditions>") +
|
|
93
94
|
"<saml:AuthnStatement SessionIndex=\"_sess-1\" AuthnInstant=\"" + issueInstant + "\">" +
|
|
94
95
|
"<saml:AuthnContext><saml:AuthnContextClassRef>" +
|
|
95
96
|
"urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport" +
|
|
@@ -273,11 +274,53 @@ async function testHolderOfKeyNotOnOrAfterRefused() {
|
|
|
273
274
|
_verifyThrows(sp, b64bad, vopts) === "auth-saml/no-valid-confirmation");
|
|
274
275
|
}
|
|
275
276
|
|
|
277
|
+
// #B0 — a signed assertion with NO AudienceRestriction is not bound to THIS
|
|
278
|
+
// SP. Accepting it is audience-confusion: an IdP-signed assertion minted for
|
|
279
|
+
// another SP is replayed here. Must fail closed (default on).
|
|
280
|
+
async function testMissingAudienceRestrictionRefused() {
|
|
281
|
+
var idp = await _mintRsaCert("idp.example");
|
|
282
|
+
var sp = _newSp(idp);
|
|
283
|
+
var inResponseTo = "_req-noaud";
|
|
284
|
+
var b64 = _buildSignedResponse(idp, {
|
|
285
|
+
tag: "no-aud",
|
|
286
|
+
method: "urn:oasis:names:tc:SAML:2.0:cm:bearer",
|
|
287
|
+
nameId: "u@example.com",
|
|
288
|
+
scd: _bearerScd(" NotOnOrAfter=\"" + _isoFromNow(5 * 60 * 1000) + "\"", inResponseTo), // allow:raw-time-literal — 5m future
|
|
289
|
+
conditions: "<saml:Conditions NotBefore=\"" + _isoFromNow(-5 * 60 * 1000) + // allow:raw-time-literal — 5m skew
|
|
290
|
+
"\" NotOnOrAfter=\"" + _isoFromNow(5 * 60 * 1000) + "\"></saml:Conditions>", // allow:raw-time-literal — NO AudienceRestriction
|
|
291
|
+
});
|
|
292
|
+
check("signed assertion without AudienceRestriction refused (audience-confusion)",
|
|
293
|
+
_verifyThrows(sp, b64, { expectedInResponseTo: inResponseTo }) === "auth-saml/no-audience-restriction");
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// #B0 — a present-but-unparseable Conditions NotBefore/NotOnOrAfter must fail
|
|
297
|
+
// CLOSED (the Bearer SCD path already does; the Conditions path skipped it via
|
|
298
|
+
// an isFinite() short-circuit, so an unparseable validity window was ignored).
|
|
299
|
+
async function testUnparseableConditionsTimestampRefused() {
|
|
300
|
+
var idp = await _mintRsaCert("idp.example");
|
|
301
|
+
var sp = _newSp(idp);
|
|
302
|
+
var inResponseTo = "_req-badts";
|
|
303
|
+
var b64 = _buildSignedResponse(idp, {
|
|
304
|
+
tag: "bad-ts",
|
|
305
|
+
method: "urn:oasis:names:tc:SAML:2.0:cm:bearer",
|
|
306
|
+
nameId: "u@example.com",
|
|
307
|
+
scd: _bearerScd(" NotOnOrAfter=\"" + _isoFromNow(5 * 60 * 1000) + "\"", inResponseTo), // allow:raw-time-literal — 5m future
|
|
308
|
+
conditions: "<saml:Conditions NotBefore=\"" + _isoFromNow(-5 * 60 * 1000) + // allow:raw-time-literal — valid NotBefore
|
|
309
|
+
"\" NotOnOrAfter=\"not-a-date\">" +
|
|
310
|
+
"<saml:AudienceRestriction><saml:Audience>" + SP_ENTITY_ID +
|
|
311
|
+
"</saml:Audience></saml:AudienceRestriction></saml:Conditions>",
|
|
312
|
+
});
|
|
313
|
+
check("unparseable Conditions NotOnOrAfter refused (fail-closed)",
|
|
314
|
+
_verifyThrows(sp, b64, { expectedInResponseTo: inResponseTo }) === "auth-saml/conditions-bad-timestamp");
|
|
315
|
+
}
|
|
316
|
+
|
|
276
317
|
async function run() {
|
|
277
318
|
await testBearerValidNotOnOrAfterAccepted();
|
|
278
319
|
await testBearerMissingNotOnOrAfterRefused();
|
|
279
320
|
await testBearerUnparseableNotOnOrAfterRefused();
|
|
280
321
|
await testHolderOfKeyNotOnOrAfterRefused();
|
|
322
|
+
await testMissingAudienceRestrictionRefused();
|
|
323
|
+
await testUnparseableConditionsTimestampRefused();
|
|
281
324
|
}
|
|
282
325
|
|
|
283
326
|
if (require.main === module) {
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
var helpers = require("../helpers");
|
|
22
22
|
var b = helpers.b;
|
|
23
23
|
var check = helpers.check;
|
|
24
|
+
var C = require("../../lib/constants");
|
|
24
25
|
|
|
25
26
|
async function testHappyPath() {
|
|
26
27
|
var r = await b.sandbox.run({
|
|
@@ -171,6 +172,27 @@ async function testProcessUnreachable() {
|
|
|
171
172
|
}
|
|
172
173
|
}
|
|
173
174
|
|
|
175
|
+
async function testWebAssemblyStripped() {
|
|
176
|
+
// WebAssembly linear memory is off the V8 heap, so the maxBytes-derived
|
|
177
|
+
// heap cap can't bound `new WebAssembly.Memory(...).grow(N)`. WebAssembly
|
|
178
|
+
// must be stripped from the worker global so the grow-and-touch source
|
|
179
|
+
// cannot commit unbounded off-heap RAM (CWE-770). Stripped → the source
|
|
180
|
+
// ReferenceErrors and the call refuses, rather than resolving after
|
|
181
|
+
// committing ~GiB under a 4 MiB cap.
|
|
182
|
+
try {
|
|
183
|
+
await b.sandbox.run({
|
|
184
|
+
source: "var m = new WebAssembly.Memory({ initial: 1 }); m.grow(30000); " +
|
|
185
|
+
"var v = new Uint8Array(m.buffer); for (var i = 0; i < v.length; i += 4096) v[i] = 1; " +
|
|
186
|
+
"return v.length;",
|
|
187
|
+
maxBytes: C.BYTES.mib(4),
|
|
188
|
+
timeoutMs: 9000,
|
|
189
|
+
});
|
|
190
|
+
check("WebAssembly grow should be refused (stripped)", false);
|
|
191
|
+
} catch (e) {
|
|
192
|
+
check("WebAssembly unreachable in sandbox", e && e.code === "sandbox/runtime-error");
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
174
196
|
async function testNoNetworkAccess() {
|
|
175
197
|
// require absent means http unreachable; confirm path.
|
|
176
198
|
try {
|
|
@@ -233,6 +255,7 @@ async function run() {
|
|
|
233
255
|
await testBadInput();
|
|
234
256
|
await testContainment();
|
|
235
257
|
await testProcessUnreachable();
|
|
258
|
+
await testWebAssemblyStripped();
|
|
236
259
|
await testNoNetworkAccess();
|
|
237
260
|
await testKnownSafeBuiltins();
|
|
238
261
|
}
|
|
@@ -318,6 +318,58 @@ async function testPresentWithKeyBinding() {
|
|
|
318
318
|
result.holderKey && result.holderKey.kty === "EC");
|
|
319
319
|
}
|
|
320
320
|
|
|
321
|
+
async function testKbRequiresReplayBinding() {
|
|
322
|
+
// A KB-JWT presentation verified WITHOUT opts.audience/opts.nonce must fail
|
|
323
|
+
// closed — otherwise the aud/nonce compares silently skip and the
|
|
324
|
+
// presentation is replayable + acceptable at any verifier.
|
|
325
|
+
var issuer = _newKeyPair();
|
|
326
|
+
var holder = _newKeyPair();
|
|
327
|
+
var sd = sdJwtVc.issue({
|
|
328
|
+
issuer: "https://issuer", vct: "x", claims: { x: 1 },
|
|
329
|
+
issuerKey: issuer.privateKey, holderKey: _jwk(holder.publicKey),
|
|
330
|
+
});
|
|
331
|
+
var pres = sdJwtVc.present({
|
|
332
|
+
sdJwt: sd.token, audience: "https://verifier", nonce: "n-1",
|
|
333
|
+
holderKey: holder.privateKey, algorithm: "ES256",
|
|
334
|
+
});
|
|
335
|
+
// No nonce + no audience supplied to verify → must throw.
|
|
336
|
+
var threwNone = null;
|
|
337
|
+
try {
|
|
338
|
+
await sdJwtVc.verify(pres.presentation, {
|
|
339
|
+
issuerKeyResolver: async function () { return issuer.publicKey; },
|
|
340
|
+
requireKeyBinding: true,
|
|
341
|
+
});
|
|
342
|
+
} catch (e) { threwNone = e; }
|
|
343
|
+
check("verify: KB-JWT without audience+nonce fails closed",
|
|
344
|
+
threwNone && threwNone.code === "auth-sd-jwt-vc/missing-replay-binding");
|
|
345
|
+
// audience but no nonce → still throws (replay defense needs the fresh nonce).
|
|
346
|
+
var threwNoNonce = null;
|
|
347
|
+
try {
|
|
348
|
+
await sdJwtVc.verify(pres.presentation, {
|
|
349
|
+
issuerKeyResolver: async function () { return issuer.publicKey; },
|
|
350
|
+
audience: "https://verifier", requireKeyBinding: true,
|
|
351
|
+
});
|
|
352
|
+
} catch (e) { threwNoNonce = e; }
|
|
353
|
+
check("verify: KB-JWT with audience but no nonce fails closed",
|
|
354
|
+
threwNoNonce && threwNoNonce.code === "auth-sd-jwt-vc/missing-replay-binding");
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
async function testRequireExpOpt() {
|
|
358
|
+
// requireExp + expectedIssuer are accepted opts (previously expectedIssuer
|
|
359
|
+
// was consumed but absent from the allowlist → "unknown option"). A normal
|
|
360
|
+
// token (issue() always writes a numeric exp) still verifies under requireExp.
|
|
361
|
+
var issuer = _newKeyPair();
|
|
362
|
+
var sd = sdJwtVc.issue({
|
|
363
|
+
issuer: "https://issuer", vct: "x", claims: { x: 1 }, issuerKey: issuer.privateKey,
|
|
364
|
+
});
|
|
365
|
+
var ok = await sdJwtVc.verify(sd.token, {
|
|
366
|
+
issuerKeyResolver: async function () { return issuer.publicKey; },
|
|
367
|
+
requireExp: true, expectedIssuer: "https://issuer",
|
|
368
|
+
});
|
|
369
|
+
check("verify: requireExp + expectedIssuer accepted; exp-present token verifies",
|
|
370
|
+
ok.valid === true);
|
|
371
|
+
}
|
|
372
|
+
|
|
321
373
|
async function testKbWrongAudience() {
|
|
322
374
|
var issuer = _newKeyPair();
|
|
323
375
|
var holder = _newKeyPair();
|
|
@@ -776,6 +828,8 @@ function testExports() {
|
|
|
776
828
|
await testVerifyDisclosureMismatch();
|
|
777
829
|
await testPresentSubsetThenVerify();
|
|
778
830
|
await testPresentWithKeyBinding();
|
|
831
|
+
await testKbRequiresReplayBinding();
|
|
832
|
+
await testRequireExpOpt();
|
|
779
833
|
await testKbWrongAudience();
|
|
780
834
|
await testKbWrongNonce();
|
|
781
835
|
await testRequireKeyBindingMissing();
|
|
@@ -181,6 +181,29 @@ async function testPluggableStore() {
|
|
|
181
181
|
}
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
+
async function testDestroyAllForUserPluggableNoDb() {
|
|
185
|
+
// #340: a pluggable-store consumer who never ran b.db.init() must get a
|
|
186
|
+
// clear, actionable error from destroyAllForUser — not the opaque
|
|
187
|
+
// db/not-initialized that bubbled out of the stateless valid-from bump
|
|
188
|
+
// (which writes to the framework db, not the pluggable session store).
|
|
189
|
+
var tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "ses-nodb-"));
|
|
190
|
+
try {
|
|
191
|
+
await helpers.setupVaultOnly(tmpDir); // vault up; b.db deliberately NOT initialized
|
|
192
|
+
b.session.useStore({
|
|
193
|
+
execute: function () { return Promise.resolve({ rowCount: 1 }); },
|
|
194
|
+
executeOne: function () { return Promise.resolve(null); },
|
|
195
|
+
});
|
|
196
|
+
var err = null;
|
|
197
|
+
try { await b.session.destroyAllForUser("u-1"); }
|
|
198
|
+
catch (e) { err = e; }
|
|
199
|
+
check("destroyAllForUser (pluggable store, no b.db) → clear error, not db/not-initialized",
|
|
200
|
+
err !== null && err.code !== "db/not-initialized" && /b\.db\.init\(\)/.test(err.message || ""));
|
|
201
|
+
} finally {
|
|
202
|
+
b.session.useStore(null);
|
|
203
|
+
helpers.teardownVaultOnly(tmpDir);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
184
207
|
async function testPluggableStoreValidation() {
|
|
185
208
|
var threw = false;
|
|
186
209
|
try { b.session.useStore({ execute: function () {} }); }
|
|
@@ -381,6 +404,7 @@ async function run() {
|
|
|
381
404
|
await testClientIpPrefixV6();
|
|
382
405
|
await testClientIpPrefixV4MappedV6();
|
|
383
406
|
await testPluggableStore();
|
|
407
|
+
await testDestroyAllForUserPluggableNoDb();
|
|
384
408
|
await testPluggableStoreValidation();
|
|
385
409
|
await testUpdateDataReplaceAndMerge();
|
|
386
410
|
await testUpdateDataPreservesFingerprint();
|
|
@@ -44,10 +44,11 @@ async function _server() {
|
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
function _get(port, urlPath) {
|
|
47
|
+
function _get(port, urlPath, headers) {
|
|
48
48
|
return new Promise(function (resolve, reject) {
|
|
49
49
|
var req = http.request({
|
|
50
50
|
hostname: "127.0.0.1", port: port, path: urlPath, method: "GET",
|
|
51
|
+
headers: headers || undefined,
|
|
51
52
|
}, function (res) {
|
|
52
53
|
var chunks = [];
|
|
53
54
|
res.on("data", function (c) { chunks.push(c); });
|
|
@@ -484,7 +485,47 @@ async function testDirectoryIndexServed() {
|
|
|
484
485
|
}
|
|
485
486
|
}
|
|
486
487
|
|
|
488
|
+
// RFC 7232 §3.3/§6: If-None-Match takes precedence over If-Modified-Since,
|
|
489
|
+
// and If-Match takes precedence over If-Unmodified-Since. When the strong
|
|
490
|
+
// entity-tag precondition is present, the recipient MUST ignore the
|
|
491
|
+
// date-based one — otherwise a changed resource is falsely reported 304, or
|
|
492
|
+
// an unchanged resource falsely 412'd.
|
|
493
|
+
async function testConditionalEntityTagPrecedence() {
|
|
494
|
+
var ctx = await _server();
|
|
495
|
+
_writeFile(ctx.dir, "doc.txt", "hello conditional");
|
|
496
|
+
var srv = await ctx.start({ contentSafety: null });
|
|
497
|
+
try {
|
|
498
|
+
var base = await _get(srv.port, "/doc.txt");
|
|
499
|
+
check("conditional: baseline serves 200 with ETag",
|
|
500
|
+
base.statusCode === 200 && typeof base.headers.etag === "string");
|
|
501
|
+
var future = new Date(Date.now() + 86400000).toUTCString();
|
|
502
|
+
var past = new Date(Date.now() - 86400000).toUTCString();
|
|
503
|
+
|
|
504
|
+
// If-None-Match present + non-matching → resource changed; the
|
|
505
|
+
// If-Modified-Since (future → would 304) must be ignored, serve 200.
|
|
506
|
+
var r1 = await _get(srv.port, "/doc.txt", {
|
|
507
|
+
"If-None-Match": '"stale-does-not-match"',
|
|
508
|
+
"If-Modified-Since": future,
|
|
509
|
+
});
|
|
510
|
+
check("If-None-Match (stale) overrides If-Modified-Since → 200, not 304",
|
|
511
|
+
r1.statusCode === 200);
|
|
512
|
+
|
|
513
|
+
// If-Match: * (matches) + If-Unmodified-Since (past → would 412) must
|
|
514
|
+
// be ignored because If-Match is present and satisfied, serve 200.
|
|
515
|
+
var r2 = await _get(srv.port, "/doc.txt", {
|
|
516
|
+
"If-Match": "*",
|
|
517
|
+
"If-Unmodified-Since": past,
|
|
518
|
+
});
|
|
519
|
+
check("If-Match (*) overrides If-Unmodified-Since → 200, not 412",
|
|
520
|
+
r2.statusCode === 200);
|
|
521
|
+
} finally {
|
|
522
|
+
srv.close();
|
|
523
|
+
ctx.cleanup();
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
487
527
|
async function run() {
|
|
528
|
+
await testConditionalEntityTagPrecedence();
|
|
488
529
|
await testForceAttachmentDefaultOff();
|
|
489
530
|
await testForceAttachmentOnHtml();
|
|
490
531
|
await testForceAttachmentOnJs();
|