@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
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* b.cryptoField per-row key (K_row) crypto-shred substrate.
|
|
4
|
+
*
|
|
5
|
+
* declarePerRowKey opts a table into per-row keying. On INSERT the write
|
|
6
|
+
* boundary materializes a fresh CSPRNG row-secret, derives K_row from it,
|
|
7
|
+
* stores the SECRET (never K_row) AAD-sealed in _blamejs_per_row_keys, and
|
|
8
|
+
* seals the row's sealed columns under K_row as vault.row: cells. Reads
|
|
9
|
+
* fetch + unwrap the secret, re-derive K_row, and decrypt. Destroying the
|
|
10
|
+
* wrapped secret (b.subject.eraseHard / b.retention) leaves WAL / replica
|
|
11
|
+
* residual ciphertext mathematically undecryptable — the crypto-shred.
|
|
12
|
+
*
|
|
13
|
+
* Regression for the v0.14.25 critical class:
|
|
14
|
+
* - pre-fix K_row derived from the PLAINTEXT-on-disk derivedHash salt,
|
|
15
|
+
* so an attacker with disk access re-derived it and deleting the wrap
|
|
16
|
+
* shred NOTHING. The row-secret is now random.
|
|
17
|
+
* - pre-fix the wrap was sealed WITHOUT AAD despite a copy-protection
|
|
18
|
+
* claim. The wrap + the cells are now AAD-bound to (table, rowId,
|
|
19
|
+
* column, schemaVersion).
|
|
20
|
+
* - pre-fix materializePerRowKey was NEVER called on INSERT (dead
|
|
21
|
+
* surface). It is now wired at the db-query write boundary.
|
|
22
|
+
*
|
|
23
|
+
* Pins: materialize-rowId == destroy-rowId == _id; the residency-tag
|
|
24
|
+
* column is NEVER K_row-sealed; a copied cell fails Poly1305; a vault
|
|
25
|
+
* keypair rotation reseals the wrapped secret old-root -> new-root.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
var helpers = require("../helpers");
|
|
29
|
+
var b = helpers.b;
|
|
30
|
+
var fs = require("fs");
|
|
31
|
+
var os = require("os");
|
|
32
|
+
var path = require("path");
|
|
33
|
+
var check = helpers.check;
|
|
34
|
+
var { setupTestDb, teardownTestDb } = require("../helpers/db");
|
|
35
|
+
|
|
36
|
+
var ROW_PREFIX = require("../../lib/constants").ROW_PREFIX;
|
|
37
|
+
|
|
38
|
+
// App table opted into per-row keying. `subjectId` is the plaintext
|
|
39
|
+
// subject column eraseHard matches on; `dataRegion` is the plaintext
|
|
40
|
+
// residency tag (declarePerRowResidency) that must NEVER be K_row-sealed;
|
|
41
|
+
// `ssn` / `note` are the sealed columns that become vault.row: cells.
|
|
42
|
+
var KEYED_SCHEMA = [{
|
|
43
|
+
name: "pr_keyed",
|
|
44
|
+
columns: {
|
|
45
|
+
_id: "TEXT PRIMARY KEY",
|
|
46
|
+
subjectId: "TEXT",
|
|
47
|
+
dataRegion: "TEXT",
|
|
48
|
+
ssn: "TEXT",
|
|
49
|
+
note: "TEXT",
|
|
50
|
+
},
|
|
51
|
+
indexes: ["subjectId"],
|
|
52
|
+
sealedFields: ["ssn", "note"],
|
|
53
|
+
subjectField: "subjectId",
|
|
54
|
+
}];
|
|
55
|
+
|
|
56
|
+
function _perRowKeyCount(rowId) {
|
|
57
|
+
var row = b.db.prepare(
|
|
58
|
+
'SELECT COUNT(*) AS n FROM "_blamejs_per_row_keys" WHERE tableName = ? AND rowId = ?'
|
|
59
|
+
).get("pr_keyed", rowId);
|
|
60
|
+
return row ? row.n : 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function _rawCell(rowId, col) {
|
|
64
|
+
var row = b.db.prepare('SELECT "' + col + '" AS v FROM "pr_keyed" WHERE _id = ?').get(rowId);
|
|
65
|
+
return row ? row.v : null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function run() {
|
|
69
|
+
var dir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-cf-prk-"));
|
|
70
|
+
try {
|
|
71
|
+
await setupTestDb(dir, KEYED_SCHEMA);
|
|
72
|
+
b.cryptoField.clearResidencyForTest();
|
|
73
|
+
b.cryptoField.declarePerRowKey("pr_keyed", { keySize: 32 });
|
|
74
|
+
b.cryptoField.declarePerRowResidency("pr_keyed", {
|
|
75
|
+
residencyColumn: "dataRegion",
|
|
76
|
+
allowedTags: ["eu", "us", "global"],
|
|
77
|
+
});
|
|
78
|
+
check("hasPerRowKey true after declare", b.cryptoField.hasPerRowKey("pr_keyed") === true);
|
|
79
|
+
|
|
80
|
+
// ---- INSERT: materialize + K_row-seal at the write boundary ----
|
|
81
|
+
var inserted = b.db.from("pr_keyed").insertOne({
|
|
82
|
+
_id: "row-1", subjectId: "subj-A", dataRegion: "eu", ssn: "123-45-6789", note: "patient note one",
|
|
83
|
+
});
|
|
84
|
+
check("insertOne returns plaintext _id", inserted._id === "row-1");
|
|
85
|
+
|
|
86
|
+
// A _blamejs_per_row_keys entry appears for this row's _id.
|
|
87
|
+
check("per-row-keys registry row created for _id", _perRowKeyCount("row-1") === 1);
|
|
88
|
+
|
|
89
|
+
// The stored wrapped secret is AAD-sealed (vault.aad: prefix).
|
|
90
|
+
var wrapRow = b.db.prepare(
|
|
91
|
+
'SELECT wrappedKey FROM "_blamejs_per_row_keys" WHERE tableName = ? AND rowId = ?'
|
|
92
|
+
).get("pr_keyed", "row-1");
|
|
93
|
+
check("wrapped secret stored AAD-sealed (vault.aad:)",
|
|
94
|
+
typeof wrapRow.wrappedKey === "string" && wrapRow.wrappedKey.indexOf("vault.aad:") === 0);
|
|
95
|
+
|
|
96
|
+
// Sealed columns on disk carry the vault.row: prefix (K_row cells) —
|
|
97
|
+
// asserted via the public b.cryptoField.isRowSealed primitive.
|
|
98
|
+
check("ssn column is a vault.row: cell", b.cryptoField.isRowSealed(_rawCell("row-1", "ssn")));
|
|
99
|
+
check("note column is a vault.row: cell", b.cryptoField.isRowSealed(_rawCell("row-1", "note")));
|
|
100
|
+
check("ROW_PREFIX is the vault.row: envelope isRowSealed detects",
|
|
101
|
+
String(_rawCell("row-1", "ssn")).indexOf(ROW_PREFIX) === 0);
|
|
102
|
+
|
|
103
|
+
// PIN: the residency-tag column stays plaintext (never K_row-sealed);
|
|
104
|
+
// isRowSealed is false for a plaintext cell.
|
|
105
|
+
check("residency tag column stays plaintext", _rawCell("row-1", "dataRegion") === "eu");
|
|
106
|
+
check("isRowSealed false for the plaintext residency tag",
|
|
107
|
+
b.cryptoField.isRowSealed(_rawCell("row-1", "dataRegion")) === false);
|
|
108
|
+
|
|
109
|
+
// ---- READ: round-trips to plaintext ----
|
|
110
|
+
var got = b.db.from("pr_keyed").where({ _id: "row-1" }).first();
|
|
111
|
+
check("read round-trips ssn", got.ssn === "123-45-6789");
|
|
112
|
+
check("read round-trips note", got.note === "patient note one");
|
|
113
|
+
check("read surfaces residency tag verbatim", got.dataRegion === "eu");
|
|
114
|
+
|
|
115
|
+
// ---- Codex P1 regression: unsealRow with NO dbHandle (the
|
|
116
|
+
// break-glass / clusterStorage read path) still decrypts a keyed row.
|
|
117
|
+
// crypto-field resolves the local db itself for the wrapped-secret
|
|
118
|
+
// lookup, so a direct caller doesn't null every K_row cell. ----
|
|
119
|
+
var rawRow = b.db.prepare('SELECT * FROM "pr_keyed" WHERE _id = ?').get("row-1");
|
|
120
|
+
check("raw row carries a vault.row: cell pre-unseal", b.cryptoField.isRowSealed(rawRow.ssn));
|
|
121
|
+
var noHandle = b.cryptoField.unsealRow("pr_keyed", rawRow); // 4th arg (dbHandle) omitted
|
|
122
|
+
check("unsealRow without a dbHandle still decrypts ssn", noHandle.ssn === "123-45-6789");
|
|
123
|
+
check("unsealRow without a dbHandle still decrypts note", noHandle.note === "patient note one");
|
|
124
|
+
|
|
125
|
+
// all() path round-trips too.
|
|
126
|
+
var all = b.db.from("pr_keyed").where({ subjectId: "subj-A" }).all();
|
|
127
|
+
check("all() round-trips one keyed row", all.length === 1 && all[0].ssn === "123-45-6789");
|
|
128
|
+
|
|
129
|
+
// ---- COPY-ROW ATTACK: paste another row's cell ----
|
|
130
|
+
b.db.from("pr_keyed").insertOne({
|
|
131
|
+
_id: "row-2", subjectId: "subj-B", dataRegion: "us", ssn: "999-88-7777", note: "patient note two",
|
|
132
|
+
});
|
|
133
|
+
var row2Ssn = _rawCell("row-2", "ssn"); // a valid vault.row: cell, but bound to row-2
|
|
134
|
+
// Overwrite row-1's ssn cell with row-2's ciphertext via raw SQL (a
|
|
135
|
+
// DB-write attacker). row-1's K_row + AAD differ, so Poly1305 fails.
|
|
136
|
+
b.db.prepare('UPDATE "pr_keyed" SET "ssn" = ? WHERE _id = ?').run(row2Ssn, "row-1");
|
|
137
|
+
var tampered = b.db.from("pr_keyed").where({ _id: "row-1" }).first();
|
|
138
|
+
check("copied cell from another row fails to decrypt (null)", tampered.ssn === null);
|
|
139
|
+
check("untouched cell on the same row still decrypts", tampered.note === "patient note one");
|
|
140
|
+
// Restore a valid row-1 ssn cell for the erase/rotation phases.
|
|
141
|
+
b.db.from("pr_keyed").where({ _id: "row-1" }).updateOne({ ssn: "123-45-6789" });
|
|
142
|
+
check("re-seal under K_row round-trips after update",
|
|
143
|
+
b.db.from("pr_keyed").where({ _id: "row-1" }).first().ssn === "123-45-6789");
|
|
144
|
+
|
|
145
|
+
// ---- ERASE-HARD: crypto-shred ----
|
|
146
|
+
var result = b.subject.eraseHard("subj-A", {
|
|
147
|
+
reason: "test-crypto-shred",
|
|
148
|
+
acknowledgements: ["no-litigation-hold", "no-statutory-retention-required"],
|
|
149
|
+
});
|
|
150
|
+
check("eraseHard destroyed 1 per-row key", result.perRowKeysDestroyed === 1);
|
|
151
|
+
check("eraseHard deleted the row", result.perTable.pr_keyed === 1);
|
|
152
|
+
check("per-row-keys registry row gone after shred", _perRowKeyCount("row-1") === 0);
|
|
153
|
+
// The row itself was DELETEd by eraseHard; assert the wrapped secret
|
|
154
|
+
// is gone (the residual-ciphertext shred). A post-shred re-insert of
|
|
155
|
+
// the SAME _id would mint a NEW random secret — the old WAL cell would
|
|
156
|
+
// never decrypt under it.
|
|
157
|
+
check("subj-B row still present + decrypts (shred is row-scoped)",
|
|
158
|
+
b.db.from("pr_keyed").where({ _id: "row-2" }).first().ssn === "999-88-7777");
|
|
159
|
+
|
|
160
|
+
// ---- POST-DESTROY read-absent: a vault.row: cell with no wrap ----
|
|
161
|
+
// Re-create a wrap-less keyed cell: insert, then destroy ONLY its
|
|
162
|
+
// wrapped secret (simulating WAL residue after shred). The read must
|
|
163
|
+
// null the field, not crash.
|
|
164
|
+
b.db.from("pr_keyed").insertOne({
|
|
165
|
+
_id: "row-3", subjectId: "subj-C", dataRegion: "global", ssn: "111-22-3333", note: "n3",
|
|
166
|
+
});
|
|
167
|
+
b.cryptoField.destroyPerRowKey("pr_keyed", "row-3", b.db);
|
|
168
|
+
check("destroyPerRowKey removed the wrap", _perRowKeyCount("row-3") === 0);
|
|
169
|
+
var shredded = b.db.from("pr_keyed").where({ _id: "row-3" }).first();
|
|
170
|
+
check("shredded cell reads as absent (null), no crash", shredded.ssn === null && shredded.note === null);
|
|
171
|
+
|
|
172
|
+
// ---- FORENSIC RESIDUAL PROOF (the crypto-shred guarantee) ----
|
|
173
|
+
// Advertised (crypto-field.js destroyPerRowKey): destroying the wrapped
|
|
174
|
+
// row-secret makes residual ciphertext (WAL / replica / backup)
|
|
175
|
+
// undecryptable EVEN WITH THE VAULT ROOT, because the random row-secret —
|
|
176
|
+
// the only seed for K_row — is gone everywhere it lived. A read returning
|
|
177
|
+
// null after shred proves only the read path; this proves the CIPHERTEXT
|
|
178
|
+
// itself is unrecoverable while the vault root + machinery stay fully
|
|
179
|
+
// functional (a sibling keyed row keeps decrypting throughout).
|
|
180
|
+
var FNEEDLE = "SECRET-SSN-FORENSIC-7Q2";
|
|
181
|
+
b.db.from("pr_keyed").insertOne({
|
|
182
|
+
_id: "row-F", subjectId: "subj-F", dataRegion: "eu", ssn: FNEEDLE, note: "forensic note",
|
|
183
|
+
});
|
|
184
|
+
var residualCipher = _rawCell("row-F", "ssn");
|
|
185
|
+
check("forensic: captured cell is a vault.row: residual envelope",
|
|
186
|
+
b.cryptoField.isRowSealed(residualCipher));
|
|
187
|
+
check("forensic: control — residual cell decrypts before shred",
|
|
188
|
+
b.db.from("pr_keyed").where({ _id: "row-F" }).first().ssn === FNEEDLE);
|
|
189
|
+
|
|
190
|
+
// Seal the working copy to durable db.enc, then scan every on-disk db
|
|
191
|
+
// file (durable sealed copy + tmpfs working copy + WAL): the plaintext must
|
|
192
|
+
// appear in NONE of them, while the sealed envelope DOES persist (a real
|
|
193
|
+
// residual an attacker could lift from WAL / a replica / a backup).
|
|
194
|
+
await b.db.flushToDisk();
|
|
195
|
+
var plainNeedle = Buffer.from(FNEEDLE, "utf8");
|
|
196
|
+
var envNeedle = Buffer.from(residualCipher, "utf8");
|
|
197
|
+
var scanned = 0, plaintextHits = 0, envelopeOnDisk = false;
|
|
198
|
+
[dir, path.join(dir, "tmpfs")].forEach(function (d) {
|
|
199
|
+
var entries;
|
|
200
|
+
try { entries = fs.readdirSync(d); } catch (_e) { return; }
|
|
201
|
+
entries.forEach(function (f) {
|
|
202
|
+
var p = path.join(d, f), st;
|
|
203
|
+
try { st = fs.statSync(p); } catch (_e) { return; }
|
|
204
|
+
if (!st.isFile()) return;
|
|
205
|
+
var buf;
|
|
206
|
+
try { buf = fs.readFileSync(p); } catch (_e) { return; }
|
|
207
|
+
scanned += 1;
|
|
208
|
+
if (buf.indexOf(plainNeedle) !== -1) plaintextHits += 1;
|
|
209
|
+
if (buf.indexOf(envNeedle) !== -1) envelopeOnDisk = true;
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
check("forensic: scanned on-disk db files (db.enc + working + wal)", scanned > 0);
|
|
213
|
+
check("forensic: plaintext ssn appears in NO on-disk file (sealed at rest)", plaintextHits === 0);
|
|
214
|
+
check("forensic: the sealed vault.row: envelope DOES persist on disk (real residual)", envelopeOnDisk);
|
|
215
|
+
|
|
216
|
+
// Shred the wrapped secret ONLY — the row + its residual cell stay put.
|
|
217
|
+
var fdestroy = b.cryptoField.destroyPerRowKey("pr_keyed", "row-F", b.db);
|
|
218
|
+
check("forensic: destroyPerRowKey removed exactly the row-F wrap", fdestroy.destroyed === 1);
|
|
219
|
+
check("forensic: wrapped secret gone from registry", _perRowKeyCount("row-F") === 0);
|
|
220
|
+
check("forensic: residual cipher still physically on the row post-shred",
|
|
221
|
+
_rawCell("row-F", "ssn") === residualCipher);
|
|
222
|
+
// The vault root + crypto-field machinery are STILL fully functional: a
|
|
223
|
+
// sibling keyed row (row-2, never shredded) decrypts normally.
|
|
224
|
+
check("forensic: vault root intact — sibling keyed row still decrypts",
|
|
225
|
+
b.db.from("pr_keyed").where({ _id: "row-2" }).first().ssn === "999-88-7777");
|
|
226
|
+
// Yet the residual cell yields NO plaintext: the random row-secret that
|
|
227
|
+
// seeded K_row is gone, and the vault root alone cannot reconstruct it.
|
|
228
|
+
check("forensic: residual cell undecryptable after shred (root intact)",
|
|
229
|
+
b.db.from("pr_keyed").where({ _id: "row-F" }).first().ssn === null);
|
|
230
|
+
|
|
231
|
+
// Re-materializing the SAME _id mints a NEW random secret -> a DIFFERENT
|
|
232
|
+
// K_row, so the captured residual (sealed under the destroyed K_row) is
|
|
233
|
+
// STILL undecryptable. The shred is irreversible, not a key-reuse window.
|
|
234
|
+
b.cryptoField.materializePerRowKey("pr_keyed", "row-F", b.db);
|
|
235
|
+
check("forensic: a fresh wrap exists again for row-F", _perRowKeyCount("row-F") === 1);
|
|
236
|
+
b.db.prepare('UPDATE "pr_keyed" SET "ssn" = ? WHERE _id = ?').run(residualCipher, "row-F");
|
|
237
|
+
check("forensic: residual STILL undecryptable under a freshly-minted K_row",
|
|
238
|
+
b.db.from("pr_keyed").where({ _id: "row-F" }).first().ssn === null);
|
|
239
|
+
b.db.prepare('DELETE FROM "pr_keyed" WHERE _id = ?').run("row-F");
|
|
240
|
+
b.cryptoField.destroyPerRowKey("pr_keyed", "row-F", b.db);
|
|
241
|
+
|
|
242
|
+
await teardownTestDb(dir);
|
|
243
|
+
|
|
244
|
+
// ---- ROTATION ROUND-TRIP ----
|
|
245
|
+
await _rotationRoundTrip();
|
|
246
|
+
} finally {
|
|
247
|
+
try { b.cryptoField.clearResidencyForTest(); } catch (_e) {}
|
|
248
|
+
try { fs.rmSync(dir, { recursive: true, force: true }); } catch (_e) {}
|
|
249
|
+
}
|
|
250
|
+
console.log("OK — crypto-field per-row-key tests");
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// A vault keypair rotation must reseal the wrapped row-secret in
|
|
254
|
+
// _blamejs_per_row_keys old-root -> new-root (registerTable({aad:true})
|
|
255
|
+
// wires rotate._rotateColumn). After the swap, the wrap unwraps under the
|
|
256
|
+
// new root, K_row re-derives, and a vault.row: data cell still decrypts.
|
|
257
|
+
async function _rotationRoundTrip() {
|
|
258
|
+
var dirNew = fs.mkdtempSync(path.join(os.tmpdir(), "prk-vr-new-"));
|
|
259
|
+
var dirA = fs.mkdtempSync(path.join(os.tmpdir(), "prk-vr-a-"));
|
|
260
|
+
var staging = path.join(os.tmpdir(), "prk-vr-stg-" + process.pid + "-" + Date.now());
|
|
261
|
+
|
|
262
|
+
async function _reset() {
|
|
263
|
+
process.env.BLAMEJS_SKIP_NTP_CHECK = "1";
|
|
264
|
+
b.cluster._resetForTest();
|
|
265
|
+
b.audit._resetForTest();
|
|
266
|
+
b.vault._resetForTest();
|
|
267
|
+
b.db._resetForTest();
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
try {
|
|
271
|
+
// Fresh keypair to rotate INTO.
|
|
272
|
+
await _reset();
|
|
273
|
+
await b.vault.init({ dataDir: dirNew, mode: "plaintext" });
|
|
274
|
+
var newKeys = JSON.parse(b.vault.getKeysJson());
|
|
275
|
+
b.vault._resetForTest();
|
|
276
|
+
|
|
277
|
+
// Live deployment with a per-row-key table + one keyed row.
|
|
278
|
+
await _reset();
|
|
279
|
+
await b.vault.init({ dataDir: dirA, mode: "plaintext" });
|
|
280
|
+
var oldKeys = JSON.parse(b.vault.getKeysJson());
|
|
281
|
+
check("rotation: old and new keypairs differ",
|
|
282
|
+
JSON.stringify(oldKeys) !== JSON.stringify(newKeys));
|
|
283
|
+
await b.db.init({
|
|
284
|
+
dataDir: dirA, tmpDir: path.join(dirA, "tmpfs"), atRest: "encrypted",
|
|
285
|
+
auditSigning: false, schema: KEYED_SCHEMA,
|
|
286
|
+
});
|
|
287
|
+
b.cryptoField.clearResidencyForTest();
|
|
288
|
+
b.cryptoField.declarePerRowKey("pr_keyed", { keySize: 32 });
|
|
289
|
+
b.cryptoField.declarePerRowResidency("pr_keyed", {
|
|
290
|
+
residencyColumn: "dataRegion", allowedTags: ["eu", "us", "global"],
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
b.db.from("pr_keyed").insertOne({
|
|
294
|
+
_id: "rot-1", subjectId: "s1", dataRegion: "eu", ssn: "555-44-3333", note: "rotate me",
|
|
295
|
+
});
|
|
296
|
+
check("rotation: keyed cell sealed as vault.row:",
|
|
297
|
+
String(_rawCell("rot-1", "ssn")).indexOf(ROW_PREFIX) === 0);
|
|
298
|
+
await b.db.flushToDisk();
|
|
299
|
+
await b.db.close();
|
|
300
|
+
|
|
301
|
+
// Rotate the keypair old -> new. externalAadResealed:true: this
|
|
302
|
+
// deployment uses none of the operator-supplied external AAD stores.
|
|
303
|
+
var rot = await b.vaultRotate.rotate({
|
|
304
|
+
dataDir: dirA, stagingDir: staging, oldKeys: oldKeys, newKeys: newKeys,
|
|
305
|
+
mode: "plaintext", externalAadResealed: true,
|
|
306
|
+
});
|
|
307
|
+
check("rotation: internal verify ok (AAD cells decrypt under new root)",
|
|
308
|
+
!!rot.verifyResult && rot.verifyResult.ok === true);
|
|
309
|
+
check("rotation: processed at least the wrapped-secret + the data cell",
|
|
310
|
+
rot.totalRowsProcessed >= 1);
|
|
311
|
+
|
|
312
|
+
// Swap staging -> dataDir, re-open under the NEW keypair.
|
|
313
|
+
["db.enc", "db.key.enc", "vault.key"].forEach(function (f) {
|
|
314
|
+
var s = path.join(staging, f);
|
|
315
|
+
if (fs.existsSync(s)) fs.copyFileSync(s, path.join(dirA, f));
|
|
316
|
+
});
|
|
317
|
+
try { fs.rmSync(path.join(dirA, "tmpfs"), { recursive: true, force: true }); } catch (_e) {}
|
|
318
|
+
|
|
319
|
+
await _reset();
|
|
320
|
+
await b.vault.init({ dataDir: dirA, mode: "plaintext" });
|
|
321
|
+
check("rotation: vault now live under the NEW keypair",
|
|
322
|
+
JSON.stringify(JSON.parse(b.vault.getKeysJson())) === JSON.stringify(newKeys));
|
|
323
|
+
await b.db.init({
|
|
324
|
+
dataDir: dirA, tmpDir: path.join(dirA, "tmpfs"), atRest: "encrypted",
|
|
325
|
+
auditSigning: false, schema: KEYED_SCHEMA,
|
|
326
|
+
});
|
|
327
|
+
b.cryptoField.clearResidencyForTest();
|
|
328
|
+
b.cryptoField.declarePerRowKey("pr_keyed", { keySize: 32 });
|
|
329
|
+
b.cryptoField.declarePerRowResidency("pr_keyed", {
|
|
330
|
+
residencyColumn: "dataRegion", allowedTags: ["eu", "us", "global"],
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
var got = b.db.from("pr_keyed").where({ _id: "rot-1" }).first();
|
|
334
|
+
check("rotation: vault.row: cell decrypts after rotation under the new keypair",
|
|
335
|
+
!!got && got.ssn === "555-44-3333" && got.note === "rotate me");
|
|
336
|
+
await b.db.close();
|
|
337
|
+
} finally {
|
|
338
|
+
await _reset();
|
|
339
|
+
try { b.cryptoField.clearResidencyForTest(); } catch (_e) {}
|
|
340
|
+
[dirNew, dirA, staging].forEach(function (d) {
|
|
341
|
+
try { fs.rmSync(d, { recursive: true, force: true }); } catch (_e) {}
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
module.exports = { run: run };
|
|
347
|
+
if (require.main === module) {
|
|
348
|
+
run().then(function () { process.exit(0); })
|
|
349
|
+
.catch(function (err) { process.exitCode = 1; throw err; });
|
|
350
|
+
}
|
|
@@ -5,12 +5,14 @@
|
|
|
5
5
|
* A DB-write attacker who can write `vault:<crafted>` / `vault.aad:<…>`
|
|
6
6
|
* payloads to sealed columns can force KEM-decapsulation / AEAD-verify on
|
|
7
7
|
* attacker-controlled bytes on every read. unsealRow already nulls the
|
|
8
|
-
* field + emits system.crypto.unseal_failed
|
|
9
|
-
*
|
|
10
|
-
* per-(actor, table, column) sliding-window failure cap: past `threshold`
|
|
8
|
+
* field + emits system.crypto.unseal_failed; the per-(actor, table,
|
|
9
|
+
* column) sliding-window failure cap bounds the oracle: past `threshold`
|
|
11
10
|
* failures inside `windowMs`, further unseal attempts for that tuple are
|
|
12
11
|
* refused for `cooldownMs` with a typed CryptoFieldRateError + a distinct
|
|
13
|
-
* system.crypto.unseal_rate_exceeded audit.
|
|
12
|
+
* system.crypto.unseal_rate_exceeded audit. The cap is ON BY DEFAULT
|
|
13
|
+
* (v0.15.0) — armed at module load — so the oracle is bounded out of the
|
|
14
|
+
* box; configureUnsealRateCap tunes the thresholds, and
|
|
15
|
+
* configureUnsealRateCap(null) is the documented opt-out to audit-only.
|
|
14
16
|
*
|
|
15
17
|
* Every window / cooldown assertion drives an INJECTED clock — no real
|
|
16
18
|
* sleeps — so the test is deterministic on contended runners.
|
|
@@ -57,14 +59,30 @@ async function run() {
|
|
|
57
59
|
throws(/unknown|allowed|cryptoField.configureUnsealRateCap/i,
|
|
58
60
|
function () { b.cryptoField.configureUnsealRateCap({ threshold: 3, bogus: 1 }); }));
|
|
59
61
|
|
|
60
|
-
// ---- default
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
// ---- default ON (v0.15.0): the cap is armed at module load, so a
|
|
63
|
+
// forged-ciphertext oracle is bounded with no operator action. The
|
|
64
|
+
// default threshold (10) trips well before 50 hammered unseals. ----
|
|
65
|
+
b.cryptoField.clearRateCapForTest(); // restores the secure DEFAULT cap
|
|
66
|
+
var defaultThrew = false;
|
|
63
67
|
for (var k = 0; k < 50; k++) {
|
|
64
|
-
try { b.cryptoField.unsealRow("cf_ratecap", { id: "r1", secret: FORGED }, "attacker"); }
|
|
68
|
+
try { b.cryptoField.unsealRow("cf_ratecap", { id: "r1", secret: FORGED }, "default-attacker"); }
|
|
69
|
+
catch (e) {
|
|
70
|
+
if (e.code === "crypto-field/unseal-rate-exceeded") defaultThrew = true;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
check("cap default-ON: forged-unseal oracle is bounded out of the box",
|
|
74
|
+
defaultThrew === true);
|
|
75
|
+
|
|
76
|
+
// ---- documented opt-out: configureUnsealRateCap(null) restores the
|
|
77
|
+
// pre-v0.15.0 audit-only behaviour (no throw on a forged read). ----
|
|
78
|
+
b.cryptoField.configureUnsealRateCap(null);
|
|
79
|
+
var offThrew = false;
|
|
80
|
+
for (var k2 = 0; k2 < 50; k2++) {
|
|
81
|
+
try { b.cryptoField.unsealRow("cf_ratecap", { id: "r1", secret: FORGED }, "optout-attacker"); }
|
|
65
82
|
catch (_e) { offThrew = true; }
|
|
66
83
|
}
|
|
67
|
-
check("
|
|
84
|
+
check("opt-out (configureUnsealRateCap(null)): 50 forged unseals never throw",
|
|
85
|
+
offThrew === false);
|
|
68
86
|
|
|
69
87
|
// ---- injected clock + audit sink ----
|
|
70
88
|
var nowMs = 1000000;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// #116: crypto-field's upgrade-on-read durable rewrite (re-hash a legacy
|
|
3
|
+
// salted-sha3 derived-hash column to the keyed MAC) hardcoded
|
|
4
|
+
// dialect: "sqlite" when building the UPDATE, ignoring the actual handle's
|
|
5
|
+
// dialect. The local b.db is sqlite, so it works there — but unsealRow accepts
|
|
6
|
+
// a caller-supplied dbHandle (db-query threads an external Postgres / MySQL
|
|
7
|
+
// handle through the read path). On a MySQL handle the sqlite-dialected UPDATE
|
|
8
|
+
// emits double-quoted identifiers ("users"), which MySQL parses as a string
|
|
9
|
+
// literal and rejects, so the durable re-hash is swallowed (try/catch) and the
|
|
10
|
+
// legacy digest stays on disk forever — the advertised keyed-MAC migration
|
|
11
|
+
// silently never happens off sqlite.
|
|
12
|
+
//
|
|
13
|
+
// RED on the buggy tree: the rewrite SQL is sqlite-quoted ("users") even when
|
|
14
|
+
// the handle declares dialect "mysql". GREEN after the fix: it is backtick-
|
|
15
|
+
// quoted (`users`) — the handle's dialect drives the builder.
|
|
16
|
+
|
|
17
|
+
var helpers = require("../helpers");
|
|
18
|
+
var b = helpers.b;
|
|
19
|
+
var check = helpers.check;
|
|
20
|
+
var fs = require("fs");
|
|
21
|
+
var os = require("os");
|
|
22
|
+
var path = require("path");
|
|
23
|
+
var { setupTestDb, teardownTestDb } = require("../helpers/db");
|
|
24
|
+
|
|
25
|
+
async function run() {
|
|
26
|
+
var dir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-cf-dialect-"));
|
|
27
|
+
try {
|
|
28
|
+
await setupTestDb(dir);
|
|
29
|
+
|
|
30
|
+
var email = "Alice@Example.com";
|
|
31
|
+
var keyed = b.cryptoField.lookupHash("users", "email", email);
|
|
32
|
+
var legacyHash = keyed.legacyValue;
|
|
33
|
+
|
|
34
|
+
// Forge a legacy-indexed row (the derived-hash column holds the salted
|
|
35
|
+
// digest a pre-v0.15.0 row carries), mirroring crypto-field-dual-read.
|
|
36
|
+
var sealed = b.cryptoField.sealRow("users", { _id: "u-mysql", email: email, name: "Alice" });
|
|
37
|
+
sealed.emailHash = legacyHash;
|
|
38
|
+
b.db.prepare(
|
|
39
|
+
'INSERT INTO "users" ("_id","email","emailHash","name") VALUES (?,?,?,?)'
|
|
40
|
+
).run(sealed._id, sealed.email, sealed.emailHash, sealed.name);
|
|
41
|
+
|
|
42
|
+
var rawRow = b.db.prepare('SELECT * FROM "users" WHERE _id = ?').get("u-mysql");
|
|
43
|
+
|
|
44
|
+
// A caller-supplied handle that DECLARES the MySQL dialect (as an external
|
|
45
|
+
// Postgres/MySQL handle does) and captures the durable-rewrite SQL. The
|
|
46
|
+
// statement is a no-op runner — we only assert the dialect of the emitted
|
|
47
|
+
// identifiers, not execute against a real MySQL.
|
|
48
|
+
var capturedSql = null;
|
|
49
|
+
var mysqlHandle = {
|
|
50
|
+
dialect: "mysql",
|
|
51
|
+
prepare: function (sqlText) {
|
|
52
|
+
capturedSql = sqlText;
|
|
53
|
+
return { run: function () { return { changes: 1 }; } };
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
var unsealed = b.cryptoField.unsealRow("users", rawRow, "actor-1", mysqlHandle);
|
|
58
|
+
check("unsealRow still decrypts the sealed email (handle independent of crypto)",
|
|
59
|
+
unsealed.email === email);
|
|
60
|
+
check("upgrade-on-read fired: a durable rewrite UPDATE was built", capturedSql !== null);
|
|
61
|
+
check("#116 the rewrite UPDATE honors the handle's MySQL dialect (backtick-quoted)",
|
|
62
|
+
capturedSql && capturedSql.indexOf("`users`") !== -1);
|
|
63
|
+
check("#116 the rewrite UPDATE does NOT emit sqlite double-quoted identifiers on a MySQL handle",
|
|
64
|
+
capturedSql && capturedSql.indexOf("\"users\"") === -1);
|
|
65
|
+
|
|
66
|
+
console.log("OK — crypto-field upgrade-on-read dialect tests");
|
|
67
|
+
} finally {
|
|
68
|
+
await teardownTestDb(dir);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = { run: run };
|
|
73
|
+
if (require.main === module) {
|
|
74
|
+
run().then(function () { process.exit(0); })
|
|
75
|
+
.catch(function (err) { process.exitCode = 1; throw err; });
|
|
76
|
+
}
|