@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
|
@@ -12,6 +12,13 @@ var helpers = require("../helpers");
|
|
|
12
12
|
var b = helpers.b;
|
|
13
13
|
var check = helpers.check;
|
|
14
14
|
var C = b.constants;
|
|
15
|
+
var fs = helpers.fs;
|
|
16
|
+
var os = helpers.os;
|
|
17
|
+
var path = helpers.path;
|
|
18
|
+
var setupTestDb = helpers.setupTestDb;
|
|
19
|
+
var teardownTestDb = helpers.teardownTestDb;
|
|
20
|
+
|
|
21
|
+
function _tmp() { return fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-dsr-")); }
|
|
15
22
|
|
|
16
23
|
function _makeDsr(extraOpts) {
|
|
17
24
|
var sources = [
|
|
@@ -759,6 +766,209 @@ async function testReceiptSignerError() {
|
|
|
759
766
|
receipt.signature === undefined);
|
|
760
767
|
}
|
|
761
768
|
|
|
769
|
+
// ---- dbTicketStore: at-rest sealing + erasure purge + upgrade path ----
|
|
770
|
+
|
|
771
|
+
function _dbDsr(extraOpts) {
|
|
772
|
+
var store = b.dsr.dbTicketStore({ db: b.db });
|
|
773
|
+
return {
|
|
774
|
+
store: store,
|
|
775
|
+
dsr: b.dsr.create(Object.assign({
|
|
776
|
+
ticketStore: store,
|
|
777
|
+
posture: "gdpr",
|
|
778
|
+
identityResolver: async function (input) {
|
|
779
|
+
if (input.email === "alice@example.com") {
|
|
780
|
+
return { subjectId: "u-1", email: "alice@example.com", phone: "+15550001111" };
|
|
781
|
+
}
|
|
782
|
+
return null;
|
|
783
|
+
},
|
|
784
|
+
sources: [{
|
|
785
|
+
name: "users",
|
|
786
|
+
query: async function () { return [{ id: 1 }]; },
|
|
787
|
+
erase: async function () { return { deletedIds: [1] }; },
|
|
788
|
+
}],
|
|
789
|
+
}, extraOpts || {})),
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
async function testDbStoreSealsAtRest() {
|
|
794
|
+
var tmpDir = _tmp();
|
|
795
|
+
await setupTestDb(tmpDir);
|
|
796
|
+
try {
|
|
797
|
+
var h = _dbDsr();
|
|
798
|
+
var ticket = await h.dsr.submit({
|
|
799
|
+
type: "access",
|
|
800
|
+
subject: { email: "alice@example.com" },
|
|
801
|
+
reason: "sealing-at-rest verification",
|
|
802
|
+
});
|
|
803
|
+
// Read the RAW row directly (bypassing the store's unseal) and assert
|
|
804
|
+
// the PII columns + payload are NOT stored in plaintext.
|
|
805
|
+
var raw = b.db.prepare(
|
|
806
|
+
"SELECT subject_email, subject_phone, subject_id, subject_email_hash, payload " +
|
|
807
|
+
"FROM dsr_tickets WHERE id = $id").all({ $id: ticket.id })[0];
|
|
808
|
+
check("dbStore: raw row exists", !!raw);
|
|
809
|
+
check("dbStore: subject_email sealed at rest (not plaintext)",
|
|
810
|
+
typeof raw.subject_email === "string" &&
|
|
811
|
+
raw.subject_email.indexOf("alice@example.com") === -1);
|
|
812
|
+
check("dbStore: subject_phone sealed at rest (not plaintext)",
|
|
813
|
+
typeof raw.subject_phone === "string" &&
|
|
814
|
+
raw.subject_phone.indexOf("+15550001111") === -1);
|
|
815
|
+
check("dbStore: payload sealed at rest (no plaintext email leak)",
|
|
816
|
+
typeof raw.payload === "string" &&
|
|
817
|
+
raw.payload.indexOf("alice@example.com") === -1);
|
|
818
|
+
check("dbStore: derived email hash populated for lookup",
|
|
819
|
+
typeof raw.subject_email_hash === "string" && raw.subject_email_hash.length > 0);
|
|
820
|
+
|
|
821
|
+
// The store still round-trips the cleartext ticket via get().
|
|
822
|
+
var got = await h.store.get(ticket.id);
|
|
823
|
+
check("dbStore: get() unseals payload back to cleartext",
|
|
824
|
+
got && got.subject.email === "alice@example.com");
|
|
825
|
+
|
|
826
|
+
// list-by-subject matches via the derived hash (sealed columns can't
|
|
827
|
+
// be matched on plaintext).
|
|
828
|
+
var listed = await h.store.list({ subject: { email: "alice@example.com" } });
|
|
829
|
+
check("dbStore: list-by-subject matches via derived hash",
|
|
830
|
+
listed.length === 1 && listed[0].id === ticket.id);
|
|
831
|
+
} finally {
|
|
832
|
+
await teardownTestDb(tmpDir);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
async function testDbStoreErasurePurgesPriorTickets() {
|
|
837
|
+
var tmpDir = _tmp();
|
|
838
|
+
await setupTestDb(tmpDir);
|
|
839
|
+
try {
|
|
840
|
+
var h = _dbDsr();
|
|
841
|
+
// Two prior access tickets for alice + a final erasure.
|
|
842
|
+
var t1 = await h.dsr.submit({ type: "access", subject: { email: "alice@example.com" }, reason: "prior access one" });
|
|
843
|
+
var t2 = await h.dsr.submit({ type: "access", subject: { email: "alice@example.com" }, reason: "prior access two" });
|
|
844
|
+
var erasure = await h.dsr.submit({
|
|
845
|
+
type: "erasure", subject: { email: "alice@example.com" },
|
|
846
|
+
reason: "right to erasure", verificationLevel: "secondary",
|
|
847
|
+
});
|
|
848
|
+
var before = await h.store.list({ subject: { email: "alice@example.com" } });
|
|
849
|
+
check("dbStore erasure: 3 tickets before completion", before.length === 3);
|
|
850
|
+
|
|
851
|
+
var processed = await h.dsr.process(erasure.id, { actor: "compliance@", verificationLevel: "secondary" });
|
|
852
|
+
check("dbStore erasure: erasure completed", processed.status === "completed");
|
|
853
|
+
|
|
854
|
+
// After completion, the subject's prior tickets are purged; the
|
|
855
|
+
// erasure ticket itself survives (audit/receipt trail).
|
|
856
|
+
var after = await h.store.list({ subject: { email: "alice@example.com" } });
|
|
857
|
+
check("dbStore erasure: only the erasure ticket remains", after.length === 1 && after[0].id === erasure.id);
|
|
858
|
+
check("dbStore erasure: prior ticket t1 gone", (await h.store.get(t1.id)) === null);
|
|
859
|
+
check("dbStore erasure: prior ticket t2 gone", (await h.store.get(t2.id)) === null);
|
|
860
|
+
} finally {
|
|
861
|
+
await teardownTestDb(tmpDir);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
async function testDbStoreUpgradePath() {
|
|
866
|
+
// Build an OLD-shape (v0.8.0) dsr_tickets table by hand — no
|
|
867
|
+
// subject_*_hash columns — then construct the store. ensureSchema must
|
|
868
|
+
// ALTER TABLE ADD COLUMN the missing columns so the first insert
|
|
869
|
+
// succeeds rather than throwing "no such column".
|
|
870
|
+
var tmpDir = _tmp();
|
|
871
|
+
await setupTestDb(tmpDir);
|
|
872
|
+
try {
|
|
873
|
+
b.db.runSql("CREATE TABLE dsr_tickets (" +
|
|
874
|
+
"id TEXT PRIMARY KEY, type TEXT NOT NULL, status TEXT NOT NULL, " +
|
|
875
|
+
"subject_id TEXT, subject_email TEXT, subject_phone TEXT, " +
|
|
876
|
+
"submitted_at INTEGER NOT NULL, deadline_at INTEGER NOT NULL, " +
|
|
877
|
+
"processed_at INTEGER, verification_level TEXT, posture TEXT, " +
|
|
878
|
+
"payload TEXT NOT NULL)");
|
|
879
|
+
// Seed a legacy plaintext row to prove the ALTER survives existing data.
|
|
880
|
+
b.db.prepare("INSERT INTO dsr_tickets (id, type, status, subject_email, submitted_at, deadline_at, payload) " +
|
|
881
|
+
"VALUES ($id, $type, $status, $email, $sa, $da, $p)").run({
|
|
882
|
+
$id: "DSR-LEGACY-1", $type: "access", $status: "pending",
|
|
883
|
+
$email: "legacy@example.com", $sa: Date.now(), $da: Date.now() + 1000,
|
|
884
|
+
$p: JSON.stringify({ id: "DSR-LEGACY-1", status: "pending", subject: { email: "legacy@example.com" } }),
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
// Constructing the store runs ensureSchema → reconciles the columns.
|
|
888
|
+
var h = _dbDsr();
|
|
889
|
+
var cols = b.db.prepare("PRAGMA table_info(dsr_tickets)").all({});
|
|
890
|
+
var names = cols.map(function (c) { return c.name; });
|
|
891
|
+
check("dbStore upgrade: subject_email_hash column added",
|
|
892
|
+
names.indexOf("subject_email_hash") !== -1);
|
|
893
|
+
check("dbStore upgrade: subject_id_hash column added",
|
|
894
|
+
names.indexOf("subject_id_hash") !== -1);
|
|
895
|
+
|
|
896
|
+
// A fresh insert against the upgraded table succeeds (the bug under
|
|
897
|
+
// test threw "no such column: subject_email_hash" here).
|
|
898
|
+
var ticket = await h.dsr.submit({
|
|
899
|
+
type: "access", subject: { email: "alice@example.com" }, reason: "post-upgrade insert",
|
|
900
|
+
});
|
|
901
|
+
check("dbStore upgrade: insert succeeds after schema reconcile",
|
|
902
|
+
typeof ticket.id === "string");
|
|
903
|
+
var got = await h.store.get(ticket.id);
|
|
904
|
+
check("dbStore upgrade: round-trips the new ticket", got && got.subject.email === "alice@example.com");
|
|
905
|
+
|
|
906
|
+
// The legacy row was seeded with a plaintext subject and NULL hash. Once a
|
|
907
|
+
// vault is present, list({ subject }) matches on the hash column, so the
|
|
908
|
+
// upgrade MUST have backfilled the legacy row's hash — otherwise it is
|
|
909
|
+
// invisible to a subject lookup and the erasure-completion purge skips it.
|
|
910
|
+
var legacyFound = await h.store.list({ subject: { email: "legacy@example.com" } });
|
|
911
|
+
check("dbStore upgrade: legacy plaintext row backfilled + found by subject",
|
|
912
|
+
legacyFound.some(function (t) { return t.id === "DSR-LEGACY-1"; }));
|
|
913
|
+
var rawLegacy = b.db.prepare(
|
|
914
|
+
"SELECT subject_email, subject_email_hash FROM dsr_tickets WHERE id = $id")
|
|
915
|
+
.all({ $id: "DSR-LEGACY-1" })[0];
|
|
916
|
+
check("dbStore upgrade: legacy subject_email_hash populated by backfill",
|
|
917
|
+
rawLegacy && typeof rawLegacy.subject_email_hash === "string" &&
|
|
918
|
+
rawLegacy.subject_email_hash.length > 0);
|
|
919
|
+
check("dbStore upgrade: legacy subject_email sealed at rest by backfill (now erasable)",
|
|
920
|
+
rawLegacy && rawLegacy.subject_email !== "legacy@example.com");
|
|
921
|
+
} finally {
|
|
922
|
+
await teardownTestDb(tmpDir);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
// ---- AAD_ROTATION descriptor + reseal ----
|
|
927
|
+
|
|
928
|
+
function testAadRotationDescriptor() {
|
|
929
|
+
var d = b.dsr.AAD_ROTATION;
|
|
930
|
+
check("AAD_ROTATION: exported", d && typeof d === "object");
|
|
931
|
+
check("AAD_ROTATION: table is dsr_tickets", d.table === "dsr_tickets");
|
|
932
|
+
check("AAD_ROTATION: rowIdField is id", d.rowIdField === "id");
|
|
933
|
+
check("AAD_ROTATION: backend external", d.backend === "external");
|
|
934
|
+
check("AAD_ROTATION: reseal is the fn", d.reseal === b.dsr.reseal && typeof d.reseal === "function");
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
async function testResealValidationAndStore() {
|
|
938
|
+
var tmpDir = _tmp();
|
|
939
|
+
await setupTestDb(tmpDir);
|
|
940
|
+
try {
|
|
941
|
+
// Missing roots are rejected at entry.
|
|
942
|
+
var threw = null;
|
|
943
|
+
try { await b.dsr.reseal({ store: { listAll: function () { return []; }, putResealed: function () {} } }); }
|
|
944
|
+
catch (e) { threw = e; }
|
|
945
|
+
check("reseal: missing root snapshots refused",
|
|
946
|
+
threw && /dsr\/bad-root/.test(threw.code));
|
|
947
|
+
|
|
948
|
+
// Bad store shape is rejected.
|
|
949
|
+
var threw2 = null;
|
|
950
|
+
try { await b.dsr.reseal({ oldRootJson: "{}", newRootJson: "{}", store: {} }); }
|
|
951
|
+
catch (e) { threw2 = e; }
|
|
952
|
+
check("reseal: store missing listAll/putResealed refused",
|
|
953
|
+
threw2 && /dsr\/bad-reseal-store/.test(threw2.code));
|
|
954
|
+
|
|
955
|
+
// A store whose rows carry no AAD-sealed cells re-seals nothing
|
|
956
|
+
// (plaintext rows pass through) — exercises the row-walk + putResealed
|
|
957
|
+
// contract without needing a full keypair rotation.
|
|
958
|
+
var keys = b.vault.getKeysJson();
|
|
959
|
+
var puts = [];
|
|
960
|
+
var plainStore = {
|
|
961
|
+
listAll: function () { return [{ id: "T1", payload: "plain-json" }]; },
|
|
962
|
+
putResealed: function (row) { puts.push(row.id); },
|
|
963
|
+
};
|
|
964
|
+
var rv = await b.dsr.reseal({ oldRootJson: keys, newRootJson: keys, store: plainStore });
|
|
965
|
+
check("reseal: returns table + resealed count", rv.table === "dsr_tickets" && rv.resealed === 0);
|
|
966
|
+
check("reseal: plaintext rows not re-persisted", puts.length === 0);
|
|
967
|
+
} finally {
|
|
968
|
+
await teardownTestDb(tmpDir);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
|
|
762
972
|
// ---- Run all ----
|
|
763
973
|
|
|
764
974
|
(async function run() {
|
|
@@ -812,4 +1022,12 @@ async function testReceiptSignerError() {
|
|
|
812
1022
|
await testReceiptNotTerminal();
|
|
813
1023
|
await testReceiptWithSigner();
|
|
814
1024
|
await testReceiptSignerError();
|
|
1025
|
+
|
|
1026
|
+
// dbTicketStore at-rest sealing + erasure purge + upgrade path
|
|
1027
|
+
await testDbStoreSealsAtRest();
|
|
1028
|
+
await testDbStoreErasurePurgesPriorTickets();
|
|
1029
|
+
await testDbStoreUpgradePath();
|
|
1030
|
+
// AAD_ROTATION descriptor + reseal
|
|
1031
|
+
testAadRotationDescriptor();
|
|
1032
|
+
await testResealValidationAndStore();
|
|
815
1033
|
})().catch(function (e) { console.error(e); process.exit(1); });
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* b.cryptoField.eraseRow posture-driven VACUUM cascade.
|
|
4
|
+
*
|
|
5
|
+
* The advertised guarantee (crypto-field.js eraseRow docstring +
|
|
6
|
+
* compliance.js POSTURE_DEFAULTS): under a regulatory posture whose
|
|
7
|
+
* requireVacuumAfterErase floor is true, calling eraseRow on a sealed-
|
|
8
|
+
* column table automatically runs b.db.vacuumAfterErase({ mode:"full" })
|
|
9
|
+
* so SQLite's freed B-tree pages don't keep sealed-column ciphertext
|
|
10
|
+
* recoverable from a forensic disk image — the residual that would
|
|
11
|
+
* defeat the right-to-erasure the regime guarantees.
|
|
12
|
+
*
|
|
13
|
+
* This drives that guarantee end-to-end on a REAL encrypted-at-rest DB
|
|
14
|
+
* (wrapped vault, sealed db.enc, signed audit chain — the production
|
|
15
|
+
* path via setupTestDb). The vacuum entry point is wrapped with a
|
|
16
|
+
* call-through spy: the real VACUUM still executes against the real
|
|
17
|
+
* tmpfs SQLite, AND the framework's own `db.vacuum_after_erase` row is
|
|
18
|
+
* read back out of the signed audit chain as the observable side
|
|
19
|
+
* effect.
|
|
20
|
+
*
|
|
21
|
+
* gdpr / hipaa MUST trigger the vacuum (positive control).
|
|
22
|
+
*
|
|
23
|
+
* uk-gdpr / appi-jp / pdpa-sg are all in
|
|
24
|
+
* b.compliance.CROSS_BORDER_REGULATED_POSTURES — the same cross-border
|
|
25
|
+
* privacy regimes whose Art. 17-equivalent right-to-erasure applies
|
|
26
|
+
* identically (uk-gdpr is literally "retained EU GDPR"), so they MUST
|
|
27
|
+
* trigger the same mandatory residue VACUUM. An invariant below asserts
|
|
28
|
+
* that EVERY cross-border regulated posture carries the
|
|
29
|
+
* requireVacuumAfterErase floor, so a newly-added regime can never
|
|
30
|
+
* silently drop the residue cleanup the right-to-erasure depends on.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
var fs = require("node:fs");
|
|
34
|
+
var os = require("node:os");
|
|
35
|
+
var path = require("node:path");
|
|
36
|
+
|
|
37
|
+
var helpers = require("../helpers");
|
|
38
|
+
var b = helpers.b;
|
|
39
|
+
var check = helpers.check;
|
|
40
|
+
var setupTestDb = require("../helpers/db").setupTestDb;
|
|
41
|
+
var teardownTestDb = require("../helpers/db").teardownTestDb;
|
|
42
|
+
|
|
43
|
+
var SEALED_SCHEMA = [
|
|
44
|
+
{
|
|
45
|
+
name: "patients",
|
|
46
|
+
columns: {
|
|
47
|
+
_id: "TEXT PRIMARY KEY",
|
|
48
|
+
ssn: "TEXT",
|
|
49
|
+
ssnHash: "TEXT",
|
|
50
|
+
name: "TEXT",
|
|
51
|
+
},
|
|
52
|
+
indexes: ["ssnHash"],
|
|
53
|
+
sealedFields: ["ssn", "name"],
|
|
54
|
+
derivedHashes: { ssnHash: { from: "ssn" } },
|
|
55
|
+
// AAD-bound envelope so the table satisfies the HIPAA
|
|
56
|
+
// sealEnvelopeFloor:"aad" gate as well as the no-floor postures.
|
|
57
|
+
aad: true,
|
|
58
|
+
rowIdField: "_id",
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
// Drive the full erase under one posture against a real encrypted DB
|
|
63
|
+
// and report whether the posture-cascade actually fired the VACUUM.
|
|
64
|
+
// Returns { vacuumCalled, modeFull, auditRowSeen }.
|
|
65
|
+
async function eraseUnderPosture(posture) {
|
|
66
|
+
var dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-erasevac-"));
|
|
67
|
+
await setupTestDb(dataDir, SEALED_SCHEMA);
|
|
68
|
+
|
|
69
|
+
// Pin the posture. The cascade calls cryptoField.applyPosture(posture)
|
|
70
|
+
// (records _activePosture) + db.applyPosture(posture). This is the
|
|
71
|
+
// exact boot wiring an operator running under `posture` gets.
|
|
72
|
+
b.compliance._resetForTest();
|
|
73
|
+
b.compliance.set(posture);
|
|
74
|
+
check("compliance.current cascaded -> " + posture,
|
|
75
|
+
b.compliance.current() === posture);
|
|
76
|
+
check("cryptoField active posture cascaded -> " + posture,
|
|
77
|
+
b.cryptoField.getActivePosture() === posture);
|
|
78
|
+
|
|
79
|
+
// The sealed table is already in the field-crypto registry —
|
|
80
|
+
// setupTestDb's db.init registered it from the schema (sealedFields /
|
|
81
|
+
// derivedHashes / aad) — so eraseRow has a schema to tombstone against.
|
|
82
|
+
|
|
83
|
+
// Persist a real row through the real field-crypto + db path
|
|
84
|
+
// (insertOne seals the sealed columns itself), then delete it so the
|
|
85
|
+
// upcoming erase + vacuum has freed B-tree pages to reclaim. The
|
|
86
|
+
// residue the VACUUM clears is exactly those freed pages — they still
|
|
87
|
+
// hold the sealed-column ciphertext until a full rewrite.
|
|
88
|
+
var inserted = b.db.from("patients").insertOne({
|
|
89
|
+
ssn: "123-45-6789",
|
|
90
|
+
name: "Alice Patient",
|
|
91
|
+
});
|
|
92
|
+
b.db.from("patients").where({ _id: inserted._id }).deleteOne();
|
|
93
|
+
|
|
94
|
+
// A sealed-shaped in-memory row for eraseRow to tombstone (eraseRow
|
|
95
|
+
// operates on a row object, NULLing its sealed + derived columns).
|
|
96
|
+
var sealed = b.cryptoField.sealRow("patients", {
|
|
97
|
+
_id: inserted._id,
|
|
98
|
+
ssn: "123-45-6789",
|
|
99
|
+
name: "Alice Patient",
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Wrap the real vacuum entry point with a call-through spy. eraseRow
|
|
103
|
+
// reaches it via require("./db").vacuumAfterErase, which is the same
|
|
104
|
+
// object as b.db — so the wrapper is what eraseRow invokes, and the
|
|
105
|
+
// real VACUUM still runs against the encrypted tmpfs SQLite.
|
|
106
|
+
var realVacuum = b.db.vacuumAfterErase;
|
|
107
|
+
var spy = { called: false, mode: null, threw: null };
|
|
108
|
+
b.db.vacuumAfterErase = function (opts) {
|
|
109
|
+
spy.called = true;
|
|
110
|
+
spy.mode = opts && opts.mode;
|
|
111
|
+
try {
|
|
112
|
+
return realVacuum.call(b.db, opts); // real VACUUM executes
|
|
113
|
+
} catch (e) {
|
|
114
|
+
spy.threw = e;
|
|
115
|
+
throw e;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
var auditRowSeen = false;
|
|
120
|
+
try {
|
|
121
|
+
var sinceMs = Date.now();
|
|
122
|
+
// The advertised cascade: erasing a sealed row under a vacuum-floor
|
|
123
|
+
// posture auto-runs b.db.vacuumAfterErase({ mode:"full" }).
|
|
124
|
+
var erased = b.cryptoField.eraseRow("patients", sealed);
|
|
125
|
+
check("eraseRow NULLed the sealed ssn (" + posture + ")", erased.ssn === null);
|
|
126
|
+
check("eraseRow NULLed the derived ssnHash (" + posture + ")", erased.ssnHash === null);
|
|
127
|
+
|
|
128
|
+
// Real side effect: the framework's own audit row for the vacuum
|
|
129
|
+
// must be present in the signed chain when (and only when) the
|
|
130
|
+
// posture actually triggered it.
|
|
131
|
+
await b.audit.flush();
|
|
132
|
+
var rows = await b.audit.query({
|
|
133
|
+
action: "db.vacuum_after_erase",
|
|
134
|
+
from: sinceMs - 1000,
|
|
135
|
+
limit: 50,
|
|
136
|
+
});
|
|
137
|
+
// The audit chain stores metadata as a JSON string column; parse
|
|
138
|
+
// it back to confirm the vacuum row carried mode:"full".
|
|
139
|
+
auditRowSeen = Array.isArray(rows) && rows.some(function (r) {
|
|
140
|
+
if (!r || r.metadata == null) return false;
|
|
141
|
+
var md = r.metadata;
|
|
142
|
+
if (typeof md === "string") { try { md = JSON.parse(md); } catch (_e) { return false; } }
|
|
143
|
+
return md && md.mode === "full";
|
|
144
|
+
});
|
|
145
|
+
} finally {
|
|
146
|
+
b.db.vacuumAfterErase = realVacuum;
|
|
147
|
+
await teardownTestDb(dataDir);
|
|
148
|
+
b.compliance._resetForTest();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
vacuumCalled: spy.called,
|
|
153
|
+
modeFull: spy.mode === "full",
|
|
154
|
+
vacuumThrew: spy.threw,
|
|
155
|
+
auditRowSeen: auditRowSeen,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function run() {
|
|
160
|
+
// ---- Positive control: gdpr + hipaa MUST trigger the full VACUUM,
|
|
161
|
+
// the real VACUUM must execute, and the audit row must land. This is
|
|
162
|
+
// the genuine end-to-end proof of the advertised guarantee.
|
|
163
|
+
var gdpr = await eraseUnderPosture("gdpr");
|
|
164
|
+
check("gdpr eraseRow TRIGGERED vacuumAfterErase", gdpr.vacuumCalled === true);
|
|
165
|
+
check("gdpr vacuum ran with mode:'full'", gdpr.modeFull === true);
|
|
166
|
+
check("gdpr real VACUUM executed without throwing", gdpr.vacuumThrew === null);
|
|
167
|
+
check("gdpr db.vacuum_after_erase landed in the signed audit chain",
|
|
168
|
+
gdpr.auditRowSeen === true);
|
|
169
|
+
|
|
170
|
+
var hipaa = await eraseUnderPosture("hipaa");
|
|
171
|
+
check("hipaa eraseRow TRIGGERED vacuumAfterErase", hipaa.vacuumCalled === true);
|
|
172
|
+
check("hipaa vacuum ran with mode:'full'", hipaa.modeFull === true);
|
|
173
|
+
check("hipaa real VACUUM executed without throwing", hipaa.vacuumThrew === null);
|
|
174
|
+
check("hipaa db.vacuum_after_erase landed in the signed audit chain",
|
|
175
|
+
hipaa.auditRowSeen === true);
|
|
176
|
+
|
|
177
|
+
// ---- Invariant (recurrence guard): EVERY cross-border regulated posture
|
|
178
|
+
// must carry the requireVacuumAfterErase floor. A regime added to
|
|
179
|
+
// CROSS_BORDER_REGULATED_POSTURES without a POSTURE_DEFAULTS entry would
|
|
180
|
+
// silently skip the mandatory residue cleanup — this catches that drift.
|
|
181
|
+
var crossBorder = b.compliance.CROSS_BORDER_REGULATED_POSTURES.slice();
|
|
182
|
+
check("there is at least one cross-border regulated posture to check",
|
|
183
|
+
crossBorder.length > 0);
|
|
184
|
+
for (var k = 0; k < crossBorder.length; k += 1) {
|
|
185
|
+
check(crossBorder[k] + " carries the requireVacuumAfterErase floor",
|
|
186
|
+
b.compliance.postureDefault(crossBorder[k], "requireVacuumAfterErase") === true);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ---- End-to-end: erasing under each cross-border posture triggers the
|
|
190
|
+
// same mandatory residue VACUUM the positive controls (gdpr/hipaa) do.
|
|
191
|
+
var sample = ["uk-gdpr", "appi-jp", "pdpa-sg"];
|
|
192
|
+
for (var i = 0; i < sample.length; i += 1) {
|
|
193
|
+
var p = sample[i];
|
|
194
|
+
var r = await eraseUnderPosture(p);
|
|
195
|
+
check(p + " eraseRow TRIGGERED the mandatory residue VACUUM",
|
|
196
|
+
r.vacuumCalled === true);
|
|
197
|
+
check(p + " vacuum ran with mode:'full'", r.modeFull === true);
|
|
198
|
+
check(p + " db.vacuum_after_erase landed in the signed audit chain",
|
|
199
|
+
r.auditRowSeen === true);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
module.exports = { run: run };
|
|
204
|
+
|
|
205
|
+
if (require.main === module) {
|
|
206
|
+
run().then(
|
|
207
|
+
function () { console.log("[erase-posture-vacuum] OK"); },
|
|
208
|
+
function (e) { console.error(e.stack || e); process.exit(1); }
|
|
209
|
+
);
|
|
210
|
+
}
|
|
@@ -26,7 +26,10 @@ function _instrumentingDriver(opts) {
|
|
|
26
26
|
opts.failOnce = null;
|
|
27
27
|
throw e;
|
|
28
28
|
}
|
|
29
|
-
|
|
29
|
+
// assertRoleHardening composes the pg_roles scan through b.sql, which
|
|
30
|
+
// quotes the projected identifier (`SELECT "rolname" FROM pg_roles
|
|
31
|
+
// ORDER BY "rolname" ASC`). Match the quoted-or-bare form.
|
|
32
|
+
if (/^SELECT\s+"?rolname"?\s+FROM\s+pg_roles\b/i.test(sql)) {
|
|
30
33
|
return { rows: rolesRow.map(function (n) { return { rolname: n }; }), rowCount: rolesRow.length };
|
|
31
34
|
}
|
|
32
35
|
if (/^SELECT 1$/i.test(sql)) return { rows: [{ n: 1 }], rowCount: 1 };
|
|
@@ -172,12 +172,58 @@ async function run() {
|
|
|
172
172
|
async function () { await migrate.up(); },
|
|
173
173
|
/externaldb-migrate\/missing-up/);
|
|
174
174
|
|
|
175
|
-
// ---------- Cleanup ----------
|
|
176
|
-
|
|
177
175
|
driver._close();
|
|
178
176
|
b.externalDb._resetForTest();
|
|
179
177
|
fs.rmSync(dataDir, { recursive: true, force: true });
|
|
180
178
|
fs.rmSync(migDir, { recursive: true, force: true });
|
|
179
|
+
|
|
180
|
+
// ---------- Residency: framework tracking writes survive a
|
|
181
|
+
// residency-tagged backend under a cross-border regulated posture ----
|
|
182
|
+
// The migrate runner's own tracking / history / lock INSERTs are
|
|
183
|
+
// region-neutral metadata and carry the "unrestricted" tag; without
|
|
184
|
+
// that exemption the per-row residency write gate would refuse every
|
|
185
|
+
// migration with RESIDENCY_GATE_REQUIRED on an eu-tagged backend
|
|
186
|
+
// under gdpr.
|
|
187
|
+
var resDataDir = _tempDir("blamejs-xmig-res-data");
|
|
188
|
+
var resMigDir = _tempDir("blamejs-xmig-res-mig");
|
|
189
|
+
var resDriver = _makeSqliteDriver(path.join(resDataDir, "fake-pg.db"));
|
|
190
|
+
b.externalDb._resetForTest();
|
|
191
|
+
b.externalDb.init({
|
|
192
|
+
backends: {
|
|
193
|
+
main: {
|
|
194
|
+
connect: resDriver.connect, query: resDriver.query,
|
|
195
|
+
close: resDriver.close, dialect: "postgres", residencyTag: "eu",
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
// DDL-only migration — the only DML in the transaction is the
|
|
200
|
+
// framework's tracking + history INSERTs (operator DML would itself
|
|
201
|
+
// be gated, which is correct, so the migration body stays DDL).
|
|
202
|
+
_writeMigration(resMigDir, "0001-ddl-only.js",
|
|
203
|
+
'module.exports = {\n' +
|
|
204
|
+
' description: "create widget",\n' +
|
|
205
|
+
' up: async function (xdb) { await xdb.query("CREATE TABLE widget (id INTEGER PRIMARY KEY)", []); },\n' +
|
|
206
|
+
' down: async function (xdb) { await xdb.query("DROP TABLE widget", []); },\n' +
|
|
207
|
+
'};\n');
|
|
208
|
+
var resMigrate = b.externalDb.migrate.create({ dir: resMigDir });
|
|
209
|
+
b.compliance.clear();
|
|
210
|
+
b.compliance.set("gdpr");
|
|
211
|
+
try {
|
|
212
|
+
var resR = await resMigrate.up();
|
|
213
|
+
check("migrate up() succeeds on eu backend under gdpr (framework writes exempt)",
|
|
214
|
+
resR.applied.length === 1 && resR.applied[0] === "0001-ddl-only.js");
|
|
215
|
+
// The tracking row landed — re-running is idempotent (proves the
|
|
216
|
+
// framework INSERT into the tracking table actually committed).
|
|
217
|
+
var resR2 = await resMigrate.up();
|
|
218
|
+
check("migrate is idempotent after the tracked apply (tracking INSERT committed)",
|
|
219
|
+
resR2.applied.length === 0 && resR2.skipped.length === 1);
|
|
220
|
+
} finally {
|
|
221
|
+
b.compliance.clear();
|
|
222
|
+
resDriver._close();
|
|
223
|
+
b.externalDb._resetForTest();
|
|
224
|
+
fs.rmSync(resDataDir, { recursive: true, force: true });
|
|
225
|
+
fs.rmSync(resMigDir, { recursive: true, force: true });
|
|
226
|
+
}
|
|
181
227
|
}
|
|
182
228
|
|
|
183
229
|
module.exports = { run: run };
|