@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
|
@@ -37,6 +37,29 @@ var backupCrypto = require("../../lib/backup/crypto");
|
|
|
37
37
|
|
|
38
38
|
var PASS = Buffer.from("operator-bundle-passphrase-not-secret");
|
|
39
39
|
|
|
40
|
+
// Wire-form conversions mirroring audit-tools' _wireFormToRow /
|
|
41
|
+
// _rowToWireForm (hex: prefix <-> Buffer) so a test can rebuild the exact
|
|
42
|
+
// rows JSONL the verifier re-parses.
|
|
43
|
+
function _wireToRow(wire) {
|
|
44
|
+
var out = {};
|
|
45
|
+
Object.keys(wire).forEach(function (k) {
|
|
46
|
+
var v = wire[k];
|
|
47
|
+
if (typeof v === "string" && v.indexOf("hex:") === 0) out[k] = Buffer.from(v.slice(4), "hex");
|
|
48
|
+
else out[k] = v;
|
|
49
|
+
});
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
function _rowToWire(row) {
|
|
53
|
+
var out = {};
|
|
54
|
+
Object.keys(row).forEach(function (k) {
|
|
55
|
+
var v = row[k];
|
|
56
|
+
if (Buffer.isBuffer(v)) out[k] = "hex:" + v.toString("hex");
|
|
57
|
+
else if (v === undefined) out[k] = null;
|
|
58
|
+
else out[k] = v;
|
|
59
|
+
});
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
|
|
40
63
|
async function _seedAuditRows(count) {
|
|
41
64
|
b.audit.registerNamespace("test");
|
|
42
65
|
for (var i = 0; i < count; i++) {
|
|
@@ -191,6 +214,105 @@ async function run() {
|
|
|
191
214
|
r3.threw === false && r3.ok === false &&
|
|
192
215
|
/signature/i.test(r3.reason || ""));
|
|
193
216
|
|
|
217
|
+
// ---- Tamper 4: anchor-rebind — fabricate the row slice but keep the
|
|
218
|
+
// GENUINE signed checkpoint. The checkpoint's atRowHash anchors the real
|
|
219
|
+
// chain; nothing in the bundle ties it to the fabricated rows. A verifier
|
|
220
|
+
// that only checks (signature valid + atMonotonicCounter >= lastCounter)
|
|
221
|
+
// reports a wholly fabricated archive as authentic — defeating the
|
|
222
|
+
// documented "tampering that recomputes hashes still fails checkpoint
|
|
223
|
+
// verification" guarantee. The fabricated chain is internally consistent
|
|
224
|
+
// (recomputed via b.auditChain.computeRowHash — no key needed), so it
|
|
225
|
+
// passes the chain walk + manifest first/last checks; only binding the
|
|
226
|
+
// signed atRowHash to the slice's anchored row catches it.
|
|
227
|
+
var t4 = path.join(dir, "bundles", "tamper-anchor-rebind");
|
|
228
|
+
_copyDir(cleanDir, t4);
|
|
229
|
+
var m4Path = path.join(t4, "manifest.json");
|
|
230
|
+
var m4 = JSON.parse(fs.readFileSync(m4Path, "utf8"));
|
|
231
|
+
var rows4Enc = fs.readFileSync(path.join(t4, "rows.enc"));
|
|
232
|
+
var rows4Plain = (await backupCrypto.decryptWithPassphrase(rows4Enc, PASS, m4.salts.rows)).toString("utf8");
|
|
233
|
+
var genuineWire = rows4Plain.split("\n").filter(function (l) { return l.length > 0; })
|
|
234
|
+
.map(function (l) { return JSON.parse(l); });
|
|
235
|
+
// Rebuild a fabricated, internally-consistent chain off the SAME
|
|
236
|
+
// predecessor + counters but with phantom actions that never happened.
|
|
237
|
+
// The row-hash preimage excludes prevHash/rowHash/nonce/fencingToken
|
|
238
|
+
// (mirrors audit-tools _verifyChainSlice), so the recompute matches the
|
|
239
|
+
// verifier's chain walk and only the signed atRowHash bind can catch it.
|
|
240
|
+
var prev4 = m4.range.predecessorRowHash;
|
|
241
|
+
var fabricatedWire = genuineWire.map(function (w) {
|
|
242
|
+
var row = _wireToRow(w);
|
|
243
|
+
row.action = "phantom.never.happened";
|
|
244
|
+
row.outcome = "success";
|
|
245
|
+
row.prevHash = prev4;
|
|
246
|
+
var fields = Object.assign({}, row);
|
|
247
|
+
delete fields.prevHash; delete fields.rowHash; delete fields.nonce; delete fields.fencingToken;
|
|
248
|
+
row.rowHash = b.auditChain.computeRowHash(prev4, fields, row.nonce);
|
|
249
|
+
prev4 = row.rowHash;
|
|
250
|
+
return _rowToWire(row);
|
|
251
|
+
});
|
|
252
|
+
check("tamper-anchor: fabricated last rowHash differs from the genuine anchor",
|
|
253
|
+
fabricatedWire[fabricatedWire.length - 1].rowHash !== m4.checkpoint.atRowHash);
|
|
254
|
+
var fabJsonl = fabricatedWire.map(function (w) { return JSON.stringify(w); }).join("\n") + "\n";
|
|
255
|
+
var fabEnc = await backupCrypto.encryptWithFreshSalt(fabJsonl, PASS);
|
|
256
|
+
fs.writeFileSync(path.join(t4, "rows.enc"), fabEnc.encrypted);
|
|
257
|
+
m4.salts.rows = fabEnc.salt;
|
|
258
|
+
m4.checksum.rowsSha3_512 = backupCrypto.checksum(fabEnc.encrypted);
|
|
259
|
+
m4.range.firstRowHash = fabricatedWire[0].rowHash;
|
|
260
|
+
m4.range.lastRowHash = fabricatedWire[fabricatedWire.length - 1].rowHash;
|
|
261
|
+
// checkpoint.enc + manifest.checkpoint left GENUINE on purpose.
|
|
262
|
+
fs.writeFileSync(m4Path, JSON.stringify(m4));
|
|
263
|
+
var r4 = await _verify(t4);
|
|
264
|
+
check("tamper-anchor: fabricated archive is REJECTED (checkpoint not bound to slice)", r4.rejected === true);
|
|
265
|
+
check("tamper-anchor: failure points at the checkpoint anchor binding",
|
|
266
|
+
(!r4.threw && /anchor|atRowHash|checkpoint/i.test(r4.reason || "")) ||
|
|
267
|
+
(r4.threw && /anchor|atRowHash|checkpoint/i.test(r4.message || "")));
|
|
268
|
+
|
|
269
|
+
// ---- Regression: a LEGITIMATE partial archive whose covering checkpoint
|
|
270
|
+
// anchors a counter BEYOND the purgeable slice must still verify. The
|
|
271
|
+
// bundle carries the rows between the slice tip and the anchored counter
|
|
272
|
+
// as verification witnesses so the chain walks up to the signed atRowHash;
|
|
273
|
+
// purge still only touches the [first..lastCounter] slice. Driven through
|
|
274
|
+
// archive()'s injectable readers for determinism (no wall-clock gaps).
|
|
275
|
+
var fullDir = path.join(dir, "bundles", "archive-full10");
|
|
276
|
+
// Seed up to a tip of 10 and checkpoint THERE (the only checkpoint beyond
|
|
277
|
+
// counter 6), then snapshot all 10 rows + that checkpoint via a full
|
|
278
|
+
// archive so we can replay them through injected readers.
|
|
279
|
+
await _seedAuditRows(4); // counters 7..10 (6 already seeded)
|
|
280
|
+
var ckpt10 = await b.audit.checkpoint();
|
|
281
|
+
check("a checkpoint beyond the slice was anchored", Number(ckpt10.atMonotonicCounter) >= 10);
|
|
282
|
+
var full = await b.auditTools.archive({ before: Date.now() + 60000, out: fullDir, passphrase: PASS });
|
|
283
|
+
check("full archive carries >=10 rows", full.rowCount >= 10);
|
|
284
|
+
var fullM = JSON.parse(fs.readFileSync(path.join(fullDir, "manifest.json"), "utf8"));
|
|
285
|
+
var fullRowsPlain = (await backupCrypto.decryptWithPassphrase(
|
|
286
|
+
fs.readFileSync(path.join(fullDir, "rows.enc")), PASS, fullM.salts.rows)).toString("utf8");
|
|
287
|
+
var allRows = fullRowsPlain.split("\n").filter(function (l) { return l.length > 0; })
|
|
288
|
+
.map(function (l) { return _wireToRow(JSON.parse(l)); });
|
|
289
|
+
var fullCkptPlain = (await backupCrypto.decryptWithPassphrase(
|
|
290
|
+
fs.readFileSync(path.join(fullDir, "checkpoint.enc")), PASS, fullM.salts.checkpoint)).toString("utf8");
|
|
291
|
+
var genuineCkpt10 = _wireToRow(JSON.parse(fullCkptPlain));
|
|
292
|
+
var sliceLast = Number(allRows[5].monotonicCounter); // 6th row's counter (purge boundary)
|
|
293
|
+
var partialDir = path.join(dir, "bundles", "archive-partial-witness");
|
|
294
|
+
var partial = await b.auditTools.archive({
|
|
295
|
+
before: Date.now() + 60000,
|
|
296
|
+
out: partialDir,
|
|
297
|
+
passphrase: PASS,
|
|
298
|
+
readRows: function (criteria) {
|
|
299
|
+
if (criteria.firstCounter != null) {
|
|
300
|
+
// witness request: rows (sliceLast .. anchorCounter]
|
|
301
|
+
return allRows.filter(function (r) {
|
|
302
|
+
var c = Number(r.monotonicCounter);
|
|
303
|
+
return c >= criteria.firstCounter && c <= criteria.lastCounter;
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
return allRows.slice(0, 6); // purgeable slice = first 6 rows
|
|
307
|
+
},
|
|
308
|
+
readCoveringCheckpoint: function () { return genuineCkpt10; }, // anchors counter 10
|
|
309
|
+
readPredecessorRowHash: function () { return b.auditChain.ZERO_HASH; }, // slice starts at counter 1
|
|
310
|
+
});
|
|
311
|
+
check("partial archive purge range stops at the slice tip (witnesses excluded from purge range)",
|
|
312
|
+
Number(partial.range.lastCounter) === sliceLast);
|
|
313
|
+
var rp = await _verify(partialDir);
|
|
314
|
+
check("legit partial archive (anchor beyond slice) verifies via witness rows", rp.threw === false && rp.ok === true);
|
|
315
|
+
|
|
194
316
|
console.log("OK — audit verifyBundle tamper-detection (" + helpers.getChecks() + " checks)");
|
|
195
317
|
} finally {
|
|
196
318
|
await teardownTestDb(dir);
|
|
@@ -308,10 +308,47 @@ async function testEscalationFnInvoked() {
|
|
|
308
308
|
check("escalationFn invoked when threshold crossed", escalated === 1);
|
|
309
309
|
}
|
|
310
310
|
|
|
311
|
+
// Concurrent failures on one key must not lose increments. A read-modify-
|
|
312
|
+
// write over an async store races without per-key serialization: N parallel
|
|
313
|
+
// recordFailure calls each read the same pre-write counter, so the failure
|
|
314
|
+
// count lands at 1 and the gate never crosses its thresholds. The same N
|
|
315
|
+
// calls run sequentially must reach the locked stage; the concurrent set
|
|
316
|
+
// must reach the identical stage.
|
|
317
|
+
async function testConcurrentFailuresDoNotLoseIncrements() {
|
|
318
|
+
var lockout = _fakeLockout();
|
|
319
|
+
var gate = b.authBotChallenge.create({
|
|
320
|
+
botGuard: _fakeBotGuard("pass"),
|
|
321
|
+
lockout: lockout,
|
|
322
|
+
sessionStore: _memoryStore(),
|
|
323
|
+
threshold: 2,
|
|
324
|
+
escalationThreshold: 4,
|
|
325
|
+
audit: _captureAudit(),
|
|
326
|
+
});
|
|
327
|
+
var key = "race@example.com";
|
|
328
|
+
|
|
329
|
+
var results = await Promise.all([
|
|
330
|
+
gate.recordFailure(key),
|
|
331
|
+
gate.recordFailure(key),
|
|
332
|
+
gate.recordFailure(key),
|
|
333
|
+
gate.recordFailure(key),
|
|
334
|
+
]);
|
|
335
|
+
// The serialized chain produces a strictly increasing failure count.
|
|
336
|
+
var failures = results.map(function (r) { return r.failures; }).sort(function (a, c) { return a - c; });
|
|
337
|
+
check("4 concurrent failures increment to 1..4 (no lost update)",
|
|
338
|
+
failures[0] === 1 && failures[1] === 2 && failures[2] === 3 && failures[3] === 4);
|
|
339
|
+
var locked = await gate.check(key);
|
|
340
|
+
check("4 concurrent failures reach the same locked stage as sequential",
|
|
341
|
+
locked.stage === "locked");
|
|
342
|
+
// The lockout subscriber sees every failure, not a collapsed single call.
|
|
343
|
+
check("lockout subscriber receives all 4 propagated failures",
|
|
344
|
+
lockout.calls.fail === 4);
|
|
345
|
+
}
|
|
346
|
+
|
|
311
347
|
async function run() {
|
|
312
348
|
testSurface();
|
|
313
349
|
testCreateRejectsBadOpts();
|
|
314
350
|
await testStaircaseAdvances();
|
|
351
|
+
await testConcurrentFailuresDoNotLoseIncrements();
|
|
315
352
|
await testMiddlewareChallengeFn();
|
|
316
353
|
await testMiddlewareLockedReturns423();
|
|
317
354
|
await testRecordSuccessClears();
|
|
@@ -291,7 +291,7 @@ function testPrmSignedMetadataEmits() {
|
|
|
291
291
|
|
|
292
292
|
// -------- AUTH-22 — CIBA parseNotification lowercase only --------
|
|
293
293
|
|
|
294
|
-
function testCibaParseNotificationLowercaseOnly() {
|
|
294
|
+
async function testCibaParseNotificationLowercaseOnly() {
|
|
295
295
|
// Lowercase-Authorization is what node:http delivers. Capital-A
|
|
296
296
|
// fallback is now structurally removed. We exercise the rejection
|
|
297
297
|
// path with no header at all — the lowercase-only path is still
|
|
@@ -306,7 +306,9 @@ function testCibaParseNotificationLowercaseOnly() {
|
|
|
306
306
|
clientNotificationToken: "abc-token-very-long-and-opaque-enough-for-ciba-minimum-entropy-guard-padding",
|
|
307
307
|
});
|
|
308
308
|
var threw = null;
|
|
309
|
-
|
|
309
|
+
// parseNotification is async (it verifies a pushed id_token) — the
|
|
310
|
+
// missing-bearer refusal surfaces as a rejection.
|
|
311
|
+
try { await ciba.parseNotification({ headers: {} }, { body: {} }); }
|
|
310
312
|
catch (e) { threw = e; }
|
|
311
313
|
check("AUTH-22 — parseNotification requires lowercase 'authorization' header",
|
|
312
314
|
threw && /missing-bearer/.test(threw.code || ""));
|
|
@@ -340,7 +342,7 @@ async function run() {
|
|
|
340
342
|
await testPasskeyExtensionsAllowlist();
|
|
341
343
|
testPrmHttpsEnforced();
|
|
342
344
|
testPrmSignedMetadataEmits();
|
|
343
|
-
testCibaParseNotificationLowercaseOnly();
|
|
345
|
+
await testCibaParseNotificationLowercaseOnly();
|
|
344
346
|
testDpopMiddlewareShutdownExposed();
|
|
345
347
|
}
|
|
346
348
|
|
|
@@ -208,6 +208,26 @@ async function testRecordFailureCounter() {
|
|
|
208
208
|
check("check: bob is unaffected", bob.locked === false && bob.attempts === 0);
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
+
// Concurrent failures on one key must each be counted. recordFailure reads
|
|
212
|
+
// the counter from the async cache, increments, and writes it back; without
|
|
213
|
+
// per-key serialization, N parallel calls all read the same pre-write value
|
|
214
|
+
// and the counter lands at 1 — an attacker who fires attempts in parallel
|
|
215
|
+
// stays under the lockout threshold forever.
|
|
216
|
+
async function testConcurrentFailuresAllCounted() {
|
|
217
|
+
var lockout = b.auth.lockout.create({
|
|
218
|
+
cache: _newCache("ns-race"), namespace: "login", maxAttempts: 10,
|
|
219
|
+
});
|
|
220
|
+
await Promise.all([
|
|
221
|
+
lockout.recordFailure("eve"),
|
|
222
|
+
lockout.recordFailure("eve"),
|
|
223
|
+
lockout.recordFailure("eve"),
|
|
224
|
+
lockout.recordFailure("eve"),
|
|
225
|
+
lockout.recordFailure("eve"),
|
|
226
|
+
]);
|
|
227
|
+
var state = await lockout.check("eve");
|
|
228
|
+
check("5 concurrent failures all counted (no lost update)", state.attempts === 5);
|
|
229
|
+
}
|
|
230
|
+
|
|
211
231
|
async function testNonMutatingCheck() {
|
|
212
232
|
var lockout = b.auth.lockout.create({
|
|
213
233
|
cache: _newCache("ns-nonmutating"), namespace: "login", maxAttempts: 5,
|
|
@@ -525,6 +545,7 @@ async function run() {
|
|
|
525
545
|
testCreateRejectsBadOpts();
|
|
526
546
|
await testKeyValidation();
|
|
527
547
|
await testRecordFailureCounter();
|
|
548
|
+
await testConcurrentFailuresAllCounted();
|
|
528
549
|
await testNonMutatingCheck();
|
|
529
550
|
await testRecordSuccessClears();
|
|
530
551
|
await testExponentialLadder();
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// b.auth.statusList — IETF Token Status List (draft) revocation bitstring.
|
|
3
|
+
//
|
|
4
|
+
// B11: the relying-party read path (fromJwt().list.get) had NO bounds check —
|
|
5
|
+
// _getAt over-reads the inflated buffer and returns 0 for an out-of-range index,
|
|
6
|
+
// and status 0 = VALID. So a credential whose status_list index points PAST the
|
|
7
|
+
// decoded list read as "not revoked": a fail-OPEN revocation bypass. create().get
|
|
8
|
+
// already throws on a bad index; fromJwt().get must match (fail closed).
|
|
9
|
+
//
|
|
10
|
+
// RED on the buggy tree: fromJwt(...).list.get(size + N) returns 0 (valid).
|
|
11
|
+
// GREEN after the fix: it throws status-list/bad-index.
|
|
12
|
+
//
|
|
13
|
+
// Also the first dedicated behavioral coverage for the primitive (was only an
|
|
14
|
+
// export-presence check), driving the real create -> toJwt -> fromJwt path.
|
|
15
|
+
|
|
16
|
+
var nodeCrypto = require("node:crypto");
|
|
17
|
+
var helpers = require("../helpers");
|
|
18
|
+
var b = helpers.b;
|
|
19
|
+
var check = helpers.check;
|
|
20
|
+
|
|
21
|
+
async function run() {
|
|
22
|
+
check("b.auth.statusList.create is a function", typeof b.auth.statusList.create === "function");
|
|
23
|
+
check("b.auth.statusList.fromJwt is a function", typeof b.auth.statusList.fromJwt === "function");
|
|
24
|
+
|
|
25
|
+
// ML-DSA-87 is available on the Node 24 CI floor (SLH-DSA keygen is Node-26+).
|
|
26
|
+
var kp = nodeCrypto.generateKeyPairSync("ml-dsa-87");
|
|
27
|
+
|
|
28
|
+
var list = b.auth.statusList.create({ size: 64, bits: 1 });
|
|
29
|
+
list.set(5, 1); // revoke index 5
|
|
30
|
+
|
|
31
|
+
// create()/get already fail closed on a bad index (control for the contract).
|
|
32
|
+
var createThrew = null;
|
|
33
|
+
try { list.get(64); } catch (e) { createThrew = e.code; }
|
|
34
|
+
check("create().get throws on an out-of-range index (fail closed)",
|
|
35
|
+
createThrew === "status-list/bad-index");
|
|
36
|
+
|
|
37
|
+
var token = await list.toJwt({
|
|
38
|
+
issuer: "https://issuer.example", subject: "https://issuer.example/sl/1",
|
|
39
|
+
privateKey: kp.privateKey, algorithm: "ML-DSA-87",
|
|
40
|
+
});
|
|
41
|
+
var parsed = await b.auth.statusList.fromJwt(token, {
|
|
42
|
+
publicKey: kp.publicKey, algorithms: ["ML-DSA-87"],
|
|
43
|
+
expectedIssuer: "https://issuer.example",
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
check("fromJwt round-trips the decoded list size", parsed.list.size === 64);
|
|
47
|
+
check("fromJwt: revoked index reads as revoked (status 1)", parsed.list.get(5) === 1);
|
|
48
|
+
check("fromJwt: an unset index reads as valid (status 0)", parsed.list.get(0) === 0);
|
|
49
|
+
|
|
50
|
+
// B11: the out-of-range read MUST fail closed, not read 0/valid.
|
|
51
|
+
var oobThrew = null, oobValue;
|
|
52
|
+
try { oobValue = parsed.list.get(parsed.list.size + 10); }
|
|
53
|
+
catch (e) { oobThrew = e.code; }
|
|
54
|
+
check("fromJwt().get throws on an out-of-range index (no fail-open revocation bypass)",
|
|
55
|
+
oobThrew === "status-list/bad-index");
|
|
56
|
+
check("fromJwt().get did NOT return a status for the out-of-range index",
|
|
57
|
+
oobValue === undefined);
|
|
58
|
+
// Negative + non-integer indices fail closed too.
|
|
59
|
+
var negThrew = null;
|
|
60
|
+
try { parsed.list.get(-1); } catch (e) { negThrew = e.code; }
|
|
61
|
+
check("fromJwt().get fails closed on a negative index", negThrew === "status-list/bad-index");
|
|
62
|
+
var fracThrew = null;
|
|
63
|
+
try { parsed.list.get(3.5); } catch (e) { fracThrew = e.code; }
|
|
64
|
+
check("fromJwt().get fails closed on a non-integer index", fracThrew === "status-list/bad-index");
|
|
65
|
+
|
|
66
|
+
console.log("OK — auth status-list (" + helpers.getChecks() + " checks)");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
module.exports = { run: run };
|
|
70
|
+
if (require.main === module) {
|
|
71
|
+
run().then(function () { process.exit(0); })
|
|
72
|
+
.catch(function (err) { process.exitCode = 1; throw err; });
|
|
73
|
+
}
|
|
@@ -91,12 +91,29 @@ function testOverridesAndSkips() {
|
|
|
91
91
|
check("string blockedAgents pattern is refused", threw === "bot-guard/bad-pattern");
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
function testPeerGatedAuditIp() {
|
|
95
|
+
// Audit-attribution IP can be peer-gated so a forged X-Forwarded-For from a
|
|
96
|
+
// direct caller can't pollute it. Construction wiring + validation.
|
|
97
|
+
var okTrusted = true;
|
|
98
|
+
try { b.middleware.botGuard({ trustedProxies: ["10.0.0.0/8"] }); } catch (_e) { okTrusted = false; }
|
|
99
|
+
check("botGuard: trustedProxies accepted", okTrusted === true);
|
|
100
|
+
|
|
101
|
+
var okResolver = true;
|
|
102
|
+
try { b.middleware.botGuard({ clientIpResolver: function () { return "1.2.3.4"; } }); } catch (_e) { okResolver = false; }
|
|
103
|
+
check("botGuard: clientIpResolver accepted", okResolver === true);
|
|
104
|
+
|
|
105
|
+
var threwCidr = null;
|
|
106
|
+
try { b.middleware.botGuard({ trustedProxies: ["nope"] }); } catch (e) { threwCidr = e.code; }
|
|
107
|
+
check("botGuard: malformed trustedProxies CIDR refused", threwCidr === "bot-guard/bad-opt");
|
|
108
|
+
}
|
|
109
|
+
|
|
94
110
|
async function run() {
|
|
95
111
|
testSurface();
|
|
96
112
|
testSecFetchNeverBlocks();
|
|
97
113
|
testBotsStillBlocked();
|
|
98
114
|
testTagModeAdvisory();
|
|
99
115
|
testOverridesAndSkips();
|
|
116
|
+
testPeerGatedAuditIp();
|
|
100
117
|
}
|
|
101
118
|
module.exports = { run: run };
|
|
102
119
|
if (require.main === module) { run().then(function () { console.log("[bot-guard] OK — " + helpers.getChecks() + " checks passed"); }, function (e) { console.error("FAIL: " + helpers.formatErr(e)); process.exit(1); }); }
|
|
@@ -74,12 +74,26 @@ function testInitOptsValidation() {
|
|
|
74
74
|
try { b.breakGlass.init({ now: 123 }); } catch (_e) { threwNow = true; }
|
|
75
75
|
check("init: removed `now` opt throws", threwNow);
|
|
76
76
|
|
|
77
|
-
// trustProxy is
|
|
77
|
+
// A bare trustProxy is refused — the grant IP pin would bind to a forgeable
|
|
78
|
+
// X-Forwarded-For. Operators declare peer-gating instead.
|
|
78
79
|
var threwTrustProxy = false;
|
|
79
80
|
try { b.breakGlass.init({ trustProxy: true }); } catch (_e) { threwTrustProxy = true; }
|
|
80
|
-
check("init: trustProxy
|
|
81
|
+
check("init: bare trustProxy refused (spoofable IP pin)", threwTrustProxy);
|
|
81
82
|
|
|
82
|
-
|
|
83
|
+
var threwTrusted = false;
|
|
84
|
+
try { b.breakGlass.init({ trustedProxies: ["10.0.0.0/8"] }); } catch (_e) { threwTrusted = true; }
|
|
85
|
+
check("init: trustedProxies accepted", !threwTrusted);
|
|
86
|
+
|
|
87
|
+
var threwResolver = false;
|
|
88
|
+
try { b.breakGlass.init({ clientIpResolver: function (rq) { return rq && rq.headers && rq.headers["true-client-ip"]; } }); }
|
|
89
|
+
catch (_e) { threwResolver = true; }
|
|
90
|
+
check("init: clientIpResolver accepted", !threwResolver);
|
|
91
|
+
|
|
92
|
+
var threwBadCidr = false;
|
|
93
|
+
try { b.breakGlass.init({ trustedProxies: ["nope"] }); } catch (_e) { threwBadCidr = true; }
|
|
94
|
+
check("init: malformed trustedProxies CIDR refused", threwBadCidr);
|
|
95
|
+
|
|
96
|
+
// bare init() with no opts still works (resolves the socket address).
|
|
83
97
|
var threwBare = false;
|
|
84
98
|
try { b.breakGlass.init(); } catch (_e) { threwBare = true; }
|
|
85
99
|
check("init: no-opts init still works", !threwBare);
|
|
@@ -526,6 +540,50 @@ async function testIpPinEnforcement() {
|
|
|
526
540
|
}
|
|
527
541
|
}
|
|
528
542
|
|
|
543
|
+
async function testIpPinPeerGated() {
|
|
544
|
+
// With trustedProxies, the grant IP pins to the real client behind the
|
|
545
|
+
// proxy — and a direct attacker forging X-Forwarded-For cannot satisfy it.
|
|
546
|
+
var tmpDir = _tmp();
|
|
547
|
+
await setupTestDb(tmpDir);
|
|
548
|
+
try {
|
|
549
|
+
b.breakGlass.init({ trustedProxies: ["10.0.0.0/8"] });
|
|
550
|
+
b.queue.init({ backends: { primary: { protocol: "local" } } });
|
|
551
|
+
var jid = await b.queue.enqueue("ip-pin-pg-q", { secret: "row-ip-pin-pg" });
|
|
552
|
+
await b.breakGlass.policy.set("_blamejs_jobs", {
|
|
553
|
+
columns: ["payload"], factors: ["totp"], maxRowsPerGrant: 5,
|
|
554
|
+
pinIp: true, sessionPin: false,
|
|
555
|
+
});
|
|
556
|
+
var totp = _validTotp();
|
|
557
|
+
// Mint behind the trusted proxy: peer 10.0.0.9 trusted → pin to the
|
|
558
|
+
// forwarded client 203.0.113.7, NOT the proxy address.
|
|
559
|
+
var grant = await b.breakGlass.grant({
|
|
560
|
+
req: _fakeReq({ socket: { remoteAddress: "10.0.0.9" }, headers: { "x-forwarded-for": "203.0.113.7" } }),
|
|
561
|
+
table: "_blamejs_jobs",
|
|
562
|
+
reason: "ip-pin peer-gated: minting behind trusted proxy for the real client",
|
|
563
|
+
factor: { type: "totp", code: totp.code, secret: totp.secret },
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
// Direct attacker forging the client IP via XFF (untrusted peer) → refused.
|
|
567
|
+
var threwSpoof = null;
|
|
568
|
+
try {
|
|
569
|
+
await b.breakGlass.unsealRow(grant, "_blamejs_jobs", jid.jobId,
|
|
570
|
+
{ req: _fakeReq({ socket: { remoteAddress: "198.51.100.66" }, headers: { "x-forwarded-for": "203.0.113.7" } }) });
|
|
571
|
+
} catch (e) { threwSpoof = e; }
|
|
572
|
+
check("ip-pin peer-gated: forged XFF redeem refused",
|
|
573
|
+
threwSpoof && /breakglass\/grant-ip-mismatch/.test(threwSpoof.code));
|
|
574
|
+
|
|
575
|
+
// Legitimate redeem through the proxy → succeeds (resolves to 203.0.113.7).
|
|
576
|
+
var ok = await b.breakGlass.unsealRow(grant, "_blamejs_jobs", jid.jobId,
|
|
577
|
+
{ req: _fakeReq({ socket: { remoteAddress: "10.0.0.9" }, headers: { "x-forwarded-for": "203.0.113.7" } }) });
|
|
578
|
+
check("ip-pin peer-gated: proxied redeem of real client succeeds",
|
|
579
|
+
ok && ok.payload && ok.payload.indexOf("row-ip-pin-pg") !== -1);
|
|
580
|
+
|
|
581
|
+
try { await b.queue.shutdown({ timeoutMs: 200 }); } catch (_e) {}
|
|
582
|
+
} finally {
|
|
583
|
+
await teardownTestDb(tmpDir);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
529
587
|
async function testSessionPinEnforcement() {
|
|
530
588
|
var tmpDir = _tmp();
|
|
531
589
|
await setupTestDb(tmpDir);
|
|
@@ -1079,6 +1137,7 @@ async function run() {
|
|
|
1079
1137
|
await testSweepExpiredGrants();
|
|
1080
1138
|
// grant binding enforcement (IP / session pin + fail-closed) + TOTP replay
|
|
1081
1139
|
await testIpPinEnforcement();
|
|
1140
|
+
await testIpPinPeerGated();
|
|
1082
1141
|
await testSessionPinEnforcement();
|
|
1083
1142
|
await testIpPinFailClosedOnNullBinding();
|
|
1084
1143
|
await testTotpReplayDefense();
|
|
@@ -61,6 +61,18 @@ function testUnknownTokenRefused() {
|
|
|
61
61
|
try { b.middleware.clearSiteData({ types: ["bogus"] }); }
|
|
62
62
|
catch (e) { threw = e instanceof TypeError && /unknown type/.test(e.message); }
|
|
63
63
|
check("clearSiteData: unknown token refused at config-time", threw);
|
|
64
|
+
|
|
65
|
+
// B13: an Object.prototype member name must NOT pass the allowlist. A bracket
|
|
66
|
+
// lookup on the plain-object KNOWN_TYPES resolved inherited functions to
|
|
67
|
+
// truthy, so "toString" / "constructor" / "hasOwnProperty" slipped through and
|
|
68
|
+
// were emitted as bogus directives (prototype shadowing).
|
|
69
|
+
var csd = b.middleware._modules.clearSiteData;
|
|
70
|
+
["toString", "constructor", "hasOwnProperty", "__proto__", "valueOf"].forEach(function (proto) {
|
|
71
|
+
var t2 = false;
|
|
72
|
+
try { csd.headerValue([proto]); }
|
|
73
|
+
catch (e) { t2 = e instanceof TypeError && /unknown type/.test(e.message); }
|
|
74
|
+
check("clearSiteData: prototype member '" + proto + "' refused (no proto-shadow)", t2);
|
|
75
|
+
});
|
|
64
76
|
}
|
|
65
77
|
|
|
66
78
|
function testEmptyArrayRefused() {
|
|
@@ -102,12 +102,52 @@ async function testTransactionRejectsBadArg() {
|
|
|
102
102
|
check("tx: non-function arg rejected", threw && threw.code === "cluster-storage/bad-arg");
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
// A WITH (CTE) read must reach the caller's row set. The local-exec method
|
|
106
|
+
// choice keyed on a leading "SELECT" mis-routed "WITH c AS (...) SELECT ..." to
|
|
107
|
+
// .run(), which on node:sqlite returns only a changes count and SILENTLY DROPS
|
|
108
|
+
// the rows — the caller saw an empty result. The classifier now resolves the
|
|
109
|
+
// CTE's effective verb so the read uses .all().
|
|
110
|
+
async function testCteReadReturnsRows() {
|
|
111
|
+
var tmp = fs.mkdtempSync(path.join(os.tmpdir(), "cs-cte-read-"));
|
|
112
|
+
try {
|
|
113
|
+
await setupTestDb(tmp, SCHEMA);
|
|
114
|
+
var cs = b.clusterStorage;
|
|
115
|
+
await cs.execute("INSERT INTO cs_tx_t (k, v) VALUES (?, ?)", ["a", 1]);
|
|
116
|
+
await cs.execute("INSERT INTO cs_tx_t (k, v) VALUES (?, ?)", ["b", 2]);
|
|
117
|
+
|
|
118
|
+
// Plain SELECT control — already worked.
|
|
119
|
+
var plain = await cs.execute("SELECT k, v FROM cs_tx_t ORDER BY k");
|
|
120
|
+
check("plain SELECT returns both rows", plain.rows.length === 2);
|
|
121
|
+
|
|
122
|
+
// CTE read — the regression. Was: rows.length === 0 (rows dropped by .run()).
|
|
123
|
+
var cte = await cs.execute(
|
|
124
|
+
"WITH picked AS (SELECT k, v FROM cs_tx_t WHERE v >= ?) SELECT k, v FROM picked ORDER BY k", [1]);
|
|
125
|
+
check("WITH (CTE) read returns rows (not silently dropped)", cte.rows.length === 2);
|
|
126
|
+
check("WITH (CTE) read returns the actual data", cte.rows[0].k === "a" && cte.rows[1].k === "b");
|
|
127
|
+
|
|
128
|
+
// executeOne over a CTE read resolves the single row too.
|
|
129
|
+
var one = await cs.executeOne(
|
|
130
|
+
"WITH t AS (SELECT COUNT(*) AS n FROM cs_tx_t) SELECT n FROM t");
|
|
131
|
+
check("executeOne over a CTE read resolves the row", one && one.n === 2);
|
|
132
|
+
|
|
133
|
+
// A WITH (CTE) write still applies its changes (not mis-read as a query).
|
|
134
|
+
await cs.execute(
|
|
135
|
+
"WITH src AS (SELECT 'c' AS k, 3 AS v) INSERT INTO cs_tx_t (k, v) SELECT k, v FROM src");
|
|
136
|
+
var after = await cs.executeOne("SELECT COUNT(*) AS n FROM cs_tx_t");
|
|
137
|
+
check("WITH (CTE) write persisted its row", after.n === 3);
|
|
138
|
+
} finally {
|
|
139
|
+
b.db._resetForTest();
|
|
140
|
+
await teardownTestDb(tmp);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
105
144
|
async function run() {
|
|
106
145
|
testSurface();
|
|
107
146
|
await testTransactionCommits();
|
|
108
147
|
await testTransactionRollsBackOnThrow();
|
|
109
148
|
await testTransactionSerializesExecute();
|
|
110
149
|
await testTransactionRejectsBadArg();
|
|
150
|
+
await testCteReadReturnsRows();
|
|
111
151
|
}
|
|
112
152
|
|
|
113
153
|
module.exports = { run: run };
|