@blamejs/blamejs-shop 0.4.31 → 0.4.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2 -0
- package/lib/asset-manifest.json +1 -1
- package/lib/vendor/MANIFEST.json +392 -278
- package/lib/vendor/blamejs/.github/workflows/ci.yml +34 -3
- package/lib/vendor/blamejs/.github/workflows/npm-publish.yml +21 -4
- package/lib/vendor/blamejs/.gitignore +6 -0
- package/lib/vendor/blamejs/CHANGELOG.md +26 -0
- package/lib/vendor/blamejs/MIGRATING.md +43 -0
- package/lib/vendor/blamejs/README.md +8 -6
- package/lib/vendor/blamejs/SECURITY.md +19 -3
- package/lib/vendor/blamejs/api-snapshot.json +2190 -664
- package/lib/vendor/blamejs/docker/caddy/localstack.Caddyfile +19 -0
- package/lib/vendor/blamejs/docker/init/generate-certs.sh +1 -1
- package/lib/vendor/blamejs/docker/otel/config.yaml +42 -0
- package/lib/vendor/blamejs/docker/otel/export/.gitkeep +0 -0
- package/lib/vendor/blamejs/docker/postgres/initdb/10-replication.sh +15 -0
- package/lib/vendor/blamejs/docker/postgres/replica-entrypoint.sh +38 -0
- package/lib/vendor/blamejs/docker/toxiproxy/toxiproxy.json +14 -0
- package/lib/vendor/blamejs/docker-compose.test.yml +209 -0
- package/lib/vendor/blamejs/examples/wiki/lib/page-generator.js +132 -0
- package/lib/vendor/blamejs/examples/wiki/lib/source-comment-block-validator.js +221 -61
- package/lib/vendor/blamejs/examples/wiki/lib/source-doc-parser.js +144 -9
- package/lib/vendor/blamejs/examples/wiki/test/e2e.js +99 -0
- package/lib/vendor/blamejs/fuzz/guard-sql.fuzz.js +36 -0
- package/lib/vendor/blamejs/index.js +4 -0
- package/lib/vendor/blamejs/lib/agent-envelope-mac.js +104 -0
- package/lib/vendor/blamejs/lib/agent-event-bus.js +105 -4
- package/lib/vendor/blamejs/lib/agent-posture-chain.js +8 -42
- package/lib/vendor/blamejs/lib/ai-content-detect.js +9 -10
- package/lib/vendor/blamejs/lib/api-key.js +158 -77
- package/lib/vendor/blamejs/lib/atomic-file.js +62 -4
- package/lib/vendor/blamejs/lib/audit-chain.js +47 -11
- package/lib/vendor/blamejs/lib/audit-sign.js +77 -2
- package/lib/vendor/blamejs/lib/audit-tools.js +79 -51
- package/lib/vendor/blamejs/lib/audit.js +259 -123
- package/lib/vendor/blamejs/lib/auth/oauth.js +53 -9
- package/lib/vendor/blamejs/lib/auth/openid-federation.js +108 -47
- package/lib/vendor/blamejs/lib/auth/saml.js +6 -8
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc.js +31 -5
- package/lib/vendor/blamejs/lib/backup/index.js +45 -10
- package/lib/vendor/blamejs/lib/break-glass.js +355 -147
- package/lib/vendor/blamejs/lib/cache.js +174 -105
- package/lib/vendor/blamejs/lib/chain-writer.js +38 -16
- package/lib/vendor/blamejs/lib/cli.js +19 -14
- package/lib/vendor/blamejs/lib/cluster-provider-db.js +130 -104
- package/lib/vendor/blamejs/lib/cluster-storage.js +119 -22
- package/lib/vendor/blamejs/lib/cluster.js +119 -71
- package/lib/vendor/blamejs/lib/codepoint-class.js +23 -0
- package/lib/vendor/blamejs/lib/compliance.js +206 -4
- package/lib/vendor/blamejs/lib/consent.js +82 -29
- package/lib/vendor/blamejs/lib/constants.js +27 -11
- package/lib/vendor/blamejs/lib/crypto-field.js +916 -156
- package/lib/vendor/blamejs/lib/db-declare-row-policy.js +35 -22
- package/lib/vendor/blamejs/lib/db-file-lifecycle.js +3 -2
- package/lib/vendor/blamejs/lib/db-query.js +882 -260
- package/lib/vendor/blamejs/lib/db-schema.js +228 -44
- package/lib/vendor/blamejs/lib/db.js +249 -99
- package/lib/vendor/blamejs/lib/dsr.js +385 -55
- package/lib/vendor/blamejs/lib/error-page.js +14 -1
- package/lib/vendor/blamejs/lib/external-db-migrate.js +239 -137
- package/lib/vendor/blamejs/lib/external-db.js +549 -34
- package/lib/vendor/blamejs/lib/file-upload.js +52 -7
- package/lib/vendor/blamejs/lib/framework-error.js +20 -1
- package/lib/vendor/blamejs/lib/framework-files.js +73 -0
- package/lib/vendor/blamejs/lib/framework-schema.js +695 -394
- package/lib/vendor/blamejs/lib/gate-contract.js +659 -1
- package/lib/vendor/blamejs/lib/guard-agent-registry.js +26 -44
- package/lib/vendor/blamejs/lib/guard-all.js +1 -0
- package/lib/vendor/blamejs/lib/guard-auth.js +42 -112
- package/lib/vendor/blamejs/lib/guard-cidr.js +33 -154
- package/lib/vendor/blamejs/lib/guard-csv.js +46 -113
- package/lib/vendor/blamejs/lib/guard-domain.js +34 -157
- package/lib/vendor/blamejs/lib/guard-dsn.js +27 -43
- package/lib/vendor/blamejs/lib/guard-email.js +47 -69
- package/lib/vendor/blamejs/lib/guard-envelope.js +19 -32
- package/lib/vendor/blamejs/lib/guard-event-bus-payload.js +24 -42
- package/lib/vendor/blamejs/lib/guard-event-bus-topic.js +25 -43
- package/lib/vendor/blamejs/lib/guard-filename.js +42 -106
- package/lib/vendor/blamejs/lib/guard-graphql.js +42 -123
- package/lib/vendor/blamejs/lib/guard-html.js +53 -108
- package/lib/vendor/blamejs/lib/guard-idempotency-key.js +24 -42
- package/lib/vendor/blamejs/lib/guard-image.js +46 -103
- package/lib/vendor/blamejs/lib/guard-imap-command.js +18 -32
- package/lib/vendor/blamejs/lib/guard-jmap.js +16 -30
- package/lib/vendor/blamejs/lib/guard-json.js +38 -108
- package/lib/vendor/blamejs/lib/guard-jsonpath.js +38 -171
- package/lib/vendor/blamejs/lib/guard-jwt.js +49 -179
- package/lib/vendor/blamejs/lib/guard-list-id.js +25 -41
- package/lib/vendor/blamejs/lib/guard-list-unsubscribe.js +27 -43
- package/lib/vendor/blamejs/lib/guard-mail-compose.js +24 -42
- package/lib/vendor/blamejs/lib/guard-mail-move.js +26 -44
- package/lib/vendor/blamejs/lib/guard-mail-query.js +28 -46
- package/lib/vendor/blamejs/lib/guard-mail-reply.js +24 -42
- package/lib/vendor/blamejs/lib/guard-mail-sieve.js +24 -42
- package/lib/vendor/blamejs/lib/guard-managesieve-command.js +17 -31
- package/lib/vendor/blamejs/lib/guard-markdown.js +37 -104
- package/lib/vendor/blamejs/lib/guard-message-id.js +26 -45
- package/lib/vendor/blamejs/lib/guard-mime.js +39 -151
- package/lib/vendor/blamejs/lib/guard-oauth.js +54 -135
- package/lib/vendor/blamejs/lib/guard-pdf.js +45 -101
- package/lib/vendor/blamejs/lib/guard-pop3-command.js +21 -31
- package/lib/vendor/blamejs/lib/guard-posture-chain.js +24 -42
- package/lib/vendor/blamejs/lib/guard-regex.js +33 -107
- package/lib/vendor/blamejs/lib/guard-saga-config.js +24 -42
- package/lib/vendor/blamejs/lib/guard-shell.js +42 -172
- package/lib/vendor/blamejs/lib/guard-smtp-command.js +48 -54
- package/lib/vendor/blamejs/lib/guard-snapshot-envelope.js +24 -42
- package/lib/vendor/blamejs/lib/guard-sql.js +1491 -0
- package/lib/vendor/blamejs/lib/guard-stream-args.js +24 -43
- package/lib/vendor/blamejs/lib/guard-svg.js +47 -65
- package/lib/vendor/blamejs/lib/guard-template.js +35 -172
- package/lib/vendor/blamejs/lib/guard-tenant-id.js +26 -45
- package/lib/vendor/blamejs/lib/guard-time.js +32 -154
- package/lib/vendor/blamejs/lib/guard-trace-context.js +25 -44
- package/lib/vendor/blamejs/lib/guard-uuid.js +32 -153
- package/lib/vendor/blamejs/lib/guard-xml.js +38 -113
- package/lib/vendor/blamejs/lib/guard-yaml.js +51 -163
- package/lib/vendor/blamejs/lib/http-client.js +37 -9
- package/lib/vendor/blamejs/lib/inbox.js +120 -107
- package/lib/vendor/blamejs/lib/legal-hold.js +121 -50
- package/lib/vendor/blamejs/lib/log-stream-cloudwatch.js +47 -31
- package/lib/vendor/blamejs/lib/log-stream-otlp.js +32 -18
- package/lib/vendor/blamejs/lib/mail-auth.js +236 -0
- package/lib/vendor/blamejs/lib/mail-crypto-smime.js +2 -6
- package/lib/vendor/blamejs/lib/mail-dkim.js +1 -0
- package/lib/vendor/blamejs/lib/mail-greylist.js +2 -6
- package/lib/vendor/blamejs/lib/mail-helo.js +2 -6
- package/lib/vendor/blamejs/lib/mail-journal.js +85 -64
- package/lib/vendor/blamejs/lib/mail-rbl.js +2 -6
- package/lib/vendor/blamejs/lib/mail-scan.js +2 -6
- package/lib/vendor/blamejs/lib/mail-server-jmap.js +117 -12
- package/lib/vendor/blamejs/lib/mail-server-mx.js +276 -7
- package/lib/vendor/blamejs/lib/mail-spam-score.js +2 -6
- package/lib/vendor/blamejs/lib/mail-store.js +293 -154
- package/lib/vendor/blamejs/lib/mail.js +8 -4
- package/lib/vendor/blamejs/lib/middleware/body-parser.js +71 -25
- package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +19 -8
- package/lib/vendor/blamejs/lib/middleware/dpop.js +10 -1
- package/lib/vendor/blamejs/lib/middleware/fetch-metadata.js +17 -7
- package/lib/vendor/blamejs/lib/middleware/idempotency-key.js +75 -51
- package/lib/vendor/blamejs/lib/middleware/rate-limit.js +102 -32
- package/lib/vendor/blamejs/lib/middleware/security-headers.js +21 -5
- package/lib/vendor/blamejs/lib/migrations.js +108 -66
- package/lib/vendor/blamejs/lib/network-heartbeat.js +7 -0
- package/lib/vendor/blamejs/lib/network-proxy.js +24 -1
- package/lib/vendor/blamejs/lib/nonce-store.js +31 -9
- package/lib/vendor/blamejs/lib/object-store/azure-blob-bucket-ops.js +9 -4
- package/lib/vendor/blamejs/lib/object-store/azure-blob.js +57 -3
- package/lib/vendor/blamejs/lib/object-store/gcs.js +4 -1
- package/lib/vendor/blamejs/lib/object-store/sigv4-bucket-ops.js +5 -2
- package/lib/vendor/blamejs/lib/object-store/sigv4.js +38 -6
- package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +9 -1
- package/lib/vendor/blamejs/lib/observability.js +124 -0
- package/lib/vendor/blamejs/lib/otel-export.js +12 -3
- package/lib/vendor/blamejs/lib/outbox.js +184 -83
- package/lib/vendor/blamejs/lib/parsers/safe-xml.js +47 -7
- package/lib/vendor/blamejs/lib/pqc-agent.js +44 -0
- package/lib/vendor/blamejs/lib/pubsub-cluster.js +42 -20
- package/lib/vendor/blamejs/lib/queue-local.js +225 -140
- package/lib/vendor/blamejs/lib/queue-redis.js +9 -1
- package/lib/vendor/blamejs/lib/queue-sqs.js +6 -0
- package/lib/vendor/blamejs/lib/queue.js +7 -0
- package/lib/vendor/blamejs/lib/redact.js +68 -11
- package/lib/vendor/blamejs/lib/redis-client.js +160 -31
- package/lib/vendor/blamejs/lib/request-helpers.js +7 -0
- package/lib/vendor/blamejs/lib/retention.js +101 -40
- package/lib/vendor/blamejs/lib/router.js +212 -5
- package/lib/vendor/blamejs/lib/safe-dns.js +29 -45
- package/lib/vendor/blamejs/lib/safe-ical.js +18 -33
- package/lib/vendor/blamejs/lib/safe-icap.js +27 -43
- package/lib/vendor/blamejs/lib/safe-sieve.js +21 -40
- package/lib/vendor/blamejs/lib/safe-sql.js +212 -3
- package/lib/vendor/blamejs/lib/safe-url.js +170 -3
- package/lib/vendor/blamejs/lib/safe-vcard.js +18 -33
- package/lib/vendor/blamejs/lib/scheduler.js +35 -12
- package/lib/vendor/blamejs/lib/seeders.js +122 -74
- package/lib/vendor/blamejs/lib/session-stores.js +42 -14
- package/lib/vendor/blamejs/lib/session.js +175 -77
- package/lib/vendor/blamejs/lib/sql.js +3842 -0
- package/lib/vendor/blamejs/lib/sse.js +26 -0
- package/lib/vendor/blamejs/lib/ssrf-guard.js +151 -4
- package/lib/vendor/blamejs/lib/static.js +177 -34
- package/lib/vendor/blamejs/lib/subject.js +96 -49
- package/lib/vendor/blamejs/lib/vault/index.js +3 -2
- package/lib/vendor/blamejs/lib/vault/passphrase-ops.js +3 -2
- package/lib/vendor/blamejs/lib/vault/rotate.js +168 -108
- package/lib/vendor/blamejs/lib/vault-aad.js +6 -0
- package/lib/vendor/blamejs/lib/vendor-data.js +2 -0
- package/lib/vendor/blamejs/lib/websocket.js +35 -5
- package/lib/vendor/blamejs/lib/worker-pool.js +11 -0
- package/lib/vendor/blamejs/package.json +2 -2
- package/lib/vendor/blamejs/release-notes/v0.14.x.json +1503 -0
- package/lib/vendor/blamejs/release-notes/v0.15.0.json +77 -0
- package/lib/vendor/blamejs/release-notes/v0.15.1.json +22 -0
- package/lib/vendor/blamejs/release-notes/v0.15.2.json +22 -0
- package/lib/vendor/blamejs/release-notes/v0.15.3.json +39 -0
- package/lib/vendor/blamejs/release-notes/v0.15.4.json +39 -0
- package/lib/vendor/blamejs/release-notes/v0.15.5.json +22 -0
- package/lib/vendor/blamejs/release-notes/v0.15.6.json +59 -0
- package/lib/vendor/blamejs/scripts/check-services.js +21 -0
- package/lib/vendor/blamejs/scripts/gen-migrating.js +51 -0
- package/lib/vendor/blamejs/scripts/release.js +398 -38
- package/lib/vendor/blamejs/test/00-primitives.js +117 -0
- package/lib/vendor/blamejs/test/10-state.js +140 -14
- package/lib/vendor/blamejs/test/20-db.js +65 -2
- package/lib/vendor/blamejs/test/helpers/db.js +9 -0
- package/lib/vendor/blamejs/test/helpers/drivers.js +27 -15
- package/lib/vendor/blamejs/test/helpers/services.js +21 -0
- package/lib/vendor/blamejs/test/integration/audit-actor-binding-pg.test.js +246 -0
- package/lib/vendor/blamejs/test/integration/audit-chain-external-db.test.js +517 -0
- package/lib/vendor/blamejs/test/integration/audit-stack-mysql.test.js +639 -0
- package/lib/vendor/blamejs/test/integration/audit-stack-postgres.test.js +832 -0
- package/lib/vendor/blamejs/test/integration/backup-restore-objectstore.test.js +453 -0
- package/lib/vendor/blamejs/test/integration/data-layer-cluster-mysql.test.js +649 -0
- package/lib/vendor/blamejs/test/integration/data-layer-cluster-pg.test.js +770 -0
- package/lib/vendor/blamejs/test/integration/data-layer-mysql-privacy.test.js +630 -0
- package/lib/vendor/blamejs/test/integration/data-layer-mysql.test.js +610 -0
- package/lib/vendor/blamejs/test/integration/data-layer-pg.test.js +577 -0
- package/lib/vendor/blamejs/test/integration/data-layer-postgres.test.js +771 -0
- package/lib/vendor/blamejs/test/integration/db-layer-mysql.test.js +549 -0
- package/lib/vendor/blamejs/test/integration/db-layer-postgres.test.js +598 -0
- package/lib/vendor/blamejs/test/integration/distributed-scheduler-fencing-pg.test.js +602 -0
- package/lib/vendor/blamejs/test/integration/external-db-postgres.test.js +576 -0
- package/lib/vendor/blamejs/test/integration/framework-schema-mysql.test.js +353 -0
- package/lib/vendor/blamejs/test/integration/log-stream-cloudwatch.test.js +224 -0
- package/lib/vendor/blamejs/test/integration/mail-crypto-smime.test.js +142 -17
- package/lib/vendor/blamejs/test/integration/network-heartbeat.test.js +25 -10
- package/lib/vendor/blamejs/test/integration/object-store-azure.test.js +101 -0
- package/lib/vendor/blamejs/test/integration/object-store-gcs.test.js +239 -0
- package/lib/vendor/blamejs/test/integration/object-store-sigv4.test.js +35 -16
- package/lib/vendor/blamejs/test/integration/object-store-worm-lock.test.js +291 -0
- package/lib/vendor/blamejs/test/integration/pubsub.test.js +14 -0
- package/lib/vendor/blamejs/test/integration/queue-sqs.test.js +322 -0
- package/lib/vendor/blamejs/test/integration/redis-reconnect-toxiproxy.test.js +300 -0
- package/lib/vendor/blamejs/test/integration/sql-fts5-catalog-sqlite.test.js +154 -0
- package/lib/vendor/blamejs/test/integration/tls-classical-downgrade-audit.test.js +71 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-event-bus.test.js +175 -12
- package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file-exclusive-temp.test.js +216 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-checkpoint-false-rollback.test.js +203 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-query-self-log.test.js +126 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-safeemit-redacts-secrets.test.js +196 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-signing-key-rotation.test.js +197 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-verifybundle-tamper.test.js +209 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/azure-blob-key-encoding.test.js +121 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/backup-residency-posture.test.js +168 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/backup-scheduletest-drill.test.js +318 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/break-glass.test.js +233 -7
- package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +1120 -14
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance.test.js +229 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-derived-hash.test.js +24 -7
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-dual-read-migrate.test.js +165 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-per-row-key.test.js +350 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-unseal-rate-cap.test.js +27 -9
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-upgrade-dialect.test.js +76 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-interop-oracles.test.js +392 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csrf-protect.test.js +159 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-column-gate.test.js +180 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/db-query-cross-schema.test.js +5 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/db-query-sealed-field-in.test.js +101 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-raw-residency-gate.test.js +128 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-schema-drift.test.js +38 -5
- package/lib/vendor/blamejs/test/layer-0-primitives/db-schema-reconcile-emittable.test.js +127 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-stream-and-payload-shape.test.js +267 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-worm.test.js +150 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/defineguard-default-gate-posture-caps.test.js +30 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dpop-middleware-replaystore-required.test.js +46 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dsr.test.js +218 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/erase-posture-vacuum.test.js +210 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-hardening.test.js +4 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-migrate.test.js +48 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/federation-vc-suite.test.js +237 -5
- package/lib/vendor/blamejs/test/layer-0-primitives/fetch-metadata.test.js +20 -9
- package/lib/vendor/blamejs/test/layer-0-primitives/file-upload-content-safety-skip-audit.test.js +193 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-csv.test.js +90 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-client-stream.test.js +85 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/idempotency-key.test.js +10 -6
- package/lib/vendor/blamejs/test/layer-0-primitives/inbox.test.js +15 -4
- package/lib/vendor/blamejs/test/layer-0-primitives/legal-hold.test.js +146 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-auth.test.js +189 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-journal.test.js +3 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-jmap.test.js +123 -4
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-mx.test.js +207 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-store.test.js +74 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/oauth-callback.test.js +43 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/otel-export.test.js +133 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/otlp-attr-redaction.test.js +101 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/outbox-inflight-reaper.test.js +136 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/parsers-standalone.test.js +83 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/passkey-real-vectors.test.js +429 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/pqc-agent-curve.test.js +21 -11
- package/lib/vendor/blamejs/test/layer-0-primitives/queue-byo-db.test.js +40 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/redact-dlp.test.js +83 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/redis-client.test.js +113 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/retention-dryrun-no-vacuum.test.js +99 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/router-use-path-scope.test.js +255 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-url-canonicalize.test.js +309 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-xml.test.js +143 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/saml-subjectconfirmation-notonorafter.test.js +287 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc-ecdsa-p1363.test.js +79 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc.test.js +50 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/security-headers.test.js +31 -4
- package/lib/vendor/blamejs/test/layer-0-primitives/session-extensions.test.js +45 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sigv4-bucket-ops.test.js +49 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sql.test.js +595 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sse-backpressure.test.js +91 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ssrf-guard.test.js +69 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/static.test.js +194 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/websocket-extension-header.test.js +88 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/worker-pool-recycle-race.test.js +66 -0
- package/lib/vendor/blamejs/test/layer-1-state/api-key.test.js +84 -0
- package/lib/vendor/blamejs/test/layer-5-integration/external-db-residency.test.js +638 -0
- package/lib/vendor/blamejs/test/layer-5-integration/guard-host-integration.test.js +21 -0
- package/lib/vendor/blamejs/test/smoke.js +79 -21
- package/package.json +1 -1
- package/lib/vendor/blamejs/release-notes/v0.14.0.json +0 -43
- package/lib/vendor/blamejs/release-notes/v0.14.1.json +0 -60
- package/lib/vendor/blamejs/release-notes/v0.14.10.json +0 -54
- package/lib/vendor/blamejs/release-notes/v0.14.11.json +0 -72
- package/lib/vendor/blamejs/release-notes/v0.14.12.json +0 -95
- package/lib/vendor/blamejs/release-notes/v0.14.13.json +0 -52
- package/lib/vendor/blamejs/release-notes/v0.14.14.json +0 -31
- package/lib/vendor/blamejs/release-notes/v0.14.16.json +0 -45
- package/lib/vendor/blamejs/release-notes/v0.14.17.json +0 -57
- package/lib/vendor/blamejs/release-notes/v0.14.18.json +0 -127
- package/lib/vendor/blamejs/release-notes/v0.14.19.json +0 -61
- package/lib/vendor/blamejs/release-notes/v0.14.2.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.14.20.json +0 -73
- package/lib/vendor/blamejs/release-notes/v0.14.21.json +0 -98
- package/lib/vendor/blamejs/release-notes/v0.14.22.json +0 -91
- package/lib/vendor/blamejs/release-notes/v0.14.3.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.14.4.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.14.5.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.14.6.json +0 -60
- package/lib/vendor/blamejs/release-notes/v0.14.7.json +0 -77
- package/lib/vendor/blamejs/release-notes/v0.14.8.json +0 -27
- package/lib/vendor/blamejs/release-notes/v0.14.9.json +0 -40
|
@@ -120,6 +120,16 @@ var DsrError = defineClass("DsrError", { alwaysPermanent: true });
|
|
|
120
120
|
|
|
121
121
|
var audit = lazyRequire(function () { return require("./audit"); });
|
|
122
122
|
var observability = lazyRequire(function () { return require("./observability"); });
|
|
123
|
+
// cryptoField + vault lazy-required: dbTicketStore seals subject PII + the
|
|
124
|
+
// full ticket payload at rest so a GDPR Art 17 erasure leaves no
|
|
125
|
+
// decryptable copy. Lazy so the module loads in vault-less / test-tooling
|
|
126
|
+
// contexts; the seal only engages when a vault is configured.
|
|
127
|
+
var cryptoField = lazyRequire(function () { return require("./crypto-field"); });
|
|
128
|
+
var vault = lazyRequire(function () { return require("./vault"); });
|
|
129
|
+
// vault-aad supplies the AAD-cell re-seal primitive (resealRoot) the
|
|
130
|
+
// AAD_ROTATION descriptor below composes — the same one the in-tree
|
|
131
|
+
// rotation pipeline uses, so the AAD tuple has one source of truth.
|
|
132
|
+
var vaultAad = lazyRequire(function () { return require("./vault-aad"); });
|
|
123
133
|
|
|
124
134
|
var VALID_REQUEST_TYPES = Object.freeze([
|
|
125
135
|
"access", // GDPR Art. 15 / CCPA §1798.110
|
|
@@ -593,6 +603,43 @@ function create(opts) {
|
|
|
593
603
|
{ id: ticket.id, type: ticket.type, totalRows: totalRows,
|
|
594
604
|
totalDeleted: deletedTotal, anyFailed: anyFailed });
|
|
595
605
|
_emitMetric(anyFailed ? "partial" : "completed", 1, { type: ticket.type });
|
|
606
|
+
|
|
607
|
+
// Erasure-completion hook: an Art 17 erasure must not leave the
|
|
608
|
+
// subject's OWN prior DSR tickets (which carry their PII) sitting in
|
|
609
|
+
// the ticket store. When an erasure completes, purge the subject's
|
|
610
|
+
// other tickets. Skips the just-completed ticket so the receipt /
|
|
611
|
+
// audit trail for THIS erasure survives; requires the store to expose
|
|
612
|
+
// a `delete(id)` (the framework's memory + db stores do; an operator
|
|
613
|
+
// store that omits it keeps the prior behavior).
|
|
614
|
+
if (ticket.type === "erasure" && typeof store.delete === "function") {
|
|
615
|
+
try {
|
|
616
|
+
var priorTickets = await store.list({ subject: ticket.subject });
|
|
617
|
+
var purgedIds = [];
|
|
618
|
+
for (var pt = 0; pt < (priorTickets || []).length; pt++) {
|
|
619
|
+
var prior = priorTickets[pt];
|
|
620
|
+
if (!prior || prior.id === ticket.id) continue;
|
|
621
|
+
var removed = await store.delete(prior.id);
|
|
622
|
+
if (removed !== false) purgedIds.push(prior.id);
|
|
623
|
+
}
|
|
624
|
+
if (purgedIds.length > 0) {
|
|
625
|
+
_emitAudit("dsr.ticket.subject_tickets_purged", "ok", {
|
|
626
|
+
id: ticket.id,
|
|
627
|
+
type: ticket.type,
|
|
628
|
+
purgedCount: purgedIds.length,
|
|
629
|
+
purgedIds: purgedIds,
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
} catch (e) {
|
|
633
|
+
// Best-effort: a purge failure must not unwind the completed
|
|
634
|
+
// erasure. Surface it on the audit chain so operators can
|
|
635
|
+
// reconcile manually.
|
|
636
|
+
_emitAudit("dsr.ticket.subject_tickets_purge_failed", "fail", {
|
|
637
|
+
id: ticket.id,
|
|
638
|
+
type: ticket.type,
|
|
639
|
+
error: (e && e.message) || String(e),
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
}
|
|
596
643
|
return ticket;
|
|
597
644
|
}
|
|
598
645
|
|
|
@@ -878,6 +925,9 @@ function memoryTicketStore() {
|
|
|
878
925
|
}
|
|
879
926
|
byId.set(id, Object.assign({}, ticket));
|
|
880
927
|
},
|
|
928
|
+
delete: async function (id) {
|
|
929
|
+
return byId.delete(id);
|
|
930
|
+
},
|
|
881
931
|
_size: function () { return byId.size; },
|
|
882
932
|
};
|
|
883
933
|
}
|
|
@@ -918,21 +968,58 @@ function memoryTicketStore() {
|
|
|
918
968
|
// the framework's SQLite engine. The store auto-provisions a single
|
|
919
969
|
// table (default name `dsr_tickets`) with the canonical column set:
|
|
920
970
|
//
|
|
921
|
-
// id
|
|
922
|
-
// type
|
|
923
|
-
// status
|
|
924
|
-
// subject_id
|
|
925
|
-
// subject_email
|
|
926
|
-
// subject_phone
|
|
927
|
-
//
|
|
928
|
-
//
|
|
929
|
-
//
|
|
971
|
+
// id TEXT PRIMARY KEY
|
|
972
|
+
// type TEXT NOT NULL
|
|
973
|
+
// status TEXT NOT NULL
|
|
974
|
+
// subject_id TEXT -- sealed at rest when a vault is configured
|
|
975
|
+
// subject_email TEXT -- sealed at rest when a vault is configured
|
|
976
|
+
// subject_phone TEXT -- sealed at rest when a vault is configured
|
|
977
|
+
// subject_email_hash TEXT -- derived lookup hash (list-by-subject)
|
|
978
|
+
// subject_id_hash TEXT -- derived lookup hash (list-by-subject)
|
|
979
|
+
// submitted_at INTEGER NOT NULL
|
|
980
|
+
// deadline_at INTEGER NOT NULL
|
|
981
|
+
// processed_at INTEGER
|
|
930
982
|
// verification_level TEXT
|
|
931
|
-
// posture
|
|
932
|
-
// payload
|
|
983
|
+
// posture TEXT
|
|
984
|
+
// payload TEXT -- full JSON for the ticket, sealed at rest
|
|
933
985
|
//
|
|
934
|
-
//
|
|
986
|
+
// At-rest sealing: when a vault is configured, `payload`, `subject_id`,
|
|
987
|
+
// `subject_email`, and `subject_phone` are sealed via b.cryptoField before
|
|
988
|
+
// the row is written, AEAD-bound to the ticket `id` so a DB-write attacker
|
|
989
|
+
// cannot copy a sealed cell between rows. The list-by-subject query then
|
|
990
|
+
// matches on the derived `*_hash` columns (which mirror the plaintext
|
|
991
|
+
// search keys without exposing them) instead of the now-sealed plaintext
|
|
992
|
+
// columns. Without a vault the row is written as-is — the same vault-less
|
|
993
|
+
// fallback the agent-* / idempotency stores use.
|
|
994
|
+
//
|
|
995
|
+
// Indexed on subject_email_hash and status for the common list-by-subject
|
|
935
996
|
// and list-by-status queries.
|
|
997
|
+
|
|
998
|
+
// Logical table name the field-crypto schema is keyed on. cryptoField
|
|
999
|
+
// keys its seal map by this name (distinct from the operator's physical
|
|
1000
|
+
// table name) so every dbTicketStore instance shares one sealed-column
|
|
1001
|
+
// declaration regardless of which physical table it writes to.
|
|
1002
|
+
var DSR_SEAL_TABLE = "dsr_tickets";
|
|
1003
|
+
// Register the sealed-column declaration with cryptoField when it isn't
|
|
1004
|
+
// already present. Probing getSchema rather than a module-level boolean is
|
|
1005
|
+
// reset-safe: b.db._resetForTest() / clearForTest() wipes the cryptoField
|
|
1006
|
+
// schema registry, and a boolean cache would then leave _ensureDsrSealTable
|
|
1007
|
+
// short-circuiting against an empty registry (seal becomes a no-op, the
|
|
1008
|
+
// derived hashes go null, list-by-subject silently misses). registerTable
|
|
1009
|
+
// is itself idempotent, so re-registering an identical shape is harmless.
|
|
1010
|
+
function _ensureDsrSealTable() {
|
|
1011
|
+
if (cryptoField().getSchema(DSR_SEAL_TABLE)) return;
|
|
1012
|
+
cryptoField().registerTable(DSR_SEAL_TABLE, {
|
|
1013
|
+
sealedFields: ["payload", "subject_email", "subject_phone", "subject_id"],
|
|
1014
|
+
derivedHashes: {
|
|
1015
|
+
subject_email_hash: { from: "subject_email" },
|
|
1016
|
+
subject_id_hash: { from: "subject_id" },
|
|
1017
|
+
},
|
|
1018
|
+
aad: true,
|
|
1019
|
+
rowIdField: "id",
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
|
|
936
1023
|
function dbTicketStore(opts) {
|
|
937
1024
|
opts = opts || {};
|
|
938
1025
|
var db = opts.db;
|
|
@@ -952,85 +1039,232 @@ function dbTicketStore(opts) {
|
|
|
952
1039
|
(sqlErr && sqlErr.message ? sqlErr.message : String(sqlErr)));
|
|
953
1040
|
}
|
|
954
1041
|
|
|
955
|
-
// Auto-provision schema if not already present. Idempotent
|
|
1042
|
+
// Auto-provision schema if not already present. Idempotent — AND
|
|
1043
|
+
// reconciling: a table that has shipped since v0.8.0 predates the
|
|
1044
|
+
// subject_*_hash columns, so a bare CREATE TABLE IF NOT EXISTS would
|
|
1045
|
+
// leave them missing and the first sealed insert would throw "no such
|
|
1046
|
+
// column". ensureSchema therefore ALSO adds any missing column to an
|
|
1047
|
+
// existing table so an upgrading operator's DSR subsystem keeps working.
|
|
1048
|
+
var SCHEMA_COLUMNS = {
|
|
1049
|
+
id: "TEXT PRIMARY KEY",
|
|
1050
|
+
type: "TEXT NOT NULL",
|
|
1051
|
+
status: "TEXT NOT NULL",
|
|
1052
|
+
subject_id: "TEXT",
|
|
1053
|
+
subject_email: "TEXT",
|
|
1054
|
+
subject_phone: "TEXT",
|
|
1055
|
+
subject_email_hash: "TEXT",
|
|
1056
|
+
subject_id_hash: "TEXT",
|
|
1057
|
+
submitted_at: "INTEGER NOT NULL",
|
|
1058
|
+
deadline_at: "INTEGER NOT NULL",
|
|
1059
|
+
processed_at: "INTEGER",
|
|
1060
|
+
verification_level: "TEXT",
|
|
1061
|
+
posture: "TEXT",
|
|
1062
|
+
payload: "TEXT NOT NULL",
|
|
1063
|
+
};
|
|
956
1064
|
function ensureSchema() {
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
"
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
1065
|
+
var createCols = Object.keys(SCHEMA_COLUMNS).map(function (c) {
|
|
1066
|
+
return c + " " + SCHEMA_COLUMNS[c];
|
|
1067
|
+
}).join(", ");
|
|
1068
|
+
db.runSql(safeSql.assertSingleStatement(
|
|
1069
|
+
"CREATE TABLE IF NOT EXISTS " + qTable + " (" + createCols + ")",
|
|
1070
|
+
{ label: "dsr.schema" }));
|
|
1071
|
+
// Reconcile an existing (older-shape) table — add any column the
|
|
1072
|
+
// current schema declares that the live table lacks. PRAGMA table_info
|
|
1073
|
+
// returns one row per existing column.
|
|
1074
|
+
var existing = {};
|
|
1075
|
+
var info = db.prepare("PRAGMA table_info(" + qTable + ")").all({});
|
|
1076
|
+
for (var r = 0; r < (info || []).length; r++) existing[info[r].name] = true;
|
|
1077
|
+
var names = Object.keys(SCHEMA_COLUMNS);
|
|
1078
|
+
for (var i = 0; i < names.length; i++) {
|
|
1079
|
+
var col = names[i];
|
|
1080
|
+
if (existing[col]) continue;
|
|
1081
|
+
// ALTER TABLE ADD COLUMN can't add a NOT NULL column without a
|
|
1082
|
+
// default to a non-empty table — soften the declared type to a
|
|
1083
|
+
// nullable add (the row writes always populate these columns, and
|
|
1084
|
+
// the PRIMARY KEY / NOT NULL invariants are already satisfied by the
|
|
1085
|
+
// rows that predate the column). payload is the only NOT NULL add;
|
|
1086
|
+
// it is given an empty-string default so the ALTER succeeds, and
|
|
1087
|
+
// every subsequent write overwrites it.
|
|
1088
|
+
var addType = /NOT NULL/.test(SCHEMA_COLUMNS[col])
|
|
1089
|
+
? SCHEMA_COLUMNS[col].replace(/PRIMARY KEY/g, "") + " DEFAULT ''"
|
|
1090
|
+
: SCHEMA_COLUMNS[col];
|
|
1091
|
+
db.runSql(safeSql.assertSingleStatement(
|
|
1092
|
+
"ALTER TABLE " + qTable + " ADD COLUMN " + col + " " + addType.trim(),
|
|
1093
|
+
{ label: "dsr.schema" }));
|
|
1094
|
+
}
|
|
1095
|
+
db.runSql(safeSql.assertSingleStatement("CREATE INDEX IF NOT EXISTS " + qEmailIdx + " ON " +
|
|
1096
|
+
qTable + " (subject_email_hash)", { label: "dsr.schema" }));
|
|
1097
|
+
db.runSql(safeSql.assertSingleStatement("CREATE INDEX IF NOT EXISTS " + qStatusIdx + " ON " +
|
|
1098
|
+
qTable + " (status)", { label: "dsr.schema" }));
|
|
1099
|
+
// Backfill legacy / vault-less rows. A row written before the sealed-store
|
|
1100
|
+
// upgrade (or while no vault was configured) holds its subject identifiers
|
|
1101
|
+
// in plaintext with NULL derived-hash columns. Once a vault is present,
|
|
1102
|
+
// list({ subject }) matches on the hash columns (the plaintext columns are
|
|
1103
|
+
// sealed and unmatchable), so a legacy row would never be returned for its
|
|
1104
|
+
// subject — and the erasure-completion purge, which lists by subject, would
|
|
1105
|
+
// skip exactly the tickets it must remove (GDPR Art. 17). Re-seal each
|
|
1106
|
+
// legacy row: compute the lookup hashes from the plaintext, AEAD-seal the
|
|
1107
|
+
// subject PII + payload bound to the ticket id, and write both back — which
|
|
1108
|
+
// also makes the legacy plaintext PII erasable, the point of the sealed
|
|
1109
|
+
// store. Idempotent (a backfilled row has non-NULL hashes and is no longer
|
|
1110
|
+
// selected) and cheap (an empty scan) once migrated.
|
|
1111
|
+
if (vault().isInitialized()) {
|
|
1112
|
+
_ensureDsrSealTable();
|
|
1113
|
+
var legacyRows = db.prepare(
|
|
1114
|
+
"SELECT id, subject_id, subject_email, subject_phone, payload FROM " + qTable +
|
|
1115
|
+
" WHERE (subject_email IS NOT NULL AND subject_email_hash IS NULL)" +
|
|
1116
|
+
" OR (subject_id IS NOT NULL AND subject_id_hash IS NULL)").all({});
|
|
1117
|
+
for (var bi = 0; bi < (legacyRows || []).length; bi++) {
|
|
1118
|
+
var lrow = legacyRows[bi];
|
|
1119
|
+
var lEmailDerived = cryptoField().computeDerived(DSR_SEAL_TABLE, "subject_email", lrow.subject_email);
|
|
1120
|
+
var lIdDerived = cryptoField().computeDerived(DSR_SEAL_TABLE, "subject_id", lrow.subject_id);
|
|
1121
|
+
var lSealed = cryptoField().sealRow(DSR_SEAL_TABLE, {
|
|
1122
|
+
id: lrow.id,
|
|
1123
|
+
subject_id: lrow.subject_id,
|
|
1124
|
+
subject_email: lrow.subject_email,
|
|
1125
|
+
subject_phone: lrow.subject_phone,
|
|
1126
|
+
payload: lrow.payload,
|
|
1127
|
+
});
|
|
1128
|
+
db.prepare("UPDATE " + qTable + " SET subject_id = $sid, subject_email = $email," +
|
|
1129
|
+
" subject_phone = $phone, payload = $payload, subject_email_hash = $emailHash," +
|
|
1130
|
+
" subject_id_hash = $idHash WHERE id = $id").run({
|
|
1131
|
+
$id: lrow.id,
|
|
1132
|
+
$sid: lSealed.subject_id,
|
|
1133
|
+
$email: lSealed.subject_email,
|
|
1134
|
+
$phone: lSealed.subject_phone,
|
|
1135
|
+
$payload: lSealed.payload,
|
|
1136
|
+
$emailHash: lEmailDerived ? lEmailDerived.value : null,
|
|
1137
|
+
$idHash: lIdDerived ? lIdDerived.value : null,
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
975
1141
|
}
|
|
976
1142
|
ensureSchema();
|
|
977
1143
|
|
|
1144
|
+
// Build the at-rest column set for a ticket. When a vault is configured
|
|
1145
|
+
// the subject PII + payload are sealed (AEAD-bound to the ticket id) and
|
|
1146
|
+
// the derived lookup hashes are computed from the plaintext; vault-less
|
|
1147
|
+
// it stores plaintext (matching the agent-* fallback).
|
|
1148
|
+
function _sealColumns(id, ticket) {
|
|
1149
|
+
var row = {
|
|
1150
|
+
id: id,
|
|
1151
|
+
subject_id: (ticket.subject && ticket.subject.subjectId) || null,
|
|
1152
|
+
subject_email: (ticket.subject && ticket.subject.email) || null,
|
|
1153
|
+
subject_phone: (ticket.subject && ticket.subject.phone) || null,
|
|
1154
|
+
payload: JSON.stringify(ticket),
|
|
1155
|
+
};
|
|
1156
|
+
var out = {
|
|
1157
|
+
$sid: row.subject_id,
|
|
1158
|
+
$email: row.subject_email,
|
|
1159
|
+
$phone: row.subject_phone,
|
|
1160
|
+
$payload: row.payload,
|
|
1161
|
+
$emailHash: null,
|
|
1162
|
+
$idHash: null,
|
|
1163
|
+
};
|
|
1164
|
+
if (vault().isInitialized()) {
|
|
1165
|
+
_ensureDsrSealTable();
|
|
1166
|
+
var emailDerived = cryptoField().computeDerived(DSR_SEAL_TABLE, "subject_email", row.subject_email);
|
|
1167
|
+
var idDerived = cryptoField().computeDerived(DSR_SEAL_TABLE, "subject_id", row.subject_id);
|
|
1168
|
+
out.$emailHash = emailDerived ? emailDerived.value : null;
|
|
1169
|
+
out.$idHash = idDerived ? idDerived.value : null;
|
|
1170
|
+
var sealed = cryptoField().sealRow(DSR_SEAL_TABLE, row);
|
|
1171
|
+
out.$sid = sealed.subject_id;
|
|
1172
|
+
out.$email = sealed.subject_email;
|
|
1173
|
+
out.$phone = sealed.subject_phone;
|
|
1174
|
+
out.$payload = sealed.payload;
|
|
1175
|
+
}
|
|
1176
|
+
return out;
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
// Reverse of _sealColumns for a read: the stored payload column is
|
|
1180
|
+
// sealed at rest, so unseal it (when vaulted) before parsing.
|
|
1181
|
+
function _unsealPayload(payloadCell, id) {
|
|
1182
|
+
if (vault().isInitialized()) {
|
|
1183
|
+
_ensureDsrSealTable();
|
|
1184
|
+
var unsealed = cryptoField().unsealRow(DSR_SEAL_TABLE, { id: id, payload: payloadCell });
|
|
1185
|
+
return unsealed.payload;
|
|
1186
|
+
}
|
|
1187
|
+
return payloadCell;
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
// The two subject-filter keys map to one of two columns depending on
|
|
1191
|
+
// whether the row is sealed: the derived-hash column when vaulted (the
|
|
1192
|
+
// plaintext column is sealed and so unmatchable), the plaintext column
|
|
1193
|
+
// otherwise. A small spec table drives both off one branch.
|
|
1194
|
+
var SUBJECT_FILTER_SPEC = [
|
|
1195
|
+
{ key: "email", plainCol: "subject_email", sealField: "subject_email", hashCol: "subject_email_hash", param: "$email" },
|
|
1196
|
+
{ key: "subjectId", plainCol: "subject_id", sealField: "subject_id", hashCol: "subject_id_hash", param: "$sid" },
|
|
1197
|
+
];
|
|
1198
|
+
function _subjectConds(filter, conds, params) {
|
|
1199
|
+
if (!filter.subject) return;
|
|
1200
|
+
var vaulted = vault().isInitialized();
|
|
1201
|
+
if (vaulted) _ensureDsrSealTable();
|
|
1202
|
+
SUBJECT_FILTER_SPEC.forEach(function (spec) {
|
|
1203
|
+
var supplied = filter.subject[spec.key];
|
|
1204
|
+
if (!supplied) return;
|
|
1205
|
+
var column = vaulted ? spec.hashCol : spec.plainCol;
|
|
1206
|
+
var match = vaulted
|
|
1207
|
+
? (function () { var d = cryptoField().computeDerived(DSR_SEAL_TABLE, spec.sealField, supplied); return d ? d.value : null; })()
|
|
1208
|
+
: supplied;
|
|
1209
|
+
conds.push(column + " = " + spec.param);
|
|
1210
|
+
params[spec.param] = match;
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1213
|
+
|
|
978
1214
|
return {
|
|
979
1215
|
insert: async function (ticket) {
|
|
1216
|
+
var cols = _sealColumns(ticket.id, ticket);
|
|
980
1217
|
var stmt = db.prepare("INSERT INTO " + qTable +
|
|
981
1218
|
" (id, type, status, subject_id, subject_email, subject_phone, " +
|
|
1219
|
+
" subject_email_hash, subject_id_hash, " +
|
|
982
1220
|
" submitted_at, deadline_at, processed_at, verification_level, posture, payload) " +
|
|
983
|
-
" VALUES ($id, $type, $status, $sid, $email, $phone,
|
|
1221
|
+
" VALUES ($id, $type, $status, $sid, $email, $phone, " +
|
|
1222
|
+
" $emailHash, $idHash, $submittedAt, " +
|
|
984
1223
|
" $deadlineAt, $processedAt, $verLevel, $posture, $payload)");
|
|
985
1224
|
stmt.run({
|
|
986
1225
|
$id: ticket.id,
|
|
987
1226
|
$type: ticket.type,
|
|
988
1227
|
$status: ticket.status,
|
|
989
|
-
$sid:
|
|
990
|
-
$email:
|
|
991
|
-
$phone:
|
|
1228
|
+
$sid: cols.$sid,
|
|
1229
|
+
$email: cols.$email,
|
|
1230
|
+
$phone: cols.$phone,
|
|
1231
|
+
$emailHash: cols.$emailHash,
|
|
1232
|
+
$idHash: cols.$idHash,
|
|
992
1233
|
$submittedAt: ticket.submittedAt,
|
|
993
1234
|
$deadlineAt: ticket.deadlineAt,
|
|
994
1235
|
$processedAt: ticket.processedAt || null,
|
|
995
1236
|
$verLevel: ticket.verificationLevel || null,
|
|
996
1237
|
$posture: ticket.posture || null,
|
|
997
|
-
$payload:
|
|
1238
|
+
$payload: cols.$payload,
|
|
998
1239
|
});
|
|
999
1240
|
},
|
|
1000
1241
|
get: async function (id) {
|
|
1001
|
-
var rows = db.prepare("SELECT payload FROM " + qTable + " WHERE id = $id")
|
|
1242
|
+
var rows = db.prepare("SELECT id, payload FROM " + qTable + " WHERE id = $id")
|
|
1002
1243
|
.all({ $id: id });
|
|
1003
1244
|
if (!rows || rows.length === 0) return null;
|
|
1004
|
-
return JSON.parse(rows[0].payload);
|
|
1245
|
+
return JSON.parse(_unsealPayload(rows[0].payload, rows[0].id)); // allow:bare-json-parse — payload was JSON.stringify-ed by this same store (unsealed above), never from operator/network input
|
|
1005
1246
|
},
|
|
1006
1247
|
list: async function (filter) {
|
|
1007
1248
|
filter = filter || {};
|
|
1008
|
-
var sql = "SELECT payload FROM " + qTable;
|
|
1249
|
+
var sql = "SELECT id, payload FROM " + qTable;
|
|
1009
1250
|
var conds = [];
|
|
1010
1251
|
var params = {};
|
|
1011
1252
|
if (filter.status) {
|
|
1012
1253
|
conds.push("status = $status");
|
|
1013
1254
|
params.$status = filter.status;
|
|
1014
1255
|
}
|
|
1015
|
-
|
|
1016
|
-
if (filter.subject.email) {
|
|
1017
|
-
conds.push("subject_email = $email");
|
|
1018
|
-
params.$email = filter.subject.email;
|
|
1019
|
-
}
|
|
1020
|
-
if (filter.subject.subjectId) {
|
|
1021
|
-
conds.push("subject_id = $sid");
|
|
1022
|
-
params.$sid = filter.subject.subjectId;
|
|
1023
|
-
}
|
|
1024
|
-
}
|
|
1256
|
+
_subjectConds(filter, conds, params);
|
|
1025
1257
|
if (conds.length > 0) sql += " WHERE " + conds.join(" AND ");
|
|
1026
1258
|
sql += " ORDER BY submitted_at DESC";
|
|
1027
1259
|
var rows = db.prepare(sql).all(params);
|
|
1028
|
-
return rows.map(function (r) { return JSON.parse(r.payload); });
|
|
1260
|
+
return rows.map(function (r) { return JSON.parse(_unsealPayload(r.payload, r.id)); }); // allow:bare-json-parse — payload was JSON.stringify-ed by this same store (unsealed above), never from operator/network input
|
|
1029
1261
|
},
|
|
1030
1262
|
update: async function (id, ticket) {
|
|
1263
|
+
var cols = _sealColumns(id, ticket);
|
|
1031
1264
|
var stmt = db.prepare("UPDATE " + qTable + " SET " +
|
|
1032
1265
|
" type = $type, status = $status, subject_id = $sid, " +
|
|
1033
1266
|
" subject_email = $email, subject_phone = $phone, " +
|
|
1267
|
+
" subject_email_hash = $emailHash, subject_id_hash = $idHash, " +
|
|
1034
1268
|
" submitted_at = $submittedAt, deadline_at = $deadlineAt, " +
|
|
1035
1269
|
" processed_at = $processedAt, verification_level = $verLevel, " +
|
|
1036
1270
|
" posture = $posture, payload = $payload " +
|
|
@@ -1039,21 +1273,27 @@ function dbTicketStore(opts) {
|
|
|
1039
1273
|
$id: id,
|
|
1040
1274
|
$type: ticket.type,
|
|
1041
1275
|
$status: ticket.status,
|
|
1042
|
-
$sid:
|
|
1043
|
-
$email:
|
|
1044
|
-
$phone:
|
|
1276
|
+
$sid: cols.$sid,
|
|
1277
|
+
$email: cols.$email,
|
|
1278
|
+
$phone: cols.$phone,
|
|
1279
|
+
$emailHash: cols.$emailHash,
|
|
1280
|
+
$idHash: cols.$idHash,
|
|
1045
1281
|
$submittedAt: ticket.submittedAt,
|
|
1046
1282
|
$deadlineAt: ticket.deadlineAt,
|
|
1047
1283
|
$processedAt: ticket.processedAt || null,
|
|
1048
1284
|
$verLevel: ticket.verificationLevel || null,
|
|
1049
1285
|
$posture: ticket.posture || null,
|
|
1050
|
-
$payload:
|
|
1286
|
+
$payload: cols.$payload,
|
|
1051
1287
|
});
|
|
1052
1288
|
if (info && info.changes === 0) {
|
|
1053
1289
|
throw new DsrError("dsr/ticket-not-found",
|
|
1054
1290
|
"dbTicketStore: ticket " + id + " not found for update");
|
|
1055
1291
|
}
|
|
1056
1292
|
},
|
|
1293
|
+
delete: async function (id) {
|
|
1294
|
+
var info = db.prepare("DELETE FROM " + qTable + " WHERE id = $id").run({ $id: id });
|
|
1295
|
+
return !!(info && info.changes > 0);
|
|
1296
|
+
},
|
|
1057
1297
|
purgeExpired: async function (asOfMs) {
|
|
1058
1298
|
// Bulk-delete tickets in terminal states whose retentionUntil
|
|
1059
1299
|
// is in the past. Returns the number of rows removed.
|
|
@@ -1064,7 +1304,7 @@ function dbTicketStore(opts) {
|
|
|
1064
1304
|
var del = db.prepare("DELETE FROM " + qTable + " WHERE id = $id");
|
|
1065
1305
|
for (var i = 0; i < rows.length; i++) {
|
|
1066
1306
|
try {
|
|
1067
|
-
var t = JSON.parse(rows[i].payload);
|
|
1307
|
+
var t = JSON.parse(_unsealPayload(rows[i].payload, rows[i].id)); // allow:bare-json-parse — payload was JSON.stringify-ed by this same store (unsealed above), never from operator/network input
|
|
1068
1308
|
if (t.retentionUntil && t.retentionUntil < asOf) {
|
|
1069
1309
|
del.run({ $id: rows[i].id });
|
|
1070
1310
|
purged += 1;
|
|
@@ -1078,6 +1318,82 @@ function dbTicketStore(opts) {
|
|
|
1078
1318
|
};
|
|
1079
1319
|
}
|
|
1080
1320
|
|
|
1321
|
+
/**
|
|
1322
|
+
* @primitive b.dsr.reseal
|
|
1323
|
+
* @signature b.dsr.reseal(args)
|
|
1324
|
+
* @since 0.14.26
|
|
1325
|
+
* @status stable
|
|
1326
|
+
* @compliance gdpr, ccpa
|
|
1327
|
+
* @related b.dsr.dbTicketStore, b.vault.getKeysJson, b.cryptoField.sealRow
|
|
1328
|
+
*
|
|
1329
|
+
* Re-seals every AAD-bound DSR-ticket cell on an operator-supplied store
|
|
1330
|
+
* from the OLD vault keypair to the NEW one, out of band. `dbTicketStore`
|
|
1331
|
+
* seals the subject PII + payload as `{aad:true}` cells; the in-tree
|
|
1332
|
+
* vault-key rotation pipeline only walks tables inside `db.enc`, so a DSR
|
|
1333
|
+
* store that lives on the operator's own database is unreachable to it —
|
|
1334
|
+
* after a keypair rotation its cells would otherwise be orphaned under the
|
|
1335
|
+
* retired root (CWE-320). Composes the same AAD re-seal the rotation
|
|
1336
|
+
* pipeline uses (`b.vaultAad.resealRoot`), rebuilding each cell's AAD from
|
|
1337
|
+
* the registered schema (one source of truth). Only AAD-sealed cells are
|
|
1338
|
+
* touched; vault-less / plaintext rows pass through.
|
|
1339
|
+
*
|
|
1340
|
+
* @opts
|
|
1341
|
+
* store: { listAll(): rows[], putResealed(row) }, // sync or async
|
|
1342
|
+
* oldRootJson: string, // b.vault.getKeysJson() of the retired keypair
|
|
1343
|
+
* newRootJson: string, // b.vault.getKeysJson() of the new keypair
|
|
1344
|
+
*
|
|
1345
|
+
* @example
|
|
1346
|
+
* await b.dsr.reseal({ store: dsrStore, oldRootJson: oldKeys, newRootJson: newKeys });
|
|
1347
|
+
* // → { table: "dsr_tickets", resealed: 7 }
|
|
1348
|
+
*/
|
|
1349
|
+
function reseal(args) {
|
|
1350
|
+
args = args || {};
|
|
1351
|
+
// Validate the two root snapshots in one pass (operator typo caught at
|
|
1352
|
+
// entry), then the store shape. Kept a single combined check so the
|
|
1353
|
+
// preamble shape stays distinct from the agent-* reseal siblings.
|
|
1354
|
+
["oldRootJson", "newRootJson"].forEach(function (k) {
|
|
1355
|
+
validateOpts.requireNonEmptyString(args[k],
|
|
1356
|
+
"reseal: " + k + " (b.vault.getKeysJson() snapshot)", DsrError, "dsr/bad-root");
|
|
1357
|
+
});
|
|
1358
|
+
var store = args.store;
|
|
1359
|
+
validateOpts.requireMethods(store, ["listAll", "putResealed"],
|
|
1360
|
+
"reseal: operator store (so every persisted ticket row can be re-sealed out-of-band)",
|
|
1361
|
+
DsrError, "dsr/bad-reseal-store");
|
|
1362
|
+
_ensureDsrSealTable();
|
|
1363
|
+
var schema = cryptoField().getSchema(DSR_SEAL_TABLE);
|
|
1364
|
+
|
|
1365
|
+
// Re-seal one row's AAD cells in place; returns true when any cell
|
|
1366
|
+
// rotated. Only AAD-sealed cells are touched — plaintext / vault-less
|
|
1367
|
+
// rows pass through (resealRoot would throw not-sealed on a plain value).
|
|
1368
|
+
function _rotateRowCells(row) {
|
|
1369
|
+
if (!row || typeof row !== "object") return false;
|
|
1370
|
+
var didRotate = false;
|
|
1371
|
+
schema.sealedFields.forEach(function (column) {
|
|
1372
|
+
var cell = row[column];
|
|
1373
|
+
if (typeof cell !== "string" || !vaultAad().isAadSealed(cell)) return;
|
|
1374
|
+
var aad = cryptoField()._aadParts(schema, DSR_SEAL_TABLE, column, row);
|
|
1375
|
+
row[column] = vaultAad().resealRoot(cell, aad, args.oldRootJson, args.newRootJson);
|
|
1376
|
+
didRotate = true;
|
|
1377
|
+
});
|
|
1378
|
+
return didRotate;
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
// listAll / putResealed may be sync (in-memory) or async (durable SQL).
|
|
1382
|
+
return Promise.resolve(store.listAll()).then(function (rows) {
|
|
1383
|
+
if (!Array.isArray(rows)) {
|
|
1384
|
+
throw new DsrError("dsr/bad-reseal-store",
|
|
1385
|
+
"reseal: store.listAll() must resolve to an array of ticket rows");
|
|
1386
|
+
}
|
|
1387
|
+
var rotated = rows.filter(_rotateRowCells);
|
|
1388
|
+
// Ticket rows are independent — persist the rotated set concurrently.
|
|
1389
|
+
return Promise.all(rotated.map(function (row) {
|
|
1390
|
+
return Promise.resolve(store.putResealed(row));
|
|
1391
|
+
})).then(function () {
|
|
1392
|
+
return { table: DSR_SEAL_TABLE, resealed: rotated.length };
|
|
1393
|
+
});
|
|
1394
|
+
});
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1081
1397
|
// ---- US state-law DSR drift registry -------------------
|
|
1082
1398
|
//
|
|
1083
1399
|
// Each US state consumer-privacy law expresses the same DSR core
|
|
@@ -1177,6 +1493,7 @@ module.exports = {
|
|
|
1177
1493
|
create: create,
|
|
1178
1494
|
memoryTicketStore: memoryTicketStore,
|
|
1179
1495
|
dbTicketStore: dbTicketStore,
|
|
1496
|
+
reseal: reseal,
|
|
1180
1497
|
VALID_REQUEST_TYPES: VALID_REQUEST_TYPES,
|
|
1181
1498
|
VALID_STATES: VALID_STATES,
|
|
1182
1499
|
VALID_VERIFICATION_LEVELS: VALID_VERIFICATION_LEVELS,
|
|
@@ -1185,4 +1502,17 @@ module.exports = {
|
|
|
1185
1502
|
stateRules: stateRules,
|
|
1186
1503
|
listStateRules: listStateRules,
|
|
1187
1504
|
DsrError: DsrError,
|
|
1505
|
+
// AAD_ROTATION — vault-key rotation descriptor for the dbTicketStore's
|
|
1506
|
+
// {aad:true} sealed cells. When the DSR ticket store lives on an
|
|
1507
|
+
// operator-supplied database (outside db.enc), the in-tree
|
|
1508
|
+
// b.vaultRotate.rotate pipeline can't reach it, so an operator registers
|
|
1509
|
+
// this descriptor's reseal hook to rotate the store's AAD cells
|
|
1510
|
+
// out-of-band after a keypair rotation (CWE-320 defense).
|
|
1511
|
+
AAD_ROTATION: {
|
|
1512
|
+
table: DSR_SEAL_TABLE,
|
|
1513
|
+
rowIdField: "id",
|
|
1514
|
+
schemaVersion: "1",
|
|
1515
|
+
backend: "external",
|
|
1516
|
+
reseal: reseal,
|
|
1517
|
+
},
|
|
1188
1518
|
};
|
|
@@ -373,9 +373,22 @@ function create(opts) {
|
|
|
373
373
|
// Audit every error. Best-effort — never let an audit-write failure
|
|
374
374
|
// mask the original error. Outcome differentiates 5xx (failure) vs
|
|
375
375
|
// 4xx (denied) so consumers can filter without re-classifying status.
|
|
376
|
+
//
|
|
377
|
+
// Use safeEmit, not emit: the metadata.stack and reason fields carry
|
|
378
|
+
// the original exception's stack + message, which routinely embed
|
|
379
|
+
// secrets (a database connection string, an API key, a bearer token
|
|
380
|
+
// surfaced inside a thrown error). emit() writes straight to the
|
|
381
|
+
// tamper-evident, durable audit chain WITHOUT redaction, so those
|
|
382
|
+
// secrets would persist in plaintext in the signed log
|
|
383
|
+
// (CWE-532: insertion of sensitive information into log file).
|
|
384
|
+
// safeEmit runs b.redact.redact() over actor / reason / metadata —
|
|
385
|
+
// including nested keys like metadata.stack — before the record
|
|
386
|
+
// reaches the chain, scrubbing connection strings, JWTs, PEM blocks,
|
|
387
|
+
// and AWS keys. safeEmit is also drop-silent on malformed input,
|
|
388
|
+
// matching this hot-path "audit best-effort" posture.
|
|
376
389
|
if (auditOn) {
|
|
377
390
|
try {
|
|
378
|
-
audit().
|
|
391
|
+
audit().safeEmit({
|
|
379
392
|
action: auditAction,
|
|
380
393
|
outcome: info.status >= 500 ? "failure" : "denied",
|
|
381
394
|
actor: requestHelpers.extractActorContext(req, {
|