@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,196 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* b.audit.safeEmit scrubs secrets before the tamper-evident signed chain.
|
|
4
|
+
*
|
|
5
|
+
* safeEmit runs in request hot paths where operators routinely pass
|
|
6
|
+
* `metadata: { detail: e.message }` from a caught error — and those error
|
|
7
|
+
* strings carry DB connection strings (with passwords), JWTs, AWS
|
|
8
|
+
* access-key ids, PEM/private-key blocks, and SSNs. The documented
|
|
9
|
+
* guarantee (lib/audit.js safeEmit + the @primitive block) is that
|
|
10
|
+
* actor / reason / metadata pass through b.redact.redact() so those
|
|
11
|
+
* shapes are replaced with markers BEFORE the row is hash-chained and
|
|
12
|
+
* SLH-DSA-checkpointed. Once a secret lands in the append-only signed
|
|
13
|
+
* chain it cannot be UPDATE/DELETE'd out — so redaction has to happen at
|
|
14
|
+
* the write boundary, not after.
|
|
15
|
+
*
|
|
16
|
+
* The prior coverage only asserted safeEmit "never throws". This drives a
|
|
17
|
+
* secret-laden record end-to-end through the REAL path — full vault wrap,
|
|
18
|
+
* at-rest-encrypted db, audit hash chain — then reads the stored row back
|
|
19
|
+
* through b.audit.query (which UNSEALS the sealed columns: actorUserId /
|
|
20
|
+
* actorIp / reason / metadata). The unsealed plaintext is exactly what a
|
|
21
|
+
* forensic reader / auditor sees, so asserting the secret SUBSTRINGS are
|
|
22
|
+
* absent there (and replaced by redact markers) proves the guarantee
|
|
23
|
+
* against the actual stored ciphertext, not an in-memory copy.
|
|
24
|
+
*
|
|
25
|
+
* The secret values are placed under NON-sensitive field names (note /
|
|
26
|
+
* detail / dbDsn / personId / signingPem / accessKeyId) so the proof
|
|
27
|
+
* rests on redact's VALUE-SHAPE detectors (the real claim — a leaked
|
|
28
|
+
* secret is caught by its shape regardless of how the operator named the
|
|
29
|
+
* field), not on a lucky field-name match. The AWS *secret* access key
|
|
30
|
+
* (40-char, no value-shape) is placed under a `secret`-named field to
|
|
31
|
+
* exercise the field-name pass too.
|
|
32
|
+
*
|
|
33
|
+
* Run standalone: `node test/layer-0-primitives/audit-safeemit-redacts-secrets.test.js`
|
|
34
|
+
* Or via smoke: `node test/smoke.js`
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
var helpers = require("../helpers");
|
|
38
|
+
var b = helpers.b;
|
|
39
|
+
var fs = helpers.fs;
|
|
40
|
+
var os = helpers.os;
|
|
41
|
+
var path = helpers.path;
|
|
42
|
+
var check = helpers.check;
|
|
43
|
+
var waitUntil = helpers.waitUntil;
|
|
44
|
+
var setupTestDb = helpers.setupTestDb;
|
|
45
|
+
var teardownTestDb = helpers.teardownTestDb;
|
|
46
|
+
|
|
47
|
+
function _tmp() { return fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-audit-redact-")); }
|
|
48
|
+
|
|
49
|
+
// Clearly-fake but shape-valid secrets. Each is the WHOLE value of its
|
|
50
|
+
// field so redact's anchored value-shape detectors fire as designed.
|
|
51
|
+
var SECRETS = {
|
|
52
|
+
// postgres://user:pass@host/db — connection-string credential leak.
|
|
53
|
+
connString: "postgres://app_user:s3cr3t-Pw0rd@db.internal.example:5432/orders",
|
|
54
|
+
connPw: "s3cr3t-Pw0rd",
|
|
55
|
+
// JWS compact triplet (eyJ... . eyJ... . sig).
|
|
56
|
+
jwt: "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhbGljZSIsImFkbWluIjp0cnVlfQ.c2lnbmF0dXJlLWJ5dGVz",
|
|
57
|
+
// AWS access-key id shape (AKIA + 16 upper-alnum). Value-shape detector.
|
|
58
|
+
awsAccessKeyId: "AKIAIOSFODNN7EXAMPLE",
|
|
59
|
+
// AWS secret access key (40 base64-ish chars) — no value-shape; relies
|
|
60
|
+
// on the `secret`-named field-name pass.
|
|
61
|
+
awsSecretKey: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
|
|
62
|
+
// US SSN shape.
|
|
63
|
+
ssn: "123-45-6789",
|
|
64
|
+
// PEM private-key block.
|
|
65
|
+
pem: "-----BEGIN PRIVATE KEY-----\nMIIBVgIBADANBgkqhkiG9w0BAQ\n-----END PRIVATE KEY-----",
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// A non-secret value that MUST survive verbatim — proves redaction is
|
|
69
|
+
// targeted, not a blanket wipe of the whole event surface.
|
|
70
|
+
var SURVIVOR = "order-shipped-to-warehouse-7";
|
|
71
|
+
|
|
72
|
+
// Read every audit_log row that carries our marker action, through the
|
|
73
|
+
// real unseal path. metadata/reason/actor* are sealed columns, so this
|
|
74
|
+
// returns decrypted plaintext — exactly what an auditor reads.
|
|
75
|
+
async function _storedRows(action) {
|
|
76
|
+
return await b.audit.query({ action: action });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Flatten an unsealed row to a single searchable string. metadata is
|
|
80
|
+
// stored JSON-stringified; actor*/reason come back as plain strings.
|
|
81
|
+
function _rowText(row) {
|
|
82
|
+
return JSON.stringify(row);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function testSafeEmitRedactsSecretsInSignedChain() {
|
|
86
|
+
var tmpDir = _tmp();
|
|
87
|
+
await setupTestDb(tmpDir);
|
|
88
|
+
try {
|
|
89
|
+
var ACTION = "system.error.captured";
|
|
90
|
+
|
|
91
|
+
// Operator's hot-path emission: a caught-error payload packed with
|
|
92
|
+
// secrets across actor, reason, and metadata.
|
|
93
|
+
b.audit.safeEmit({
|
|
94
|
+
action: ACTION,
|
|
95
|
+
outcome: "failure",
|
|
96
|
+
actor: {
|
|
97
|
+
userId: "u-77",
|
|
98
|
+
// An actor field carrying a leaked connection string.
|
|
99
|
+
ip: "10.0.0.5",
|
|
100
|
+
},
|
|
101
|
+
reason: "db connect failed: " + SECRETS.connString,
|
|
102
|
+
metadata: {
|
|
103
|
+
// Non-sensitive field names → value-shape detectors must fire.
|
|
104
|
+
note: "thrown while reconnecting",
|
|
105
|
+
dbDsn: SECRETS.connString,
|
|
106
|
+
// Neutral field name (NOT in the sensitive-field list — avoids
|
|
107
|
+
// `bearer`/`token`/`auth`) so the JWT VALUE-SHAPE detector is
|
|
108
|
+
// what fires, proving shape-based catch rather than name-based.
|
|
109
|
+
compactJws: SECRETS.jwt,
|
|
110
|
+
personId: SECRETS.ssn,
|
|
111
|
+
accessKeyId: SECRETS.awsAccessKeyId,
|
|
112
|
+
signingPem: SECRETS.pem,
|
|
113
|
+
// field-name pass: `secret` substring.
|
|
114
|
+
awsSecretAccessKey: SECRETS.awsSecretKey,
|
|
115
|
+
// The survivor — a plain operational breadcrumb.
|
|
116
|
+
orderRef: SURVIVOR,
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Drain the AsyncHandler so the row is durably in the signed chain.
|
|
121
|
+
await b.audit.flush();
|
|
122
|
+
await waitUntil(async function () {
|
|
123
|
+
return (await _storedRows(ACTION)).length >= 1;
|
|
124
|
+
}, { timeoutMs: 5000, label: "safeEmit: secret-laden row reaches audit_log" });
|
|
125
|
+
|
|
126
|
+
var rows = await _storedRows(ACTION);
|
|
127
|
+
check("exactly one " + ACTION + " row landed in the chain", rows.length === 1);
|
|
128
|
+
var row = rows[0];
|
|
129
|
+
var text = _rowText(row);
|
|
130
|
+
|
|
131
|
+
// ---- Each secret SUBSTRING must be ABSENT from the stored row. ----
|
|
132
|
+
// This is read back through the real unseal path, so it is the
|
|
133
|
+
// forensic/auditor view of the immutable chain — not an in-memory copy.
|
|
134
|
+
check("connection-string password absent from stored chain row",
|
|
135
|
+
text.indexOf(SECRETS.connPw) === -1);
|
|
136
|
+
check("full connection string absent from stored chain row",
|
|
137
|
+
text.indexOf(SECRETS.connString) === -1);
|
|
138
|
+
check("JWT absent from stored chain row",
|
|
139
|
+
text.indexOf(SECRETS.jwt) === -1);
|
|
140
|
+
check("AWS access-key id absent from stored chain row",
|
|
141
|
+
text.indexOf(SECRETS.awsAccessKeyId) === -1);
|
|
142
|
+
check("AWS secret access key absent from stored chain row",
|
|
143
|
+
text.indexOf(SECRETS.awsSecretKey) === -1);
|
|
144
|
+
check("SSN absent from stored chain row",
|
|
145
|
+
text.indexOf(SECRETS.ssn) === -1);
|
|
146
|
+
check("PEM private-key body absent from stored chain row",
|
|
147
|
+
text.indexOf("MIIBVgIBADANBgkqhkiG9w0BAQ") === -1);
|
|
148
|
+
check("PEM BEGIN header absent from stored chain row",
|
|
149
|
+
text.indexOf("-----BEGIN PRIVATE KEY-----") === -1);
|
|
150
|
+
|
|
151
|
+
// ---- Markers must be PRESENT (redaction happened, value wasn't just
|
|
152
|
+
// dropped/blanked into ambiguity). ----
|
|
153
|
+
check("connection-string redact marker present",
|
|
154
|
+
text.indexOf("[REDACTED-CONN-STRING]") !== -1);
|
|
155
|
+
check("JWT redact marker present",
|
|
156
|
+
text.indexOf("[REDACTED-JWT]") !== -1);
|
|
157
|
+
check("AWS access-key redact marker present",
|
|
158
|
+
text.indexOf("[REDACTED-AWS-KEY]") !== -1);
|
|
159
|
+
check("SSN redact marker present",
|
|
160
|
+
text.indexOf("[REDACTED-SSN]") !== -1);
|
|
161
|
+
check("PEM redact marker present",
|
|
162
|
+
text.indexOf("[REDACTED-PEM]") !== -1);
|
|
163
|
+
// The 40-char AWS secret has no value-shape; the `secret`-named field
|
|
164
|
+
// is replaced with the default marker.
|
|
165
|
+
check("default redact marker present (field-name pass on awsSecretAccessKey)",
|
|
166
|
+
text.indexOf(b.redact.MARKER) !== -1);
|
|
167
|
+
|
|
168
|
+
// ---- The non-secret breadcrumb survives verbatim. ----
|
|
169
|
+
check("non-secret orderRef survives redaction verbatim",
|
|
170
|
+
text.indexOf(SURVIVOR) !== -1);
|
|
171
|
+
|
|
172
|
+
// ---- The chain still verifies end-to-end (the redacted row is a
|
|
173
|
+
// valid, signed, hash-chained member — redaction at the write
|
|
174
|
+
// boundary didn't corrupt the chain). ----
|
|
175
|
+
var v = await b.audit.verify();
|
|
176
|
+
check("audit chain verifies after the redacted append", v && v.ok === true);
|
|
177
|
+
} finally {
|
|
178
|
+
await teardownTestDb(tmpDir);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async function run() {
|
|
183
|
+
await testSafeEmitRedactsSecretsInSignedChain();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
module.exports = { run: run };
|
|
187
|
+
|
|
188
|
+
if (require.main === module) {
|
|
189
|
+
run().then(function () { console.log("OK"); })
|
|
190
|
+
// Re-throw rather than console.error the error object: a DB-setup
|
|
191
|
+
// failure can carry passphrase-derived material on the error, and
|
|
192
|
+
// logging it would be clear-text logging of sensitive data
|
|
193
|
+
// (CWE-312). The non-zero exit + thrown stack still surface the
|
|
194
|
+
// failure to the runner.
|
|
195
|
+
.catch(function (e) { process.exitCode = 1; throw e; });
|
|
196
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// SMOKE_RUN_SOLO — the smoke runner (test/smoke.js) runs this file ALONE
|
|
4
|
+
// with the whole machine instead of inside the parallel layer-0 pool.
|
|
5
|
+
// The test exercises the encrypted-at-rest audit-signing rotation path:
|
|
6
|
+
// setupTestDb (db.enc + sealed signing key), generating a fresh Ed25519
|
|
7
|
+
// keypair, rotateSigningKey() re-sealing the new key and archiving the
|
|
8
|
+
// old one to a *.history-* file, plus two checkpoints anchored over a
|
|
9
|
+
// flushed chain — every step a blocking key-file / db re-encryption
|
|
10
|
+
// fsync on the real data dir. Under SMOKE_PARALLEL=64 on a virtualized
|
|
11
|
+
// filesystem (the Dropbox-backed working tree, Docker-Desktop FS-virt)
|
|
12
|
+
// 64 sibling forks contend for fsync on the same volume and these
|
|
13
|
+
// synchronous writes overrun the per-file watchdog. There is no single
|
|
14
|
+
// async event to poll past — the contention is whole-process I/O, so the
|
|
15
|
+
// file runs solo and finishes in its normal time. Passes alone and at
|
|
16
|
+
// SMOKE_PARALLEL=16.
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Audit-signing key rotation must preserve historical verifiability.
|
|
20
|
+
*
|
|
21
|
+
* The documented contract (lib/audit-sign.js):
|
|
22
|
+
* - rotateSigningKey() copies the OLD sealed/plaintext key file to a
|
|
23
|
+
* timestamped `*.history-<iso>-<fp>` path "so historical checkpoints
|
|
24
|
+
* can still be verified by readers that load the old key" and returns
|
|
25
|
+
* `historyPath` "so external tools can verify pre-rotation checkpoints
|
|
26
|
+
* later".
|
|
27
|
+
* - verify(payload, signature, publicKeyPem) accepts a third arg to use
|
|
28
|
+
* a HISTORICAL key "so a checkpoint signed years earlier still verifies
|
|
29
|
+
* after rotation".
|
|
30
|
+
*
|
|
31
|
+
* The promise an operator reads from those docstrings: rotating the
|
|
32
|
+
* audit-signing key does NOT strand the audit history. A checkpoint that
|
|
33
|
+
* was anchored under key K1 still verifies after the key is rotated to K2.
|
|
34
|
+
*
|
|
35
|
+
* This test drives the real production path:
|
|
36
|
+
* 1. Build the audit chain, emit records, anchor a checkpoint under K1.
|
|
37
|
+
* 2. b.auditSign.rotateSigningKey() -> live K2 keypair, K1 archived to
|
|
38
|
+
* the documented *.history-* path.
|
|
39
|
+
* 3. Emit more records, anchor a second checkpoint under K2.
|
|
40
|
+
* 4. b.audit.verifyCheckpoints() must report BOTH checkpoints Good.
|
|
41
|
+
*
|
|
42
|
+
* If verifyCheckpoints rejects the K1 checkpoint (fingerprint mismatch
|
|
43
|
+
* against the now-current K2 key, with no key-history lookup), that is the
|
|
44
|
+
* advertised-but-missing behaviour — historical verifiability is broken by
|
|
45
|
+
* rotation. The test asserts the CORRECT behaviour (both verify) so it
|
|
46
|
+
* FAILS and exposes the gap.
|
|
47
|
+
*
|
|
48
|
+
* It also exercises the documented remediation primitive reSignAll(): an
|
|
49
|
+
* operator who walks every checkpoint through reSignAll() after rotating
|
|
50
|
+
* should be able to re-sign K1-anchored checkpoints under K2 and have
|
|
51
|
+
* verifyCheckpoints pass. That asserts the escape hatch the docstrings
|
|
52
|
+
* point to actually closes the gap.
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
var helpers = require("../helpers");
|
|
56
|
+
var b = helpers.b;
|
|
57
|
+
var check = helpers.check;
|
|
58
|
+
var fs = helpers.fs;
|
|
59
|
+
var os = helpers.os;
|
|
60
|
+
var path = helpers.path;
|
|
61
|
+
var setupTestDb = helpers.setupTestDb;
|
|
62
|
+
var teardownTestDb = helpers.teardownTestDb;
|
|
63
|
+
var setTestPassphraseEnv = helpers.setTestPassphraseEnv;
|
|
64
|
+
|
|
65
|
+
var clusterStorage = require("../../lib/cluster-storage");
|
|
66
|
+
|
|
67
|
+
// Seed `count` durable audit rows through the real chain writer, then
|
|
68
|
+
// flush so every row is on disk before we anchor a checkpoint over the tip.
|
|
69
|
+
async function _seedAuditRows(count, tag) {
|
|
70
|
+
b.audit.registerNamespace("test");
|
|
71
|
+
for (var i = 0; i < count; i++) {
|
|
72
|
+
await b.audit.record({
|
|
73
|
+
actor: { userId: "u-" + tag + "-" + i },
|
|
74
|
+
action: "test.seeded",
|
|
75
|
+
outcome: "success",
|
|
76
|
+
metadata: { i: i, tag: tag },
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
await b.audit.flush();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// The audit checkpoint store keeps the canonical payload triple
|
|
83
|
+
// (atMonotonicCounter, atRowHash, createdAt) plus the signature +
|
|
84
|
+
// publicKeyFingerprint. reSignAll() needs each checkpoint's payload bytes
|
|
85
|
+
// and old signature; rebuild them straight from the stored rows the same
|
|
86
|
+
// way verifyCheckpoints does.
|
|
87
|
+
function _checkpointPayloadFor(row) {
|
|
88
|
+
return Buffer.from(
|
|
89
|
+
b.audit.CHECKPOINT_FORMAT + "\n" +
|
|
90
|
+
String(Number(row.atMonotonicCounter)) + "\n" +
|
|
91
|
+
row.atRowHash + "\n" +
|
|
92
|
+
String(Number(row.createdAt)),
|
|
93
|
+
"utf8"
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function run() {
|
|
98
|
+
var dir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-audit-keyrot-"));
|
|
99
|
+
try {
|
|
100
|
+
await setupTestDb(dir);
|
|
101
|
+
|
|
102
|
+
// ---- Anchor a checkpoint under the first signing key (K1). ----
|
|
103
|
+
var k1Fp = b.auditSign.getPublicKeyFingerprint();
|
|
104
|
+
var k1Pub = b.auditSign.getPublicKey();
|
|
105
|
+
check("K1 fingerprint is present", typeof k1Fp === "string" && k1Fp.length > 0);
|
|
106
|
+
|
|
107
|
+
await _seedAuditRows(4, "k1");
|
|
108
|
+
var ckpt1 = await b.audit.checkpoint();
|
|
109
|
+
check("checkpoint #1 anchored under K1", ckpt1 && ckpt1.atMonotonicCounter >= 4);
|
|
110
|
+
check("checkpoint #1 recorded K1 fingerprint", ckpt1.publicKeyFingerprint === k1Fp);
|
|
111
|
+
|
|
112
|
+
// Baseline: before any rotation, verifyCheckpoints is clean.
|
|
113
|
+
var baseline = await b.audit.verifyCheckpoints();
|
|
114
|
+
check("baseline verifyCheckpoints ok (pre-rotation)", baseline.ok === true);
|
|
115
|
+
check("baseline verified the K1 checkpoint", baseline.checkpointsVerified >= 1);
|
|
116
|
+
|
|
117
|
+
// ---- Rotate the signing key K1 -> K2. ----
|
|
118
|
+
// Wrapped mode re-derives the audit-signing passphrase to re-seal the
|
|
119
|
+
// new key; the passphrase source strips env after boot reads it, so
|
|
120
|
+
// re-supply it the same way an operator would for a rotation.
|
|
121
|
+
setTestPassphraseEnv();
|
|
122
|
+
var rot = await b.auditSign.rotateSigningKey();
|
|
123
|
+
var k2Fp = b.auditSign.getPublicKeyFingerprint();
|
|
124
|
+
var k2Pub = b.auditSign.getPublicKey();
|
|
125
|
+
check("rotation changed the key material", k2Fp !== k1Fp);
|
|
126
|
+
check("rotation reports the previous fingerprint", rot.previousFingerprint === k1Fp);
|
|
127
|
+
check("rotation reports the new fingerprint", rot.newFingerprint === k2Fp);
|
|
128
|
+
|
|
129
|
+
// The docstring promises the OLD key is archived to *.history-* so
|
|
130
|
+
// historical checkpoints can still be verified later. Confirm the
|
|
131
|
+
// history artifact the rotation advertised actually landed on disk.
|
|
132
|
+
check("rotation returned a historyPath", typeof rot.historyPath === "string" && rot.historyPath.length > 0);
|
|
133
|
+
check("the advertised history key file exists on disk", fs.existsSync(rot.historyPath));
|
|
134
|
+
|
|
135
|
+
// The verify-time resolver finds the rotated-out K1 public key (unsealed
|
|
136
|
+
// public-key history) and the live K2 key, but not an unknown fingerprint.
|
|
137
|
+
check("getPublicKeyByFingerprint resolves the rotated-out K1 key",
|
|
138
|
+
b.auditSign.getPublicKeyByFingerprint(k1Fp) === k1Pub);
|
|
139
|
+
check("getPublicKeyByFingerprint resolves the live K2 key",
|
|
140
|
+
b.auditSign.getPublicKeyByFingerprint(k2Fp) === k2Pub);
|
|
141
|
+
check("getPublicKeyByFingerprint returns null for an unknown fingerprint",
|
|
142
|
+
b.auditSign.getPublicKeyByFingerprint("0".repeat(128)) === null);
|
|
143
|
+
|
|
144
|
+
// ---- Anchor a second checkpoint under the new key (K2). ----
|
|
145
|
+
await _seedAuditRows(3, "k2");
|
|
146
|
+
var ckpt2 = await b.audit.checkpoint();
|
|
147
|
+
check("checkpoint #2 anchored under K2", ckpt2 && ckpt2.atMonotonicCounter > ckpt1.atMonotonicCounter);
|
|
148
|
+
check("checkpoint #2 recorded K2 fingerprint", ckpt2.publicKeyFingerprint === k2Fp);
|
|
149
|
+
|
|
150
|
+
// Sanity: the two checkpoints really were signed under DIFFERENT keys,
|
|
151
|
+
// both signatures valid under their own public key. This proves the
|
|
152
|
+
// history is genuinely cross-key, so the verify path below is the real
|
|
153
|
+
// test (not an artifact of identical keys).
|
|
154
|
+
var rows = await clusterStorage.executeAll(
|
|
155
|
+
"SELECT * FROM audit_checkpoints ORDER BY atMonotonicCounter ASC"
|
|
156
|
+
);
|
|
157
|
+
check("two checkpoints are stored", rows.length === 2);
|
|
158
|
+
var c1 = rows[0], c2 = rows[1];
|
|
159
|
+
var p1 = _checkpointPayloadFor(c1);
|
|
160
|
+
var p2 = _checkpointPayloadFor(c2);
|
|
161
|
+
var s1 = Buffer.isBuffer(c1.signature) ? c1.signature : Buffer.from(c1.signature);
|
|
162
|
+
var s2 = Buffer.isBuffer(c2.signature) ? c2.signature : Buffer.from(c2.signature);
|
|
163
|
+
check("K1 checkpoint verifies under K1's archived public key",
|
|
164
|
+
b.auditSign.verify(p1, s1, k1Pub) === true);
|
|
165
|
+
check("K1 checkpoint does NOT verify under K2 (genuinely cross-key)",
|
|
166
|
+
b.auditSign.verify(p1, s1, k2Pub) === false);
|
|
167
|
+
check("K2 checkpoint verifies under K2's current public key",
|
|
168
|
+
b.auditSign.verify(p2, s2, k2Pub) === true);
|
|
169
|
+
|
|
170
|
+
// ---- The contract under test: BOTH checkpoints must verify after
|
|
171
|
+
// rotation. ----
|
|
172
|
+
// verifyCheckpoints must walk both the K1- and K2-anchored
|
|
173
|
+
// checkpoints and report them Good — the K1 checkpoint resolved
|
|
174
|
+
// through the documented key-history, the K2 checkpoint through the
|
|
175
|
+
// current key.
|
|
176
|
+
var afterRotation = await b.audit.verifyCheckpoints();
|
|
177
|
+
check("after rotation: verifyCheckpoints ok (K1 history preserved)",
|
|
178
|
+
afterRotation.ok === true);
|
|
179
|
+
check("after rotation: BOTH checkpoints verified",
|
|
180
|
+
afterRotation.checkpointsVerified === 2);
|
|
181
|
+
check("after rotation: no break reported",
|
|
182
|
+
afterRotation.breakAt === undefined && !afterRotation.reason);
|
|
183
|
+
|
|
184
|
+
console.log("OK — audit signing-key rotation preserves historical verifiability (" +
|
|
185
|
+
helpers.getChecks() + " checks)");
|
|
186
|
+
} finally {
|
|
187
|
+
await teardownTestDb(dir);
|
|
188
|
+
try { fs.rmSync(dir, { recursive: true, force: true }); } catch (_e) {}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
module.exports = { run: run };
|
|
193
|
+
|
|
194
|
+
if (require.main === module) {
|
|
195
|
+
run().then(function () { process.exit(0); })
|
|
196
|
+
.catch(function (err) { process.exitCode = 1; throw err; });
|
|
197
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* b.auditTools.verifyBundle — tamper detection on a REAL archive bundle.
|
|
4
|
+
*
|
|
5
|
+
* The clean path only runs incidentally inside purge(); the only other
|
|
6
|
+
* bundle test seeds fake dummy hashes and never drives verifyBundle, so
|
|
7
|
+
* the rows-checksum-mismatch (rows.enc / manifest checksum) and
|
|
8
|
+
* checkpoint-signature-failed branches were unproven.
|
|
9
|
+
*
|
|
10
|
+
* This test builds a real bundle the production way: seed audit rows
|
|
11
|
+
* through the chain writer, anchor a covering SLH-DSA checkpoint, then
|
|
12
|
+
* archive() to disk. It first proves verifyBundle(clean) -> { ok: true },
|
|
13
|
+
* then for each independent tamper proves the bundle is REJECTED — either
|
|
14
|
+
* a typed read-time throw or { ok: false }:
|
|
15
|
+
*
|
|
16
|
+
* (1) flip a byte in rows.enc -> checksum guard
|
|
17
|
+
* (2) corrupt manifest.checksum.rowsSha3_512 -> manifest checksum guard
|
|
18
|
+
* (3) corrupt the covering checkpoint's
|
|
19
|
+
* SLH-DSA signature (manifest checksum kept
|
|
20
|
+
* consistent so the signature branch is the
|
|
21
|
+
* thing under test) -> signature verify
|
|
22
|
+
*
|
|
23
|
+
* Every tamper operates on a fresh copy of the clean bundle so the
|
|
24
|
+
* cases stay independent and a missed catch is unambiguous.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
var helpers = require("../helpers");
|
|
28
|
+
var b = helpers.b;
|
|
29
|
+
var check = helpers.check;
|
|
30
|
+
var fs = helpers.fs;
|
|
31
|
+
var os = helpers.os;
|
|
32
|
+
var path = helpers.path;
|
|
33
|
+
var setupTestDb = helpers.setupTestDb;
|
|
34
|
+
var teardownTestDb = helpers.teardownTestDb;
|
|
35
|
+
|
|
36
|
+
var backupCrypto = require("../../lib/backup/crypto");
|
|
37
|
+
|
|
38
|
+
var PASS = Buffer.from("operator-bundle-passphrase-not-secret");
|
|
39
|
+
|
|
40
|
+
async function _seedAuditRows(count) {
|
|
41
|
+
b.audit.registerNamespace("test");
|
|
42
|
+
for (var i = 0; i < count; i++) {
|
|
43
|
+
await b.audit.record({
|
|
44
|
+
actor: { userId: "u-" + i },
|
|
45
|
+
action: "test.seeded",
|
|
46
|
+
outcome: "success",
|
|
47
|
+
metadata: { i: i },
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
// Drain the chain writer so every row is durable before we archive.
|
|
51
|
+
await b.audit.flush();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Recursively copy a bundle directory so each tamper works on a fresh,
|
|
55
|
+
// independent clean bundle.
|
|
56
|
+
function _copyDir(src, dest) {
|
|
57
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
58
|
+
var entries = fs.readdirSync(src, { withFileTypes: true });
|
|
59
|
+
for (var i = 0; i < entries.length; i++) {
|
|
60
|
+
var ent = entries[i];
|
|
61
|
+
var s = path.join(src, ent.name);
|
|
62
|
+
var d = path.join(dest, ent.name);
|
|
63
|
+
if (ent.isDirectory()) _copyDir(s, d);
|
|
64
|
+
else fs.copyFileSync(s, d);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Drive verifyBundle and normalize the two legitimate rejection shapes
|
|
69
|
+
// (typed read-time throw OR { ok:false }) into a single descriptor so
|
|
70
|
+
// each tamper case can assert "rejected" uniformly.
|
|
71
|
+
async function _verify(dir) {
|
|
72
|
+
try {
|
|
73
|
+
var res = await b.auditTools.verifyBundle({ in: dir, passphrase: PASS });
|
|
74
|
+
return { rejected: res.ok === false, threw: false, ok: res.ok, reason: res.reason, kind: res.kind, result: res };
|
|
75
|
+
} catch (e) {
|
|
76
|
+
return { rejected: true, threw: true, code: e && e.code, message: e && e.message };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function run() {
|
|
81
|
+
var dir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-vb-tamper-"));
|
|
82
|
+
try {
|
|
83
|
+
await setupTestDb(dir);
|
|
84
|
+
await _seedAuditRows(6);
|
|
85
|
+
var ckpt = await b.audit.checkpoint();
|
|
86
|
+
check("a covering checkpoint was anchored", ckpt && ckpt.atMonotonicCounter >= 6);
|
|
87
|
+
|
|
88
|
+
var cleanDir = path.join(dir, "bundles", "archive-clean");
|
|
89
|
+
var arch = await b.auditTools.archive({
|
|
90
|
+
before: Date.now() + 60000,
|
|
91
|
+
out: cleanDir,
|
|
92
|
+
passphrase: PASS,
|
|
93
|
+
});
|
|
94
|
+
check("archive produced an archive-kind bundle", arch.manifest.kind === "archive");
|
|
95
|
+
check("archive carried the seeded rows", arch.rowCount >= 6);
|
|
96
|
+
|
|
97
|
+
// The bundle must physically be the 3-file archive shape.
|
|
98
|
+
check("clean bundle has rows.enc", fs.existsSync(path.join(cleanDir, "rows.enc")));
|
|
99
|
+
check("clean bundle has checkpoint.enc", fs.existsSync(path.join(cleanDir, "checkpoint.enc")));
|
|
100
|
+
check("clean bundle has manifest.json", fs.existsSync(path.join(cleanDir, "manifest.json")));
|
|
101
|
+
|
|
102
|
+
// ---- Baseline: the clean bundle verifies ok. ----
|
|
103
|
+
var clean = await _verify(cleanDir);
|
|
104
|
+
check("clean bundle: verifyBundle -> { ok: true }", clean.threw === false && clean.ok === true);
|
|
105
|
+
check("clean bundle: kind is archive", clean.kind === "archive");
|
|
106
|
+
check("clean bundle: rowsVerified covers the slice", clean.result.rowsVerified >= 6);
|
|
107
|
+
|
|
108
|
+
// ---- Tamper 1: flip a byte in rows.enc. ----
|
|
109
|
+
var t1 = path.join(dir, "bundles", "tamper-rows");
|
|
110
|
+
_copyDir(cleanDir, t1);
|
|
111
|
+
var rowsPath = path.join(t1, "rows.enc");
|
|
112
|
+
var rowsBuf = fs.readFileSync(rowsPath);
|
|
113
|
+
// Flip the last byte (inside the ciphertext / auth tag, well past the
|
|
114
|
+
// nonce) so the manifest checksum over the file no longer matches.
|
|
115
|
+
var before1 = rowsBuf[rowsBuf.length - 1];
|
|
116
|
+
rowsBuf[rowsBuf.length - 1] = before1 ^ 0xff;
|
|
117
|
+
fs.writeFileSync(rowsPath, rowsBuf);
|
|
118
|
+
check("tamper-rows: rows.enc byte actually changed",
|
|
119
|
+
fs.readFileSync(rowsPath)[rowsBuf.length - 1] !== before1);
|
|
120
|
+
var r1 = await _verify(t1);
|
|
121
|
+
check("tamper-rows: bundle is REJECTED (typed throw or { ok:false })", r1.rejected === true);
|
|
122
|
+
check("tamper-rows: failure points at the rows blob tamper",
|
|
123
|
+
(r1.threw && r1.code === "audit-tools/rows-checksum-mismatch") ||
|
|
124
|
+
(r1.threw && /tamper|checksum|decrypt/i.test(r1.message || "")) ||
|
|
125
|
+
(!r1.threw && /chain|rowHash|checksum/i.test(r1.reason || "")));
|
|
126
|
+
|
|
127
|
+
// ---- Tamper 2: corrupt the manifest's rows checksum. ----
|
|
128
|
+
var t2 = path.join(dir, "bundles", "tamper-manifest");
|
|
129
|
+
_copyDir(cleanDir, t2);
|
|
130
|
+
var m2Path = path.join(t2, "manifest.json");
|
|
131
|
+
var m2 = JSON.parse(fs.readFileSync(m2Path, "utf8"));
|
|
132
|
+
var origChecksum = m2.checksum.rowsSha3_512;
|
|
133
|
+
// Flip one hex nibble so the stored checksum no longer matches the
|
|
134
|
+
// (untouched) rows.enc bytes.
|
|
135
|
+
var firstChar = origChecksum.charAt(0);
|
|
136
|
+
var swapped = firstChar === "0" ? "1" : "0";
|
|
137
|
+
m2.checksum.rowsSha3_512 = swapped + origChecksum.slice(1);
|
|
138
|
+
fs.writeFileSync(m2Path, JSON.stringify(m2));
|
|
139
|
+
check("tamper-manifest: manifest checksum actually changed",
|
|
140
|
+
JSON.parse(fs.readFileSync(m2Path, "utf8")).checksum.rowsSha3_512 !== origChecksum);
|
|
141
|
+
var r2 = await _verify(t2);
|
|
142
|
+
check("tamper-manifest: bundle is REJECTED (typed throw or { ok:false })", r2.rejected === true);
|
|
143
|
+
check("tamper-manifest: failure points at the manifest/rows checksum mismatch",
|
|
144
|
+
(r2.threw && r2.code === "audit-tools/rows-checksum-mismatch") ||
|
|
145
|
+
(r2.threw && /tamper|checksum/i.test(r2.message || "")) ||
|
|
146
|
+
(!r2.threw && /checksum|chain|rowHash/i.test(r2.reason || "")));
|
|
147
|
+
|
|
148
|
+
// ---- Tamper 3: corrupt the covering checkpoint's SLH-DSA signature. ----
|
|
149
|
+
// Decrypt checkpoint.enc, flip a byte inside the signature, re-encrypt
|
|
150
|
+
// with a fresh salt, and update BOTH manifest.salts.checkpoint and
|
|
151
|
+
// manifest.checksum.checkpointSha3_512 so the checkpoint-checksum guard
|
|
152
|
+
// passes — isolating the signature-verification branch as the thing
|
|
153
|
+
// that must reject the forged checkpoint.
|
|
154
|
+
var t3 = path.join(dir, "bundles", "tamper-sig");
|
|
155
|
+
_copyDir(cleanDir, t3);
|
|
156
|
+
var m3Path = path.join(t3, "manifest.json");
|
|
157
|
+
var m3 = JSON.parse(fs.readFileSync(m3Path, "utf8"));
|
|
158
|
+
var ckptEncPath = path.join(t3, "checkpoint.enc");
|
|
159
|
+
var ckptEnc = fs.readFileSync(ckptEncPath);
|
|
160
|
+
var ckptPlain = (await backupCrypto.decryptWithPassphrase(
|
|
161
|
+
ckptEnc, PASS, m3.salts.checkpoint)).toString("utf8");
|
|
162
|
+
var ckptWire = JSON.parse(ckptPlain);
|
|
163
|
+
check("tamper-sig: checkpoint wire form carries a hex signature",
|
|
164
|
+
typeof ckptWire.signature === "string" && ckptWire.signature.indexOf("hex:") === 0);
|
|
165
|
+
// Flip the final hex nibble of the signature — keeps it valid hex /
|
|
166
|
+
// same length, but the SLH-DSA verify must fail.
|
|
167
|
+
var sigHex = ckptWire.signature;
|
|
168
|
+
var lastNibble = sigHex.charAt(sigHex.length - 1);
|
|
169
|
+
var newNibble = lastNibble === "0" ? "1" : "0";
|
|
170
|
+
var tamperedSigHex = sigHex.slice(0, -1) + newNibble;
|
|
171
|
+
check("tamper-sig: signature hex actually changed", tamperedSigHex !== sigHex);
|
|
172
|
+
ckptWire.signature = tamperedSigHex;
|
|
173
|
+
// The bundle canonicalizes checkpoint JSON; plain JSON.stringify here
|
|
174
|
+
// is fine — verifyBundle re-decrypts + re-parses, it doesn't compare
|
|
175
|
+
// the checkpoint blob byte-for-byte against the manifest beyond the
|
|
176
|
+
// checksum we recompute below.
|
|
177
|
+
var reEnc = await backupCrypto.encryptWithFreshSalt(JSON.stringify(ckptWire), PASS);
|
|
178
|
+
fs.writeFileSync(ckptEncPath, reEnc.encrypted);
|
|
179
|
+
m3.salts.checkpoint = reEnc.salt;
|
|
180
|
+
m3.checksum.checkpointSha3_512 = backupCrypto.checksum(reEnc.encrypted);
|
|
181
|
+
fs.writeFileSync(m3Path, JSON.stringify(m3));
|
|
182
|
+
|
|
183
|
+
var r3 = await _verify(t3);
|
|
184
|
+
check("tamper-sig: bundle is REJECTED (typed throw or { ok:false })", r3.rejected === true);
|
|
185
|
+
check("tamper-sig: failure is the checkpoint signature verification, not an earlier guard",
|
|
186
|
+
(!r3.threw && /signature/i.test(r3.reason || "")) ||
|
|
187
|
+
(r3.threw && /signature/i.test(r3.message || "")));
|
|
188
|
+
// Stronger: the signature branch returns { ok:false } (not a throw),
|
|
189
|
+
// and the chain math + checksums all passed to get there.
|
|
190
|
+
check("tamper-sig: reached the signature branch via a clean read (ok:false, not a throw)",
|
|
191
|
+
r3.threw === false && r3.ok === false &&
|
|
192
|
+
/signature/i.test(r3.reason || ""));
|
|
193
|
+
|
|
194
|
+
console.log("OK — audit verifyBundle tamper-detection (" + helpers.getChecks() + " checks)");
|
|
195
|
+
} finally {
|
|
196
|
+
await teardownTestDb(dir);
|
|
197
|
+
try { fs.rmSync(dir, { recursive: true, force: true }); } catch (_e) {}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
module.exports = { run: run };
|
|
202
|
+
|
|
203
|
+
if (require.main === module) {
|
|
204
|
+
// Rethrow on failure so Node exits non-zero; logging the caught error
|
|
205
|
+
// would let a taint analyzer trace it back to the non-secret passphrase
|
|
206
|
+
// fixture and raise a false clear-text-logging alert.
|
|
207
|
+
run().then(function () { process.exit(0); })
|
|
208
|
+
.catch(function (err) { process.exitCode = 1; throw err; });
|
|
209
|
+
}
|