@blamejs/blamejs-shop 0.4.30 → 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 +4 -0
- package/lib/asset-manifest.json +1 -1
- package/lib/checkout.js +8 -0
- package/lib/order.js +71 -11
- 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
|
@@ -29,9 +29,20 @@ var SCHEMA = [{
|
|
|
29
29
|
status: "TEXT DEFAULT 'active'",
|
|
30
30
|
},
|
|
31
31
|
indexes: ["status"],
|
|
32
|
+
}, {
|
|
33
|
+
// Residency-gate fixture table — `dataRegion` carries the per-row
|
|
34
|
+
// residency tag (plaintext, inspected before sealRow); `addr` is a
|
|
35
|
+
// region-bound column for the per-column gate.
|
|
36
|
+
name: "residents",
|
|
37
|
+
columns: {
|
|
38
|
+
_id: "TEXT PRIMARY KEY",
|
|
39
|
+
name: "TEXT",
|
|
40
|
+
dataRegion: "TEXT",
|
|
41
|
+
addr: "TEXT",
|
|
42
|
+
},
|
|
32
43
|
}];
|
|
33
44
|
|
|
34
|
-
async function initDb(tmpDir, columnGate) {
|
|
45
|
+
async function initDb(tmpDir, columnGate, dataResidency) {
|
|
35
46
|
process.env.BLAMEJS_SKIP_NTP_CHECK = "1";
|
|
36
47
|
helpers.setTestPassphraseEnv();
|
|
37
48
|
b.cluster._resetForTest();
|
|
@@ -41,6 +52,7 @@ async function initDb(tmpDir, columnGate) {
|
|
|
41
52
|
await b.vault.init({ dataDir: tmpDir });
|
|
42
53
|
var opts = { dataDir: tmpDir, tmpDir: path.join(tmpDir, "tmpfs"), schema: SCHEMA };
|
|
43
54
|
if (columnGate !== undefined) opts.columnGate = columnGate;
|
|
55
|
+
if (dataResidency !== undefined) opts.dataResidency = dataResidency;
|
|
44
56
|
await b.db.init(opts);
|
|
45
57
|
}
|
|
46
58
|
|
|
@@ -136,9 +148,176 @@ async function run() {
|
|
|
136
148
|
b.vault._resetForTest();
|
|
137
149
|
b.cluster._resetForTest();
|
|
138
150
|
|
|
151
|
+
// ---- per-row + per-column residency write gates via db.from(...) ----
|
|
152
|
+
await runRowResidencyGate();
|
|
153
|
+
await runColumnResidencyGate();
|
|
154
|
+
await runRowResidencyRegionGate();
|
|
155
|
+
|
|
139
156
|
console.log("OK — db column-gate tests");
|
|
140
157
|
}
|
|
141
158
|
|
|
159
|
+
// Local per-row residency gate driven through db.from(table).insertOne /
|
|
160
|
+
// .updateOne. The fixture's db has NO dataResidency region configured,
|
|
161
|
+
// so the local-mismatch refusal can't fire here (the gate passes when no
|
|
162
|
+
// region is set) — this covers the missing-tag / invalid-tag refusals,
|
|
163
|
+
// the valid-tag success path (row written + readable back), and the
|
|
164
|
+
// update matrix. Region-mismatch lives in runRowResidencyRegionGate.
|
|
165
|
+
async function runRowResidencyGate() {
|
|
166
|
+
var tmp = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-rowresid-"));
|
|
167
|
+
await initDb(tmp);
|
|
168
|
+
b.cryptoField.clearResidencyForTest();
|
|
169
|
+
try {
|
|
170
|
+
b.cryptoField.declarePerRowResidency("residents", {
|
|
171
|
+
residencyColumn: "dataRegion",
|
|
172
|
+
allowedTags: ["eu-west-1", "us-east-1", "global"],
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// INSERT without the tag column → refused.
|
|
176
|
+
check("insert without residency tag refused",
|
|
177
|
+
insertCode("residents", { _id: "r-miss", name: "x" }) ===
|
|
178
|
+
"db-query/row-residency-tag-missing");
|
|
179
|
+
|
|
180
|
+
// INSERT with a tag outside allowedTags → refused.
|
|
181
|
+
check("insert with tag not in allowedTags refused",
|
|
182
|
+
insertCode("residents", { _id: "r-bad", name: "x", dataRegion: "ap-south-1" }) ===
|
|
183
|
+
"db-query/row-residency-tag-invalid");
|
|
184
|
+
|
|
185
|
+
// INSERT with a valid tag → row written AND readable back.
|
|
186
|
+
var ins = b.db.from("residents").insertOne({
|
|
187
|
+
_id: "r-ok", name: "alice", dataRegion: "eu-west-1",
|
|
188
|
+
});
|
|
189
|
+
check("insert with valid residency tag returns the row",
|
|
190
|
+
ins && ins._id === "r-ok" && ins.dataRegion === "eu-west-1");
|
|
191
|
+
var readBack = b.db.from("residents").where({ _id: "r-ok" }).first();
|
|
192
|
+
check("valid-tag row is readable back after insert",
|
|
193
|
+
readBack && readBack.name === "alice" && readBack.dataRegion === "eu-west-1");
|
|
194
|
+
|
|
195
|
+
// UPDATE that does NOT touch the residency column on a declared
|
|
196
|
+
// table → passes (not a transfer).
|
|
197
|
+
var updNonResidency = b.db.from("residents").where({ _id: "r-ok" }).updateOne({ name: "alice-2" });
|
|
198
|
+
check("update not touching residency column passes", updNonResidency === true);
|
|
199
|
+
check("non-residency update persisted",
|
|
200
|
+
b.db.from("residents").where({ _id: "r-ok" }).first().name === "alice-2");
|
|
201
|
+
|
|
202
|
+
// UPDATE that sets an out-of-allowlist tag → refused.
|
|
203
|
+
check("update to out-of-allowlist residency tag refused",
|
|
204
|
+
updateCode("residents", { _id: "r-ok" }, { dataRegion: "ap-south-1" }) ===
|
|
205
|
+
"db-query/row-residency-tag-invalid");
|
|
206
|
+
|
|
207
|
+
// UPDATE that explicitly NULLs the residency column → refused (the
|
|
208
|
+
// row must not be nullable into an untagged state — INSERT requires
|
|
209
|
+
// a tag, UPDATE must not clear it).
|
|
210
|
+
check("update clearing residency column to null refused",
|
|
211
|
+
updateCode("residents", { _id: "r-ok" }, { dataRegion: null }) ===
|
|
212
|
+
"db-query/row-residency-tag-missing");
|
|
213
|
+
check("residency tag intact after the refused null-update",
|
|
214
|
+
b.db.from("residents").where({ _id: "r-ok" }).first().dataRegion === "eu-west-1");
|
|
215
|
+
} finally {
|
|
216
|
+
b.cryptoField.clearResidencyForTest();
|
|
217
|
+
b.compliance.clear();
|
|
218
|
+
b.db.close();
|
|
219
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
220
|
+
b.audit._resetForTest();
|
|
221
|
+
b.db._resetForTest();
|
|
222
|
+
b.vault._resetForTest();
|
|
223
|
+
b.cluster._resetForTest();
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Per-column residency gate wiring through db.from(...).insertOne. The
|
|
228
|
+
// fixture's db has a dataResidency region ("eu-west-1"); a column bound
|
|
229
|
+
// to a different region ("us-east-1") refuses under a cross-border
|
|
230
|
+
// regulated posture (gdpr) and passes (advisory) without a posture.
|
|
231
|
+
async function runColumnResidencyGate() {
|
|
232
|
+
var tmp = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-colresid-"));
|
|
233
|
+
await initDb(tmp, undefined, { region: "eu-west-1" });
|
|
234
|
+
b.cryptoField.clearResidencyForTest();
|
|
235
|
+
try {
|
|
236
|
+
b.cryptoField.declareColumnResidency("residents", {
|
|
237
|
+
columnResidency: { addr: "us-east-1" },
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// Without a posture → write passes (advisory only).
|
|
241
|
+
var insAdvisory = b.db.from("residents").insertOne({
|
|
242
|
+
_id: "c-advisory", name: "noposture", addr: "10 Main St",
|
|
243
|
+
});
|
|
244
|
+
check("column-residency mismatch without posture passes (advisory)",
|
|
245
|
+
insAdvisory && insAdvisory._id === "c-advisory");
|
|
246
|
+
|
|
247
|
+
// Under gdpr → mismatched column refused.
|
|
248
|
+
b.compliance.set("gdpr");
|
|
249
|
+
check("column-residency mismatch under gdpr refused",
|
|
250
|
+
insertCode("residents", { _id: "c-gdpr", name: "y", addr: "20 Main St" }) ===
|
|
251
|
+
"db-query/column-residency-mismatch");
|
|
252
|
+
} finally {
|
|
253
|
+
b.compliance.clear();
|
|
254
|
+
b.cryptoField.clearResidencyForTest();
|
|
255
|
+
b.db.close();
|
|
256
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
257
|
+
b.audit._resetForTest();
|
|
258
|
+
b.db._resetForTest();
|
|
259
|
+
b.vault._resetForTest();
|
|
260
|
+
b.cluster._resetForTest();
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Per-row residency region gate — the fixture supports a custom
|
|
265
|
+
// dataResidency region, so this drives the cross-border local-mismatch
|
|
266
|
+
// refusal: under gdpr a tag in allowedTags but OUTSIDE the deployment's
|
|
267
|
+
// region set is refused; a tag inside the region set or "global" passes.
|
|
268
|
+
async function runRowResidencyRegionGate() {
|
|
269
|
+
var tmp = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-rowresid-region-"));
|
|
270
|
+
await initDb(tmp, undefined, { region: "eu-west-1" });
|
|
271
|
+
b.cryptoField.clearResidencyForTest();
|
|
272
|
+
try {
|
|
273
|
+
b.cryptoField.declarePerRowResidency("residents", {
|
|
274
|
+
residencyColumn: "dataRegion",
|
|
275
|
+
allowedTags: ["eu-west-1", "us-east-1", "global"],
|
|
276
|
+
});
|
|
277
|
+
b.compliance.set("gdpr");
|
|
278
|
+
|
|
279
|
+
// Tag in allowedTags but outside the deployment region → refused.
|
|
280
|
+
check("row tag outside deployment region under gdpr refused",
|
|
281
|
+
insertCode("residents", { _id: "rr-us", name: "u", dataRegion: "us-east-1" }) ===
|
|
282
|
+
"db-query/row-residency-local-mismatch");
|
|
283
|
+
|
|
284
|
+
// Tag matching the deployment region → passes.
|
|
285
|
+
var insEu = b.db.from("residents").insertOne({
|
|
286
|
+
_id: "rr-eu", name: "e", dataRegion: "eu-west-1",
|
|
287
|
+
});
|
|
288
|
+
check("row tag matching deployment region passes",
|
|
289
|
+
insEu && insEu._id === "rr-eu");
|
|
290
|
+
|
|
291
|
+
// "global" tag → passes any region.
|
|
292
|
+
var insGlobal = b.db.from("residents").insertOne({
|
|
293
|
+
_id: "rr-global", name: "g", dataRegion: "global",
|
|
294
|
+
});
|
|
295
|
+
check("row tag 'global' passes any region", insGlobal && insGlobal._id === "rr-global");
|
|
296
|
+
} finally {
|
|
297
|
+
b.compliance.clear();
|
|
298
|
+
b.cryptoField.clearResidencyForTest();
|
|
299
|
+
b.db.close();
|
|
300
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
301
|
+
b.audit._resetForTest();
|
|
302
|
+
b.db._resetForTest();
|
|
303
|
+
b.vault._resetForTest();
|
|
304
|
+
b.cluster._resetForTest();
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// insertCode / updateCode — return the thrown error's .code when the
|
|
309
|
+
// write refuses, else null. Compose the same chainable surface the gate
|
|
310
|
+
// runs under, so the refusal is exercised end-to-end (not a unit call).
|
|
311
|
+
function insertCode(table, row) {
|
|
312
|
+
try { b.db.from(table).insertOne(row); } catch (e) { return e && e.code; }
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function updateCode(table, whereObj, changes) {
|
|
317
|
+
try { b.db.from(table).where(whereObj).updateOne(changes); } catch (e) { return e && e.code; }
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
|
|
142
321
|
module.exports = { run: run };
|
|
143
322
|
if (require.main === module) {
|
|
144
323
|
// Rethrow on failure so Node surfaces the error and exits non-zero,
|
|
@@ -87,7 +87,8 @@ async function testSchemaQualifiedSelectShape() {
|
|
|
87
87
|
new dbQuery.Query(db, "audit.events").where({ recordedAt: 1 }).count();
|
|
88
88
|
check("count() emits COUNT(*) FROM \"audit\".\"events\"",
|
|
89
89
|
db.prepared.some(function (s) {
|
|
90
|
-
|
|
90
|
+
// b.sql quotes the aggregate alias by construction: AS "n".
|
|
91
|
+
return /SELECT COUNT\(\*\) AS "n" FROM "audit"\."events"/.test(s);
|
|
91
92
|
}));
|
|
92
93
|
}
|
|
93
94
|
|
|
@@ -109,7 +110,9 @@ async function testSchemaQualifiedInsertUpdateDelete() {
|
|
|
109
110
|
}));
|
|
110
111
|
check("updateOne sub-select uses qualified name",
|
|
111
112
|
db.prepared.some(function (s) {
|
|
112
|
-
|
|
113
|
+
// b.sql quotes the rowid pseudo-column by construction; the
|
|
114
|
+
// single-row idiom is "rowid" = (SELECT "rowid" FROM ...).
|
|
115
|
+
return /SELECT "rowid" FROM "audit"\."events"/.test(s);
|
|
113
116
|
}));
|
|
114
117
|
|
|
115
118
|
db.prepared.length = 0;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// #141: a membership query on a SEALED column must work. db-query's sealed
|
|
3
|
+
// field → derived-hash rewrite handled "=" (single value → keyed hash, with a
|
|
4
|
+
// dual-read of the legacy digest across the v0.15.0 keyed-MAC flip) and "!=",
|
|
5
|
+
// but for "IN" it passed the WHOLE candidate array to cryptoField.lookupHash
|
|
6
|
+
// as if it were one plaintext — producing a single bogus hash, which the
|
|
7
|
+
// later array-shape check then rejected with "where IN requires a non-empty
|
|
8
|
+
// array". So b.db.from().whereIn("email", [...]) and
|
|
9
|
+
// b.db.collection().find({ email: { $in: [...] } }) on a sealed column were
|
|
10
|
+
// unusable: the documented derived-hash query path supported equality but not
|
|
11
|
+
// membership. The fix maps EACH array element through lookupHash and builds
|
|
12
|
+
// the combined IN-list (including each element's legacy digest for dual-read).
|
|
13
|
+
//
|
|
14
|
+
// RED on the buggy tree: whereIn / $in on a sealed field throws. GREEN after
|
|
15
|
+
// the fix: both consumer paths return the matching rows, including legacy-
|
|
16
|
+
// digested (un-migrated) rows.
|
|
17
|
+
|
|
18
|
+
var helpers = require("../helpers");
|
|
19
|
+
var b = helpers.b;
|
|
20
|
+
var check = helpers.check;
|
|
21
|
+
var fs = require("fs");
|
|
22
|
+
var os = require("os");
|
|
23
|
+
var path = require("path");
|
|
24
|
+
var { setupTestDb, teardownTestDb } = require("../helpers/db");
|
|
25
|
+
|
|
26
|
+
function _insertSealed(id, email, name) {
|
|
27
|
+
var sealed = b.cryptoField.sealRow("users", { _id: id, email: email, name: name });
|
|
28
|
+
b.db.prepare(
|
|
29
|
+
'INSERT INTO "users" ("_id","email","emailHash","name") VALUES (?,?,?,?)'
|
|
30
|
+
).run(sealed._id, sealed.email, sealed.emailHash, sealed.name);
|
|
31
|
+
return sealed;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function run() {
|
|
35
|
+
var dir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-sealed-in-"));
|
|
36
|
+
try {
|
|
37
|
+
await setupTestDb(dir);
|
|
38
|
+
|
|
39
|
+
var alice = "alice@example.com";
|
|
40
|
+
var bob = "bob@example.com";
|
|
41
|
+
var carol = "carol@example.com";
|
|
42
|
+
_insertSealed("u-alice", alice, "Alice");
|
|
43
|
+
_insertSealed("u-bob", bob, "Bob");
|
|
44
|
+
_insertSealed("u-carol", carol, "Carol");
|
|
45
|
+
|
|
46
|
+
// ---- equality still works (the path that was never broken) ----
|
|
47
|
+
var eqRows = await b.db.from("users").where("email", alice).all();
|
|
48
|
+
check("sealed equality query still finds the row",
|
|
49
|
+
eqRows.length === 1 && eqRows[0]._id === "u-alice");
|
|
50
|
+
|
|
51
|
+
// ---- #141: whereIn on a sealed column (the real consumer path) ----
|
|
52
|
+
var inRows = await b.db.from("users").whereIn("email", [alice, bob]).all();
|
|
53
|
+
var inIds = inRows.map(function (r) { return r._id; }).sort();
|
|
54
|
+
check("#141 whereIn on a sealed column returns every matching row",
|
|
55
|
+
inIds.length === 2 && inIds[0] === "u-alice" && inIds[1] === "u-bob");
|
|
56
|
+
check("#141 whereIn on a sealed column excludes the non-listed row",
|
|
57
|
+
inIds.indexOf("u-carol") === -1);
|
|
58
|
+
|
|
59
|
+
// ---- #141: the collection $in consumer path (same root) ----
|
|
60
|
+
var coll = b.db.collection("users");
|
|
61
|
+
var collRows = coll.find({ email: { $in: [alice, carol] } });
|
|
62
|
+
var collIds = collRows.map(function (r) { return r._id; }).sort();
|
|
63
|
+
check("#141 collection.find $in on a sealed column returns every match",
|
|
64
|
+
collIds.length === 2 && collIds[0] === "u-alice" && collIds[1] === "u-carol");
|
|
65
|
+
|
|
66
|
+
// ---- #141: dual-read — a legacy-digested (un-migrated) row is found ----
|
|
67
|
+
// Forge a row carrying the pre-v0.15.0 salted digest, like the dual-read
|
|
68
|
+
// migrate test. whereIn must include the legacy digest per element.
|
|
69
|
+
var dave = "dave@example.com";
|
|
70
|
+
var lk = b.cryptoField.lookupHash("users", "email", dave);
|
|
71
|
+
var sealedDave = b.cryptoField.sealRow("users", { _id: "u-dave", email: dave, name: "Dave" });
|
|
72
|
+
sealedDave.emailHash = lk.legacyValue; // pin the LEGACY digest
|
|
73
|
+
b.db.prepare(
|
|
74
|
+
'INSERT INTO "users" ("_id","email","emailHash","name") VALUES (?,?,?,?)'
|
|
75
|
+
).run(sealedDave._id, sealedDave.email, sealedDave.emailHash, sealedDave.name);
|
|
76
|
+
var dualRows = await b.db.from("users").whereIn("email", [dave, bob]).all();
|
|
77
|
+
var dualIds = dualRows.map(function (r) { return r._id; }).sort();
|
|
78
|
+
check("#141 whereIn dual-reads the legacy digest (finds an un-migrated row)",
|
|
79
|
+
dualIds.indexOf("u-dave") !== -1 && dualIds.indexOf("u-bob") !== -1);
|
|
80
|
+
|
|
81
|
+
// ---- single-element membership ----
|
|
82
|
+
var oneRows = await b.db.from("users").whereIn("email", [carol]).all();
|
|
83
|
+
check("#141 single-element whereIn on a sealed column works",
|
|
84
|
+
oneRows.length === 1 && oneRows[0]._id === "u-carol");
|
|
85
|
+
|
|
86
|
+
// ---- regression: whereIn on a NON-sealed column is unchanged ----
|
|
87
|
+
var idRows = await b.db.from("users").whereIn("_id", ["u-alice", "u-bob"]).all();
|
|
88
|
+
check("non-sealed whereIn is unaffected",
|
|
89
|
+
idRows.map(function (r) { return r._id; }).sort().join(",") === "u-alice,u-bob");
|
|
90
|
+
|
|
91
|
+
console.log("OK — db-query sealed-field IN / $in tests");
|
|
92
|
+
} finally {
|
|
93
|
+
await teardownTestDb(dir);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = { run: run };
|
|
98
|
+
if (require.main === module) {
|
|
99
|
+
run().then(function () { process.exit(0); })
|
|
100
|
+
.catch(function (err) { process.stderr.write(String(err && err.stack || err) + "\n"); process.exit(1); });
|
|
101
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* b.db.runSql / b.db.prepare(...).run(...) — local per-row residency gate on
|
|
4
|
+
* the RAW execution path. _assertLocalResidency is wired only at the
|
|
5
|
+
* structured builder boundary (insertOne/updateOne). The raw paths
|
|
6
|
+
* b.db.runSql (execRaw) and b.db.prepare(sql).run(...) write a cross-border
|
|
7
|
+
* row straight to disk with NO residency check under a regulated posture.
|
|
8
|
+
* RED today: builder refuses (control), raw paths do not.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
var helpers = require("../helpers");
|
|
12
|
+
var b = helpers.b;
|
|
13
|
+
var check = helpers.check;
|
|
14
|
+
var fs = helpers.fs;
|
|
15
|
+
var os = helpers.os;
|
|
16
|
+
var path = helpers.path;
|
|
17
|
+
|
|
18
|
+
var SCHEMA = [{
|
|
19
|
+
name: "residents",
|
|
20
|
+
columns: { _id: "TEXT PRIMARY KEY", name: "TEXT", dataRegion: "TEXT" },
|
|
21
|
+
}];
|
|
22
|
+
|
|
23
|
+
async function initDb(tmpDir) {
|
|
24
|
+
process.env.BLAMEJS_SKIP_NTP_CHECK = "1";
|
|
25
|
+
helpers.setTestPassphraseEnv();
|
|
26
|
+
b.cluster._resetForTest();
|
|
27
|
+
b.audit._resetForTest();
|
|
28
|
+
b.vault._resetForTest();
|
|
29
|
+
b.db._resetForTest();
|
|
30
|
+
await b.vault.init({ dataDir: tmpDir });
|
|
31
|
+
await b.db.init({
|
|
32
|
+
dataDir: tmpDir,
|
|
33
|
+
tmpDir: path.join(tmpDir, "tmpfs"),
|
|
34
|
+
schema: SCHEMA,
|
|
35
|
+
dataResidency: { region: "eu-west-1" },
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function codeOf(fn) {
|
|
40
|
+
try { fn(); } catch (e) { return (e && e.code) || (e && e.message) || "threw"; }
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function run() {
|
|
45
|
+
var tmp = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-raw-resid-"));
|
|
46
|
+
await initDb(tmp);
|
|
47
|
+
b.cryptoField.clearResidencyForTest();
|
|
48
|
+
try {
|
|
49
|
+
b.cryptoField.declarePerRowResidency("residents", {
|
|
50
|
+
residencyColumn: "dataRegion",
|
|
51
|
+
allowedTags: ["eu-west-1", "us-east-1", "global"],
|
|
52
|
+
});
|
|
53
|
+
b.compliance.set("gdpr");
|
|
54
|
+
|
|
55
|
+
// CONTROL: the structured builder path refuses the cross-border tag.
|
|
56
|
+
check("control: db.from() builder refuses the cross-border tag",
|
|
57
|
+
codeOf(function () {
|
|
58
|
+
b.db.from("residents").insertOne({ _id: "ctl", name: "x", dataRegion: "us-east-1" });
|
|
59
|
+
}) === "db-query/row-residency-local-mismatch");
|
|
60
|
+
|
|
61
|
+
// RAW PATH 1: b.db.runSql (execRaw). RED today — no refusal.
|
|
62
|
+
var runSqlRefusedCode = codeOf(function () {
|
|
63
|
+
b.db.runSql(
|
|
64
|
+
"INSERT INTO \"residents\" (_id, name, dataRegion) VALUES ('raw-1', 'x', 'us-east-1')");
|
|
65
|
+
});
|
|
66
|
+
check("b.db.runSql cross-border write is refused (residency gate fires on the raw path)",
|
|
67
|
+
runSqlRefusedCode === "db-query/row-residency-local-mismatch");
|
|
68
|
+
check("b.db.runSql cross-border row did not persist (no us-east-1 row landed)",
|
|
69
|
+
b.db.from("residents").where({ _id: "raw-1" }).first() === null);
|
|
70
|
+
|
|
71
|
+
// RAW PATH 2: b.db.prepare(sql).run(...) — the seeders' consumer path.
|
|
72
|
+
var prepareRefusedCode = codeOf(function () {
|
|
73
|
+
b.db.prepare(
|
|
74
|
+
"INSERT INTO \"residents\" (_id, name, dataRegion) VALUES (?, ?, ?)")
|
|
75
|
+
.run("raw-2", "x", "us-east-1");
|
|
76
|
+
});
|
|
77
|
+
check("b.db.prepare().run() cross-border write is refused (residency gate fires)",
|
|
78
|
+
prepareRefusedCode === "db-query/row-residency-local-mismatch");
|
|
79
|
+
check("b.db.prepare().run() cross-border row did not persist",
|
|
80
|
+
b.db.from("residents").where({ _id: "raw-2" }).first() === null);
|
|
81
|
+
|
|
82
|
+
// An in-region raw write still succeeds (no over-rejection).
|
|
83
|
+
b.db.runSql(
|
|
84
|
+
"INSERT INTO \"residents\" (_id, name, dataRegion) VALUES ('raw-eu', 'y', 'eu-west-1')");
|
|
85
|
+
check("an in-region raw write still persists (gate does not over-reject)",
|
|
86
|
+
(b.db.from("residents").where({ _id: "raw-eu" }).first() || {}).dataRegion === "eu-west-1");
|
|
87
|
+
|
|
88
|
+
// RAW PATH 3: a SCHEMA-QUALIFIED table name must resolve to the table and
|
|
89
|
+
// still gate. SQLite accepts INSERT INTO main.residents; capturing only the
|
|
90
|
+
// "main" qualifier would skip the gate for the real target table.
|
|
91
|
+
var qualifiedRefusedCode = codeOf(function () {
|
|
92
|
+
b.db.runSql(
|
|
93
|
+
"INSERT INTO main.residents (_id, name, dataRegion) VALUES ('raw-q', 'x', 'us-east-1')");
|
|
94
|
+
});
|
|
95
|
+
check("qualified-name raw write is refused (main.residents resolves to residents)",
|
|
96
|
+
qualifiedRefusedCode === "db-query/row-residency-local-mismatch");
|
|
97
|
+
check("qualified-name cross-border row did not persist",
|
|
98
|
+
b.db.from("residents").where({ _id: "raw-q" }).first() === null);
|
|
99
|
+
|
|
100
|
+
// RAW PATH 4: an UPDATE whose SET value contains the word WHERE inside a
|
|
101
|
+
// quoted string must be parsed quote-aware, so the residency-column
|
|
102
|
+
// assignment after it is still seen and gated.
|
|
103
|
+
var quotedWhereRefusedCode = codeOf(function () {
|
|
104
|
+
b.db.runSql(
|
|
105
|
+
"UPDATE residents SET name='x WHERE y', dataRegion='us-east-1' WHERE _id='raw-eu'");
|
|
106
|
+
});
|
|
107
|
+
check("update with a quoted WHERE in a SET value is parsed quote-aware + refused",
|
|
108
|
+
quotedWhereRefusedCode === "db-query/row-residency-local-mismatch");
|
|
109
|
+
check("the quoted-WHERE update did not move the row cross-border",
|
|
110
|
+
(b.db.from("residents").where({ _id: "raw-eu" }).first() || {}).dataRegion === "eu-west-1");
|
|
111
|
+
} finally {
|
|
112
|
+
b.compliance.clear();
|
|
113
|
+
b.cryptoField.clearResidencyForTest();
|
|
114
|
+
b.db.close();
|
|
115
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
116
|
+
b.audit._resetForTest();
|
|
117
|
+
b.db._resetForTest();
|
|
118
|
+
b.vault._resetForTest();
|
|
119
|
+
b.cluster._resetForTest();
|
|
120
|
+
}
|
|
121
|
+
console.log("OK — db raw-path residency gate tests");
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
module.exports = { run: run };
|
|
125
|
+
if (require.main === module) {
|
|
126
|
+
run().then(function () { process.exit(0); })
|
|
127
|
+
.catch(function (err) { process.exitCode = 1; throw err; });
|
|
128
|
+
}
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* dbSchema.reconcile / reconcileTable —
|
|
3
|
+
* dbSchema.reconcile / reconcileTable — schema-drift detection.
|
|
4
4
|
*
|
|
5
5
|
* reconcile is additive-only (CREATE IF NOT EXISTS + ALTER ADD COLUMN);
|
|
6
6
|
* it never drops columns. The onDrift opt adds detection of config-vs-
|
|
7
7
|
* live divergence without changing that non-destructive contract:
|
|
8
8
|
*
|
|
9
|
-
* -
|
|
10
|
-
*
|
|
9
|
+
* - onDrift unset = posture-driven default (v0.15.0): "ignore" on an
|
|
10
|
+
* unpinned / non-regulated deployment (back-compat); "refuse" when a
|
|
11
|
+
* regulated compliance posture is globally pinned.
|
|
12
|
+
* - "ignore" = tolerate a live table with an extra column silently
|
|
13
|
+
* (the explicit opt-out under a regulated posture).
|
|
11
14
|
* - "warn" = detect, never throw (report returned to the caller).
|
|
12
15
|
* - "refuse" = throw at boot on the first drifted table (strict-schema
|
|
13
16
|
* posture).
|
|
@@ -25,9 +28,10 @@ var os = helpers.os;
|
|
|
25
28
|
var path = helpers.path;
|
|
26
29
|
var sqlite = require("node:sqlite");
|
|
27
30
|
var dbSchema = require("../../lib/db-schema");
|
|
31
|
+
var compliance = require("../../lib/compliance");
|
|
28
32
|
|
|
29
|
-
function _openDb(tmpDir) {
|
|
30
|
-
return new sqlite.DatabaseSync(path.join(tmpDir, "drift.db"));
|
|
33
|
+
function _openDb(tmpDir, name) {
|
|
34
|
+
return new sqlite.DatabaseSync(path.join(tmpDir, name || "drift.db"));
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
function _liveColumns(db, table) {
|
|
@@ -134,6 +138,35 @@ async function run() {
|
|
|
134
138
|
/onDrift must be one of/));
|
|
135
139
|
dbBad.close();
|
|
136
140
|
|
|
141
|
+
// ---- posture-driven default (v0.15.0): a regulated pinned posture
|
|
142
|
+
// flips the unset-onDrift default from "ignore" to "refuse"; an explicit
|
|
143
|
+
// onDrift always wins (including "ignore" to opt back out). ----
|
|
144
|
+
compliance._resetForTest();
|
|
145
|
+
try {
|
|
146
|
+
// Build a freshly-reconciled DB, then introduce out-of-band drift —
|
|
147
|
+
// all while UNPINNED so the clean setup never trips the default.
|
|
148
|
+
var dbPosture = _openDb(tmp, "drift-posture.db");
|
|
149
|
+
dbSchema.reconcile(dbPosture, SCHEMA);
|
|
150
|
+
dbSchema.runSql(dbPosture, 'ALTER TABLE "widgets" ADD COLUMN "posture_extra" TEXT');
|
|
151
|
+
|
|
152
|
+
// Unpinned: unset onDrift stays "ignore" — drift tolerated silently.
|
|
153
|
+
check("unpinned + unset onDrift tolerates drift (ignore default)",
|
|
154
|
+
threwMatching(function () { dbSchema.reconcile(dbPosture, SCHEMA); }, /.*/) === null);
|
|
155
|
+
|
|
156
|
+
// Pin a regulated posture: unset onDrift now refuses on the SAME drift.
|
|
157
|
+
compliance.set("gdpr");
|
|
158
|
+
check("regulated posture pinned + unset onDrift refuses drift (refuse default)",
|
|
159
|
+
!!threwMatching(function () { dbSchema.reconcile(dbPosture, SCHEMA); },
|
|
160
|
+
/schema drift on table 'widgets'/));
|
|
161
|
+
// Explicit opt-out: onDrift "ignore" tolerates drift even under the
|
|
162
|
+
// regulated posture (the documented escape hatch).
|
|
163
|
+
check("explicit onDrift:'ignore' opts out under a regulated posture",
|
|
164
|
+
threwMatching(function () { dbSchema.reconcile(dbPosture, SCHEMA, { onDrift: "ignore" }); }, /.*/) === null);
|
|
165
|
+
dbPosture.close();
|
|
166
|
+
} finally {
|
|
167
|
+
compliance._resetForTest();
|
|
168
|
+
}
|
|
169
|
+
|
|
137
170
|
fs.rmSync(tmp, { recursive: true, force: true });
|
|
138
171
|
console.log("OK — db schema-drift detection tests");
|
|
139
172
|
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* dbSchema.reconcileTable verbatim column-TYPE string — statement-emittable
|
|
4
|
+
* gate parity with b.sql.createTable. The TYPE string is concatenated
|
|
5
|
+
* verbatim into CREATE TABLE (db-schema.js:264) + ALTER TABLE ADD COLUMN
|
|
6
|
+
* (db-schema.js:271); neither concat routes through the catalog emittable
|
|
7
|
+
* gate b.sql.createTable got in v0.15.3 (#105). RED today: reconcileTable
|
|
8
|
+
* does NOT throw and the stacked DROP destroys the sentinel table.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
var helpers = require("../helpers");
|
|
12
|
+
var check = helpers.check;
|
|
13
|
+
var fs = helpers.fs;
|
|
14
|
+
var os = helpers.os;
|
|
15
|
+
var path = helpers.path;
|
|
16
|
+
var sqlite = require("node:sqlite");
|
|
17
|
+
var dbSchema = require("../../lib/db-schema");
|
|
18
|
+
|
|
19
|
+
// Real NUL built from a code point — never a literal NUL typed into source.
|
|
20
|
+
var NUL = String.fromCharCode(0);
|
|
21
|
+
|
|
22
|
+
function _openDb(tmpDir, name) {
|
|
23
|
+
return new sqlite.DatabaseSync(path.join(tmpDir, name || "emit.db"));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function threwMatching(fn, pattern) {
|
|
27
|
+
try { fn(); } catch (e) { return pattern.test(e.message || "") ? e : null; }
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function _sentinelAlive(db) {
|
|
32
|
+
return db.prepare(
|
|
33
|
+
"SELECT count(*) AS n FROM sqlite_master WHERE type='table' AND name='secrets'"
|
|
34
|
+
).get().n === 1;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function _seedSentinel(db) {
|
|
38
|
+
dbSchema.runSql(db, "DROP TABLE IF EXISTS secrets");
|
|
39
|
+
dbSchema.runSql(db, "CREATE TABLE secrets (k TEXT)");
|
|
40
|
+
dbSchema.runSql(db, "INSERT INTO secrets VALUES ('topsecret')");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function run() {
|
|
44
|
+
var tmp = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-reconcile-emit-"));
|
|
45
|
+
|
|
46
|
+
// ---- CREATE TABLE path: hostile TYPE stacks a DROP ----
|
|
47
|
+
var dbCreate = _openDb(tmp, "create.db");
|
|
48
|
+
_seedSentinel(dbCreate);
|
|
49
|
+
check("setup: sentinel 'secrets' table exists before reconcile",
|
|
50
|
+
_sentinelAlive(dbCreate));
|
|
51
|
+
|
|
52
|
+
var createErr = threwMatching(function () {
|
|
53
|
+
dbSchema.reconcileTable(dbCreate, {
|
|
54
|
+
name: "t_create",
|
|
55
|
+
columns: { id: "TEXT PRIMARY KEY", evil: "TEXT); DROP TABLE secrets; --" },
|
|
56
|
+
}, { onDrift: "ignore" });
|
|
57
|
+
}, /.*/);
|
|
58
|
+
// RED today: createErr === null (no throw). GREEN: the catalog gate refuses.
|
|
59
|
+
check("reconcileTable refuses a CREATE-path verbatim type that stacks a statement",
|
|
60
|
+
!!createErr);
|
|
61
|
+
// Load-bearing RED proof: the stacked DROP must NOT have executed.
|
|
62
|
+
check("reconcileTable did not let the stacked DROP destroy the sentinel table (CREATE path)",
|
|
63
|
+
_sentinelAlive(dbCreate));
|
|
64
|
+
dbCreate.close();
|
|
65
|
+
|
|
66
|
+
// ---- ADD COLUMN path: reconcile a table, then re-reconcile with a
|
|
67
|
+
// hostile new-column TYPE so the ALTER TABLE ADD COLUMN concat fires. ----
|
|
68
|
+
var dbAlter = _openDb(tmp, "alter.db");
|
|
69
|
+
_seedSentinel(dbAlter);
|
|
70
|
+
dbSchema.reconcileTable(dbAlter, {
|
|
71
|
+
name: "t_alter", columns: { id: "TEXT PRIMARY KEY" },
|
|
72
|
+
}, { onDrift: "ignore" });
|
|
73
|
+
|
|
74
|
+
var alterErr = threwMatching(function () {
|
|
75
|
+
dbSchema.reconcileTable(dbAlter, {
|
|
76
|
+
name: "t_alter",
|
|
77
|
+
columns: { id: "TEXT PRIMARY KEY", evil2: "TEXT); DROP TABLE secrets; --" },
|
|
78
|
+
}, { onDrift: "ignore" });
|
|
79
|
+
}, /.*/);
|
|
80
|
+
check("reconcileTable refuses an ADD-COLUMN verbatim type that stacks a statement",
|
|
81
|
+
!!alterErr);
|
|
82
|
+
check("reconcileTable did not let the stacked DROP destroy the sentinel table (ADD COLUMN path)",
|
|
83
|
+
_sentinelAlive(dbAlter));
|
|
84
|
+
dbAlter.close();
|
|
85
|
+
|
|
86
|
+
// ---- unbalanced-quote TYPE ----
|
|
87
|
+
var dbQuote = _openDb(tmp, "quote.db");
|
|
88
|
+
_seedSentinel(dbQuote);
|
|
89
|
+
check("reconcileTable refuses a verbatim type with an unbalanced quote",
|
|
90
|
+
!!threwMatching(function () {
|
|
91
|
+
dbSchema.reconcileTable(dbQuote, {
|
|
92
|
+
name: "t_quote", columns: { id: "TEXT PRIMARY KEY", c: "TEXT'" },
|
|
93
|
+
}, { onDrift: "ignore" });
|
|
94
|
+
}, /.*/));
|
|
95
|
+
dbQuote.close();
|
|
96
|
+
|
|
97
|
+
// ---- NUL byte in the TYPE string (built via String.fromCharCode(0)) ----
|
|
98
|
+
var dbNul = _openDb(tmp, "nul.db");
|
|
99
|
+
_seedSentinel(dbNul);
|
|
100
|
+
check("reconcileTable refuses a verbatim type carrying a NUL byte",
|
|
101
|
+
!!threwMatching(function () {
|
|
102
|
+
dbSchema.reconcileTable(dbNul, {
|
|
103
|
+
name: "t_nul", columns: { id: "TEXT PRIMARY KEY", c: "TEXT" + NUL + " DEFAULT x" },
|
|
104
|
+
}, { onDrift: "ignore" });
|
|
105
|
+
}, /.*/));
|
|
106
|
+
dbNul.close();
|
|
107
|
+
|
|
108
|
+
// ---- a legitimate verbatim type still reconciles (no over-rejection) ----
|
|
109
|
+
var dbOk = _openDb(tmp, "ok.db");
|
|
110
|
+
check("reconcileTable still allows a legitimate multi-word verbatim type",
|
|
111
|
+
threwMatching(function () {
|
|
112
|
+
dbSchema.reconcileTable(dbOk, {
|
|
113
|
+
name: "t_ok",
|
|
114
|
+
columns: { id: "TEXT PRIMARY KEY", n: "INTEGER NOT NULL DEFAULT 0", v: "VARCHAR(255)" },
|
|
115
|
+
}, { onDrift: "ignore" });
|
|
116
|
+
}, /.*/) === null);
|
|
117
|
+
dbOk.close();
|
|
118
|
+
|
|
119
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
120
|
+
console.log("OK — db-schema reconcileTable emittable-gate tests");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
module.exports = { run: run };
|
|
124
|
+
if (require.main === module) {
|
|
125
|
+
run().then(function () { process.exit(0); })
|
|
126
|
+
.catch(function (err) { process.exitCode = 1; throw err; });
|
|
127
|
+
}
|