@blamejs/blamejs-shop 0.4.31 → 0.4.32
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/asset-manifest.json +1 -1
- package/lib/vendor/MANIFEST.json +392 -278
- package/lib/vendor/blamejs/.github/workflows/ci.yml +34 -3
- package/lib/vendor/blamejs/.github/workflows/npm-publish.yml +21 -4
- package/lib/vendor/blamejs/.gitignore +6 -0
- package/lib/vendor/blamejs/CHANGELOG.md +26 -0
- package/lib/vendor/blamejs/MIGRATING.md +43 -0
- package/lib/vendor/blamejs/README.md +8 -6
- package/lib/vendor/blamejs/SECURITY.md +19 -3
- package/lib/vendor/blamejs/api-snapshot.json +2190 -664
- package/lib/vendor/blamejs/docker/caddy/localstack.Caddyfile +19 -0
- package/lib/vendor/blamejs/docker/init/generate-certs.sh +1 -1
- package/lib/vendor/blamejs/docker/otel/config.yaml +42 -0
- package/lib/vendor/blamejs/docker/otel/export/.gitkeep +0 -0
- package/lib/vendor/blamejs/docker/postgres/initdb/10-replication.sh +15 -0
- package/lib/vendor/blamejs/docker/postgres/replica-entrypoint.sh +38 -0
- package/lib/vendor/blamejs/docker/toxiproxy/toxiproxy.json +14 -0
- package/lib/vendor/blamejs/docker-compose.test.yml +209 -0
- package/lib/vendor/blamejs/examples/wiki/lib/page-generator.js +132 -0
- package/lib/vendor/blamejs/examples/wiki/lib/source-comment-block-validator.js +221 -61
- package/lib/vendor/blamejs/examples/wiki/lib/source-doc-parser.js +144 -9
- package/lib/vendor/blamejs/examples/wiki/test/e2e.js +99 -0
- package/lib/vendor/blamejs/fuzz/guard-sql.fuzz.js +36 -0
- package/lib/vendor/blamejs/index.js +4 -0
- package/lib/vendor/blamejs/lib/agent-envelope-mac.js +104 -0
- package/lib/vendor/blamejs/lib/agent-event-bus.js +105 -4
- package/lib/vendor/blamejs/lib/agent-posture-chain.js +8 -42
- package/lib/vendor/blamejs/lib/ai-content-detect.js +9 -10
- package/lib/vendor/blamejs/lib/api-key.js +158 -77
- package/lib/vendor/blamejs/lib/atomic-file.js +62 -4
- package/lib/vendor/blamejs/lib/audit-chain.js +47 -11
- package/lib/vendor/blamejs/lib/audit-sign.js +77 -2
- package/lib/vendor/blamejs/lib/audit-tools.js +79 -51
- package/lib/vendor/blamejs/lib/audit.js +259 -123
- package/lib/vendor/blamejs/lib/auth/oauth.js +53 -9
- package/lib/vendor/blamejs/lib/auth/openid-federation.js +108 -47
- package/lib/vendor/blamejs/lib/auth/saml.js +6 -8
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc.js +31 -5
- package/lib/vendor/blamejs/lib/backup/index.js +45 -10
- package/lib/vendor/blamejs/lib/break-glass.js +355 -147
- package/lib/vendor/blamejs/lib/cache.js +174 -105
- package/lib/vendor/blamejs/lib/chain-writer.js +38 -16
- package/lib/vendor/blamejs/lib/cli.js +19 -14
- package/lib/vendor/blamejs/lib/cluster-provider-db.js +130 -104
- package/lib/vendor/blamejs/lib/cluster-storage.js +119 -22
- package/lib/vendor/blamejs/lib/cluster.js +119 -71
- package/lib/vendor/blamejs/lib/codepoint-class.js +23 -0
- package/lib/vendor/blamejs/lib/compliance.js +206 -4
- package/lib/vendor/blamejs/lib/consent.js +82 -29
- package/lib/vendor/blamejs/lib/constants.js +27 -11
- package/lib/vendor/blamejs/lib/crypto-field.js +916 -156
- package/lib/vendor/blamejs/lib/db-declare-row-policy.js +35 -22
- package/lib/vendor/blamejs/lib/db-file-lifecycle.js +3 -2
- package/lib/vendor/blamejs/lib/db-query.js +882 -260
- package/lib/vendor/blamejs/lib/db-schema.js +228 -44
- package/lib/vendor/blamejs/lib/db.js +249 -99
- package/lib/vendor/blamejs/lib/dsr.js +385 -55
- package/lib/vendor/blamejs/lib/error-page.js +14 -1
- package/lib/vendor/blamejs/lib/external-db-migrate.js +239 -137
- package/lib/vendor/blamejs/lib/external-db.js +549 -34
- package/lib/vendor/blamejs/lib/file-upload.js +52 -7
- package/lib/vendor/blamejs/lib/framework-error.js +20 -1
- package/lib/vendor/blamejs/lib/framework-files.js +73 -0
- package/lib/vendor/blamejs/lib/framework-schema.js +695 -394
- package/lib/vendor/blamejs/lib/gate-contract.js +659 -1
- package/lib/vendor/blamejs/lib/guard-agent-registry.js +26 -44
- package/lib/vendor/blamejs/lib/guard-all.js +1 -0
- package/lib/vendor/blamejs/lib/guard-auth.js +42 -112
- package/lib/vendor/blamejs/lib/guard-cidr.js +33 -154
- package/lib/vendor/blamejs/lib/guard-csv.js +46 -113
- package/lib/vendor/blamejs/lib/guard-domain.js +34 -157
- package/lib/vendor/blamejs/lib/guard-dsn.js +27 -43
- package/lib/vendor/blamejs/lib/guard-email.js +47 -69
- package/lib/vendor/blamejs/lib/guard-envelope.js +19 -32
- package/lib/vendor/blamejs/lib/guard-event-bus-payload.js +24 -42
- package/lib/vendor/blamejs/lib/guard-event-bus-topic.js +25 -43
- package/lib/vendor/blamejs/lib/guard-filename.js +42 -106
- package/lib/vendor/blamejs/lib/guard-graphql.js +42 -123
- package/lib/vendor/blamejs/lib/guard-html.js +53 -108
- package/lib/vendor/blamejs/lib/guard-idempotency-key.js +24 -42
- package/lib/vendor/blamejs/lib/guard-image.js +46 -103
- package/lib/vendor/blamejs/lib/guard-imap-command.js +18 -32
- package/lib/vendor/blamejs/lib/guard-jmap.js +16 -30
- package/lib/vendor/blamejs/lib/guard-json.js +38 -108
- package/lib/vendor/blamejs/lib/guard-jsonpath.js +38 -171
- package/lib/vendor/blamejs/lib/guard-jwt.js +49 -179
- package/lib/vendor/blamejs/lib/guard-list-id.js +25 -41
- package/lib/vendor/blamejs/lib/guard-list-unsubscribe.js +27 -43
- package/lib/vendor/blamejs/lib/guard-mail-compose.js +24 -42
- package/lib/vendor/blamejs/lib/guard-mail-move.js +26 -44
- package/lib/vendor/blamejs/lib/guard-mail-query.js +28 -46
- package/lib/vendor/blamejs/lib/guard-mail-reply.js +24 -42
- package/lib/vendor/blamejs/lib/guard-mail-sieve.js +24 -42
- package/lib/vendor/blamejs/lib/guard-managesieve-command.js +17 -31
- package/lib/vendor/blamejs/lib/guard-markdown.js +37 -104
- package/lib/vendor/blamejs/lib/guard-message-id.js +26 -45
- package/lib/vendor/blamejs/lib/guard-mime.js +39 -151
- package/lib/vendor/blamejs/lib/guard-oauth.js +54 -135
- package/lib/vendor/blamejs/lib/guard-pdf.js +45 -101
- package/lib/vendor/blamejs/lib/guard-pop3-command.js +21 -31
- package/lib/vendor/blamejs/lib/guard-posture-chain.js +24 -42
- package/lib/vendor/blamejs/lib/guard-regex.js +33 -107
- package/lib/vendor/blamejs/lib/guard-saga-config.js +24 -42
- package/lib/vendor/blamejs/lib/guard-shell.js +42 -172
- package/lib/vendor/blamejs/lib/guard-smtp-command.js +48 -54
- package/lib/vendor/blamejs/lib/guard-snapshot-envelope.js +24 -42
- package/lib/vendor/blamejs/lib/guard-sql.js +1491 -0
- package/lib/vendor/blamejs/lib/guard-stream-args.js +24 -43
- package/lib/vendor/blamejs/lib/guard-svg.js +47 -65
- package/lib/vendor/blamejs/lib/guard-template.js +35 -172
- package/lib/vendor/blamejs/lib/guard-tenant-id.js +26 -45
- package/lib/vendor/blamejs/lib/guard-time.js +32 -154
- package/lib/vendor/blamejs/lib/guard-trace-context.js +25 -44
- package/lib/vendor/blamejs/lib/guard-uuid.js +32 -153
- package/lib/vendor/blamejs/lib/guard-xml.js +38 -113
- package/lib/vendor/blamejs/lib/guard-yaml.js +51 -163
- package/lib/vendor/blamejs/lib/http-client.js +37 -9
- package/lib/vendor/blamejs/lib/inbox.js +120 -107
- package/lib/vendor/blamejs/lib/legal-hold.js +121 -50
- package/lib/vendor/blamejs/lib/log-stream-cloudwatch.js +47 -31
- package/lib/vendor/blamejs/lib/log-stream-otlp.js +32 -18
- package/lib/vendor/blamejs/lib/mail-auth.js +236 -0
- package/lib/vendor/blamejs/lib/mail-crypto-smime.js +2 -6
- package/lib/vendor/blamejs/lib/mail-dkim.js +1 -0
- package/lib/vendor/blamejs/lib/mail-greylist.js +2 -6
- package/lib/vendor/blamejs/lib/mail-helo.js +2 -6
- package/lib/vendor/blamejs/lib/mail-journal.js +85 -64
- package/lib/vendor/blamejs/lib/mail-rbl.js +2 -6
- package/lib/vendor/blamejs/lib/mail-scan.js +2 -6
- package/lib/vendor/blamejs/lib/mail-server-jmap.js +117 -12
- package/lib/vendor/blamejs/lib/mail-server-mx.js +276 -7
- package/lib/vendor/blamejs/lib/mail-spam-score.js +2 -6
- package/lib/vendor/blamejs/lib/mail-store.js +293 -154
- package/lib/vendor/blamejs/lib/mail.js +8 -4
- package/lib/vendor/blamejs/lib/middleware/body-parser.js +71 -25
- package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +19 -8
- package/lib/vendor/blamejs/lib/middleware/dpop.js +10 -1
- package/lib/vendor/blamejs/lib/middleware/fetch-metadata.js +17 -7
- package/lib/vendor/blamejs/lib/middleware/idempotency-key.js +75 -51
- package/lib/vendor/blamejs/lib/middleware/rate-limit.js +102 -32
- package/lib/vendor/blamejs/lib/middleware/security-headers.js +21 -5
- package/lib/vendor/blamejs/lib/migrations.js +108 -66
- package/lib/vendor/blamejs/lib/network-heartbeat.js +7 -0
- package/lib/vendor/blamejs/lib/network-proxy.js +24 -1
- package/lib/vendor/blamejs/lib/nonce-store.js +31 -9
- package/lib/vendor/blamejs/lib/object-store/azure-blob-bucket-ops.js +9 -4
- package/lib/vendor/blamejs/lib/object-store/azure-blob.js +57 -3
- package/lib/vendor/blamejs/lib/object-store/gcs.js +4 -1
- package/lib/vendor/blamejs/lib/object-store/sigv4-bucket-ops.js +5 -2
- package/lib/vendor/blamejs/lib/object-store/sigv4.js +38 -6
- package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +9 -1
- package/lib/vendor/blamejs/lib/observability.js +124 -0
- package/lib/vendor/blamejs/lib/otel-export.js +12 -3
- package/lib/vendor/blamejs/lib/outbox.js +184 -83
- package/lib/vendor/blamejs/lib/parsers/safe-xml.js +47 -7
- package/lib/vendor/blamejs/lib/pqc-agent.js +44 -0
- package/lib/vendor/blamejs/lib/pubsub-cluster.js +42 -20
- package/lib/vendor/blamejs/lib/queue-local.js +225 -140
- package/lib/vendor/blamejs/lib/queue-redis.js +9 -1
- package/lib/vendor/blamejs/lib/queue-sqs.js +6 -0
- package/lib/vendor/blamejs/lib/queue.js +7 -0
- package/lib/vendor/blamejs/lib/redact.js +68 -11
- package/lib/vendor/blamejs/lib/redis-client.js +160 -31
- package/lib/vendor/blamejs/lib/request-helpers.js +7 -0
- package/lib/vendor/blamejs/lib/retention.js +101 -40
- package/lib/vendor/blamejs/lib/router.js +212 -5
- package/lib/vendor/blamejs/lib/safe-dns.js +29 -45
- package/lib/vendor/blamejs/lib/safe-ical.js +18 -33
- package/lib/vendor/blamejs/lib/safe-icap.js +27 -43
- package/lib/vendor/blamejs/lib/safe-sieve.js +21 -40
- package/lib/vendor/blamejs/lib/safe-sql.js +212 -3
- package/lib/vendor/blamejs/lib/safe-url.js +170 -3
- package/lib/vendor/blamejs/lib/safe-vcard.js +18 -33
- package/lib/vendor/blamejs/lib/scheduler.js +35 -12
- package/lib/vendor/blamejs/lib/seeders.js +122 -74
- package/lib/vendor/blamejs/lib/session-stores.js +42 -14
- package/lib/vendor/blamejs/lib/session.js +175 -77
- package/lib/vendor/blamejs/lib/sql.js +3842 -0
- package/lib/vendor/blamejs/lib/sse.js +26 -0
- package/lib/vendor/blamejs/lib/ssrf-guard.js +151 -4
- package/lib/vendor/blamejs/lib/static.js +177 -34
- package/lib/vendor/blamejs/lib/subject.js +96 -49
- package/lib/vendor/blamejs/lib/vault/index.js +3 -2
- package/lib/vendor/blamejs/lib/vault/passphrase-ops.js +3 -2
- package/lib/vendor/blamejs/lib/vault/rotate.js +168 -108
- package/lib/vendor/blamejs/lib/vault-aad.js +6 -0
- package/lib/vendor/blamejs/lib/vendor-data.js +2 -0
- package/lib/vendor/blamejs/lib/websocket.js +35 -5
- package/lib/vendor/blamejs/lib/worker-pool.js +11 -0
- package/lib/vendor/blamejs/package.json +2 -2
- package/lib/vendor/blamejs/release-notes/v0.14.x.json +1503 -0
- package/lib/vendor/blamejs/release-notes/v0.15.0.json +77 -0
- package/lib/vendor/blamejs/release-notes/v0.15.1.json +22 -0
- package/lib/vendor/blamejs/release-notes/v0.15.2.json +22 -0
- package/lib/vendor/blamejs/release-notes/v0.15.3.json +39 -0
- package/lib/vendor/blamejs/release-notes/v0.15.4.json +39 -0
- package/lib/vendor/blamejs/release-notes/v0.15.5.json +22 -0
- package/lib/vendor/blamejs/release-notes/v0.15.6.json +59 -0
- package/lib/vendor/blamejs/scripts/check-services.js +21 -0
- package/lib/vendor/blamejs/scripts/gen-migrating.js +51 -0
- package/lib/vendor/blamejs/scripts/release.js +398 -38
- package/lib/vendor/blamejs/test/00-primitives.js +117 -0
- package/lib/vendor/blamejs/test/10-state.js +140 -14
- package/lib/vendor/blamejs/test/20-db.js +65 -2
- package/lib/vendor/blamejs/test/helpers/db.js +9 -0
- package/lib/vendor/blamejs/test/helpers/drivers.js +27 -15
- package/lib/vendor/blamejs/test/helpers/services.js +21 -0
- package/lib/vendor/blamejs/test/integration/audit-actor-binding-pg.test.js +246 -0
- package/lib/vendor/blamejs/test/integration/audit-chain-external-db.test.js +517 -0
- package/lib/vendor/blamejs/test/integration/audit-stack-mysql.test.js +639 -0
- package/lib/vendor/blamejs/test/integration/audit-stack-postgres.test.js +832 -0
- package/lib/vendor/blamejs/test/integration/backup-restore-objectstore.test.js +453 -0
- package/lib/vendor/blamejs/test/integration/data-layer-cluster-mysql.test.js +649 -0
- package/lib/vendor/blamejs/test/integration/data-layer-cluster-pg.test.js +770 -0
- package/lib/vendor/blamejs/test/integration/data-layer-mysql-privacy.test.js +630 -0
- package/lib/vendor/blamejs/test/integration/data-layer-mysql.test.js +610 -0
- package/lib/vendor/blamejs/test/integration/data-layer-pg.test.js +577 -0
- package/lib/vendor/blamejs/test/integration/data-layer-postgres.test.js +771 -0
- package/lib/vendor/blamejs/test/integration/db-layer-mysql.test.js +549 -0
- package/lib/vendor/blamejs/test/integration/db-layer-postgres.test.js +598 -0
- package/lib/vendor/blamejs/test/integration/distributed-scheduler-fencing-pg.test.js +602 -0
- package/lib/vendor/blamejs/test/integration/external-db-postgres.test.js +576 -0
- package/lib/vendor/blamejs/test/integration/framework-schema-mysql.test.js +353 -0
- package/lib/vendor/blamejs/test/integration/log-stream-cloudwatch.test.js +224 -0
- package/lib/vendor/blamejs/test/integration/mail-crypto-smime.test.js +142 -17
- package/lib/vendor/blamejs/test/integration/network-heartbeat.test.js +25 -10
- package/lib/vendor/blamejs/test/integration/object-store-azure.test.js +101 -0
- package/lib/vendor/blamejs/test/integration/object-store-gcs.test.js +239 -0
- package/lib/vendor/blamejs/test/integration/object-store-sigv4.test.js +35 -16
- package/lib/vendor/blamejs/test/integration/object-store-worm-lock.test.js +291 -0
- package/lib/vendor/blamejs/test/integration/pubsub.test.js +14 -0
- package/lib/vendor/blamejs/test/integration/queue-sqs.test.js +322 -0
- package/lib/vendor/blamejs/test/integration/redis-reconnect-toxiproxy.test.js +300 -0
- package/lib/vendor/blamejs/test/integration/sql-fts5-catalog-sqlite.test.js +154 -0
- package/lib/vendor/blamejs/test/integration/tls-classical-downgrade-audit.test.js +71 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-event-bus.test.js +175 -12
- package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file-exclusive-temp.test.js +216 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-checkpoint-false-rollback.test.js +203 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-query-self-log.test.js +126 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-safeemit-redacts-secrets.test.js +196 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-signing-key-rotation.test.js +197 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-verifybundle-tamper.test.js +209 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/azure-blob-key-encoding.test.js +121 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/backup-residency-posture.test.js +168 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/backup-scheduletest-drill.test.js +318 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/break-glass.test.js +233 -7
- package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +1120 -14
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance.test.js +229 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-derived-hash.test.js +24 -7
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-dual-read-migrate.test.js +165 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-per-row-key.test.js +350 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-unseal-rate-cap.test.js +27 -9
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-upgrade-dialect.test.js +76 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-interop-oracles.test.js +392 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csrf-protect.test.js +159 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-column-gate.test.js +180 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/db-query-cross-schema.test.js +5 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/db-query-sealed-field-in.test.js +101 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-raw-residency-gate.test.js +128 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-schema-drift.test.js +38 -5
- package/lib/vendor/blamejs/test/layer-0-primitives/db-schema-reconcile-emittable.test.js +127 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-stream-and-payload-shape.test.js +267 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-worm.test.js +150 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/defineguard-default-gate-posture-caps.test.js +30 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dpop-middleware-replaystore-required.test.js +46 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dsr.test.js +218 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/erase-posture-vacuum.test.js +210 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-hardening.test.js +4 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-migrate.test.js +48 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/federation-vc-suite.test.js +237 -5
- package/lib/vendor/blamejs/test/layer-0-primitives/fetch-metadata.test.js +20 -9
- package/lib/vendor/blamejs/test/layer-0-primitives/file-upload-content-safety-skip-audit.test.js +193 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-csv.test.js +90 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-client-stream.test.js +85 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/idempotency-key.test.js +10 -6
- package/lib/vendor/blamejs/test/layer-0-primitives/inbox.test.js +15 -4
- package/lib/vendor/blamejs/test/layer-0-primitives/legal-hold.test.js +146 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-auth.test.js +189 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-journal.test.js +3 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-jmap.test.js +123 -4
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-mx.test.js +207 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-store.test.js +74 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/oauth-callback.test.js +43 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/otel-export.test.js +133 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/otlp-attr-redaction.test.js +101 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/outbox-inflight-reaper.test.js +136 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/parsers-standalone.test.js +83 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/passkey-real-vectors.test.js +429 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/pqc-agent-curve.test.js +21 -11
- package/lib/vendor/blamejs/test/layer-0-primitives/queue-byo-db.test.js +40 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/redact-dlp.test.js +83 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/redis-client.test.js +113 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/retention-dryrun-no-vacuum.test.js +99 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/router-use-path-scope.test.js +255 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-url-canonicalize.test.js +309 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-xml.test.js +143 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/saml-subjectconfirmation-notonorafter.test.js +287 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc-ecdsa-p1363.test.js +79 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc.test.js +50 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/security-headers.test.js +31 -4
- package/lib/vendor/blamejs/test/layer-0-primitives/session-extensions.test.js +45 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sigv4-bucket-ops.test.js +49 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sql.test.js +595 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sse-backpressure.test.js +91 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ssrf-guard.test.js +69 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/static.test.js +194 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/websocket-extension-header.test.js +88 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/worker-pool-recycle-race.test.js +66 -0
- package/lib/vendor/blamejs/test/layer-1-state/api-key.test.js +84 -0
- package/lib/vendor/blamejs/test/layer-5-integration/external-db-residency.test.js +638 -0
- package/lib/vendor/blamejs/test/layer-5-integration/guard-host-integration.test.js +21 -0
- package/lib/vendor/blamejs/test/smoke.js +79 -21
- package/package.json +1 -1
- package/lib/vendor/blamejs/release-notes/v0.14.0.json +0 -43
- package/lib/vendor/blamejs/release-notes/v0.14.1.json +0 -60
- package/lib/vendor/blamejs/release-notes/v0.14.10.json +0 -54
- package/lib/vendor/blamejs/release-notes/v0.14.11.json +0 -72
- package/lib/vendor/blamejs/release-notes/v0.14.12.json +0 -95
- package/lib/vendor/blamejs/release-notes/v0.14.13.json +0 -52
- package/lib/vendor/blamejs/release-notes/v0.14.14.json +0 -31
- package/lib/vendor/blamejs/release-notes/v0.14.16.json +0 -45
- package/lib/vendor/blamejs/release-notes/v0.14.17.json +0 -57
- package/lib/vendor/blamejs/release-notes/v0.14.18.json +0 -127
- package/lib/vendor/blamejs/release-notes/v0.14.19.json +0 -61
- package/lib/vendor/blamejs/release-notes/v0.14.2.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.14.20.json +0 -73
- package/lib/vendor/blamejs/release-notes/v0.14.21.json +0 -98
- package/lib/vendor/blamejs/release-notes/v0.14.22.json +0 -91
- package/lib/vendor/blamejs/release-notes/v0.14.3.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.14.4.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.14.5.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.14.6.json +0 -60
- package/lib/vendor/blamejs/release-notes/v0.14.7.json +0 -77
- package/lib/vendor/blamejs/release-notes/v0.14.8.json +0 -27
- package/lib/vendor/blamejs/release-notes/v0.14.9.json +0 -40
|
@@ -0,0 +1,638 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* external-db-residency — per-row residency write gate
|
|
4
|
+
* (b.externalDb.query / transaction / read.query).
|
|
5
|
+
*
|
|
6
|
+
* Drives the cross-border DML write gate end-to-end against a fake
|
|
7
|
+
* backend so the wire (the backend's query hook) is observed for every
|
|
8
|
+
* enforcement-matrix cell:
|
|
9
|
+
*
|
|
10
|
+
* - unregulated posture + residency-tagged backend → DML passes
|
|
11
|
+
* - regulated posture (gdpr) + untagged backend → DML passes
|
|
12
|
+
* - gdpr + tagged backend + no rowResidencyTag → RESIDENCY_GATE_REQUIRED (wire NOT reached)
|
|
13
|
+
* - gdpr + tagged backend + matching tag → passes (statement reaches the wire)
|
|
14
|
+
* - gdpr + tagged backend + "global" → passes
|
|
15
|
+
* - gdpr + tagged backend + mismatched tag → RESIDENCY_TAG_MISMATCH
|
|
16
|
+
* - empty-string rowResidencyTag → INVALID_OPT
|
|
17
|
+
* - SELECT under gdpr to a tagged backend → passes (non-DML not gated)
|
|
18
|
+
* - transaction: tx-level + per-call override tags → mismatch rolls back (no COMMIT)
|
|
19
|
+
* - read-replica residency incompatibility → REPLICA_RESIDENCY_INCOMPATIBLE / allowCrossBorder bypass
|
|
20
|
+
*
|
|
21
|
+
* The gate reads the active posture from b.compliance.current(), so each
|
|
22
|
+
* regulated-posture block flips the posture via b.compliance.set("gdpr")
|
|
23
|
+
* and ALWAYS restores it in a finally so parallel smoke files aren't
|
|
24
|
+
* poisoned.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
var helpers = require("../helpers");
|
|
28
|
+
var b = helpers.b;
|
|
29
|
+
var check = helpers.check;
|
|
30
|
+
|
|
31
|
+
// Tracking driver: records every SQL statement the backend's query hook
|
|
32
|
+
// sees so tests assert whether the wire was reached. Returns rowCount 1
|
|
33
|
+
// for DML, an empty result for SELECT, and a no-op for BEGIN / COMMIT /
|
|
34
|
+
// ROLLBACK so the transaction machinery completes.
|
|
35
|
+
function _trackingDriver(label) {
|
|
36
|
+
var seen = [];
|
|
37
|
+
return {
|
|
38
|
+
label: label,
|
|
39
|
+
seen: seen,
|
|
40
|
+
connect: async function () { return { id: label + "-client" }; },
|
|
41
|
+
query: async function (_client, sql, _params) {
|
|
42
|
+
seen.push(sql);
|
|
43
|
+
if (/^(BEGIN|COMMIT|ROLLBACK)\b/i.test(sql)) return { rows: [], rowCount: 0 };
|
|
44
|
+
if (/^SELECT\b/i.test(sql)) return { rows: [{ from: label }], rowCount: 1 };
|
|
45
|
+
return { rows: [], rowCount: 1 };
|
|
46
|
+
},
|
|
47
|
+
close: async function () { /* no-op */ },
|
|
48
|
+
ping: async function () { return true; },
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function _saw(driver, re) {
|
|
53
|
+
return driver.seen.some(function (s) { return re.test(s); });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Run fn() under the gdpr posture, restoring whatever posture (or none)
|
|
57
|
+
// was pinned before. clear()+set() because compliance.set refuses a
|
|
58
|
+
// runtime switch when a different posture is already pinned.
|
|
59
|
+
async function _underGdpr(fn) {
|
|
60
|
+
var prior = b.compliance.current();
|
|
61
|
+
b.compliance.clear();
|
|
62
|
+
b.compliance.set("gdpr");
|
|
63
|
+
try {
|
|
64
|
+
await fn();
|
|
65
|
+
} finally {
|
|
66
|
+
b.compliance.clear();
|
|
67
|
+
if (prior) b.compliance.set(prior);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function _expectThrow(label, fn, expectedCode) {
|
|
72
|
+
var threw = null;
|
|
73
|
+
try { await fn(); } catch (e) { threw = e; }
|
|
74
|
+
check(label + ": threw " + expectedCode,
|
|
75
|
+
threw !== null && threw.code === expectedCode);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function run() {
|
|
79
|
+
// Confirm the cross-border vocabulary is shared between the gate and
|
|
80
|
+
// the compliance catalog (the gate consults isCrossBorderRegulated).
|
|
81
|
+
check("compliance.isCrossBorderRegulated('gdpr') === true",
|
|
82
|
+
b.compliance.isCrossBorderRegulated("gdpr") === true);
|
|
83
|
+
check("compliance.isCrossBorderRegulated('soc2') === false",
|
|
84
|
+
b.compliance.isCrossBorderRegulated("soc2") === false);
|
|
85
|
+
check("CROSS_BORDER_REGULATED_POSTURES includes gdpr",
|
|
86
|
+
Array.isArray(b.compliance.CROSS_BORDER_REGULATED_POSTURES) &&
|
|
87
|
+
b.compliance.CROSS_BORDER_REGULATED_POSTURES.indexOf("gdpr") !== -1);
|
|
88
|
+
|
|
89
|
+
// ---- unregulated posture + residency-tagged backend → DML passes ----
|
|
90
|
+
// No posture pinned (default) → the gate does not engage even for a
|
|
91
|
+
// residency-tagged backend, even without a rowResidencyTag.
|
|
92
|
+
b.externalDb._resetForTest();
|
|
93
|
+
var euDriver = _trackingDriver("eu");
|
|
94
|
+
b.externalDb.init({
|
|
95
|
+
backends: {
|
|
96
|
+
main: {
|
|
97
|
+
connect: euDriver.connect, query: euDriver.query,
|
|
98
|
+
close: euDriver.close, residencyTag: "eu",
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
await b.externalDb.query("INSERT INTO orders (id) VALUES ($1)", ["o-1"]);
|
|
103
|
+
check("unregulated + tagged backend + untagged DML → passes, wire reached",
|
|
104
|
+
_saw(euDriver, /INSERT INTO orders/));
|
|
105
|
+
|
|
106
|
+
// ---- gdpr + UNTAGGED (unrestricted) backend + untagged DML → passes ----
|
|
107
|
+
b.externalDb._resetForTest();
|
|
108
|
+
var freeDriver = _trackingDriver("free");
|
|
109
|
+
b.externalDb.init({
|
|
110
|
+
backends: {
|
|
111
|
+
main: {
|
|
112
|
+
connect: freeDriver.connect, query: freeDriver.query,
|
|
113
|
+
close: freeDriver.close, // no residencyTag → "unrestricted"
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
await _underGdpr(async function () {
|
|
118
|
+
await b.externalDb.query("UPDATE orders SET total = $1 WHERE id = $2", [10, "o-1"]);
|
|
119
|
+
});
|
|
120
|
+
check("gdpr + unrestricted backend + untagged DML → passes, wire reached",
|
|
121
|
+
_saw(freeDriver, /UPDATE orders/));
|
|
122
|
+
|
|
123
|
+
// ---- gdpr + eu backend + DML without tag → RESIDENCY_GATE_REQUIRED ----
|
|
124
|
+
b.externalDb._resetForTest();
|
|
125
|
+
var gateDriver = _trackingDriver("gate");
|
|
126
|
+
b.externalDb.init({
|
|
127
|
+
backends: {
|
|
128
|
+
main: {
|
|
129
|
+
connect: gateDriver.connect, query: gateDriver.query,
|
|
130
|
+
close: gateDriver.close, residencyTag: "eu",
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
await _underGdpr(async function () {
|
|
135
|
+
await _expectThrow("gdpr + eu backend + untagged DML",
|
|
136
|
+
function () {
|
|
137
|
+
return b.externalDb.query("DELETE FROM orders WHERE id = $1", ["o-1"]);
|
|
138
|
+
},
|
|
139
|
+
"RESIDENCY_GATE_REQUIRED");
|
|
140
|
+
});
|
|
141
|
+
check("RESIDENCY_GATE_REQUIRED → wire NOT reached",
|
|
142
|
+
gateDriver.seen.every(function (s) { return !/DELETE FROM orders/.test(s); }));
|
|
143
|
+
|
|
144
|
+
// ---- gdpr + eu backend + matching tag "eu" → passes (success path) ----
|
|
145
|
+
b.externalDb._resetForTest();
|
|
146
|
+
var okDriver = _trackingDriver("ok");
|
|
147
|
+
b.externalDb.init({
|
|
148
|
+
backends: {
|
|
149
|
+
main: {
|
|
150
|
+
connect: okDriver.connect, query: okDriver.query,
|
|
151
|
+
close: okDriver.close, residencyTag: "eu",
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
await _underGdpr(async function () {
|
|
156
|
+
var res = await b.externalDb.query(
|
|
157
|
+
"INSERT INTO orders (id, region) VALUES ($1, $2)",
|
|
158
|
+
["o-2", "eu"],
|
|
159
|
+
{ rowResidencyTag: "eu" });
|
|
160
|
+
check("gdpr + eu backend + tag 'eu' → rowCount returned",
|
|
161
|
+
res && res.rowCount === 1);
|
|
162
|
+
});
|
|
163
|
+
check("gdpr + eu backend + tag 'eu' → wire reached with the statement",
|
|
164
|
+
_saw(okDriver, /INSERT INTO orders \(id, region\)/));
|
|
165
|
+
|
|
166
|
+
// ---- gdpr + eu backend + tag "global" → passes ----
|
|
167
|
+
b.externalDb._resetForTest();
|
|
168
|
+
var globalDriver = _trackingDriver("global");
|
|
169
|
+
b.externalDb.init({
|
|
170
|
+
backends: {
|
|
171
|
+
main: {
|
|
172
|
+
connect: globalDriver.connect, query: globalDriver.query,
|
|
173
|
+
close: globalDriver.close, residencyTag: "eu",
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
await _underGdpr(async function () {
|
|
178
|
+
await b.externalDb.query(
|
|
179
|
+
"UPDATE orders SET total = $1 WHERE id = $2",
|
|
180
|
+
[99, "o-2"],
|
|
181
|
+
{ rowResidencyTag: "global" });
|
|
182
|
+
});
|
|
183
|
+
check("gdpr + eu backend + tag 'global' → passes, wire reached",
|
|
184
|
+
_saw(globalDriver, /UPDATE orders/));
|
|
185
|
+
|
|
186
|
+
// ---- gdpr + eu backend + tag "us" → RESIDENCY_TAG_MISMATCH ----
|
|
187
|
+
b.externalDb._resetForTest();
|
|
188
|
+
var mismatchDriver = _trackingDriver("mismatch");
|
|
189
|
+
b.externalDb.init({
|
|
190
|
+
backends: {
|
|
191
|
+
main: {
|
|
192
|
+
connect: mismatchDriver.connect, query: mismatchDriver.query,
|
|
193
|
+
close: mismatchDriver.close, residencyTag: "eu",
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
await _underGdpr(async function () {
|
|
198
|
+
await _expectThrow("gdpr + eu backend + tag 'us'",
|
|
199
|
+
function () {
|
|
200
|
+
return b.externalDb.query(
|
|
201
|
+
"INSERT INTO orders (id) VALUES ($1)",
|
|
202
|
+
["o-3"],
|
|
203
|
+
{ rowResidencyTag: "us" });
|
|
204
|
+
},
|
|
205
|
+
"RESIDENCY_TAG_MISMATCH");
|
|
206
|
+
});
|
|
207
|
+
check("RESIDENCY_TAG_MISMATCH → wire NOT reached",
|
|
208
|
+
mismatchDriver.seen.every(function (s) { return !/INSERT INTO orders/.test(s); }));
|
|
209
|
+
|
|
210
|
+
// ---- rowResidencyTag present but empty string → INVALID_OPT ----
|
|
211
|
+
// The empty-string guard runs ahead of posture / DML classification —
|
|
212
|
+
// exercise it on a DML statement under gdpr so the path is realistic.
|
|
213
|
+
b.externalDb._resetForTest();
|
|
214
|
+
var emptyDriver = _trackingDriver("empty");
|
|
215
|
+
b.externalDb.init({
|
|
216
|
+
backends: {
|
|
217
|
+
main: {
|
|
218
|
+
connect: emptyDriver.connect, query: emptyDriver.query,
|
|
219
|
+
close: emptyDriver.close, residencyTag: "eu",
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
await _underGdpr(async function () {
|
|
224
|
+
await _expectThrow("empty-string rowResidencyTag",
|
|
225
|
+
function () {
|
|
226
|
+
return b.externalDb.query(
|
|
227
|
+
"INSERT INTO orders (id) VALUES ($1)",
|
|
228
|
+
["o-4"],
|
|
229
|
+
{ rowResidencyTag: "" });
|
|
230
|
+
},
|
|
231
|
+
"INVALID_OPT");
|
|
232
|
+
});
|
|
233
|
+
check("INVALID_OPT → wire NOT reached",
|
|
234
|
+
emptyDriver.seen.every(function (s) { return !/INSERT INTO orders/.test(s); }));
|
|
235
|
+
|
|
236
|
+
// ---- multi-statement SQL hiding a trailing DML behind a SELECT
|
|
237
|
+
// prefix → MULTI_STATEMENT_REFUSED (the classifier reads only the
|
|
238
|
+
// leading keyword; a trailing INSERT must not slip the gate) ----
|
|
239
|
+
b.externalDb._resetForTest();
|
|
240
|
+
var multiDriver = _trackingDriver("multi");
|
|
241
|
+
b.externalDb.init({
|
|
242
|
+
backends: {
|
|
243
|
+
main: {
|
|
244
|
+
connect: multiDriver.connect, query: multiDriver.query,
|
|
245
|
+
close: multiDriver.close, residencyTag: "eu",
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
await _underGdpr(async function () {
|
|
250
|
+
await _expectThrow("multi-statement SELECT;INSERT refused",
|
|
251
|
+
function () {
|
|
252
|
+
return b.externalDb.query(
|
|
253
|
+
"SELECT 1; INSERT INTO orders (id) VALUES ('o-x')", []);
|
|
254
|
+
},
|
|
255
|
+
"MULTI_STATEMENT_REFUSED");
|
|
256
|
+
});
|
|
257
|
+
check("MULTI_STATEMENT_REFUSED → wire NOT reached",
|
|
258
|
+
multiDriver.seen.every(function (s) { return !/INSERT INTO orders/.test(s); }));
|
|
259
|
+
// A single statement ending in a bare ; (no trailing statement) is fine.
|
|
260
|
+
await _underGdpr(async function () {
|
|
261
|
+
var ok = await b.externalDb.query(
|
|
262
|
+
"INSERT INTO orders (id) VALUES ('o-semi');", [], { rowResidencyTag: "eu" });
|
|
263
|
+
check("single statement with trailing ; passes", ok && ok.rowCount === 1);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// ---- transaction-level rowResidencyTag shape validated at entry ----
|
|
267
|
+
b.externalDb._resetForTest();
|
|
268
|
+
var txShapeDriver = _trackingDriver("txshape");
|
|
269
|
+
b.externalDb.init({
|
|
270
|
+
backends: {
|
|
271
|
+
main: {
|
|
272
|
+
connect: txShapeDriver.connect, query: txShapeDriver.query,
|
|
273
|
+
close: txShapeDriver.close, residencyTag: "eu",
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
await _underGdpr(async function () {
|
|
278
|
+
await _expectThrow("empty tx-level rowResidencyTag refused at entry",
|
|
279
|
+
function () {
|
|
280
|
+
return b.externalDb.transaction(async function (tx) {
|
|
281
|
+
await tx.query("INSERT INTO orders (id) VALUES ('o-tx')", []);
|
|
282
|
+
}, { rowResidencyTag: "" });
|
|
283
|
+
},
|
|
284
|
+
"INVALID_OPT");
|
|
285
|
+
});
|
|
286
|
+
check("tx-level shape refusal → no BEGIN reached the backend",
|
|
287
|
+
txShapeDriver.seen.every(function (s) { return !/BEGIN/i.test(s); }));
|
|
288
|
+
|
|
289
|
+
// ---- SELECT under gdpr to an eu backend without tag → passes (non-DML) ----
|
|
290
|
+
b.externalDb._resetForTest();
|
|
291
|
+
var selectDriver = _trackingDriver("select");
|
|
292
|
+
b.externalDb.init({
|
|
293
|
+
backends: {
|
|
294
|
+
main: {
|
|
295
|
+
connect: selectDriver.connect, query: selectDriver.query,
|
|
296
|
+
close: selectDriver.close, residencyTag: "eu",
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
});
|
|
300
|
+
await _underGdpr(async function () {
|
|
301
|
+
var res = await b.externalDb.query("SELECT id FROM orders WHERE id = $1", ["o-1"]);
|
|
302
|
+
check("gdpr + eu backend + untagged SELECT → passes, rows returned",
|
|
303
|
+
res && res.rowCount === 1);
|
|
304
|
+
});
|
|
305
|
+
check("gdpr + eu backend + untagged SELECT → wire reached (non-DML not gated)",
|
|
306
|
+
_saw(selectDriver, /SELECT id FROM orders/));
|
|
307
|
+
|
|
308
|
+
// ---- transaction: tx-level tag applies to tx.query; mismatch rolls back ----
|
|
309
|
+
// A transaction-level rowResidencyTag of "us" against an "eu" backend
|
|
310
|
+
// makes the first DML tx.query throw RESIDENCY_TAG_MISMATCH, which
|
|
311
|
+
// rolls the transaction back: the fake backend sees BEGIN then
|
|
312
|
+
// ROLLBACK, and NEVER COMMIT or the gated INSERT.
|
|
313
|
+
b.externalDb._resetForTest();
|
|
314
|
+
var txMismatchDriver = _trackingDriver("txmismatch");
|
|
315
|
+
b.externalDb.init({
|
|
316
|
+
backends: {
|
|
317
|
+
main: {
|
|
318
|
+
connect: txMismatchDriver.connect, query: txMismatchDriver.query,
|
|
319
|
+
close: txMismatchDriver.close, residencyTag: "eu",
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
await _underGdpr(async function () {
|
|
324
|
+
await _expectThrow("transaction: tx-level tag 'us' on eu backend",
|
|
325
|
+
function () {
|
|
326
|
+
return b.externalDb.transaction(async function (tx) {
|
|
327
|
+
await tx.query("INSERT INTO orders (id) VALUES ($1)", ["tx-1"]);
|
|
328
|
+
}, { rowResidencyTag: "us" });
|
|
329
|
+
},
|
|
330
|
+
"RESIDENCY_TAG_MISMATCH");
|
|
331
|
+
});
|
|
332
|
+
check("transaction mismatch: BEGIN issued",
|
|
333
|
+
_saw(txMismatchDriver, /^BEGIN\b/));
|
|
334
|
+
check("transaction mismatch: ROLLBACK issued (transaction rolled back)",
|
|
335
|
+
_saw(txMismatchDriver, /^ROLLBACK\b/));
|
|
336
|
+
check("transaction mismatch: COMMIT NOT reached",
|
|
337
|
+
txMismatchDriver.seen.every(function (s) { return !/^COMMIT\b/i.test(s); }));
|
|
338
|
+
check("transaction mismatch: gated INSERT NOT reached",
|
|
339
|
+
txMismatchDriver.seen.every(function (s) { return !/INSERT INTO orders/.test(s); }));
|
|
340
|
+
|
|
341
|
+
// tx-level matching tag "eu" → the statement commits end-to-end.
|
|
342
|
+
b.externalDb._resetForTest();
|
|
343
|
+
var txOkDriver = _trackingDriver("txok");
|
|
344
|
+
b.externalDb.init({
|
|
345
|
+
backends: {
|
|
346
|
+
main: {
|
|
347
|
+
connect: txOkDriver.connect, query: txOkDriver.query,
|
|
348
|
+
close: txOkDriver.close, residencyTag: "eu",
|
|
349
|
+
},
|
|
350
|
+
},
|
|
351
|
+
});
|
|
352
|
+
await _underGdpr(async function () {
|
|
353
|
+
await b.externalDb.transaction(async function (tx) {
|
|
354
|
+
await tx.query("INSERT INTO orders (id) VALUES ($1)", ["tx-2"]);
|
|
355
|
+
}, { rowResidencyTag: "eu" });
|
|
356
|
+
});
|
|
357
|
+
check("transaction tx-level tag 'eu': INSERT reached the wire",
|
|
358
|
+
_saw(txOkDriver, /INSERT INTO orders/));
|
|
359
|
+
check("transaction tx-level tag 'eu': COMMIT reached",
|
|
360
|
+
_saw(txOkDriver, /^COMMIT\b/));
|
|
361
|
+
|
|
362
|
+
// per-call third-arg override wins over the transaction-level tag.
|
|
363
|
+
// Transaction-level tag "us" (would refuse), but the per-call override
|
|
364
|
+
// "eu" replaces it for that statement → the DML reaches the wire and
|
|
365
|
+
// the transaction commits.
|
|
366
|
+
b.externalDb._resetForTest();
|
|
367
|
+
var txOverrideDriver = _trackingDriver("txoverride");
|
|
368
|
+
b.externalDb.init({
|
|
369
|
+
backends: {
|
|
370
|
+
main: {
|
|
371
|
+
connect: txOverrideDriver.connect, query: txOverrideDriver.query,
|
|
372
|
+
close: txOverrideDriver.close, residencyTag: "eu",
|
|
373
|
+
},
|
|
374
|
+
},
|
|
375
|
+
});
|
|
376
|
+
await _underGdpr(async function () {
|
|
377
|
+
await b.externalDb.transaction(async function (tx) {
|
|
378
|
+
await tx.query(
|
|
379
|
+
"INSERT INTO orders (id) VALUES ($1)",
|
|
380
|
+
["tx-3"],
|
|
381
|
+
{ rowResidencyTag: "eu" });
|
|
382
|
+
}, { rowResidencyTag: "us" });
|
|
383
|
+
});
|
|
384
|
+
check("transaction per-call override 'eu' beats tx-level 'us': INSERT reached",
|
|
385
|
+
_saw(txOverrideDriver, /INSERT INTO orders/));
|
|
386
|
+
check("transaction per-call override 'eu' beats tx-level 'us': COMMIT reached",
|
|
387
|
+
_saw(txOverrideDriver, /^COMMIT\b/));
|
|
388
|
+
|
|
389
|
+
// and the converse: per-call override "us" beats a benign tx-level
|
|
390
|
+
// "eu" → that statement is refused and the transaction rolls back.
|
|
391
|
+
b.externalDb._resetForTest();
|
|
392
|
+
var txOverrideBadDriver = _trackingDriver("txoverridebad");
|
|
393
|
+
b.externalDb.init({
|
|
394
|
+
backends: {
|
|
395
|
+
main: {
|
|
396
|
+
connect: txOverrideBadDriver.connect, query: txOverrideBadDriver.query,
|
|
397
|
+
close: txOverrideBadDriver.close, residencyTag: "eu",
|
|
398
|
+
},
|
|
399
|
+
},
|
|
400
|
+
});
|
|
401
|
+
await _underGdpr(async function () {
|
|
402
|
+
await _expectThrow("transaction per-call override 'us' beats tx-level 'eu'",
|
|
403
|
+
function () {
|
|
404
|
+
return b.externalDb.transaction(async function (tx) {
|
|
405
|
+
await tx.query(
|
|
406
|
+
"INSERT INTO orders (id) VALUES ($1)",
|
|
407
|
+
["tx-4"],
|
|
408
|
+
{ rowResidencyTag: "us" });
|
|
409
|
+
}, { rowResidencyTag: "eu" });
|
|
410
|
+
},
|
|
411
|
+
"RESIDENCY_TAG_MISMATCH");
|
|
412
|
+
});
|
|
413
|
+
check("transaction per-call override 'us': COMMIT NOT reached",
|
|
414
|
+
txOverrideBadDriver.seen.every(function (s) { return !/^COMMIT\b/i.test(s); }));
|
|
415
|
+
|
|
416
|
+
// ---- read-replica residency incompatibility ----
|
|
417
|
+
// The replica-vs-row read gate is distinct from the replica-vs-primary
|
|
418
|
+
// CONFIG gate. To reach the read gate with allowCrossBorder:false (init
|
|
419
|
+
// would refuse a cross-border replica otherwise), the primary is left
|
|
420
|
+
// "unrestricted" — so the "us" replica is config-compatible with the
|
|
421
|
+
// primary and init accepts it — and the incompatibility is between the
|
|
422
|
+
// ROW's tag ("eu") and the replica's tag ("us") at read time.
|
|
423
|
+
b.externalDb._resetForTest();
|
|
424
|
+
var roPrimary = _trackingDriver("ro-primary");
|
|
425
|
+
var usReplica = _trackingDriver("us-replica");
|
|
426
|
+
b.externalDb.init({
|
|
427
|
+
backends: {
|
|
428
|
+
main: {
|
|
429
|
+
connect: roPrimary.connect, query: roPrimary.query,
|
|
430
|
+
close: roPrimary.close, // primary unrestricted
|
|
431
|
+
replicas: [
|
|
432
|
+
{
|
|
433
|
+
connect: usReplica.connect, query: usReplica.query,
|
|
434
|
+
close: usReplica.close, residencyTag: "us",
|
|
435
|
+
allowCrossBorder: false,
|
|
436
|
+
},
|
|
437
|
+
],
|
|
438
|
+
replicaFallbackToPrimary: false,
|
|
439
|
+
},
|
|
440
|
+
},
|
|
441
|
+
});
|
|
442
|
+
await _underGdpr(async function () {
|
|
443
|
+
await _expectThrow("read to us replica for eu row, allowCrossBorder false",
|
|
444
|
+
function () {
|
|
445
|
+
return b.externalDb.read.query(
|
|
446
|
+
"SELECT id FROM orders WHERE id = $1",
|
|
447
|
+
["o-1"],
|
|
448
|
+
{ rowResidencyTag: "eu" });
|
|
449
|
+
},
|
|
450
|
+
"REPLICA_RESIDENCY_INCOMPATIBLE");
|
|
451
|
+
});
|
|
452
|
+
check("REPLICA_RESIDENCY_INCOMPATIBLE → replica wire NOT reached",
|
|
453
|
+
usReplica.seen.every(function (s) { return !/SELECT id FROM orders/.test(s); }));
|
|
454
|
+
|
|
455
|
+
// allowCrossBorder true on the replica → the cross-border read is
|
|
456
|
+
// permitted (audited) and reaches the replica wire.
|
|
457
|
+
b.externalDb._resetForTest();
|
|
458
|
+
var roPrimary2 = _trackingDriver("ro-primary2");
|
|
459
|
+
var usReplicaOk = _trackingDriver("us-replica-ok");
|
|
460
|
+
b.externalDb.init({
|
|
461
|
+
backends: {
|
|
462
|
+
main: {
|
|
463
|
+
connect: roPrimary2.connect, query: roPrimary2.query,
|
|
464
|
+
close: roPrimary2.close, // primary unrestricted
|
|
465
|
+
replicas: [
|
|
466
|
+
{
|
|
467
|
+
connect: usReplicaOk.connect, query: usReplicaOk.query,
|
|
468
|
+
close: usReplicaOk.close, residencyTag: "us",
|
|
469
|
+
allowCrossBorder: true,
|
|
470
|
+
},
|
|
471
|
+
],
|
|
472
|
+
replicaFallbackToPrimary: false,
|
|
473
|
+
},
|
|
474
|
+
},
|
|
475
|
+
});
|
|
476
|
+
await _underGdpr(async function () {
|
|
477
|
+
var res = await b.externalDb.read.query(
|
|
478
|
+
"SELECT id FROM orders WHERE id = $1",
|
|
479
|
+
["o-1"],
|
|
480
|
+
{ rowResidencyTag: "eu" });
|
|
481
|
+
check("read to us replica for eu row, allowCrossBorder true → rows returned",
|
|
482
|
+
res && res.rowCount === 1);
|
|
483
|
+
});
|
|
484
|
+
check("allowCrossBorder replica read → replica wire reached",
|
|
485
|
+
_saw(usReplicaOk, /SELECT id FROM orders/));
|
|
486
|
+
|
|
487
|
+
// ---- read-replica residency gate: OMITTED rowResidencyTag must fail closed ----
|
|
488
|
+
// Symmetric with the WRITE gate's RESIDENCY_GATE_REQUIRED. When the read gate
|
|
489
|
+
// is skipped on an absent tag, a SELECT of EU-resident rows silently lands on
|
|
490
|
+
// a tagged US replica — the fan-out drops the per-row residency tag. Under a
|
|
491
|
+
// cross-border-regulated posture a tagged, non-cross-border replica must
|
|
492
|
+
// refuse the untagged read rather than route it.
|
|
493
|
+
b.externalDb._resetForTest();
|
|
494
|
+
var roPrimaryOmit = _trackingDriver("ro-primary-omit");
|
|
495
|
+
var usReplicaOmit = _trackingDriver("us-replica-omit");
|
|
496
|
+
b.externalDb.init({
|
|
497
|
+
backends: {
|
|
498
|
+
main: {
|
|
499
|
+
connect: roPrimaryOmit.connect, query: roPrimaryOmit.query,
|
|
500
|
+
close: roPrimaryOmit.close, // primary unrestricted
|
|
501
|
+
replicas: [
|
|
502
|
+
{
|
|
503
|
+
connect: usReplicaOmit.connect, query: usReplicaOmit.query,
|
|
504
|
+
close: usReplicaOmit.close, residencyTag: "us",
|
|
505
|
+
allowCrossBorder: false,
|
|
506
|
+
},
|
|
507
|
+
],
|
|
508
|
+
replicaFallbackToPrimary: false,
|
|
509
|
+
},
|
|
510
|
+
},
|
|
511
|
+
});
|
|
512
|
+
await _underGdpr(async function () {
|
|
513
|
+
// No rowResidencyTag opt → the read gate must NOT silently pass.
|
|
514
|
+
await _expectThrow("read with OMITTED rowResidencyTag under gdpr to a tagged replica fails closed",
|
|
515
|
+
function () {
|
|
516
|
+
return b.externalDb.read.query("SELECT id FROM orders WHERE id = $1", ["o-1"]);
|
|
517
|
+
},
|
|
518
|
+
"REPLICA_RESIDENCY_TAG_REQUIRED");
|
|
519
|
+
});
|
|
520
|
+
check("omitted-tag read → us-replica wire NOT reached (gate fail-closed)",
|
|
521
|
+
usReplicaOmit.seen.every(function (s) { return !/SELECT id FROM orders/.test(s); }));
|
|
522
|
+
|
|
523
|
+
// ---- write verbs that wear a harmless leading keyword must still be
|
|
524
|
+
// gated: WITH (CTE) / COPY FROM / EXPLAIN ANALYZE / CALL / EXECUTE /
|
|
525
|
+
// REPLACE / DO all PLACE rows, so under gdpr + eu backend they require
|
|
526
|
+
// a tag; recognized pure reads (WITH...SELECT, COPY...TO, plain
|
|
527
|
+
// EXPLAIN) pass untagged. One driver, exercised across the matrix. ----
|
|
528
|
+
function _classDriver() { return _trackingDriver("class"); }
|
|
529
|
+
|
|
530
|
+
async function _refusedUntagged(label, sql, code) {
|
|
531
|
+
b.externalDb._resetForTest();
|
|
532
|
+
var d = _classDriver();
|
|
533
|
+
b.externalDb.init({ backends: { main: {
|
|
534
|
+
connect: d.connect, query: d.query, close: d.close, residencyTag: "eu",
|
|
535
|
+
} } });
|
|
536
|
+
await _underGdpr(async function () {
|
|
537
|
+
await _expectThrow(label, function () { return b.externalDb.query(sql, []); }, code);
|
|
538
|
+
});
|
|
539
|
+
check(label + " → wire NOT reached", d.seen.length === 0);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
async function _passesUntagged(label, sql) {
|
|
543
|
+
b.externalDb._resetForTest();
|
|
544
|
+
var d = _classDriver();
|
|
545
|
+
b.externalDb.init({ backends: { main: {
|
|
546
|
+
connect: d.connect, query: d.query, close: d.close, residencyTag: "eu",
|
|
547
|
+
} } });
|
|
548
|
+
var err = null;
|
|
549
|
+
await _underGdpr(async function () {
|
|
550
|
+
try { await b.externalDb.query(sql, []); } catch (e) { err = e; }
|
|
551
|
+
});
|
|
552
|
+
check(label + " → passes untagged, wire reached", err === null && d.seen.length === 1);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
async function _passesWithTag(label, sql) {
|
|
556
|
+
b.externalDb._resetForTest();
|
|
557
|
+
var d = _classDriver();
|
|
558
|
+
b.externalDb.init({ backends: { main: {
|
|
559
|
+
connect: d.connect, query: d.query, close: d.close, residencyTag: "eu",
|
|
560
|
+
} } });
|
|
561
|
+
var err = null;
|
|
562
|
+
await _underGdpr(async function () {
|
|
563
|
+
try { await b.externalDb.query(sql, [], { rowResidencyTag: "eu" }); }
|
|
564
|
+
catch (e) { err = e; }
|
|
565
|
+
});
|
|
566
|
+
check(label + " → matching tag reaches the wire", err === null && d.seen.length === 1);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// CTE-wrapped DML (the Codex P1 shape) — write, gated.
|
|
570
|
+
await _refusedUntagged("WITH ... INSERT (CTE write) untagged",
|
|
571
|
+
"WITH src AS (SELECT 1 AS id) INSERT INTO eu_users (id) SELECT id FROM src",
|
|
572
|
+
"RESIDENCY_GATE_REQUIRED");
|
|
573
|
+
await _passesWithTag("WITH ... INSERT (CTE write) tag 'eu'",
|
|
574
|
+
"WITH src AS (SELECT 1 AS id) INSERT INTO eu_users (id) SELECT id FROM src");
|
|
575
|
+
// CTE-wrapped SELECT — read, passes untagged.
|
|
576
|
+
await _passesUntagged("WITH ... SELECT (CTE read) untagged",
|
|
577
|
+
"WITH src AS (SELECT 1 AS id) SELECT id FROM src");
|
|
578
|
+
// RECURSIVE + MATERIALIZED keywords before the main verb don't confuse it.
|
|
579
|
+
await _refusedUntagged("WITH RECURSIVE ... UPDATE untagged",
|
|
580
|
+
"WITH RECURSIVE t AS (SELECT 1) UPDATE eu_users SET id = 2 WHERE id IN (SELECT * FROM t)",
|
|
581
|
+
"RESIDENCY_GATE_REQUIRED");
|
|
582
|
+
|
|
583
|
+
// COPY ... FROM loads rows (write); COPY ... TO exports (read).
|
|
584
|
+
await _refusedUntagged("COPY ... FROM (bulk load) untagged",
|
|
585
|
+
"COPY eu_users (id) FROM STDIN", "RESIDENCY_GATE_REQUIRED");
|
|
586
|
+
await _passesUntagged("COPY (query) TO (export) untagged",
|
|
587
|
+
"COPY (SELECT id FROM eu_users) TO STDOUT");
|
|
588
|
+
|
|
589
|
+
// EXPLAIN ANALYZE EXECUTES the wrapped statement; plain EXPLAIN does not.
|
|
590
|
+
await _refusedUntagged("EXPLAIN ANALYZE INSERT untagged",
|
|
591
|
+
"EXPLAIN ANALYZE INSERT INTO eu_users (id) VALUES (1)", "RESIDENCY_GATE_REQUIRED");
|
|
592
|
+
await _refusedUntagged("EXPLAIN (ANALYZE, FORMAT JSON) UPDATE untagged",
|
|
593
|
+
"EXPLAIN (ANALYZE, FORMAT JSON) UPDATE eu_users SET id = 2 WHERE id = 1",
|
|
594
|
+
"RESIDENCY_GATE_REQUIRED");
|
|
595
|
+
await _passesUntagged("EXPLAIN (plan-only) INSERT untagged",
|
|
596
|
+
"EXPLAIN INSERT INTO eu_users (id) VALUES (1)");
|
|
597
|
+
await _passesUntagged("EXPLAIN SELECT untagged",
|
|
598
|
+
"EXPLAIN SELECT id FROM eu_users");
|
|
599
|
+
|
|
600
|
+
// Opaque-write verbs — CALL / EXECUTE / DO — gated (fail-closed).
|
|
601
|
+
await _refusedUntagged("CALL stored-proc untagged",
|
|
602
|
+
"CALL load_eu_rows('x')", "RESIDENCY_GATE_REQUIRED");
|
|
603
|
+
await _refusedUntagged("EXECUTE prepared untagged",
|
|
604
|
+
"EXECUTE ins_eu (1)", "RESIDENCY_GATE_REQUIRED");
|
|
605
|
+
await _refusedUntagged("DO anonymous block untagged",
|
|
606
|
+
"DO $$ BEGIN INSERT INTO eu_users (id) VALUES (1); END $$",
|
|
607
|
+
"RESIDENCY_GATE_REQUIRED");
|
|
608
|
+
|
|
609
|
+
// REPLACE INTO (mysql/sqlite) — delete-then-insert, a write.
|
|
610
|
+
await _refusedUntagged("REPLACE INTO untagged",
|
|
611
|
+
"REPLACE INTO eu_users (id) VALUES (1)", "RESIDENCY_GATE_REQUIRED");
|
|
612
|
+
|
|
613
|
+
// A `;` inside a dollar-quoted body is DATA, not a statement
|
|
614
|
+
// separator — must not false-positive as MULTI_STATEMENT_REFUSED; the
|
|
615
|
+
// DO block is gated as the ROUTINE write it is.
|
|
616
|
+
await _refusedUntagged("DO body with inner ; is one statement",
|
|
617
|
+
"DO $$ BEGIN INSERT INTO eu_users (id) VALUES (1); INSERT INTO eu_users (id) VALUES (2); END $$",
|
|
618
|
+
"RESIDENCY_GATE_REQUIRED");
|
|
619
|
+
|
|
620
|
+
// An unresolvable WITH (no main verb past the CTE list) fails closed.
|
|
621
|
+
await _refusedUntagged("unresolvable WITH refused",
|
|
622
|
+
"WITH x AS (SELECT 1)", "STATEMENT_UNRESOLVED_REFUSED");
|
|
623
|
+
|
|
624
|
+
// ---- Final clean ----
|
|
625
|
+
b.externalDb._resetForTest();
|
|
626
|
+
b.compliance.clear();
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
module.exports = { run: run };
|
|
630
|
+
|
|
631
|
+
if (require.main === module) {
|
|
632
|
+
run().then(function () {
|
|
633
|
+
process.stdout.write("OK — external-db-residency: " + helpers.getChecks() + " checks passed\n");
|
|
634
|
+
}, function (e) {
|
|
635
|
+
process.stderr.write("FAIL: " + (e && e.stack || e) + "\n");
|
|
636
|
+
process.exit(1);
|
|
637
|
+
});
|
|
638
|
+
}
|
|
@@ -345,6 +345,25 @@ async function _runIdentifierGuard(g) {
|
|
|
345
345
|
!b.guardAll.list().some(function (entry) { return entry.name === g.NAME; }));
|
|
346
346
|
}
|
|
347
347
|
|
|
348
|
+
async function _runSqlGuard(g) {
|
|
349
|
+
var fx = g.INTEGRATION_FIXTURES;
|
|
350
|
+
var gate = g.gate({ profile: "strict" });
|
|
351
|
+
|
|
352
|
+
// Benign parameterized fragment → serve.
|
|
353
|
+
var rvBenign = await gate.check({ sql: fx.benignSql });
|
|
354
|
+
check("[" + g.NAME + "] direct gate: benign SQL → serve",
|
|
355
|
+
rvBenign.ok === true && rvBenign.action === "serve");
|
|
356
|
+
|
|
357
|
+
// Hostile fragment (stacked statement) → not serve.
|
|
358
|
+
var rvHostile = await gate.check({ sql: fx.hostileSql });
|
|
359
|
+
check("[" + g.NAME + "] direct gate: hostile SQL → not serve",
|
|
360
|
+
rvHostile.action !== "serve");
|
|
361
|
+
|
|
362
|
+
// Standalone primitive — not in guardAll's content-type dispatch.
|
|
363
|
+
check("[" + g.NAME + "] NOT registered in guardAll content-type dispatch",
|
|
364
|
+
!b.guardAll.list().some(function (entry) { return entry.name === g.NAME; }));
|
|
365
|
+
}
|
|
366
|
+
|
|
348
367
|
async function _runAuthBundleGuard(g) {
|
|
349
368
|
var fx = g.INTEGRATION_FIXTURES;
|
|
350
369
|
var gate = g.gate({ profile: "strict" });
|
|
@@ -431,6 +450,8 @@ async function testGuardHostIntegrationAdaptive() {
|
|
|
431
450
|
await _runFilenameGuard(g);
|
|
432
451
|
} else if (g.KIND === "identifier") {
|
|
433
452
|
await _runIdentifierGuard(g);
|
|
453
|
+
} else if (g.KIND === "sql") {
|
|
454
|
+
await _runSqlGuard(g);
|
|
434
455
|
} else if (g.KIND === "oauth-flow") {
|
|
435
456
|
await _runOauthFlowGuard(g);
|
|
436
457
|
} else if (g.KIND === "graphql-request") {
|