@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
|
@@ -0,0 +1,639 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Live MySQL coverage for the b.sql data layer of:
|
|
4
|
+
*
|
|
5
|
+
* lib/audit.js — record() → chain-writer, checkpoint(),
|
|
6
|
+
* _upsertAuditTip() fencing-token guard (the MySQL
|
|
7
|
+
* ON DUPLICATE KEY UPDATE + IF-fold + readback path),
|
|
8
|
+
* verify(), verifyCheckpoints()
|
|
9
|
+
* lib/audit-tools.js — exportSlice / verifyBundle reading the live MySQL
|
|
10
|
+
* audit_log + the purge-anchor UPSERT
|
|
11
|
+
* lib/chain-writer.js — _insertRow / counter primer / tip read on MySQL
|
|
12
|
+
* lib/break-glass.js — policy.set/get/list (ON DUPLICATE KEY UPSERT) +
|
|
13
|
+
* grant + unsealRow consume (the backtick-quoted
|
|
14
|
+
* rowsConsumed increment), routed to live MySQL
|
|
15
|
+
* lib/crypto-field.js — a K_row (vault.row:) sealed cell stored as TEXT in
|
|
16
|
+
* MySQL and read back + derived-hash dual-read
|
|
17
|
+
*
|
|
18
|
+
* Each of these files threads { dialect: clusterStorage.dialect() } into
|
|
19
|
+
* every framework-table b.sql call, so in cluster mode against a MySQL
|
|
20
|
+
* backend the emitted SQL is backtick-quoted with ON DUPLICATE KEY UPDATE —
|
|
21
|
+
* what MySQL accepts. Defaulting to "sqlite" emitted double-quoted
|
|
22
|
+
* identifiers (string literals on MySQL) + ON CONFLICT (a syntax error),
|
|
23
|
+
* which is what this file proves is no longer the case.
|
|
24
|
+
*
|
|
25
|
+
* The driver is a docker-exec mysql shim (per-statement, like the
|
|
26
|
+
* data-layer-cluster-mysql file). None of the five files under test use
|
|
27
|
+
* clusterStorage.transaction, so the per-statement driver is sufficient.
|
|
28
|
+
*
|
|
29
|
+
* RUN: node scripts/test-integration.js --skip-service-check audit-stack-mysql
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
var execFileSync = require("node:child_process").execFileSync;
|
|
33
|
+
var fs = require("node:fs");
|
|
34
|
+
var os = require("node:os");
|
|
35
|
+
var path = require("node:path");
|
|
36
|
+
|
|
37
|
+
var helpers = require("../helpers");
|
|
38
|
+
var check = helpers.check;
|
|
39
|
+
var services = require("../helpers/services");
|
|
40
|
+
var setupTestDb = require("../helpers/db").setupTestDb;
|
|
41
|
+
var teardownTestDb = require("../helpers/db").teardownTestDb;
|
|
42
|
+
var b = require("../../");
|
|
43
|
+
|
|
44
|
+
var CONTAINER = "blamejs-test-mysql";
|
|
45
|
+
var DB_NAME = "blamejs_audit_mysql_test";
|
|
46
|
+
|
|
47
|
+
// ---- one-shot mysql (setup / teardown / out-of-band assertions) ----
|
|
48
|
+
function _mysqlRoot(sql, dbName) {
|
|
49
|
+
var args = ["exec", "-i", CONTAINER, "mysql", "-uroot", "-pblamejs_test_root", "--batch", "--raw"];
|
|
50
|
+
if (dbName) args.push(dbName);
|
|
51
|
+
args.push("-e", sql);
|
|
52
|
+
return execFileSync("docker", args, { stdio: ["pipe", "pipe", "pipe"] }).toString("utf8");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// --batch output: tab-separated, header row, "NULL" sentinel. Every cell is
|
|
56
|
+
// text (BIGINT included) — coerceRow's job on the framework readback.
|
|
57
|
+
function _parseBatch(out) {
|
|
58
|
+
var lines = out.split(/\r?\n/).filter(function (l) { return l.length > 0; });
|
|
59
|
+
if (lines.length < 1) return { rows: [] };
|
|
60
|
+
var headers = lines[0].split("\t");
|
|
61
|
+
var rows = [];
|
|
62
|
+
for (var i = 1; i < lines.length; i++) {
|
|
63
|
+
var cells = lines[i].split("\t");
|
|
64
|
+
var row = {};
|
|
65
|
+
for (var j = 0; j < headers.length; j++) {
|
|
66
|
+
var v = cells[j];
|
|
67
|
+
row[headers[j]] = (v === "NULL" || v === undefined) ? null : v;
|
|
68
|
+
}
|
|
69
|
+
rows.push(row);
|
|
70
|
+
}
|
|
71
|
+
return { rows: rows };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function _countMysql(table, whereClause) {
|
|
75
|
+
var sql = "SELECT count(*) AS n FROM " + table + (whereClause ? " WHERE " + whereClause : "");
|
|
76
|
+
var parsed = _parseBatch(_mysqlRoot(sql, DB_NAME));
|
|
77
|
+
return parsed.rows[0] ? Number(parsed.rows[0].n) : 0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function _scalar(sql) {
|
|
81
|
+
var parsed = _parseBatch(_mysqlRoot(sql, DB_NAME));
|
|
82
|
+
if (!parsed.rows[0]) return null;
|
|
83
|
+
var k = Object.keys(parsed.rows[0])[0];
|
|
84
|
+
return parsed.rows[0][k];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ---- docker-exec mysql driver (faithful to a text-protocol driver) ----
|
|
88
|
+
// SQL is piped over STDIN, NOT passed as an `-e` argument: a sealed cell can
|
|
89
|
+
// push a single INSERT past the OS command-line length limit (ENAMETOOLONG),
|
|
90
|
+
// whereas a real protocol driver streams it. STDIN keeps the shim faithful at
|
|
91
|
+
// any statement size.
|
|
92
|
+
function _exec(sql) {
|
|
93
|
+
try {
|
|
94
|
+
return execFileSync("docker",
|
|
95
|
+
["exec", "-i", CONTAINER, "mysql", "-uroot", "-pblamejs_test_root",
|
|
96
|
+
"--batch", "--raw", DB_NAME],
|
|
97
|
+
{ input: sql + "\n", stdio: ["pipe", "pipe", "pipe"] }).toString("utf8");
|
|
98
|
+
} catch (e) {
|
|
99
|
+
var msg = e.stderr ? e.stderr.toString("utf8") : (e.message || String(e));
|
|
100
|
+
var errLine = (msg.split(/\r?\n/).filter(function (l) { return /ERROR \d+/.test(l); })[0]) || msg.trim();
|
|
101
|
+
var err = new Error(errLine.trim());
|
|
102
|
+
var m = /ERROR (\d+) \(([0-9A-Za-z]{5})\)/.exec(msg);
|
|
103
|
+
if (m) { err.errno = Number(m[1]); err.code = m[2]; err.sqlState = m[2]; }
|
|
104
|
+
throw err;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function _bindParams(sql, params) {
|
|
109
|
+
var i = 0;
|
|
110
|
+
return sql.replace(/\?/g, function () {
|
|
111
|
+
if (i >= params.length) throw new Error("placeholder/param count mismatch");
|
|
112
|
+
var p = params[i++];
|
|
113
|
+
if (p === null || p === undefined) return "NULL";
|
|
114
|
+
if (Buffer.isBuffer(p)) return "x'" + p.toString("hex") + "'"; // BLOB literal (nonce / signature)
|
|
115
|
+
if (typeof p === "number") return String(p);
|
|
116
|
+
if (typeof p === "boolean") return p ? "1" : "0";
|
|
117
|
+
return "'" + String(p).replace(/\\/g, "\\\\").replace(/'/g, "''") + "'";
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// A real mysql2 driver parses the binary protocol, so a BLOB comes back as a
|
|
122
|
+
// Buffer and a TEXT cell carrying embedded newlines / tabs round-trips
|
|
123
|
+
// intact. The docker-exec `--batch --raw` shim emits cells verbatim into a
|
|
124
|
+
// TSV, so (a) binary BLOBs (nonce / signature) corrupt the stream and (b) a
|
|
125
|
+
// sealed `vault:` text cell containing literal newlines shatters one row
|
|
126
|
+
// across several TSV lines — column misalignment that breaks the chain-hash
|
|
127
|
+
// recompute. Emulate the live driver faithfully: for a `SELECT * FROM
|
|
128
|
+
// <framework table>`, expand the star to an explicit projection that
|
|
129
|
+
// HEX()-encodes EVERY column (so no cell can contain a tab or newline), then
|
|
130
|
+
// decode each cell by type — blob columns to Buffers, everything else to a
|
|
131
|
+
// UTF-8 string (coerceRows then turns BIGINT strings into numbers). Keeps the
|
|
132
|
+
// test honest (bytes in == bytes out, like mysql2) without touching any
|
|
133
|
+
// framework SQL.
|
|
134
|
+
// Per-column metadata: { name, kind } where kind is "blob" | "text" |
|
|
135
|
+
// "numeric". HEX() of a NUMERIC column returns the hex of the integer value
|
|
136
|
+
// (HEX(255) -> "FF"), NOT the hex of its ASCII digits — so numeric columns
|
|
137
|
+
// must NOT be HEX-wrapped (they carry no tabs/newlines and round-trip as
|
|
138
|
+
// plain text the framework's coerceRows turns back into numbers). Only
|
|
139
|
+
// text/blob columns get HEX-encoded (they may carry sealed bytes / embedded
|
|
140
|
+
// newlines that would shatter the TSV).
|
|
141
|
+
var _COLMETA_CACHE = {}; // table -> [{ name, kind }]
|
|
142
|
+
function _columnMeta(table) {
|
|
143
|
+
if (_COLMETA_CACHE[table] !== undefined) return _COLMETA_CACHE[table];
|
|
144
|
+
var out = _mysqlRoot(
|
|
145
|
+
"SELECT column_name, data_type FROM information_schema.columns " +
|
|
146
|
+
"WHERE table_schema = '" + DB_NAME + "' AND table_name = '" + table + "' " +
|
|
147
|
+
"ORDER BY ordinal_position", DB_NAME);
|
|
148
|
+
var meta = _parseBatch(out).rows.map(function (r) {
|
|
149
|
+
var name = r.column_name || r.COLUMN_NAME;
|
|
150
|
+
var dt = (r.data_type || r.DATA_TYPE || "").toLowerCase();
|
|
151
|
+
var kind;
|
|
152
|
+
if (/(longblob|mediumblob|tinyblob|blob|varbinary|binary)/.test(dt)) kind = "blob";
|
|
153
|
+
else if (/(int|decimal|numeric|float|double|bit|year)/.test(dt)) kind = "numeric";
|
|
154
|
+
else kind = "text";
|
|
155
|
+
return { name: name, kind: kind };
|
|
156
|
+
});
|
|
157
|
+
_COLMETA_CACHE[table] = meta;
|
|
158
|
+
return meta;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function _makeDockerMysqlDriver() {
|
|
162
|
+
return {
|
|
163
|
+
connect: async function () { return { id: 1 }; },
|
|
164
|
+
query: async function (_client, sql, params) {
|
|
165
|
+
params = params || [];
|
|
166
|
+
var bound = _bindParams(sql, params);
|
|
167
|
+
var t = bound.trim();
|
|
168
|
+
if (/^(CREATE|ALTER|INSERT|UPDATE|DELETE|DROP|REPLACE|TRUNCATE)\b/i.test(t)) {
|
|
169
|
+
var stmt = bound.replace(/;\s*$/, "");
|
|
170
|
+
var ar = _exec(stmt + "; SELECT ROW_COUNT() AS n");
|
|
171
|
+
var parsed = _parseBatch(ar);
|
|
172
|
+
var n = parsed.rows[0] ? Number(parsed.rows[0].n) : 0;
|
|
173
|
+
if (!isFinite(n) || n < 0) n = 0;
|
|
174
|
+
return { rows: [], affectedRows: n, rowCount: n };
|
|
175
|
+
}
|
|
176
|
+
// Newline/binary-safe SELECT *: HEX-encode every column so the TSV can
|
|
177
|
+
// never be shattered by an embedded tab/newline (sealed vault: cells),
|
|
178
|
+
// then decode by type. Only the `SELECT * FROM <table>` shape needs it;
|
|
179
|
+
// explicit-projection framework reads never project a blob/multiline
|
|
180
|
+
// cell.
|
|
181
|
+
var meta = null;
|
|
182
|
+
var starMatch = /^SELECT \* FROM `?([A-Za-z0-9_]+)`?\b/i.exec(bound);
|
|
183
|
+
if (starMatch) {
|
|
184
|
+
meta = _columnMeta(starMatch[1]);
|
|
185
|
+
if (meta.length > 0) {
|
|
186
|
+
var proj = meta.map(function (m) {
|
|
187
|
+
// Numeric columns pass through raw (HEX of a number is the hex of
|
|
188
|
+
// its value, not its digits); text/blob get HEX-encoded so binary
|
|
189
|
+
// / embedded newlines survive the TSV.
|
|
190
|
+
return m.kind === "numeric"
|
|
191
|
+
? "`" + m.name + "`"
|
|
192
|
+
: "HEX(`" + m.name + "`) AS `" + m.name + "`";
|
|
193
|
+
}).join(", ");
|
|
194
|
+
bound = bound.replace(/^SELECT \*/i, "SELECT " + proj);
|
|
195
|
+
} else {
|
|
196
|
+
meta = null;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
var parsedSel = _parseBatch(_exec(bound));
|
|
200
|
+
if (meta) {
|
|
201
|
+
var byName = {};
|
|
202
|
+
for (var mi = 0; mi < meta.length; mi++) byName[meta[mi].name] = meta[mi];
|
|
203
|
+
for (var i = 0; i < parsedSel.rows.length; i++) {
|
|
204
|
+
var row = parsedSel.rows[i];
|
|
205
|
+
for (var k in row) {
|
|
206
|
+
if (!Object.prototype.hasOwnProperty.call(row, k)) continue;
|
|
207
|
+
var cell = row[k];
|
|
208
|
+
if (cell === null || cell === undefined) continue;
|
|
209
|
+
var m = byName[k];
|
|
210
|
+
if (!m || m.kind === "numeric") continue; // numeric passes through (coerceRows handles it)
|
|
211
|
+
var buf = Buffer.from(String(cell), "hex");
|
|
212
|
+
row[k] = m.kind === "blob" ? buf : buf.toString("utf8");
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return { rows: parsedSel.rows, rowCount: parsedSel.rows.length };
|
|
217
|
+
},
|
|
218
|
+
close: async function () { /* no-op */ },
|
|
219
|
+
dialect: "mysql",
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Framework + app tables this test owns. Dropped at setup + teardown so a
|
|
224
|
+
// re-run is clean and other live tests don't collide.
|
|
225
|
+
var OWNED_TABLES = [
|
|
226
|
+
"_blamejs_audit_log", "_blamejs_consent_log", "_blamejs_audit_checkpoints",
|
|
227
|
+
"_blamejs_audit_tip", "_blamejs_consent_tip", "_blamejs_audit_purge_anchor",
|
|
228
|
+
"_blamejs_break_glass_policies", "_blamejs_break_glass_grants",
|
|
229
|
+
"_blamejs_leader", "_blamejs_cluster_state",
|
|
230
|
+
"patients", "krow_demo",
|
|
231
|
+
];
|
|
232
|
+
|
|
233
|
+
function _dropOwned() {
|
|
234
|
+
for (var i = 0; i < OWNED_TABLES.length; i++) {
|
|
235
|
+
try { _mysqlRoot("DROP TABLE IF EXISTS `" + OWNED_TABLES[i] + "`", DB_NAME); } catch (_e) {}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async function run() {
|
|
240
|
+
var svc = await services.requireService("mysql");
|
|
241
|
+
if (!svc.ok) throw new Error("mysql unreachable: " + svc.reason);
|
|
242
|
+
|
|
243
|
+
_mysqlRoot("CREATE DATABASE IF NOT EXISTS " + DB_NAME);
|
|
244
|
+
_dropOwned();
|
|
245
|
+
|
|
246
|
+
var tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-audit-my-"));
|
|
247
|
+
var driver = _makeDockerMysqlDriver();
|
|
248
|
+
|
|
249
|
+
try {
|
|
250
|
+
// Boot the framework: vault + local SQLite + cryptoField schema
|
|
251
|
+
// registration for the framework tables. The glass-locked "patients"
|
|
252
|
+
// app table is sealed for the break-glass + crypto-field sections.
|
|
253
|
+
await setupTestDb(tmpDir, [
|
|
254
|
+
{
|
|
255
|
+
name: "patients",
|
|
256
|
+
columns: {
|
|
257
|
+
_id: "TEXT PRIMARY KEY", mrn: "TEXT", ssn: "TEXT",
|
|
258
|
+
residency: "TEXT", notes: "TEXT",
|
|
259
|
+
},
|
|
260
|
+
sealedFields: ["ssn", "notes"],
|
|
261
|
+
},
|
|
262
|
+
]);
|
|
263
|
+
|
|
264
|
+
// External MySQL backend + cluster mode. ensureSchema now emits MySQL DDL
|
|
265
|
+
// (backtick identifiers, BIGINT, ON DUPLICATE KEY); cluster.init flips
|
|
266
|
+
// clusterStorage to route framework SQL to the external MySQL backend.
|
|
267
|
+
b.externalDb.init({
|
|
268
|
+
backends: {
|
|
269
|
+
ops: {
|
|
270
|
+
connect: driver.connect, query: driver.query, close: driver.close,
|
|
271
|
+
dialect: "mysql",
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
});
|
|
275
|
+
await b.frameworkSchema.ensureSchema({ externalDbBackend: "ops", dialect: "mysql" });
|
|
276
|
+
check("ensureSchema created the framework tables on real MySQL (audit + break-glass)",
|
|
277
|
+
_countMysql("information_schema.tables",
|
|
278
|
+
"table_schema = '" + DB_NAME + "' AND table_name = '_blamejs_break_glass_grants'") === 1);
|
|
279
|
+
|
|
280
|
+
await b.cluster.init({
|
|
281
|
+
nodeId: "audit-stack-my",
|
|
282
|
+
role: "leader",
|
|
283
|
+
externalDbBackend: "ops",
|
|
284
|
+
dialect: "mysql",
|
|
285
|
+
});
|
|
286
|
+
check("cluster.init acquired leadership on real MySQL (gates every chain append)",
|
|
287
|
+
b.cluster.isLeader() === true);
|
|
288
|
+
check("framework is in cluster mode → framework SQL routes to external MySQL",
|
|
289
|
+
b.clusterStorage.dialect() === "mysql" &&
|
|
290
|
+
b.clusterStorage.tableName("audit_log") === "_blamejs_audit_log");
|
|
291
|
+
|
|
292
|
+
await _testAuditRecordAndChain();
|
|
293
|
+
await _testCheckpointAndFence();
|
|
294
|
+
await _testCoercionFidelity();
|
|
295
|
+
await _testAuditToolsBundle(tmpDir);
|
|
296
|
+
await _testBreakGlass();
|
|
297
|
+
await _testCryptoFieldKRowRoundTrip();
|
|
298
|
+
await _testDerivedHashDualRead();
|
|
299
|
+
await _testTamperDetection();
|
|
300
|
+
|
|
301
|
+
} finally {
|
|
302
|
+
try { await b.cluster.shutdown(); } catch (_e) {}
|
|
303
|
+
try { await b.externalDb.shutdown(); } catch (_e) {}
|
|
304
|
+
try { await teardownTestDb(tmpDir); } catch (_e) {}
|
|
305
|
+
_dropOwned();
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// ====================================================================
|
|
310
|
+
// 1. audit.record() → chain-writer._insertRow on real MySQL. The whole
|
|
311
|
+
// primitive: counter primer (MAX), tip read, seal, computeRowHash,
|
|
312
|
+
// backtick-quoted INSERT, then verify(). A correct chain verifies ok:true.
|
|
313
|
+
// ====================================================================
|
|
314
|
+
async function _testAuditRecordAndChain() {
|
|
315
|
+
var events = [
|
|
316
|
+
{ action: "system.boot", outcome: "success" },
|
|
317
|
+
{ action: "auth.login.success", outcome: "success", actor: { userId: "u-1", ip: "10.0.0.7" } },
|
|
318
|
+
{ action: "consent.granted", outcome: "success",
|
|
319
|
+
actor: { userId: "u-2" }, resource: { kind: "purpose", id: "marketing" },
|
|
320
|
+
metadata: { region: "eu" } },
|
|
321
|
+
{ action: "system.shutdown", outcome: "success" },
|
|
322
|
+
];
|
|
323
|
+
var appended = [];
|
|
324
|
+
for (var i = 0; i < events.length; i++) appended.push(await b.audit.record(events[i]));
|
|
325
|
+
check("audit.record returned a monotonic counter per row (1..4) on MySQL",
|
|
326
|
+
appended[0].monotonicCounter === 1 && appended[3].monotonicCounter === 4);
|
|
327
|
+
check("audit.record landed 4 rows in _blamejs_audit_log on real MySQL",
|
|
328
|
+
_countMysql("`_blamejs_audit_log`", null) === 4);
|
|
329
|
+
|
|
330
|
+
var v = await b.audit.verify({});
|
|
331
|
+
check("audit.verify walks the live MySQL chain and returns ok:true", v.ok === true);
|
|
332
|
+
check("audit.verify counted every stored row (rowsVerified === 4)",
|
|
333
|
+
v.ok === true && v.rowsVerified === 4);
|
|
334
|
+
if (!v.ok) check("AUDIT-VERIFY DETAIL (mysql): '" + v.reason + "' at row " + v.breakAt, false);
|
|
335
|
+
|
|
336
|
+
// Counter primer: a fresh in-process chain-writer must read MAX from MySQL
|
|
337
|
+
// and continue at 5, not restart at 1.
|
|
338
|
+
b.audit._resetForTest();
|
|
339
|
+
var more = await b.audit.record({ action: "system.boot", outcome: "success" });
|
|
340
|
+
check("counter primer read MAX(monotonicCounter) from live MySQL (continued at 5)",
|
|
341
|
+
more.monotonicCounter === 5);
|
|
342
|
+
check("5 audit rows present after primer-continued append on MySQL",
|
|
343
|
+
_countMysql("`_blamejs_audit_log`", null) === 5);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// ====================================================================
|
|
347
|
+
// 2. audit.checkpoint() → _insertCheckpoint + _upsertAuditTip on MySQL.
|
|
348
|
+
// The tip UPSERT is the MySQL ON DUPLICATE KEY UPDATE + IF(<fence>,…)
|
|
349
|
+
// fold; FENCED_OUT detection reads the stored token back (no RETURNING on
|
|
350
|
+
// MySQL). A strictly-lower incoming token must be FENCED_OUT.
|
|
351
|
+
// ====================================================================
|
|
352
|
+
async function _testCheckpointAndFence() {
|
|
353
|
+
var ck = await b.audit.checkpoint({});
|
|
354
|
+
check("audit.checkpoint anchored the live MySQL chain tip (counter 5)",
|
|
355
|
+
ck && ck.atMonotonicCounter === 5);
|
|
356
|
+
check("checkpoint row landed in _blamejs_audit_checkpoints on MySQL",
|
|
357
|
+
_countMysql("`_blamejs_audit_checkpoints`", null) === 1);
|
|
358
|
+
|
|
359
|
+
check("_upsertAuditTip wrote the single audit-tip row on MySQL",
|
|
360
|
+
_countMysql("`_blamejs_audit_tip`", "`scope` = 'audit'") === 1);
|
|
361
|
+
check("audit-tip atMonotonicCounter matches the chain tip (5) on MySQL",
|
|
362
|
+
Number(_scalar("SELECT `atMonotonicCounter` FROM `_blamejs_audit_tip` WHERE `scope` = 'audit'")) === 5);
|
|
363
|
+
|
|
364
|
+
var vc = await b.audit.verifyCheckpoints();
|
|
365
|
+
check("audit.verifyCheckpoints returns ok:true against the live MySQL checkpoint",
|
|
366
|
+
vc.ok === true && vc.checkpointsVerified === 1);
|
|
367
|
+
if (!vc.ok) check("VERIFY-CHECKPOINTS DETAIL (mysql): '" + vc.reason + "'", false);
|
|
368
|
+
|
|
369
|
+
// Fencing guard. Raise the stored token directly to a high value, then a
|
|
370
|
+
// second checkpoint at the leader's (lower) fencing token must be
|
|
371
|
+
// FENCED_OUT — the MySQL IF-fold keeps the stored token, and the readback
|
|
372
|
+
// detection surfaces the FENCED_OUT.
|
|
373
|
+
var storedTok = Number(_scalar("SELECT `fencingToken` FROM `_blamejs_audit_tip` WHERE `scope`='audit'"));
|
|
374
|
+
var highTok = storedTok + 1000000;
|
|
375
|
+
_mysqlRoot("UPDATE `_blamejs_audit_tip` SET `fencingToken` = " + highTok + " WHERE `scope`='audit'", DB_NAME);
|
|
376
|
+
// Append a row so checkpoint has a new tip to anchor, then attempt the
|
|
377
|
+
// checkpoint — its _upsertAuditTip carries the leader's lower token.
|
|
378
|
+
await b.audit.record({ action: "system.boot", outcome: "success" });
|
|
379
|
+
var fencedErr = null;
|
|
380
|
+
try { await b.audit.checkpoint({}); } catch (e) { fencedErr = e; }
|
|
381
|
+
check("checkpoint at a lower fencing token is FENCED_OUT on MySQL (IF-fold + readback)",
|
|
382
|
+
fencedErr !== null && /FENCED_OUT/.test((fencedErr.code || "") + (fencedErr.message || "")));
|
|
383
|
+
check("the stored fencingToken stayed at the higher value (lower token did not overwrite)",
|
|
384
|
+
Number(_scalar("SELECT `fencingToken` FROM `_blamejs_audit_tip` WHERE `scope`='audit'")) === highTok);
|
|
385
|
+
|
|
386
|
+
// Restore the leader's token so subsequent sections can checkpoint again.
|
|
387
|
+
_mysqlRoot("UPDATE `_blamejs_audit_tip` SET `fencingToken` = " + storedTok + " WHERE `scope`='audit'", DB_NAME);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// ====================================================================
|
|
391
|
+
// 3. Coercion fidelity: read an audit row back THROUGH clusterStorage and
|
|
392
|
+
// assert the normalized JS shape — MySQL --batch hands BIGINT as text,
|
|
393
|
+
// coerceRows must turn the framework int columns into JS numbers.
|
|
394
|
+
// ====================================================================
|
|
395
|
+
async function _testCoercionFidelity() {
|
|
396
|
+
var built = require("../../lib/sql").select("audit_log", { dialect: b.clusterStorage.dialect() })
|
|
397
|
+
.orderBy("monotonicCounter", "asc").limit(1).toSql();
|
|
398
|
+
var rows = await b.clusterStorage.executeAll(built.sql, built.params);
|
|
399
|
+
check("clusterStorage.executeAll read an audit row back from live MySQL", rows.length === 1);
|
|
400
|
+
var row = rows[0];
|
|
401
|
+
check("coercion (mysql): monotonicCounter is a JS number === 1",
|
|
402
|
+
typeof row.monotonicCounter === "number" && row.monotonicCounter === 1);
|
|
403
|
+
check("coercion (mysql): recordedAt BIGINT coerced to a JS number",
|
|
404
|
+
typeof row.recordedAt === "number");
|
|
405
|
+
check("coercion (mysql): rowHash stays a string under the camelCase key",
|
|
406
|
+
typeof row.rowHash === "string" && row.rowHash.length > 0);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// ====================================================================
|
|
410
|
+
// 4. audit-tools exportSlice → verifyBundle over the live MySQL audit_log
|
|
411
|
+
// via the default clusterStorage readers, then the purge-anchor UPSERT.
|
|
412
|
+
// ====================================================================
|
|
413
|
+
async function _testAuditToolsBundle(tmpDir) {
|
|
414
|
+
var pass = Buffer.from("audit-bundle-passphrase-not-secret-1234567890", "utf8");
|
|
415
|
+
var nRows = _countMysql("`_blamejs_audit_log`", null);
|
|
416
|
+
|
|
417
|
+
var exDir = path.join(tmpDir, "export-bundle-my");
|
|
418
|
+
var ex = await b.auditTools.exportSlice({ out: exDir, passphrase: pass });
|
|
419
|
+
check("audit-tools.exportSlice read the live MySQL chain + wrote a bundle",
|
|
420
|
+
ex.rowCount === nRows);
|
|
421
|
+
|
|
422
|
+
var exVerify = await b.auditTools.verifyBundle({ in: exDir, passphrase: pass });
|
|
423
|
+
check("audit-tools.verifyBundle round-trips the exported live-MySQL slice (ok:true)",
|
|
424
|
+
exVerify.ok === true && exVerify.rowsVerified === nRows);
|
|
425
|
+
if (!exVerify.ok) check("EXPORT-VERIFY DETAIL (mysql): '" + exVerify.reason + "'", false);
|
|
426
|
+
|
|
427
|
+
// The purge-anchor UPSERT (the external-DB piece of purge's default apply)
|
|
428
|
+
// through b.sql + clusterStorage must land on MySQL (ON DUPLICATE KEY).
|
|
429
|
+
await b.clusterStorage.execute(
|
|
430
|
+
"INSERT INTO `_blamejs_audit_purge_anchor` " +
|
|
431
|
+
"(`scope`,`lastPurgedCounter`,`lastPurgedRowHash`,`archiveBundleId`,`purgedAt`) " +
|
|
432
|
+
"VALUES ('audit', ?, ?, ?, ?) " +
|
|
433
|
+
"ON DUPLICATE KEY UPDATE `lastPurgedCounter`=VALUES(`lastPurgedCounter`), " +
|
|
434
|
+
"`lastPurgedRowHash`=VALUES(`lastPurgedRowHash`), " +
|
|
435
|
+
"`archiveBundleId`=VALUES(`archiveBundleId`), `purgedAt`=VALUES(`purgedAt`)",
|
|
436
|
+
[3, "anchor-hash", "bundle-1", Date.now()]);
|
|
437
|
+
var anchor = await b.clusterStorage.executeOne(
|
|
438
|
+
"SELECT `lastPurgedCounter`, `lastPurgedRowHash` FROM `_blamejs_audit_purge_anchor` WHERE `scope` = ?",
|
|
439
|
+
["audit"]);
|
|
440
|
+
check("purge anchor UPSERT through clusterStorage landed on MySQL + coerced counter BIGINT→number",
|
|
441
|
+
anchor && anchor.lastPurgedCounter === 3 && anchor.lastPurgedRowHash === "anchor-hash");
|
|
442
|
+
_mysqlRoot("DELETE FROM `_blamejs_audit_purge_anchor` WHERE `scope`='audit'", DB_NAME);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// ====================================================================
|
|
446
|
+
// 5. break-glass policy + grant + unsealRow consume — the whole flow on
|
|
447
|
+
// live MySQL: policy UPSERT (sealed, ON DUPLICATE KEY), policy.get/list,
|
|
448
|
+
// grant (TOTP verify → sealed grant INSERT with the derived hash NOT
|
|
449
|
+
// NULL), then unsealRow (grant fetch + backtick-quoted rowsConsumed++
|
|
450
|
+
// increment + glass-locked column unseal of a real MySQL-stored row).
|
|
451
|
+
// ====================================================================
|
|
452
|
+
async function _testBreakGlass() {
|
|
453
|
+
b.breakGlass.init({ trustProxy: false });
|
|
454
|
+
|
|
455
|
+
_mysqlRoot("CREATE TABLE IF NOT EXISTS `patients` (" +
|
|
456
|
+
"`_id` VARCHAR(64) PRIMARY KEY, `mrn` TEXT, `ssn` TEXT, " +
|
|
457
|
+
"`residency` TEXT, `notes` TEXT)", DB_NAME);
|
|
458
|
+
|
|
459
|
+
var patient = b.cryptoField.sealRow("patients", {
|
|
460
|
+
_id: "patient-001", mrn: "MRN-1", ssn: "123-45-6789",
|
|
461
|
+
residency: "eu", notes: "high blood pressure",
|
|
462
|
+
});
|
|
463
|
+
await b.clusterStorage.execute(
|
|
464
|
+
"INSERT INTO `patients` (`_id`,`mrn`,`ssn`,`residency`,`notes`) VALUES (?,?,?,?,?)",
|
|
465
|
+
[patient._id, patient.mrn, patient.ssn, patient.residency, patient.notes]);
|
|
466
|
+
check("break-glass (mysql): glass-locked ssn is stored SEALED (vault:-prefixed)",
|
|
467
|
+
/vault[:.]/.test(String(_scalar("SELECT `ssn` FROM `patients` WHERE `_id`='patient-001'"))));
|
|
468
|
+
|
|
469
|
+
var setRes = await b.breakGlass.policy.set("patients", {
|
|
470
|
+
columns: ["ssn", "notes"], factors: ["totp"],
|
|
471
|
+
grantTtl: b.constants.TIME.minutes(15), maxRowsPerGrant: 1,
|
|
472
|
+
reasonMinLength: 12, pinIp: false, sessionPin: false,
|
|
473
|
+
});
|
|
474
|
+
check("break-glass (mysql): policy.set UPSERT landed on MySQL", setRes.applied === true);
|
|
475
|
+
check("break-glass (mysql): one policy row physically present",
|
|
476
|
+
_countMysql("`_blamejs_break_glass_policies`", null) === 1);
|
|
477
|
+
|
|
478
|
+
var got = await b.breakGlass.policy.get("patients");
|
|
479
|
+
check("break-glass (mysql): policy.get reads + unseals the MySQL policy row",
|
|
480
|
+
got && got.table === "patients" && got.columns.length === 2 && got.columns.indexOf("ssn") !== -1);
|
|
481
|
+
check("break-glass (mysql): policy numeric fields coerced (grantTtl is a number)",
|
|
482
|
+
typeof got.grantTtl === "number" && got.grantTtl > 0);
|
|
483
|
+
|
|
484
|
+
var listed = await b.breakGlass.policy.list();
|
|
485
|
+
check("break-glass (mysql): policy.list enumerates the glass-locked table",
|
|
486
|
+
listed.length === 1 && listed[0].table === "patients");
|
|
487
|
+
|
|
488
|
+
var totpSecret = b.auth.totp.generateSecret();
|
|
489
|
+
var nowMs = Date.now();
|
|
490
|
+
var code = b.auth.totp.generate(totpSecret, { now: nowMs });
|
|
491
|
+
var req = {
|
|
492
|
+
user: { id: "dr-house", scopes: [] }, socket: { remoteAddress: "127.0.0.1" },
|
|
493
|
+
headers: { "user-agent": "test-agent" }, method: "POST", url: "/admin/break-glass",
|
|
494
|
+
};
|
|
495
|
+
var handle = await b.breakGlass.grant({
|
|
496
|
+
req: req, table: "patients", columns: ["ssn"],
|
|
497
|
+
reason: "ER admit verifying identity for patient-001",
|
|
498
|
+
factor: { type: "totp", secret: totpSecret, code: code, now: nowMs },
|
|
499
|
+
});
|
|
500
|
+
check("break-glass (mysql): grant minted after live TOTP verify",
|
|
501
|
+
handle && typeof handle.id === "string" && handle.id.indexOf("bg-") === 0);
|
|
502
|
+
check("break-glass (mysql): grant row physically landed",
|
|
503
|
+
_countMysql("`_blamejs_break_glass_grants`", null) === 1);
|
|
504
|
+
check("break-glass (mysql): issuedToActorHash NOT-NULL derived column populated",
|
|
505
|
+
String(_scalar("SELECT `issuedToActorHash` FROM `_blamejs_break_glass_grants` LIMIT 1") || "").length > 0);
|
|
506
|
+
|
|
507
|
+
var unsealed = await b.breakGlass.unsealRow(handle, "patients", "patient-001");
|
|
508
|
+
check("break-glass (mysql): unsealRow returned the decrypted glass-locked ssn",
|
|
509
|
+
unsealed && unsealed.ssn === "123-45-6789");
|
|
510
|
+
check("break-glass (mysql): atomic rowsConsumed++ persisted (backtick whereRaw fence)",
|
|
511
|
+
Number(_scalar("SELECT `rowsConsumed` FROM `_blamejs_break_glass_grants` LIMIT 1")) === 1);
|
|
512
|
+
|
|
513
|
+
var exhaustedErr = null;
|
|
514
|
+
try { await b.breakGlass.unsealRow(handle, "patients", "patient-001"); }
|
|
515
|
+
catch (e) { exhaustedErr = e; }
|
|
516
|
+
check("break-glass (mysql): second unseal refused — grant exhausted (row-by-row auth)",
|
|
517
|
+
exhaustedErr && /exhausted/i.test((exhaustedErr.code || "") + (exhaustedErr.message || "")));
|
|
518
|
+
|
|
519
|
+
// listActive / listActiveAll exercise the same backtick whereRaw fence.
|
|
520
|
+
var active = await b.breakGlass.listActiveAll({ table: "patients" });
|
|
521
|
+
check("break-glass (mysql): listActiveAll runs the backtick rowsConsumed<max fence (grant now exhausted → 0)",
|
|
522
|
+
Array.isArray(active) && active.length === 0);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// ====================================================================
|
|
526
|
+
// 6. crypto-field K_row (vault.row:) sealed cell stored on MySQL + read
|
|
527
|
+
// back, proving the typed codec survives a real TEXT round-trip. The
|
|
528
|
+
// wrapped row-secret lives in the LOCAL per-row-keys registry; the
|
|
529
|
+
// sealed CELL is what lands on MySQL.
|
|
530
|
+
// ====================================================================
|
|
531
|
+
async function _testCryptoFieldKRowRoundTrip() {
|
|
532
|
+
b.cryptoField.declarePerRowKey("krow_demo", { keySize: 32 });
|
|
533
|
+
b.cryptoField.registerTable("krow_demo", { sealedFields: ["secret", "blobCol", "objCol"] });
|
|
534
|
+
|
|
535
|
+
var rowId = "krow-row-1";
|
|
536
|
+
var kRow = b.cryptoField.materializePerRowKey("krow_demo", rowId, b.db);
|
|
537
|
+
check("crypto-field (mysql): materializePerRowKey produced a 32-byte K_row",
|
|
538
|
+
Buffer.isBuffer(kRow) && kRow.length === 32);
|
|
539
|
+
|
|
540
|
+
var origBuf = Buffer.from([0, 1, 2, 250, 251, 255]);
|
|
541
|
+
var origObj = { kind: "phi", level: 9 };
|
|
542
|
+
var sealed = b.cryptoField.sealRow("krow_demo",
|
|
543
|
+
{ _id: rowId, secret: "top-secret-string", blobCol: origBuf, objCol: origObj },
|
|
544
|
+
{ kRow: kRow, rowId: rowId });
|
|
545
|
+
check("crypto-field (mysql): sealRow under K_row emitted vault.row: cells",
|
|
546
|
+
b.cryptoField.isRowSealed(sealed.secret) && b.cryptoField.isRowSealed(sealed.blobCol) &&
|
|
547
|
+
b.cryptoField.isRowSealed(sealed.objCol));
|
|
548
|
+
|
|
549
|
+
_mysqlRoot("CREATE TABLE IF NOT EXISTS `krow_demo` (" +
|
|
550
|
+
"`_id` VARCHAR(64) PRIMARY KEY, `secret` TEXT, `blobCol` TEXT, `objCol` TEXT)", DB_NAME);
|
|
551
|
+
await b.clusterStorage.execute(
|
|
552
|
+
"INSERT INTO `krow_demo` (`_id`,`secret`,`blobCol`,`objCol`) VALUES (?,?,?,?)",
|
|
553
|
+
[rowId, sealed.secret, sealed.blobCol, sealed.objCol]);
|
|
554
|
+
|
|
555
|
+
var stored = await b.clusterStorage.executeOne(
|
|
556
|
+
"SELECT `_id`,`secret`,`blobCol`,`objCol` FROM `krow_demo` WHERE `_id` = ?", [rowId]);
|
|
557
|
+
check("crypto-field (mysql): vault.row: cells survived the MySQL TEXT round-trip intact",
|
|
558
|
+
stored.secret === sealed.secret && stored.blobCol === sealed.blobCol && stored.objCol === sealed.objCol);
|
|
559
|
+
|
|
560
|
+
// Unseal under K_row (the read path resolves the wrapped secret from the
|
|
561
|
+
// LOCAL per-row-keys registry) — typed codec restores original types.
|
|
562
|
+
var unsealed = b.cryptoField.unsealRow("krow_demo", stored, "svc", b.db);
|
|
563
|
+
check("crypto-field (mysql): K_row unseal restored the string value",
|
|
564
|
+
unsealed.secret === "top-secret-string");
|
|
565
|
+
check("crypto-field (mysql): K_row unseal restored the Buffer value byte-for-byte",
|
|
566
|
+
Buffer.isBuffer(unsealed.blobCol) && unsealed.blobCol.equals(origBuf));
|
|
567
|
+
check("crypto-field (mysql): K_row unseal restored the object value",
|
|
568
|
+
unsealed.objCol && unsealed.objCol.kind === "phi" && unsealed.objCol.level === 9);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// ====================================================================
|
|
572
|
+
// 7. crypto-field derived-hash dual-read on a row stored in MySQL. A row
|
|
573
|
+
// whose derived-hash column holds the LEGACY salted-sha3 digest is found
|
|
574
|
+
// via lookupHashCandidates' legacy member; reading it back through the
|
|
575
|
+
// framework leaves the keyed-MAC value the active lookup uses.
|
|
576
|
+
// ====================================================================
|
|
577
|
+
async function _testDerivedHashDualRead() {
|
|
578
|
+
b.cryptoField.registerTable("dh_my", {
|
|
579
|
+
sealedFields: ["email"],
|
|
580
|
+
derivedHashes: { emailHash: { from: "email", normalize: function (v) { return String(v).toLowerCase(); } } },
|
|
581
|
+
});
|
|
582
|
+
var email = "Carol@Example.com";
|
|
583
|
+
var lk = b.cryptoField.lookupHash("dh_my", "email", email);
|
|
584
|
+
check("derived-hash (mysql): active lookup is the keyed MAC (64 hex)", lk.value.length === 64);
|
|
585
|
+
check("derived-hash (mysql): legacyValue surfaced (128 hex)",
|
|
586
|
+
typeof lk.legacyValue === "string" && lk.legacyValue.length === 128);
|
|
587
|
+
var cands = b.cryptoField.lookupHashCandidates("dh_my", "email", email);
|
|
588
|
+
check("derived-hash (mysql): candidates carry BOTH digests (match-either)",
|
|
589
|
+
cands.values.length === 2 && cands.values.indexOf(lk.value) !== -1 &&
|
|
590
|
+
cands.values.indexOf(lk.legacyValue) !== -1);
|
|
591
|
+
|
|
592
|
+
// Forge a legacy-indexed row on MySQL.
|
|
593
|
+
_mysqlRoot("CREATE TABLE IF NOT EXISTS `dh_my` (" +
|
|
594
|
+
"`_id` VARCHAR(64) PRIMARY KEY, `email` TEXT, `emailHash` TEXT)", DB_NAME);
|
|
595
|
+
var sealed = b.cryptoField.sealRow("dh_my", { _id: "c-legacy", email: email });
|
|
596
|
+
sealed.emailHash = lk.legacyValue;
|
|
597
|
+
await b.clusterStorage.execute(
|
|
598
|
+
"INSERT INTO `dh_my` (`_id`,`email`,`emailHash`) VALUES (?,?,?)",
|
|
599
|
+
[sealed._id, sealed.email, sealed.emailHash]);
|
|
600
|
+
var foundLegacy = await b.clusterStorage.executeOne(
|
|
601
|
+
"SELECT `_id` FROM `dh_my` WHERE `emailHash` = ?", [lk.legacyValue]);
|
|
602
|
+
check("derived-hash (mysql): legacy-indexed row found via the legacy candidate hash",
|
|
603
|
+
foundLegacy && foundLegacy._id === "c-legacy");
|
|
604
|
+
_mysqlRoot("DROP TABLE IF EXISTS `dh_my`", DB_NAME);
|
|
605
|
+
b.cryptoField.clearForTest();
|
|
606
|
+
// Re-register the framework tables clearForTest dropped so later teardown
|
|
607
|
+
// (which seals/unseals through cryptoField) still has its schema.
|
|
608
|
+
// (setupTestDb registered them via db.init's FRAMEWORK_SCHEMA; clearForTest
|
|
609
|
+
// wiped the whole registry, so a fresh db.init-equivalent isn't available
|
|
610
|
+
// here — but teardown only closes the db, no further seal calls, so this is
|
|
611
|
+
// safe. Left as a note for maintainers.)
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// ====================================================================
|
|
615
|
+
// 8. Tamper detection on the live chain — mutate a hashed column, confirm
|
|
616
|
+
// verify reports ok:false. Meaningful only because the clean chain
|
|
617
|
+
// verified ok:true.
|
|
618
|
+
// ====================================================================
|
|
619
|
+
async function _testTamperDetection() {
|
|
620
|
+
// Drop the append-only WORM triggers (the privileged-DB-write attacker the
|
|
621
|
+
// chain defends against), mutate a hashed column, confirm verify catches it.
|
|
622
|
+
// Trigger names follow the framework's `no_update_<table>` / `no_delete_<table>`
|
|
623
|
+
// convention; drop both so the tampering UPDATE can land.
|
|
624
|
+
try { _mysqlRoot("DROP TRIGGER IF EXISTS `no_update__blamejs_audit_log`", DB_NAME); } catch (_e) {}
|
|
625
|
+
try { _mysqlRoot("DROP TRIGGER IF EXISTS `no_delete__blamejs_audit_log`", DB_NAME); } catch (_e) {}
|
|
626
|
+
_mysqlRoot("UPDATE `_blamejs_audit_log` SET `action` = 'auth.login.tampered' " +
|
|
627
|
+
"WHERE `monotonicCounter` = 2", DB_NAME);
|
|
628
|
+
var v = await b.audit.verify({});
|
|
629
|
+
check("audit.verify returns ok:false after a hashed column is tampered on MySQL", v.ok === false);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
module.exports = { run: run };
|
|
633
|
+
|
|
634
|
+
if (require.main === module) {
|
|
635
|
+
run().then(
|
|
636
|
+
function () { console.log("OK — " + helpers.getChecks() + " checks passed"); process.exit(0); },
|
|
637
|
+
function (e) { console.error("FAIL:", e.stack || e); process.exit(1); }
|
|
638
|
+
);
|
|
639
|
+
}
|