@blamejs/blamejs-shop 0.4.31 → 0.4.33
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/README.md +1 -1
- package/lib/asset-manifest.json +1 -1
- package/lib/vendor/MANIFEST.json +400 -282
- 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 +28 -0
- package/lib/vendor/blamejs/MIGRATING.md +55 -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/elevation-grant.js +6 -2
- package/lib/vendor/blamejs/lib/auth/oauth.js +66 -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 +36 -7
- 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 +210 -4
- package/lib/vendor/blamejs/lib/consent.js +82 -29
- package/lib/vendor/blamejs/lib/constants.js +27 -11
- package/lib/vendor/blamejs/lib/credential-hash.js +9 -0
- 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 +117 -42
- 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 +47 -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 +169 -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/release-notes/v0.15.7.json +43 -0
- package/lib/vendor/blamejs/scripts/check-services.js +21 -0
- package/lib/vendor/blamejs/scripts/gen-migrating.js +67 -0
- package/lib/vendor/blamejs/scripts/release.js +398 -38
- package/lib/vendor/blamejs/test/00-primitives.js +168 -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 +1196 -14
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance.test.js +229 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/credential-hash.test.js +18 -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/retention-floor.test.js +59 -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 +362 -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/scheduler-watchdog-stale-settle.test.js +71 -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 +2 -2
- 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,576 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Live b.externalDb test against the docker Postgres container — the
|
|
4
|
+
* framework's advertised PRIMARY external-db dialect. The "driver" is a
|
|
5
|
+
* docker-exec psql shim: connect() spawns a persistent
|
|
6
|
+
*
|
|
7
|
+
* docker exec -i blamejs-test-postgres psql -U blamejs -d blamejs_test ...
|
|
8
|
+
*
|
|
9
|
+
* subprocess (SQL fed over stdin, never as argv — no shell parsing of
|
|
10
|
+
* SQL), and query() writes one statement plus an `\echo <sentinel>`
|
|
11
|
+
* marker, reading the merged stdout/stderr block up to that sentinel.
|
|
12
|
+
* A persistent session per client is what lets the framework's
|
|
13
|
+
* transaction() keep BEGIN / SET LOCAL gucs / body / COMMIT on one
|
|
14
|
+
* backend session — the precondition for the RLS + sessionGucs proof.
|
|
15
|
+
*
|
|
16
|
+
* It removes any npm pg-driver dep AND exercises the framework's
|
|
17
|
+
* Postgres SQL, the read/write classifier, the per-row residency write
|
|
18
|
+
* gate, and ROW LEVEL SECURITY against a real server:
|
|
19
|
+
*
|
|
20
|
+
* 1. CRUD round-trip (CREATE / INSERT $1 / SELECT / UPDATE / DELETE)
|
|
21
|
+
* with real returned rows + affectedRows.
|
|
22
|
+
* 2. The read/write classifier on real SQL (SELECT read; INSERT /
|
|
23
|
+
* UPDATE / DELETE + a WITH-wrapped INSERT write) drives the gate.
|
|
24
|
+
* 3. The residency write gate WIRED on the real query path: a cross-
|
|
25
|
+
* border write is refused (and never persists), an in-region write
|
|
26
|
+
* succeeds (and persists).
|
|
27
|
+
* 4. declareRowPolicy RLS actually blocks a row for an unauthorized
|
|
28
|
+
* tenant while the authorized tenant sees its row, via the
|
|
29
|
+
* framework's transaction({ sessionGucs }) SET LOCAL plumbing and a
|
|
30
|
+
* restricted non-owner role.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
var spawn = require("node:child_process").spawn;
|
|
34
|
+
var execFileSync = require("node:child_process").execFileSync;
|
|
35
|
+
var helpers = require("../helpers");
|
|
36
|
+
var check = helpers.check;
|
|
37
|
+
var services = require("../helpers/services");
|
|
38
|
+
var b = require("../../");
|
|
39
|
+
|
|
40
|
+
var CONTAINER = "blamejs-test-postgres";
|
|
41
|
+
// The field separator is set over stdin via `\pset fieldsep '\t'` (psql
|
|
42
|
+
// interprets the backslash escape into a real TAB there); passing -F'\t'
|
|
43
|
+
// in argv through `sh -c` would deliver the literal two chars backslash-t
|
|
44
|
+
// instead. -A = unaligned WITH a header row (column names recoverable).
|
|
45
|
+
// NOTE: no -q — quiet mode suppresses the per-statement command tags
|
|
46
|
+
// ("INSERT 0 1" / "UPDATE 2" / "DELETE 1") that carry affectedRows; the
|
|
47
|
+
// one-time prelude confirmation lines are drained before the first query.
|
|
48
|
+
// null sentinel distinguishes a real NULL from an empty string.
|
|
49
|
+
var PSQL_ARGS = "psql -U blamejs -d blamejs_test -A " +
|
|
50
|
+
"-v ON_ERROR_STOP=0 -P null=__BJNULL__ 2>&1";
|
|
51
|
+
var NULL_SENTINEL = "__BJNULL__";
|
|
52
|
+
|
|
53
|
+
// ---- one-shot psql (setup / teardown / out-of-band assertions) ----
|
|
54
|
+
// Shell-free SQL: the statement travels on stdin, never in argv.
|
|
55
|
+
function _psql(sql) {
|
|
56
|
+
var prelude = "\\pset fieldsep '\\t'\n";
|
|
57
|
+
var out = execFileSync(
|
|
58
|
+
"docker",
|
|
59
|
+
["exec", "-i", CONTAINER, "sh", "-c",
|
|
60
|
+
"psql -U blamejs -d blamejs_test -qtA -P null=__BJNULL__ 2>&1"],
|
|
61
|
+
{ input: prelude + sql + "\n", stdio: ["pipe", "pipe", "pipe"] }
|
|
62
|
+
).toString("utf8");
|
|
63
|
+
if (/^ERROR:/m.test(out)) {
|
|
64
|
+
throw new Error("psql setup failed for [" + sql + "]:\n" + out);
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ---- persistent-session docker-exec psql driver ----
|
|
70
|
+
//
|
|
71
|
+
// Each client is a long-lived psql subprocess. Statements are written to
|
|
72
|
+
// its stdin terminated by an `\echo <sentinel>`; the driver reads merged
|
|
73
|
+
// stdout until the sentinel line, then parses the block. SQLSTATE-coded
|
|
74
|
+
// ERROR lines (psql VERBOSITY verbose) throw an Error carrying `.code`,
|
|
75
|
+
// so the framework's auth-failure / deadlock / RLS-denial handling sees a
|
|
76
|
+
// real Postgres SQLSTATE.
|
|
77
|
+
function _makeDockerPgDriver() {
|
|
78
|
+
return {
|
|
79
|
+
connect: function () {
|
|
80
|
+
return new Promise(function (resolve, reject) {
|
|
81
|
+
var child = spawn(
|
|
82
|
+
"docker",
|
|
83
|
+
["exec", "-i", CONTAINER, "sh", "-c",
|
|
84
|
+
PSQL_ARGS + " ; echo __BLAMEJS_PSQL_EXIT__"],
|
|
85
|
+
{ stdio: ["pipe", "pipe", "pipe"] }
|
|
86
|
+
);
|
|
87
|
+
var client = {
|
|
88
|
+
child: child,
|
|
89
|
+
buf: "",
|
|
90
|
+
pending: null, // { sentinel, resolve, reject }
|
|
91
|
+
closed: false,
|
|
92
|
+
exitErr: null,
|
|
93
|
+
};
|
|
94
|
+
child.on("error", function (e) {
|
|
95
|
+
client.exitErr = e;
|
|
96
|
+
if (client.pending) { var p = client.pending; client.pending = null; p.reject(e); }
|
|
97
|
+
});
|
|
98
|
+
child.on("close", function () {
|
|
99
|
+
client.closed = true;
|
|
100
|
+
if (client.pending) {
|
|
101
|
+
var p = client.pending; client.pending = null;
|
|
102
|
+
p.reject(new Error("psql session closed mid-statement"));
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
child.stdout.on("data", function (chunk) {
|
|
106
|
+
client.buf += chunk.toString("utf8");
|
|
107
|
+
_drain(client);
|
|
108
|
+
});
|
|
109
|
+
// Prime the session: tab field separator (a real TAB via psql's
|
|
110
|
+
// backslash interpretation), unaligned WITH headers (so column
|
|
111
|
+
// names are recoverable), no footer, verbose errors (SQLSTATE).
|
|
112
|
+
// Without -q these \pset lines each print a confirmation; drain
|
|
113
|
+
// everything up to a priming sentinel so the first real query's
|
|
114
|
+
// block starts clean.
|
|
115
|
+
var primeSentinel = "__BJ_PRIME__";
|
|
116
|
+
client.pending = {
|
|
117
|
+
sentinel: primeSentinel,
|
|
118
|
+
resolve: function () { resolve(client); },
|
|
119
|
+
reject: reject,
|
|
120
|
+
};
|
|
121
|
+
client.child.stdin.write(
|
|
122
|
+
"\\pset fieldsep '\\t'\n\\pset footer off\n\\set VERBOSITY verbose\n" +
|
|
123
|
+
"\\echo " + primeSentinel + "\n");
|
|
124
|
+
});
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
query: function (client, sql, params) {
|
|
128
|
+
params = params || [];
|
|
129
|
+
var bound = _bindParams(sql, params);
|
|
130
|
+
var sentinel = "__BJ_EOR_" + (++_seq) + "__";
|
|
131
|
+
return new Promise(function (resolve, reject) {
|
|
132
|
+
if (client.closed) { reject(new Error("psql session is closed")); return; }
|
|
133
|
+
client.pending = { sentinel: sentinel, resolve: resolve, reject: reject };
|
|
134
|
+
// Terminate the statement, then echo the sentinel. A trailing ';'
|
|
135
|
+
// on an already-';'-terminated statement is a harmless empty
|
|
136
|
+
// statement to Postgres.
|
|
137
|
+
client.child.stdin.write(bound + "\n;\n\\echo " + sentinel + "\n");
|
|
138
|
+
});
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
close: function (client) {
|
|
142
|
+
return new Promise(function (resolve) {
|
|
143
|
+
if (client.closed) { resolve(); return; }
|
|
144
|
+
try { client.child.stdin.end("\\q\n"); } catch (_e) { /* best effort */ }
|
|
145
|
+
var done = false;
|
|
146
|
+
client.child.on("close", function () { if (!done) { done = true; resolve(); } });
|
|
147
|
+
setTimeout(function () {
|
|
148
|
+
if (done) return;
|
|
149
|
+
done = true;
|
|
150
|
+
try { client.child.kill("SIGKILL"); } catch (_e) {}
|
|
151
|
+
resolve();
|
|
152
|
+
}, 2000);
|
|
153
|
+
});
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
dialect: "postgres",
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
var _seq = 0;
|
|
161
|
+
|
|
162
|
+
// Read complete statement blocks out of client.buf as the sentinel for
|
|
163
|
+
// the in-flight statement appears.
|
|
164
|
+
function _drain(client) {
|
|
165
|
+
if (!client.pending) return;
|
|
166
|
+
var sentinel = client.pending.sentinel;
|
|
167
|
+
var marker = "\n" + sentinel + "\n";
|
|
168
|
+
// Sentinel may also appear at buffer start (block produced no output).
|
|
169
|
+
var idx = client.buf.indexOf(marker);
|
|
170
|
+
var startAtZero = client.buf.indexOf(sentinel + "\n") === 0;
|
|
171
|
+
var block;
|
|
172
|
+
if (idx !== -1) {
|
|
173
|
+
block = client.buf.slice(0, idx);
|
|
174
|
+
client.buf = client.buf.slice(idx + marker.length);
|
|
175
|
+
} else if (startAtZero) {
|
|
176
|
+
block = "";
|
|
177
|
+
client.buf = client.buf.slice((sentinel + "\n").length);
|
|
178
|
+
} else {
|
|
179
|
+
return; // sentinel not yet fully received
|
|
180
|
+
}
|
|
181
|
+
var p = client.pending;
|
|
182
|
+
client.pending = null;
|
|
183
|
+
var parsed;
|
|
184
|
+
try {
|
|
185
|
+
parsed = _parseBlock(block);
|
|
186
|
+
} catch (e) {
|
|
187
|
+
return p.reject(e);
|
|
188
|
+
}
|
|
189
|
+
if (parsed.error) return p.reject(parsed.error);
|
|
190
|
+
p.resolve({ rows: parsed.rows, rowCount: parsed.rowCount });
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Substitute Postgres $1/$2 placeholders with quoted literals. Every
|
|
194
|
+
// test value is operator-controlled (ids / regions / numbers / null);
|
|
195
|
+
// strings are single-quote-escaped, numbers inlined, null → NULL. The
|
|
196
|
+
// framework passes params out-of-band to a real driver; this shim folds
|
|
197
|
+
// them in because psql-over-stdin has no bind protocol.
|
|
198
|
+
function _bindParams(sql, params) {
|
|
199
|
+
return sql.replace(/\$(\d+)/g, function (_m, n) {
|
|
200
|
+
var i = Number(n) - 1;
|
|
201
|
+
if (i < 0 || i >= params.length) {
|
|
202
|
+
throw new Error("placeholder $" + n + " has no matching param");
|
|
203
|
+
}
|
|
204
|
+
var v = params[i];
|
|
205
|
+
if (v === null || v === undefined) return "NULL";
|
|
206
|
+
if (typeof v === "number") return String(v);
|
|
207
|
+
if (typeof v === "boolean") return v ? "TRUE" : "FALSE";
|
|
208
|
+
return "'" + String(v).replace(/'/g, "''") + "'";
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Command-tag lines psql prints for non-SELECT statements.
|
|
213
|
+
var _CMD_TAG_RE = /^(INSERT|UPDATE|DELETE|MERGE|SELECT|COPY|MOVE)\b(?:\s+\d+)*\s*$/;
|
|
214
|
+
// A control-keyword tag (BEGIN / COMMIT / SET / CREATE TABLE / ...) — no
|
|
215
|
+
// rows, no countable affected rows for our purposes.
|
|
216
|
+
var _CTRL_TAG_RE = /^(BEGIN|COMMIT|ROLLBACK|SET|RESET|SAVEPOINT|RELEASE|START|CREATE|DROP|ALTER|GRANT|REVOKE|TRUNCATE|COMMENT|DO|CALL|VACUUM|ANALYZE|EXPLAIN|TABLE|SHOW|DISCARD)\b/;
|
|
217
|
+
|
|
218
|
+
function _parseBlock(block) {
|
|
219
|
+
var lines = block.split(/\r?\n/);
|
|
220
|
+
// Drop a trailing empty line from the final newline.
|
|
221
|
+
while (lines.length && lines[lines.length - 1] === "") lines.pop();
|
|
222
|
+
|
|
223
|
+
// Surface a real Postgres error with its SQLSTATE so the framework's
|
|
224
|
+
// SQLSTATE-keyed paths (42501 RLS-denied, 28000 auth, 40001 deadlock)
|
|
225
|
+
// see a coded error.
|
|
226
|
+
for (var i = 0; i < lines.length; i++) {
|
|
227
|
+
var em = /^ERROR:\s+([0-9A-Za-z]{5}):\s*(.*)$/.exec(lines[i]);
|
|
228
|
+
if (em) {
|
|
229
|
+
var err = new Error("Postgres " + em[1] + ": " + em[2]);
|
|
230
|
+
err.code = em[1];
|
|
231
|
+
return { error: err };
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Affected-row count from a DML command tag (e.g. "INSERT 0 3" → 3,
|
|
236
|
+
// "UPDATE 2" → 2). The tag is the LAST tag-shaped line of the block.
|
|
237
|
+
var affected = null;
|
|
238
|
+
var dataLines = [];
|
|
239
|
+
for (var j = 0; j < lines.length; j++) {
|
|
240
|
+
var ln = lines[j];
|
|
241
|
+
if (/^(NOTICE|WARNING|DETAIL|HINT|LINE|LOCATION|CONTEXT|STATEMENT):/.test(ln)) continue;
|
|
242
|
+
var tm = _CMD_TAG_RE.exec(ln);
|
|
243
|
+
if (tm) {
|
|
244
|
+
var nums = ln.trim().split(/\s+/).slice(1).map(Number);
|
|
245
|
+
if (nums.length) affected = nums[nums.length - 1];
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
if (_CTRL_TAG_RE.test(ln) && ln.indexOf("\t") === -1) continue;
|
|
249
|
+
dataLines.push(ln);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// dataLines (if any) carry a header row first, then data rows.
|
|
253
|
+
var rows = [];
|
|
254
|
+
if (dataLines.length >= 1) {
|
|
255
|
+
var headers = dataLines[0].split("\t");
|
|
256
|
+
for (var k = 1; k < dataLines.length; k++) {
|
|
257
|
+
var cells = dataLines[k].split("\t");
|
|
258
|
+
var row = {};
|
|
259
|
+
for (var c = 0; c < headers.length; c++) {
|
|
260
|
+
var cell = cells[c];
|
|
261
|
+
if (cell === NULL_SENTINEL || cell === undefined) { row[headers[c]] = null; continue; }
|
|
262
|
+
// Native-type coercion at the driver boundary. A real pg driver
|
|
263
|
+
// returns a Postgres `boolean` as a JS true/false; the psql text
|
|
264
|
+
// protocol renders it 't'/'f'. declareRowPolicy.up() reads the
|
|
265
|
+
// boolean pg_class.relrowsecurity to decide whether to ENABLE RLS
|
|
266
|
+
// — left as the string "f" it would be JS-truthy and the ENABLE
|
|
267
|
+
// would be skipped. Coerce the boolean system columns the
|
|
268
|
+
// framework reads so the shim matches a real driver's typing.
|
|
269
|
+
if (_BOOL_COLUMNS[headers[c]] === true && (cell === "t" || cell === "f")) {
|
|
270
|
+
row[headers[c]] = (cell === "t");
|
|
271
|
+
} else {
|
|
272
|
+
row[headers[c]] = cell;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
rows.push(row);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
var rowCount = (affected !== null) ? affected : rows.length;
|
|
279
|
+
return { rows: rows, rowCount: rowCount, error: null };
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Postgres system columns the framework reads as JS booleans (a real pg
|
|
283
|
+
// driver coerces these; the psql text protocol renders them 't'/'f').
|
|
284
|
+
var _BOOL_COLUMNS = { relrowsecurity: true, relforcerowsecurity: true };
|
|
285
|
+
|
|
286
|
+
// Run fn() under the gdpr posture, restoring the prior posture in a
|
|
287
|
+
// finally so parallel smoke files aren't poisoned.
|
|
288
|
+
async function _underGdpr(fn) {
|
|
289
|
+
var prior = b.compliance.current();
|
|
290
|
+
b.compliance.clear();
|
|
291
|
+
b.compliance.set("gdpr");
|
|
292
|
+
try { await fn(); }
|
|
293
|
+
finally {
|
|
294
|
+
b.compliance.clear();
|
|
295
|
+
if (prior) b.compliance.set(prior);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async function _expectThrow(label, fn, expectedCode) {
|
|
300
|
+
var threw = null;
|
|
301
|
+
try { await fn(); } catch (e) { threw = e; }
|
|
302
|
+
check(label + ": threw " + expectedCode,
|
|
303
|
+
threw !== null && threw.code === expectedCode);
|
|
304
|
+
return threw;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
async function run() {
|
|
308
|
+
var pg = await services.requireService("postgres");
|
|
309
|
+
if (!pg.ok) throw new Error("postgres unreachable: " + pg.reason);
|
|
310
|
+
|
|
311
|
+
// ---- fresh schema ----
|
|
312
|
+
_psql([
|
|
313
|
+
"DROP TABLE IF EXISTS orders;",
|
|
314
|
+
"DROP TABLE IF EXISTS eu_rows;",
|
|
315
|
+
"DROP TABLE IF EXISTS rls_sessions;",
|
|
316
|
+
"DROP ROLE IF EXISTS blamejs_rls_app;",
|
|
317
|
+
].join("\n"));
|
|
318
|
+
|
|
319
|
+
var driver = _makeDockerPgDriver();
|
|
320
|
+
b.externalDb._resetForTest();
|
|
321
|
+
b.externalDb.init({
|
|
322
|
+
backends: {
|
|
323
|
+
ops: {
|
|
324
|
+
connect: driver.connect, query: driver.query, close: driver.close,
|
|
325
|
+
dialect: "postgres", residencyTag: "eu",
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// ====================================================================
|
|
331
|
+
// 1. CRUD round-trip against real Postgres.
|
|
332
|
+
// ====================================================================
|
|
333
|
+
await b.externalDb.query(
|
|
334
|
+
"CREATE TABLE orders (id text PRIMARY KEY, region text, total int)", []);
|
|
335
|
+
check("postgres: CREATE TABLE ran", true);
|
|
336
|
+
|
|
337
|
+
var ins = await b.externalDb.query(
|
|
338
|
+
"INSERT INTO orders (id, region, total) VALUES ($1, $2, $3)",
|
|
339
|
+
["o-1", "eu", 100]);
|
|
340
|
+
check("postgres: INSERT affectedRows = 1", ins.rowCount === 1);
|
|
341
|
+
|
|
342
|
+
await b.externalDb.query(
|
|
343
|
+
"INSERT INTO orders (id, region, total) VALUES ($1, $2, $3)",
|
|
344
|
+
["o-2", "eu", 250]);
|
|
345
|
+
|
|
346
|
+
var sel = await b.externalDb.query(
|
|
347
|
+
"SELECT id, region, total FROM orders WHERE id = $1", ["o-1"]);
|
|
348
|
+
check("postgres: SELECT returns one row", sel.rowCount === 1 && sel.rows.length === 1);
|
|
349
|
+
check("postgres: SELECT round-trips id", sel.rows[0].id === "o-1");
|
|
350
|
+
check("postgres: SELECT round-trips region", sel.rows[0].region === "eu");
|
|
351
|
+
check("postgres: SELECT round-trips total", sel.rows[0].total === "100");
|
|
352
|
+
|
|
353
|
+
var upd = await b.externalDb.query(
|
|
354
|
+
"UPDATE orders SET total = $1 WHERE id = $2", [999, "o-1"]);
|
|
355
|
+
check("postgres: UPDATE affectedRows = 1", upd.rowCount === 1);
|
|
356
|
+
var selU = await b.externalDb.query("SELECT total FROM orders WHERE id = $1", ["o-1"]);
|
|
357
|
+
check("postgres: UPDATE persisted", selU.rows[0].total === "999");
|
|
358
|
+
|
|
359
|
+
var del = await b.externalDb.query("DELETE FROM orders WHERE id = $1", ["o-2"]);
|
|
360
|
+
check("postgres: DELETE affectedRows = 1", del.rowCount === 1);
|
|
361
|
+
var selAll = await b.externalDb.query("SELECT id FROM orders ORDER BY id", []);
|
|
362
|
+
check("postgres: one row remains after DELETE",
|
|
363
|
+
selAll.rowCount === 1 && selAll.rows[0].id === "o-1");
|
|
364
|
+
|
|
365
|
+
// NULL round-trips as a real null (distinguished from empty string).
|
|
366
|
+
await b.externalDb.query(
|
|
367
|
+
"INSERT INTO orders (id, region, total) VALUES ($1, $2, $3)",
|
|
368
|
+
["o-null", null, 0]);
|
|
369
|
+
var selNull = await b.externalDb.query(
|
|
370
|
+
"SELECT region FROM orders WHERE id = $1", ["o-null"]);
|
|
371
|
+
check("postgres: NULL column round-trips as null", selNull.rows[0].region === null);
|
|
372
|
+
await b.externalDb.query("DELETE FROM orders WHERE id = $1", ["o-null"]);
|
|
373
|
+
|
|
374
|
+
// ====================================================================
|
|
375
|
+
// 2. read/write classifier on real SQL drives the gate.
|
|
376
|
+
// Under gdpr + eu backend: SELECT (read) passes untagged; a CTE-
|
|
377
|
+
// wrapped INSERT (write) is refused untagged — proving the
|
|
378
|
+
// classifier resolves the CTE main verb against real SQL the same
|
|
379
|
+
// way Postgres would execute it.
|
|
380
|
+
// ====================================================================
|
|
381
|
+
await _underGdpr(async function () {
|
|
382
|
+
var s = await b.externalDb.query("SELECT id FROM orders WHERE id = $1", ["o-1"]);
|
|
383
|
+
check("classifier: SELECT classified read → passes untagged, rows returned",
|
|
384
|
+
s.rowCount === 1);
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
await _underGdpr(async function () {
|
|
388
|
+
await _expectThrow("classifier: WITH ... INSERT (CTE write) refused untagged",
|
|
389
|
+
function () {
|
|
390
|
+
return b.externalDb.query(
|
|
391
|
+
"WITH src AS (SELECT 'o-cte' AS id) INSERT INTO orders (id) SELECT id FROM src", []);
|
|
392
|
+
},
|
|
393
|
+
"RESIDENCY_GATE_REQUIRED");
|
|
394
|
+
});
|
|
395
|
+
// The refused CTE write never reached Postgres.
|
|
396
|
+
var cteCheck = _psql("SELECT count(*) AS n FROM orders WHERE id = 'o-cte';");
|
|
397
|
+
check("classifier: refused CTE write did not persist", /\b0\b/.test(cteCheck.trim()));
|
|
398
|
+
|
|
399
|
+
// The SAME CTE write, run directly through psql, DOES place the row —
|
|
400
|
+
// confirming the statement is a genuine write that Postgres executes,
|
|
401
|
+
// not a no-op the gate refused for free.
|
|
402
|
+
_psql("WITH src AS (SELECT 'o-cte-real' AS id) INSERT INTO orders (id) SELECT id FROM src;");
|
|
403
|
+
var cteReal = _psql("SELECT count(*) AS n FROM orders WHERE id = 'o-cte-real';");
|
|
404
|
+
check("classifier: that CTE shape is a real write on Postgres", /\b1\b/.test(cteReal.trim()));
|
|
405
|
+
_psql("DELETE FROM orders WHERE id = 'o-cte-real';");
|
|
406
|
+
|
|
407
|
+
// ====================================================================
|
|
408
|
+
// 3. residency write gate WIRED on the real query path.
|
|
409
|
+
// eu backend + gdpr posture:
|
|
410
|
+
// - cross-border tag "us" → RESIDENCY_TAG_MISMATCH, row absent
|
|
411
|
+
// - in-region tag "eu" → succeeds, row present
|
|
412
|
+
// ====================================================================
|
|
413
|
+
await _underGdpr(async function () {
|
|
414
|
+
await _expectThrow("residency: cross-border write (tag 'us') refused",
|
|
415
|
+
function () {
|
|
416
|
+
return b.externalDb.query(
|
|
417
|
+
"INSERT INTO orders (id, region, total) VALUES ($1, $2, $3)",
|
|
418
|
+
["o-us", "us", 1],
|
|
419
|
+
{ rowResidencyTag: "us" });
|
|
420
|
+
},
|
|
421
|
+
"RESIDENCY_TAG_MISMATCH");
|
|
422
|
+
});
|
|
423
|
+
var usCheck = _psql("SELECT count(*) AS n FROM orders WHERE id = 'o-us';");
|
|
424
|
+
check("residency: refused cross-border write did NOT persist", /\b0\b/.test(usCheck.trim()));
|
|
425
|
+
|
|
426
|
+
await _underGdpr(async function () {
|
|
427
|
+
var ok = await b.externalDb.query(
|
|
428
|
+
"INSERT INTO orders (id, region, total) VALUES ($1, $2, $3)",
|
|
429
|
+
["o-eu", "eu", 7],
|
|
430
|
+
{ rowResidencyTag: "eu" });
|
|
431
|
+
check("residency: in-region write (tag 'eu') succeeded", ok.rowCount === 1);
|
|
432
|
+
});
|
|
433
|
+
var euCheck = _psql("SELECT count(*) AS n FROM orders WHERE id = 'o-eu';");
|
|
434
|
+
check("residency: in-region write DID persist", /\b1\b/.test(euCheck.trim()));
|
|
435
|
+
|
|
436
|
+
// Untagged write under gdpr + eu backend → gate required (wire not reached).
|
|
437
|
+
await _underGdpr(async function () {
|
|
438
|
+
await _expectThrow("residency: untagged write refused",
|
|
439
|
+
function () {
|
|
440
|
+
return b.externalDb.query(
|
|
441
|
+
"INSERT INTO orders (id) VALUES ($1)", ["o-untagged"]);
|
|
442
|
+
},
|
|
443
|
+
"RESIDENCY_GATE_REQUIRED");
|
|
444
|
+
});
|
|
445
|
+
var untaggedCheck = _psql("SELECT count(*) AS n FROM orders WHERE id = 'o-untagged';");
|
|
446
|
+
check("residency: untagged-refused write did NOT persist", /\b0\b/.test(untaggedCheck.trim()));
|
|
447
|
+
|
|
448
|
+
// ====================================================================
|
|
449
|
+
// 4 + 5. declareRowPolicy RLS + transaction({ sessionGucs }) SET LOCAL.
|
|
450
|
+
// A restricted, non-owner, non-superuser role + an RLS policy keyed
|
|
451
|
+
// on the app.tenant_id GUC. Through the framework's transaction()
|
|
452
|
+
// SET LOCAL plumbing, the authorized tenant sees its row and an
|
|
453
|
+
// unauthorized tenant sees zero — proving both the generated RLS DDL
|
|
454
|
+
// blocks rows AND the sessionGucs bindings take effect for the
|
|
455
|
+
// policy on a real session.
|
|
456
|
+
// ====================================================================
|
|
457
|
+
|
|
458
|
+
// Restricted role + table owned by the superuser; the role gets DML
|
|
459
|
+
// grants but is subject to RLS (RLS never applies to owners/superusers).
|
|
460
|
+
_psql([
|
|
461
|
+
"CREATE ROLE blamejs_rls_app NOLOGIN;",
|
|
462
|
+
"CREATE TABLE rls_sessions (id text PRIMARY KEY, tenant_id text NOT NULL, payload text);",
|
|
463
|
+
"INSERT INTO rls_sessions VALUES ('s-acme','acme','acme-secret'), ('s-globex','globex','globex-secret');",
|
|
464
|
+
"GRANT SELECT, INSERT, UPDATE, DELETE ON rls_sessions TO blamejs_rls_app;",
|
|
465
|
+
].join("\n"));
|
|
466
|
+
|
|
467
|
+
// Apply the framework-generated RLS migration. declareRowPolicy returns
|
|
468
|
+
// a migration spec whose up(xdb) issues the ENABLE + CREATE POLICY DDL;
|
|
469
|
+
// drive it with a thin xdb that calls the live backend's query path
|
|
470
|
+
// (explicit backend so a stray ALS role can't reroute it).
|
|
471
|
+
var policy = b.db.declareRowPolicy({
|
|
472
|
+
schema: "public",
|
|
473
|
+
table: "rls_sessions",
|
|
474
|
+
name: "tenant_isolation",
|
|
475
|
+
role: "blamejs_rls_app",
|
|
476
|
+
using: "tenant_id = current_setting('app.tenant_id', true)",
|
|
477
|
+
withCheck: "tenant_id = current_setting('app.tenant_id', true)",
|
|
478
|
+
command: "ALL",
|
|
479
|
+
});
|
|
480
|
+
check("declareRowPolicy: returns a migration spec", policy && typeof policy.up === "function");
|
|
481
|
+
|
|
482
|
+
var applyXdb = {
|
|
483
|
+
query: function (sql, params) {
|
|
484
|
+
return b.externalDb.query(sql, params, { backend: "ops" });
|
|
485
|
+
},
|
|
486
|
+
};
|
|
487
|
+
var applied = await policy.up(applyXdb, { externalDb: b.externalDb, backendName: "ops" });
|
|
488
|
+
check("declareRowPolicy: applied (policy name returned)",
|
|
489
|
+
applied && applied.policy === "public.rls_sessions.tenant_isolation");
|
|
490
|
+
|
|
491
|
+
// Confirm RLS is actually enabled on the real table.
|
|
492
|
+
var rlsState = _psql(
|
|
493
|
+
"SELECT relrowsecurity FROM pg_class WHERE relname = 'rls_sessions';");
|
|
494
|
+
check("RLS: ENABLE ROW LEVEL SECURITY took effect on the real table",
|
|
495
|
+
/\bt\b/.test(rlsState.trim()));
|
|
496
|
+
var polState = _psql(
|
|
497
|
+
"SELECT polname FROM pg_policy p JOIN pg_class c ON c.oid = p.polrelid " +
|
|
498
|
+
"WHERE c.relname = 'rls_sessions';");
|
|
499
|
+
check("RLS: the policy exists in pg_policy", /tenant_isolation/.test(polState));
|
|
500
|
+
|
|
501
|
+
// Authorized tenant — within a transaction set SET LOCAL role +
|
|
502
|
+
// app.tenant_id via sessionGucs; the RLS policy filters to that tenant.
|
|
503
|
+
var acmeRows = await b.externalDb.transaction(async function (tx) {
|
|
504
|
+
return await tx.query("SELECT id, tenant_id FROM rls_sessions ORDER BY id", []);
|
|
505
|
+
}, {
|
|
506
|
+
backend: "ops",
|
|
507
|
+
sessionGucs: { "role": "blamejs_rls_app", "app.tenant_id": "acme" },
|
|
508
|
+
});
|
|
509
|
+
check("RLS: authorized tenant 'acme' sees exactly its 1 row",
|
|
510
|
+
acmeRows.rowCount === 1 && acmeRows.rows.length === 1);
|
|
511
|
+
check("RLS: the visible row is acme's", acmeRows.rows[0].id === "s-acme");
|
|
512
|
+
|
|
513
|
+
// Unauthorized tenant — same table, a tenant value that owns no rows;
|
|
514
|
+
// RLS yields zero rows even though the rows physically exist.
|
|
515
|
+
var nobodyRows = await b.externalDb.transaction(async function (tx) {
|
|
516
|
+
return await tx.query("SELECT id, tenant_id FROM rls_sessions ORDER BY id", []);
|
|
517
|
+
}, {
|
|
518
|
+
backend: "ops",
|
|
519
|
+
sessionGucs: { "role": "blamejs_rls_app", "app.tenant_id": "nobody" },
|
|
520
|
+
});
|
|
521
|
+
check("RLS: unauthorized tenant 'nobody' sees ZERO rows", nobodyRows.rowCount === 0);
|
|
522
|
+
|
|
523
|
+
// The other real tenant ('globex') sees only its own row — confirms the
|
|
524
|
+
// GUC value, not a constant, drives the policy.
|
|
525
|
+
var globexRows = await b.externalDb.transaction(async function (tx) {
|
|
526
|
+
return await tx.query("SELECT id FROM rls_sessions ORDER BY id", []);
|
|
527
|
+
}, {
|
|
528
|
+
backend: "ops",
|
|
529
|
+
sessionGucs: { "role": "blamejs_rls_app", "app.tenant_id": "globex" },
|
|
530
|
+
});
|
|
531
|
+
check("RLS: tenant 'globex' sees exactly its own row",
|
|
532
|
+
globexRows.rowCount === 1 && globexRows.rows[0].id === "s-globex");
|
|
533
|
+
|
|
534
|
+
// Control: the owning superuser (no SET LOCAL role) bypasses RLS and
|
|
535
|
+
// sees every row — proves the prior zero/one results were RLS filtering,
|
|
536
|
+
// not an empty table or a broken session.
|
|
537
|
+
var allRows = await b.externalDb.query("SELECT id FROM rls_sessions ORDER BY id", []);
|
|
538
|
+
check("RLS: owner/superuser bypasses RLS and sees all rows", allRows.rowCount === 2);
|
|
539
|
+
|
|
540
|
+
// The WITH CHECK clause blocks a cross-tenant INSERT: as 'acme', try to
|
|
541
|
+
// write a 'globex' row → 42501 (policy violation). This proves the
|
|
542
|
+
// sessionGucs binding gates WRITES too, not just reads.
|
|
543
|
+
await _expectThrow("RLS: WITH CHECK blocks a cross-tenant INSERT (SQLSTATE 42501)",
|
|
544
|
+
function () {
|
|
545
|
+
return b.externalDb.transaction(async function (tx) {
|
|
546
|
+
await tx.query(
|
|
547
|
+
"INSERT INTO rls_sessions (id, tenant_id, payload) VALUES ($1, $2, $3)",
|
|
548
|
+
["s-evil", "globex", "x"]);
|
|
549
|
+
}, {
|
|
550
|
+
backend: "ops",
|
|
551
|
+
sessionGucs: { "role": "blamejs_rls_app", "app.tenant_id": "acme" },
|
|
552
|
+
});
|
|
553
|
+
},
|
|
554
|
+
"42501");
|
|
555
|
+
var evilCheck = _psql("SELECT count(*) AS n FROM rls_sessions WHERE id = 's-evil';");
|
|
556
|
+
check("RLS: the cross-tenant INSERT did NOT persist (rolled back)",
|
|
557
|
+
/\b0\b/.test(evilCheck.trim()));
|
|
558
|
+
|
|
559
|
+
// ---- teardown ----
|
|
560
|
+
await b.externalDb.shutdown();
|
|
561
|
+
b.compliance.clear();
|
|
562
|
+
_psql([
|
|
563
|
+
"DROP TABLE IF EXISTS orders;",
|
|
564
|
+
"DROP TABLE IF EXISTS rls_sessions;",
|
|
565
|
+
"DROP ROLE IF EXISTS blamejs_rls_app;",
|
|
566
|
+
].join("\n"));
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
module.exports = { run: run };
|
|
570
|
+
|
|
571
|
+
if (require.main === module) {
|
|
572
|
+
run().then(
|
|
573
|
+
function () { console.log("OK — " + helpers.getChecks() + " checks passed"); process.exit(0); },
|
|
574
|
+
function (e) { console.error("FAIL:", e.stack || e); process.exit(1); }
|
|
575
|
+
);
|
|
576
|
+
}
|