@blamejs/blamejs-shop 0.4.30 → 0.4.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/lib/asset-manifest.json +1 -1
- package/lib/checkout.js +8 -0
- package/lib/order.js +71 -11
- package/lib/vendor/MANIFEST.json +392 -278
- package/lib/vendor/blamejs/.github/workflows/ci.yml +34 -3
- package/lib/vendor/blamejs/.github/workflows/npm-publish.yml +21 -4
- package/lib/vendor/blamejs/.gitignore +6 -0
- package/lib/vendor/blamejs/CHANGELOG.md +26 -0
- package/lib/vendor/blamejs/MIGRATING.md +43 -0
- package/lib/vendor/blamejs/README.md +8 -6
- package/lib/vendor/blamejs/SECURITY.md +19 -3
- package/lib/vendor/blamejs/api-snapshot.json +2190 -664
- package/lib/vendor/blamejs/docker/caddy/localstack.Caddyfile +19 -0
- package/lib/vendor/blamejs/docker/init/generate-certs.sh +1 -1
- package/lib/vendor/blamejs/docker/otel/config.yaml +42 -0
- package/lib/vendor/blamejs/docker/otel/export/.gitkeep +0 -0
- package/lib/vendor/blamejs/docker/postgres/initdb/10-replication.sh +15 -0
- package/lib/vendor/blamejs/docker/postgres/replica-entrypoint.sh +38 -0
- package/lib/vendor/blamejs/docker/toxiproxy/toxiproxy.json +14 -0
- package/lib/vendor/blamejs/docker-compose.test.yml +209 -0
- package/lib/vendor/blamejs/examples/wiki/lib/page-generator.js +132 -0
- package/lib/vendor/blamejs/examples/wiki/lib/source-comment-block-validator.js +221 -61
- package/lib/vendor/blamejs/examples/wiki/lib/source-doc-parser.js +144 -9
- package/lib/vendor/blamejs/examples/wiki/test/e2e.js +99 -0
- package/lib/vendor/blamejs/fuzz/guard-sql.fuzz.js +36 -0
- package/lib/vendor/blamejs/index.js +4 -0
- package/lib/vendor/blamejs/lib/agent-envelope-mac.js +104 -0
- package/lib/vendor/blamejs/lib/agent-event-bus.js +105 -4
- package/lib/vendor/blamejs/lib/agent-posture-chain.js +8 -42
- package/lib/vendor/blamejs/lib/ai-content-detect.js +9 -10
- package/lib/vendor/blamejs/lib/api-key.js +158 -77
- package/lib/vendor/blamejs/lib/atomic-file.js +62 -4
- package/lib/vendor/blamejs/lib/audit-chain.js +47 -11
- package/lib/vendor/blamejs/lib/audit-sign.js +77 -2
- package/lib/vendor/blamejs/lib/audit-tools.js +79 -51
- package/lib/vendor/blamejs/lib/audit.js +259 -123
- package/lib/vendor/blamejs/lib/auth/oauth.js +53 -9
- package/lib/vendor/blamejs/lib/auth/openid-federation.js +108 -47
- package/lib/vendor/blamejs/lib/auth/saml.js +6 -8
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc.js +31 -5
- package/lib/vendor/blamejs/lib/backup/index.js +45 -10
- package/lib/vendor/blamejs/lib/break-glass.js +355 -147
- package/lib/vendor/blamejs/lib/cache.js +174 -105
- package/lib/vendor/blamejs/lib/chain-writer.js +38 -16
- package/lib/vendor/blamejs/lib/cli.js +19 -14
- package/lib/vendor/blamejs/lib/cluster-provider-db.js +130 -104
- package/lib/vendor/blamejs/lib/cluster-storage.js +119 -22
- package/lib/vendor/blamejs/lib/cluster.js +119 -71
- package/lib/vendor/blamejs/lib/codepoint-class.js +23 -0
- package/lib/vendor/blamejs/lib/compliance.js +206 -4
- package/lib/vendor/blamejs/lib/consent.js +82 -29
- package/lib/vendor/blamejs/lib/constants.js +27 -11
- package/lib/vendor/blamejs/lib/crypto-field.js +916 -156
- package/lib/vendor/blamejs/lib/db-declare-row-policy.js +35 -22
- package/lib/vendor/blamejs/lib/db-file-lifecycle.js +3 -2
- package/lib/vendor/blamejs/lib/db-query.js +882 -260
- package/lib/vendor/blamejs/lib/db-schema.js +228 -44
- package/lib/vendor/blamejs/lib/db.js +249 -99
- package/lib/vendor/blamejs/lib/dsr.js +385 -55
- package/lib/vendor/blamejs/lib/error-page.js +14 -1
- package/lib/vendor/blamejs/lib/external-db-migrate.js +239 -137
- package/lib/vendor/blamejs/lib/external-db.js +549 -34
- package/lib/vendor/blamejs/lib/file-upload.js +52 -7
- package/lib/vendor/blamejs/lib/framework-error.js +20 -1
- package/lib/vendor/blamejs/lib/framework-files.js +73 -0
- package/lib/vendor/blamejs/lib/framework-schema.js +695 -394
- package/lib/vendor/blamejs/lib/gate-contract.js +659 -1
- package/lib/vendor/blamejs/lib/guard-agent-registry.js +26 -44
- package/lib/vendor/blamejs/lib/guard-all.js +1 -0
- package/lib/vendor/blamejs/lib/guard-auth.js +42 -112
- package/lib/vendor/blamejs/lib/guard-cidr.js +33 -154
- package/lib/vendor/blamejs/lib/guard-csv.js +46 -113
- package/lib/vendor/blamejs/lib/guard-domain.js +34 -157
- package/lib/vendor/blamejs/lib/guard-dsn.js +27 -43
- package/lib/vendor/blamejs/lib/guard-email.js +47 -69
- package/lib/vendor/blamejs/lib/guard-envelope.js +19 -32
- package/lib/vendor/blamejs/lib/guard-event-bus-payload.js +24 -42
- package/lib/vendor/blamejs/lib/guard-event-bus-topic.js +25 -43
- package/lib/vendor/blamejs/lib/guard-filename.js +42 -106
- package/lib/vendor/blamejs/lib/guard-graphql.js +42 -123
- package/lib/vendor/blamejs/lib/guard-html.js +53 -108
- package/lib/vendor/blamejs/lib/guard-idempotency-key.js +24 -42
- package/lib/vendor/blamejs/lib/guard-image.js +46 -103
- package/lib/vendor/blamejs/lib/guard-imap-command.js +18 -32
- package/lib/vendor/blamejs/lib/guard-jmap.js +16 -30
- package/lib/vendor/blamejs/lib/guard-json.js +38 -108
- package/lib/vendor/blamejs/lib/guard-jsonpath.js +38 -171
- package/lib/vendor/blamejs/lib/guard-jwt.js +49 -179
- package/lib/vendor/blamejs/lib/guard-list-id.js +25 -41
- package/lib/vendor/blamejs/lib/guard-list-unsubscribe.js +27 -43
- package/lib/vendor/blamejs/lib/guard-mail-compose.js +24 -42
- package/lib/vendor/blamejs/lib/guard-mail-move.js +26 -44
- package/lib/vendor/blamejs/lib/guard-mail-query.js +28 -46
- package/lib/vendor/blamejs/lib/guard-mail-reply.js +24 -42
- package/lib/vendor/blamejs/lib/guard-mail-sieve.js +24 -42
- package/lib/vendor/blamejs/lib/guard-managesieve-command.js +17 -31
- package/lib/vendor/blamejs/lib/guard-markdown.js +37 -104
- package/lib/vendor/blamejs/lib/guard-message-id.js +26 -45
- package/lib/vendor/blamejs/lib/guard-mime.js +39 -151
- package/lib/vendor/blamejs/lib/guard-oauth.js +54 -135
- package/lib/vendor/blamejs/lib/guard-pdf.js +45 -101
- package/lib/vendor/blamejs/lib/guard-pop3-command.js +21 -31
- package/lib/vendor/blamejs/lib/guard-posture-chain.js +24 -42
- package/lib/vendor/blamejs/lib/guard-regex.js +33 -107
- package/lib/vendor/blamejs/lib/guard-saga-config.js +24 -42
- package/lib/vendor/blamejs/lib/guard-shell.js +42 -172
- package/lib/vendor/blamejs/lib/guard-smtp-command.js +48 -54
- package/lib/vendor/blamejs/lib/guard-snapshot-envelope.js +24 -42
- package/lib/vendor/blamejs/lib/guard-sql.js +1491 -0
- package/lib/vendor/blamejs/lib/guard-stream-args.js +24 -43
- package/lib/vendor/blamejs/lib/guard-svg.js +47 -65
- package/lib/vendor/blamejs/lib/guard-template.js +35 -172
- package/lib/vendor/blamejs/lib/guard-tenant-id.js +26 -45
- package/lib/vendor/blamejs/lib/guard-time.js +32 -154
- package/lib/vendor/blamejs/lib/guard-trace-context.js +25 -44
- package/lib/vendor/blamejs/lib/guard-uuid.js +32 -153
- package/lib/vendor/blamejs/lib/guard-xml.js +38 -113
- package/lib/vendor/blamejs/lib/guard-yaml.js +51 -163
- package/lib/vendor/blamejs/lib/http-client.js +37 -9
- package/lib/vendor/blamejs/lib/inbox.js +120 -107
- package/lib/vendor/blamejs/lib/legal-hold.js +121 -50
- package/lib/vendor/blamejs/lib/log-stream-cloudwatch.js +47 -31
- package/lib/vendor/blamejs/lib/log-stream-otlp.js +32 -18
- package/lib/vendor/blamejs/lib/mail-auth.js +236 -0
- package/lib/vendor/blamejs/lib/mail-crypto-smime.js +2 -6
- package/lib/vendor/blamejs/lib/mail-dkim.js +1 -0
- package/lib/vendor/blamejs/lib/mail-greylist.js +2 -6
- package/lib/vendor/blamejs/lib/mail-helo.js +2 -6
- package/lib/vendor/blamejs/lib/mail-journal.js +85 -64
- package/lib/vendor/blamejs/lib/mail-rbl.js +2 -6
- package/lib/vendor/blamejs/lib/mail-scan.js +2 -6
- package/lib/vendor/blamejs/lib/mail-server-jmap.js +117 -12
- package/lib/vendor/blamejs/lib/mail-server-mx.js +276 -7
- package/lib/vendor/blamejs/lib/mail-spam-score.js +2 -6
- package/lib/vendor/blamejs/lib/mail-store.js +293 -154
- package/lib/vendor/blamejs/lib/mail.js +8 -4
- package/lib/vendor/blamejs/lib/middleware/body-parser.js +71 -25
- package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +19 -8
- package/lib/vendor/blamejs/lib/middleware/dpop.js +10 -1
- package/lib/vendor/blamejs/lib/middleware/fetch-metadata.js +17 -7
- package/lib/vendor/blamejs/lib/middleware/idempotency-key.js +75 -51
- package/lib/vendor/blamejs/lib/middleware/rate-limit.js +102 -32
- package/lib/vendor/blamejs/lib/middleware/security-headers.js +21 -5
- package/lib/vendor/blamejs/lib/migrations.js +108 -66
- package/lib/vendor/blamejs/lib/network-heartbeat.js +7 -0
- package/lib/vendor/blamejs/lib/network-proxy.js +24 -1
- package/lib/vendor/blamejs/lib/nonce-store.js +31 -9
- package/lib/vendor/blamejs/lib/object-store/azure-blob-bucket-ops.js +9 -4
- package/lib/vendor/blamejs/lib/object-store/azure-blob.js +57 -3
- package/lib/vendor/blamejs/lib/object-store/gcs.js +4 -1
- package/lib/vendor/blamejs/lib/object-store/sigv4-bucket-ops.js +5 -2
- package/lib/vendor/blamejs/lib/object-store/sigv4.js +38 -6
- package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +9 -1
- package/lib/vendor/blamejs/lib/observability.js +124 -0
- package/lib/vendor/blamejs/lib/otel-export.js +12 -3
- package/lib/vendor/blamejs/lib/outbox.js +184 -83
- package/lib/vendor/blamejs/lib/parsers/safe-xml.js +47 -7
- package/lib/vendor/blamejs/lib/pqc-agent.js +44 -0
- package/lib/vendor/blamejs/lib/pubsub-cluster.js +42 -20
- package/lib/vendor/blamejs/lib/queue-local.js +225 -140
- package/lib/vendor/blamejs/lib/queue-redis.js +9 -1
- package/lib/vendor/blamejs/lib/queue-sqs.js +6 -0
- package/lib/vendor/blamejs/lib/queue.js +7 -0
- package/lib/vendor/blamejs/lib/redact.js +68 -11
- package/lib/vendor/blamejs/lib/redis-client.js +160 -31
- package/lib/vendor/blamejs/lib/request-helpers.js +7 -0
- package/lib/vendor/blamejs/lib/retention.js +101 -40
- package/lib/vendor/blamejs/lib/router.js +212 -5
- package/lib/vendor/blamejs/lib/safe-dns.js +29 -45
- package/lib/vendor/blamejs/lib/safe-ical.js +18 -33
- package/lib/vendor/blamejs/lib/safe-icap.js +27 -43
- package/lib/vendor/blamejs/lib/safe-sieve.js +21 -40
- package/lib/vendor/blamejs/lib/safe-sql.js +212 -3
- package/lib/vendor/blamejs/lib/safe-url.js +170 -3
- package/lib/vendor/blamejs/lib/safe-vcard.js +18 -33
- package/lib/vendor/blamejs/lib/scheduler.js +35 -12
- package/lib/vendor/blamejs/lib/seeders.js +122 -74
- package/lib/vendor/blamejs/lib/session-stores.js +42 -14
- package/lib/vendor/blamejs/lib/session.js +175 -77
- package/lib/vendor/blamejs/lib/sql.js +3842 -0
- package/lib/vendor/blamejs/lib/sse.js +26 -0
- package/lib/vendor/blamejs/lib/ssrf-guard.js +151 -4
- package/lib/vendor/blamejs/lib/static.js +177 -34
- package/lib/vendor/blamejs/lib/subject.js +96 -49
- package/lib/vendor/blamejs/lib/vault/index.js +3 -2
- package/lib/vendor/blamejs/lib/vault/passphrase-ops.js +3 -2
- package/lib/vendor/blamejs/lib/vault/rotate.js +168 -108
- package/lib/vendor/blamejs/lib/vault-aad.js +6 -0
- package/lib/vendor/blamejs/lib/vendor-data.js +2 -0
- package/lib/vendor/blamejs/lib/websocket.js +35 -5
- package/lib/vendor/blamejs/lib/worker-pool.js +11 -0
- package/lib/vendor/blamejs/package.json +2 -2
- package/lib/vendor/blamejs/release-notes/v0.14.x.json +1503 -0
- package/lib/vendor/blamejs/release-notes/v0.15.0.json +77 -0
- package/lib/vendor/blamejs/release-notes/v0.15.1.json +22 -0
- package/lib/vendor/blamejs/release-notes/v0.15.2.json +22 -0
- package/lib/vendor/blamejs/release-notes/v0.15.3.json +39 -0
- package/lib/vendor/blamejs/release-notes/v0.15.4.json +39 -0
- package/lib/vendor/blamejs/release-notes/v0.15.5.json +22 -0
- package/lib/vendor/blamejs/release-notes/v0.15.6.json +59 -0
- package/lib/vendor/blamejs/scripts/check-services.js +21 -0
- package/lib/vendor/blamejs/scripts/gen-migrating.js +51 -0
- package/lib/vendor/blamejs/scripts/release.js +398 -38
- package/lib/vendor/blamejs/test/00-primitives.js +117 -0
- package/lib/vendor/blamejs/test/10-state.js +140 -14
- package/lib/vendor/blamejs/test/20-db.js +65 -2
- package/lib/vendor/blamejs/test/helpers/db.js +9 -0
- package/lib/vendor/blamejs/test/helpers/drivers.js +27 -15
- package/lib/vendor/blamejs/test/helpers/services.js +21 -0
- package/lib/vendor/blamejs/test/integration/audit-actor-binding-pg.test.js +246 -0
- package/lib/vendor/blamejs/test/integration/audit-chain-external-db.test.js +517 -0
- package/lib/vendor/blamejs/test/integration/audit-stack-mysql.test.js +639 -0
- package/lib/vendor/blamejs/test/integration/audit-stack-postgres.test.js +832 -0
- package/lib/vendor/blamejs/test/integration/backup-restore-objectstore.test.js +453 -0
- package/lib/vendor/blamejs/test/integration/data-layer-cluster-mysql.test.js +649 -0
- package/lib/vendor/blamejs/test/integration/data-layer-cluster-pg.test.js +770 -0
- package/lib/vendor/blamejs/test/integration/data-layer-mysql-privacy.test.js +630 -0
- package/lib/vendor/blamejs/test/integration/data-layer-mysql.test.js +610 -0
- package/lib/vendor/blamejs/test/integration/data-layer-pg.test.js +577 -0
- package/lib/vendor/blamejs/test/integration/data-layer-postgres.test.js +771 -0
- package/lib/vendor/blamejs/test/integration/db-layer-mysql.test.js +549 -0
- package/lib/vendor/blamejs/test/integration/db-layer-postgres.test.js +598 -0
- package/lib/vendor/blamejs/test/integration/distributed-scheduler-fencing-pg.test.js +602 -0
- package/lib/vendor/blamejs/test/integration/external-db-postgres.test.js +576 -0
- package/lib/vendor/blamejs/test/integration/framework-schema-mysql.test.js +353 -0
- package/lib/vendor/blamejs/test/integration/log-stream-cloudwatch.test.js +224 -0
- package/lib/vendor/blamejs/test/integration/mail-crypto-smime.test.js +142 -17
- package/lib/vendor/blamejs/test/integration/network-heartbeat.test.js +25 -10
- package/lib/vendor/blamejs/test/integration/object-store-azure.test.js +101 -0
- package/lib/vendor/blamejs/test/integration/object-store-gcs.test.js +239 -0
- package/lib/vendor/blamejs/test/integration/object-store-sigv4.test.js +35 -16
- package/lib/vendor/blamejs/test/integration/object-store-worm-lock.test.js +291 -0
- package/lib/vendor/blamejs/test/integration/pubsub.test.js +14 -0
- package/lib/vendor/blamejs/test/integration/queue-sqs.test.js +322 -0
- package/lib/vendor/blamejs/test/integration/redis-reconnect-toxiproxy.test.js +300 -0
- package/lib/vendor/blamejs/test/integration/sql-fts5-catalog-sqlite.test.js +154 -0
- package/lib/vendor/blamejs/test/integration/tls-classical-downgrade-audit.test.js +71 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-event-bus.test.js +175 -12
- package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file-exclusive-temp.test.js +216 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-checkpoint-false-rollback.test.js +203 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-query-self-log.test.js +126 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-safeemit-redacts-secrets.test.js +196 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-signing-key-rotation.test.js +197 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-verifybundle-tamper.test.js +209 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/azure-blob-key-encoding.test.js +121 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/backup-residency-posture.test.js +168 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/backup-scheduletest-drill.test.js +318 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/break-glass.test.js +233 -7
- package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +1120 -14
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance.test.js +229 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-derived-hash.test.js +24 -7
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-dual-read-migrate.test.js +165 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-per-row-key.test.js +350 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-unseal-rate-cap.test.js +27 -9
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-upgrade-dialect.test.js +76 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-interop-oracles.test.js +392 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csrf-protect.test.js +159 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-column-gate.test.js +180 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/db-query-cross-schema.test.js +5 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/db-query-sealed-field-in.test.js +101 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-raw-residency-gate.test.js +128 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-schema-drift.test.js +38 -5
- package/lib/vendor/blamejs/test/layer-0-primitives/db-schema-reconcile-emittable.test.js +127 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-stream-and-payload-shape.test.js +267 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-worm.test.js +150 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/defineguard-default-gate-posture-caps.test.js +30 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dpop-middleware-replaystore-required.test.js +46 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dsr.test.js +218 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/erase-posture-vacuum.test.js +210 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-hardening.test.js +4 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-migrate.test.js +48 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/federation-vc-suite.test.js +237 -5
- package/lib/vendor/blamejs/test/layer-0-primitives/fetch-metadata.test.js +20 -9
- package/lib/vendor/blamejs/test/layer-0-primitives/file-upload-content-safety-skip-audit.test.js +193 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-csv.test.js +90 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-client-stream.test.js +85 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/idempotency-key.test.js +10 -6
- package/lib/vendor/blamejs/test/layer-0-primitives/inbox.test.js +15 -4
- package/lib/vendor/blamejs/test/layer-0-primitives/legal-hold.test.js +146 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-auth.test.js +189 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-journal.test.js +3 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-jmap.test.js +123 -4
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-mx.test.js +207 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-store.test.js +74 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/oauth-callback.test.js +43 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/otel-export.test.js +133 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/otlp-attr-redaction.test.js +101 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/outbox-inflight-reaper.test.js +136 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/parsers-standalone.test.js +83 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/passkey-real-vectors.test.js +429 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/pqc-agent-curve.test.js +21 -11
- package/lib/vendor/blamejs/test/layer-0-primitives/queue-byo-db.test.js +40 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/redact-dlp.test.js +83 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/redis-client.test.js +113 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/retention-dryrun-no-vacuum.test.js +99 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/router-use-path-scope.test.js +255 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-url-canonicalize.test.js +309 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-xml.test.js +143 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/saml-subjectconfirmation-notonorafter.test.js +287 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc-ecdsa-p1363.test.js +79 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc.test.js +50 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/security-headers.test.js +31 -4
- package/lib/vendor/blamejs/test/layer-0-primitives/session-extensions.test.js +45 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sigv4-bucket-ops.test.js +49 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sql.test.js +595 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sse-backpressure.test.js +91 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ssrf-guard.test.js +69 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/static.test.js +194 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/websocket-extension-header.test.js +88 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/worker-pool-recycle-race.test.js +66 -0
- package/lib/vendor/blamejs/test/layer-1-state/api-key.test.js +84 -0
- package/lib/vendor/blamejs/test/layer-5-integration/external-db-residency.test.js +638 -0
- package/lib/vendor/blamejs/test/layer-5-integration/guard-host-integration.test.js +21 -0
- package/lib/vendor/blamejs/test/smoke.js +79 -21
- package/package.json +1 -1
- package/lib/vendor/blamejs/release-notes/v0.14.0.json +0 -43
- package/lib/vendor/blamejs/release-notes/v0.14.1.json +0 -60
- package/lib/vendor/blamejs/release-notes/v0.14.10.json +0 -54
- package/lib/vendor/blamejs/release-notes/v0.14.11.json +0 -72
- package/lib/vendor/blamejs/release-notes/v0.14.12.json +0 -95
- package/lib/vendor/blamejs/release-notes/v0.14.13.json +0 -52
- package/lib/vendor/blamejs/release-notes/v0.14.14.json +0 -31
- package/lib/vendor/blamejs/release-notes/v0.14.16.json +0 -45
- package/lib/vendor/blamejs/release-notes/v0.14.17.json +0 -57
- package/lib/vendor/blamejs/release-notes/v0.14.18.json +0 -127
- package/lib/vendor/blamejs/release-notes/v0.14.19.json +0 -61
- package/lib/vendor/blamejs/release-notes/v0.14.2.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.14.20.json +0 -73
- package/lib/vendor/blamejs/release-notes/v0.14.21.json +0 -98
- package/lib/vendor/blamejs/release-notes/v0.14.22.json +0 -91
- package/lib/vendor/blamejs/release-notes/v0.14.3.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.14.4.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.14.5.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.14.6.json +0 -60
- package/lib/vendor/blamejs/release-notes/v0.14.7.json +0 -77
- package/lib/vendor/blamejs/release-notes/v0.14.8.json +0 -27
- package/lib/vendor/blamejs/release-notes/v0.14.9.json +0 -40
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* azure-blob — blob key percent-encoding.
|
|
4
|
+
*
|
|
5
|
+
* Blob names are interpolated into the request URL path. A key with
|
|
6
|
+
* reserved characters (`?` / `#` / space) MUST be percent-encoded, or
|
|
7
|
+
* the `?` starts the query string / `#` starts the fragment (truncating
|
|
8
|
+
* the path → wrong object or container root) and spaces / control bytes
|
|
9
|
+
* corrupt the request line (CWE-20). `/` separators are preserved
|
|
10
|
+
* (blob names are hierarchical virtual directories). A null byte is
|
|
11
|
+
* refused. We assert the on-the-wire request line by recording the URL
|
|
12
|
+
* the production adapter actually sends to a mock HTTP server.
|
|
13
|
+
*/
|
|
14
|
+
var http = require("node:http");
|
|
15
|
+
var helpers = require("../helpers");
|
|
16
|
+
var check = helpers.check;
|
|
17
|
+
var b = helpers.b;
|
|
18
|
+
var azure = require("../../lib/object-store/azure-blob");
|
|
19
|
+
|
|
20
|
+
function listenRecording() {
|
|
21
|
+
return new Promise(function (resolve) {
|
|
22
|
+
var requests = [];
|
|
23
|
+
var server = http.createServer(function (req, res) {
|
|
24
|
+
requests.push({ method: req.method, url: req.url });
|
|
25
|
+
res.statusCode = 200;
|
|
26
|
+
res.setHeader("etag", "\"mock-etag\"");
|
|
27
|
+
res.end();
|
|
28
|
+
});
|
|
29
|
+
server.listen(0, "127.0.0.1", function () {
|
|
30
|
+
resolve({
|
|
31
|
+
port: server.address().port,
|
|
32
|
+
requests: requests,
|
|
33
|
+
close: function () { return new Promise(function (r) { server.close(r); }); },
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function _client(port) {
|
|
40
|
+
var key = Buffer.from("test-shared-key-32-bytes-padded__", "utf8").toString("base64");
|
|
41
|
+
return azure.create({
|
|
42
|
+
accountName: "blamejstest",
|
|
43
|
+
accountKey: key,
|
|
44
|
+
container: "cont",
|
|
45
|
+
endpoint: "http://127.0.0.1:" + port,
|
|
46
|
+
allowedProtocols: b.safeUrl.ALLOW_HTTP_ALL,
|
|
47
|
+
allowInternal: true,
|
|
48
|
+
timeoutMs: 5000,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function run() {
|
|
53
|
+
var srv = await listenRecording();
|
|
54
|
+
try {
|
|
55
|
+
var c = _client(srv.port);
|
|
56
|
+
|
|
57
|
+
// Each tuple: [rawKey, expectedRequestPath]. The mock records req.url
|
|
58
|
+
// (path + query). The container is "cont".
|
|
59
|
+
var cases = [
|
|
60
|
+
["a b.txt", "/cont/a%20b.txt"],
|
|
61
|
+
["with?q.txt", "/cont/with%3Fq.txt"],
|
|
62
|
+
["with#h.txt", "/cont/with%23h.txt"],
|
|
63
|
+
["dir/sub dir/f.txt", "/cont/dir/sub%20dir/f.txt"],
|
|
64
|
+
["a+b&c.txt", "/cont/a%2Bb%26c.txt"],
|
|
65
|
+
["plain.txt", "/cont/plain.txt"],
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
for (var i = 0; i < cases.length; i++) {
|
|
69
|
+
var raw = cases[i][0];
|
|
70
|
+
var expected = cases[i][1];
|
|
71
|
+
srv.requests.length = 0;
|
|
72
|
+
await c.put(raw, Buffer.from("body", "utf8"), { contentType: "text/plain" });
|
|
73
|
+
var sent = srv.requests.length === 1 ? srv.requests[0].url : "(no request)";
|
|
74
|
+
check("put key " + JSON.stringify(raw) + " → encoded path " + JSON.stringify(expected),
|
|
75
|
+
sent === expected);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// get() must encode the same way (the path is what the server matches).
|
|
79
|
+
srv.requests.length = 0;
|
|
80
|
+
await c.get("with?q.txt");
|
|
81
|
+
check("get key with '?' uses the encoded path",
|
|
82
|
+
srv.requests.length === 1 && srv.requests[0].url === "/cont/with%3Fq.txt");
|
|
83
|
+
|
|
84
|
+
// A reserved char must NOT leak into the query string — the `?` is
|
|
85
|
+
// encoded, so the recorded URL has no real query separator.
|
|
86
|
+
srv.requests.length = 0;
|
|
87
|
+
await c.head("q?inject=1.txt");
|
|
88
|
+
check("reserved '?' does not start a query string",
|
|
89
|
+
srv.requests.length === 1 &&
|
|
90
|
+
srv.requests[0].url === "/cont/q%3Finject%3D1.txt" &&
|
|
91
|
+
srv.requests[0].url.indexOf("?") === -1);
|
|
92
|
+
|
|
93
|
+
// Null byte refused (CWE-20 hostile key) before any request.
|
|
94
|
+
srv.requests.length = 0;
|
|
95
|
+
var threwNull = false;
|
|
96
|
+
try { await c.put("bad" + String.fromCharCode(0) + "key.txt", Buffer.from("x", "utf8")); }
|
|
97
|
+
catch (e) { threwNull = e && e.code === "INVALID_KEY"; }
|
|
98
|
+
check("null-byte key refused with INVALID_KEY", threwNull);
|
|
99
|
+
check("null-byte key sends no request", srv.requests.length === 0);
|
|
100
|
+
|
|
101
|
+
// Presigned URL encodes the key in the PATH while the SAS signature
|
|
102
|
+
// is computed over the raw resource — the returned URL must carry the
|
|
103
|
+
// encoded path AND the SAS query params.
|
|
104
|
+
var pre = c.presignedDownloadUrl({ key: "a b?c.txt", expiresIn: 300 });
|
|
105
|
+
check("presigned URL encodes the key path",
|
|
106
|
+
pre.url.indexOf("/cont/a%20b%3Fc.txt") !== -1);
|
|
107
|
+
check("presigned URL still carries the SAS signature",
|
|
108
|
+
pre.url.indexOf("sig=") !== -1 && pre.url.indexOf("se=") !== -1);
|
|
109
|
+
} finally {
|
|
110
|
+
await srv.close();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
module.exports = { run: run };
|
|
115
|
+
|
|
116
|
+
if (require.main === module) {
|
|
117
|
+
run().then(
|
|
118
|
+
function () { console.log("OK — " + helpers.getChecks() + " checks passed"); },
|
|
119
|
+
function (e) { console.error("FAIL:", e.stack || e); process.exit(1); }
|
|
120
|
+
);
|
|
121
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* b.backup.create — residency posture enforcement.
|
|
4
|
+
*
|
|
5
|
+
* The backup-create residency gate (lib/backup/index.js) is advertised to
|
|
6
|
+
* refuse a cross-border destination under a regulated posture, advise on an
|
|
7
|
+
* undeclared destination, and (the real bug) is blind to per-row residency
|
|
8
|
+
* tags: a deployment whose region is EU but which holds per-row us-east-1
|
|
9
|
+
* rows backs up to an EU destination with no warning about the US rows.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
var helpers = require("../helpers");
|
|
13
|
+
var b = helpers.b;
|
|
14
|
+
var check = helpers.check;
|
|
15
|
+
var fs = helpers.fs;
|
|
16
|
+
var os = helpers.os;
|
|
17
|
+
var path = helpers.path;
|
|
18
|
+
|
|
19
|
+
// Capture audit.safeEmit events by call-through wrapping (same spy shape the
|
|
20
|
+
// erase-posture-vacuum test uses for vacuumAfterErase). The backup gate emits
|
|
21
|
+
// via b.audit.safeEmit, so wrapping it records the advisory.
|
|
22
|
+
function captureAudit() {
|
|
23
|
+
var events = [];
|
|
24
|
+
var real = b.audit.safeEmit;
|
|
25
|
+
b.audit.safeEmit = function (ev) {
|
|
26
|
+
try { events.push(ev); } catch (_e) { /* ignore */ }
|
|
27
|
+
return real.call(b.audit, ev);
|
|
28
|
+
};
|
|
29
|
+
return { events: events, restore: function () { b.audit.safeEmit = real; } };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function initDbWithRegion(tmpDir, region) {
|
|
33
|
+
process.env.BLAMEJS_SKIP_NTP_CHECK = "1";
|
|
34
|
+
helpers.setTestPassphraseEnv();
|
|
35
|
+
b.cluster._resetForTest();
|
|
36
|
+
b.audit._resetForTest();
|
|
37
|
+
b.vault._resetForTest();
|
|
38
|
+
b.db._resetForTest();
|
|
39
|
+
await b.vault.init({ dataDir: tmpDir });
|
|
40
|
+
await b.db.init({
|
|
41
|
+
dataDir: tmpDir,
|
|
42
|
+
tmpDir: path.join(tmpDir, "tmpfs"),
|
|
43
|
+
dataResidency: { region: region, allowedStorageRegions: ["us-east-1", "global"] },
|
|
44
|
+
schema: [{
|
|
45
|
+
name: "residents",
|
|
46
|
+
columns: { _id: "TEXT PRIMARY KEY", name: "TEXT", dataRegion: "TEXT" },
|
|
47
|
+
}],
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Minimal valid backup opts that reach the residency block in create()
|
|
52
|
+
// (dataDir exists, storage/passphrase/files/vaultKeyJson present).
|
|
53
|
+
function backupOpts(dataDir, root, extra) {
|
|
54
|
+
fs.writeFileSync(path.join(dataDir, "db.enc"), Buffer.from([1, 2, 3]));
|
|
55
|
+
fs.writeFileSync(path.join(dataDir, "db.key.enc"), Buffer.from([4, 5, 6]));
|
|
56
|
+
var opts = {
|
|
57
|
+
dataDir: dataDir,
|
|
58
|
+
storage: b.backup.diskStorage({ root: root }),
|
|
59
|
+
passphrase: Buffer.from("operator-backup-passphrase-not-secret"),
|
|
60
|
+
files: [
|
|
61
|
+
{ relativePath: "db.enc", kind: "raw", required: true },
|
|
62
|
+
{ relativePath: "db.key.enc", kind: "raw", required: true },
|
|
63
|
+
],
|
|
64
|
+
vaultKeyJson: '{"version":1,"kid":"k1"}',
|
|
65
|
+
};
|
|
66
|
+
if (extra) for (var k in extra) opts[k] = extra[k];
|
|
67
|
+
return opts;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function createCode(opts) {
|
|
71
|
+
try { b.backup.create(opts); } catch (e) { return e && e.code; }
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function run() {
|
|
76
|
+
var tmp = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-backupresid-"));
|
|
77
|
+
var data = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-backupresid-data-"));
|
|
78
|
+
var root = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-backupresid-root-"));
|
|
79
|
+
await initDbWithRegion(tmp, "eu-west-1");
|
|
80
|
+
b.compliance.clear();
|
|
81
|
+
b.compliance.set("gdpr");
|
|
82
|
+
try {
|
|
83
|
+
// ---- Case 1: MISMATCH-REFUSAL (advertised, never tested to throw) ----
|
|
84
|
+
check("gdpr + eu-west-1 db + us-east-1 destination → backup/residency-mismatch",
|
|
85
|
+
createCode(backupOpts(data, root, { residencyTag: "us-east-1" })) ===
|
|
86
|
+
"backup/residency-mismatch");
|
|
87
|
+
// The allowCrossBorder override path must SUCCEED.
|
|
88
|
+
var okOverride = null;
|
|
89
|
+
try {
|
|
90
|
+
okOverride = b.backup.create(backupOpts(data, root, {
|
|
91
|
+
residencyTag: "us-east-1", allowCrossBorder: true,
|
|
92
|
+
legalBasis: "EU SCCs 2021/914",
|
|
93
|
+
}));
|
|
94
|
+
} catch (_e) { okOverride = null; }
|
|
95
|
+
check("allowCrossBorder + legalBasis override creates the engine",
|
|
96
|
+
okOverride && typeof okOverride.run === "function");
|
|
97
|
+
|
|
98
|
+
// ---- Case 2: UNDECLARED-ADVISORY (advertised, never tested to emit) ----
|
|
99
|
+
var cap = captureAudit();
|
|
100
|
+
try {
|
|
101
|
+
var engNoTag = b.backup.create(backupOpts(data, root, {})); // no residencyTag
|
|
102
|
+
check("undeclared residencyTag does NOT throw (advisory, not refusal)",
|
|
103
|
+
engNoTag && typeof engNoTag.run === "function");
|
|
104
|
+
} finally { cap.restore(); }
|
|
105
|
+
check("undeclared residency under gdpr emits backup.residency_undeclared advisory",
|
|
106
|
+
cap.events.some(function (e) { return e && e.action === "backup.residency_undeclared"; }));
|
|
107
|
+
|
|
108
|
+
// ---- Case 3: PER-ROW BLINDNESS (the real bug — RED today) ----
|
|
109
|
+
// The deployment region is eu-west-1, but a per-row residency-tagged
|
|
110
|
+
// table admits a us-east-1 row (us-east-1 is in allowedStorageRegions).
|
|
111
|
+
// Backing up to an eu-west-1 destination must warn/refuse about the US
|
|
112
|
+
// rows — today it passes silently because the gate only sees the
|
|
113
|
+
// deployment-level region (eu-west-1 == residencyTag eu-west-1 → clean).
|
|
114
|
+
b.cryptoField.clearResidencyForTest();
|
|
115
|
+
b.cryptoField.declarePerRowResidency("residents", {
|
|
116
|
+
residencyColumn: "dataRegion",
|
|
117
|
+
allowedTags: ["eu-west-1", "us-east-1", "global"],
|
|
118
|
+
});
|
|
119
|
+
// The backup advisory reads its table set from this enumerator — assert it
|
|
120
|
+
// directly so the deployment-wide view the gate depends on is covered.
|
|
121
|
+
var declared = b.cryptoField.listPerRowResidency();
|
|
122
|
+
check("listPerRowResidency enumerates the declared table",
|
|
123
|
+
declared.length === 1 && declared[0].table === "residents" &&
|
|
124
|
+
declared[0].residencyColumn === "dataRegion" &&
|
|
125
|
+
declared[0].allowedTags.indexOf("us-east-1") !== -1);
|
|
126
|
+
b.db.from("residents").insertOne({ _id: "row-us", name: "u", dataRegion: "us-east-1" });
|
|
127
|
+
b.db.from("residents").insertOne({ _id: "row-eu", name: "e", dataRegion: "eu-west-1" });
|
|
128
|
+
|
|
129
|
+
var cap2 = captureAudit();
|
|
130
|
+
var sawPerRowAdvisory = false;
|
|
131
|
+
var threwPerRow = null;
|
|
132
|
+
try {
|
|
133
|
+
b.backup.create(backupOpts(data, root, { residencyTag: "eu-west-1" }));
|
|
134
|
+
} catch (e) { threwPerRow = e; }
|
|
135
|
+
finally {
|
|
136
|
+
sawPerRowAdvisory = cap2.events.some(function (e) {
|
|
137
|
+
return e && typeof e.action === "string" &&
|
|
138
|
+
/backup\.residency/.test(e.action) &&
|
|
139
|
+
e.metadata && (e.metadata.scope === "per-row" ||
|
|
140
|
+
/per[-_]?row|row[-_]?tag/.test(JSON.stringify(e.metadata)));
|
|
141
|
+
});
|
|
142
|
+
cap2.restore();
|
|
143
|
+
}
|
|
144
|
+
// RED: the US rows must be surfaced — either a per-row cross-border
|
|
145
|
+
// advisory is emitted, or create() refuses. Today neither happens.
|
|
146
|
+
check("per-row us-east-1 rows under an EU destination trigger a cross-border advisory or refusal",
|
|
147
|
+
sawPerRowAdvisory === true ||
|
|
148
|
+
(threwPerRow && threwPerRow.code === "backup/residency-mismatch"));
|
|
149
|
+
} finally {
|
|
150
|
+
b.cryptoField.clearResidencyForTest();
|
|
151
|
+
b.compliance.clear();
|
|
152
|
+
try { b.db.close(); } catch (_e) {}
|
|
153
|
+
b.audit._resetForTest();
|
|
154
|
+
b.db._resetForTest();
|
|
155
|
+
b.vault._resetForTest();
|
|
156
|
+
b.cluster._resetForTest();
|
|
157
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
158
|
+
fs.rmSync(data, { recursive: true, force: true });
|
|
159
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
160
|
+
}
|
|
161
|
+
console.log("OK — backup residency posture tests");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
module.exports = { run: run };
|
|
165
|
+
if (require.main === module) {
|
|
166
|
+
run().then(function () { process.exit(0); })
|
|
167
|
+
.catch(function (err) { process.exitCode = 1; throw err; });
|
|
168
|
+
}
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* b.backup.scheduleTest — HIPAA §164.308(a)(7)(ii)(D) restore-drill.
|
|
4
|
+
*
|
|
5
|
+
* The drill is the regulator-facing proof that a backup is RESTORABLE,
|
|
6
|
+
* not merely that bytes landed in storage. The framework advertises that
|
|
7
|
+
* scheduleTest:
|
|
8
|
+
* (1) restores the latest bundle into an operator staging dir,
|
|
9
|
+
* (2) verifies the bundle manifest's SLH-DSA / ML-DSA signature,
|
|
10
|
+
* (3) hands the restored bundle + parsed manifest to the operator
|
|
11
|
+
* verify callback, and
|
|
12
|
+
* (4) records a pass/fail result in the signed audit chain
|
|
13
|
+
* (backup.test.passed / backup.test.failed).
|
|
14
|
+
*
|
|
15
|
+
* This drives the real drill end-to-end against a real sealed-at-rest
|
|
16
|
+
* data dir + signed audit chain + local diskStorage backend, then drives
|
|
17
|
+
* a FAILING drill (tampered manifest signature) and asserts it reports
|
|
18
|
+
* failure rather than a false pass.
|
|
19
|
+
*
|
|
20
|
+
* Two dispatch paths are exercised:
|
|
21
|
+
* - the closure scheduleTest builds (captured + awaited directly so we
|
|
22
|
+
* observe completion deterministically), and
|
|
23
|
+
* - the genuine b.scheduler dispatch (instance._fireOnce → drill runs).
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
var fs = require("fs");
|
|
27
|
+
var os = require("os");
|
|
28
|
+
var path = require("path");
|
|
29
|
+
|
|
30
|
+
var helpers = require("../helpers");
|
|
31
|
+
var check = helpers.check;
|
|
32
|
+
var b = helpers.b;
|
|
33
|
+
var waitUntil = helpers.waitUntil;
|
|
34
|
+
var { setupTestDb, teardownTestDb } = require("../helpers/db");
|
|
35
|
+
|
|
36
|
+
// audit_log stores metadata as a JSON string column; query() returns it
|
|
37
|
+
// as-is. Parse it for field assertions.
|
|
38
|
+
function _meta(row) {
|
|
39
|
+
if (row && typeof row.metadata === "string") {
|
|
40
|
+
try { return JSON.parse(row.metadata); } catch (_e) { return {}; }
|
|
41
|
+
}
|
|
42
|
+
return (row && row.metadata) || {};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// A scheduler stand-in whose create() returns a fake whose schedule()
|
|
46
|
+
// captures the spec.run closure. This lets us AWAIT the exact drill
|
|
47
|
+
// closure scheduleTest registers (the full restore+verify+audit body)
|
|
48
|
+
// to completion — the real scheduler's fire path is fire-and-forget.
|
|
49
|
+
function _makeCapturingScheduler() {
|
|
50
|
+
var captured = { specs: [] };
|
|
51
|
+
return {
|
|
52
|
+
captured: captured,
|
|
53
|
+
create: function () {
|
|
54
|
+
return {
|
|
55
|
+
schedule: function (spec) {
|
|
56
|
+
captured.specs.push(spec);
|
|
57
|
+
return spec;
|
|
58
|
+
},
|
|
59
|
+
start: function () {},
|
|
60
|
+
stop: function () {},
|
|
61
|
+
};
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function _seedSealedDataDir() {
|
|
67
|
+
// Real bytes resembling the framework's encrypted-at-rest layout —
|
|
68
|
+
// an opaque db envelope + the sealed DEK. The drill restores + the
|
|
69
|
+
// operator verify hook confirms the restored bytes round-trip.
|
|
70
|
+
var dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "drill-data-"));
|
|
71
|
+
var dbEnc = Buffer.from("ENVELOPE-" + b.crypto.generateToken(48), "utf8");
|
|
72
|
+
var keyEnc = Buffer.from("SEALED-DEK-" + b.crypto.generateToken(32), "utf8");
|
|
73
|
+
fs.writeFileSync(path.join(dataDir, "db.enc"), dbEnc);
|
|
74
|
+
fs.writeFileSync(path.join(dataDir, "db.key.enc"), keyEnc);
|
|
75
|
+
return { dataDir: dataDir, dbEnc: dbEnc, keyEnc: keyEnc };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function main() {
|
|
79
|
+
var tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "drill-fw-"));
|
|
80
|
+
await setupTestDb(tmpDir);
|
|
81
|
+
|
|
82
|
+
var seeded = _seedSealedDataDir();
|
|
83
|
+
var storageRoot = fs.mkdtempSync(path.join(os.tmpdir(), "drill-store-"));
|
|
84
|
+
var restoreRoot = fs.mkdtempSync(path.join(os.tmpdir(), "drill-restore-"));
|
|
85
|
+
|
|
86
|
+
var storage = b.backup.diskStorage({ root: storageRoot });
|
|
87
|
+
var capSched = _makeCapturingScheduler();
|
|
88
|
+
|
|
89
|
+
var engine = b.backup.create({
|
|
90
|
+
dataDir: seeded.dataDir,
|
|
91
|
+
storage: storage,
|
|
92
|
+
passphrase: Buffer.from("operator-backup-passphrase-256bit-entropy-here"),
|
|
93
|
+
files: [
|
|
94
|
+
{ relativePath: "db.enc", kind: "raw", required: true },
|
|
95
|
+
{ relativePath: "db.key.enc", kind: "raw", required: true },
|
|
96
|
+
],
|
|
97
|
+
vaultKeyJson: '{"version":1,"kid":"k1"}',
|
|
98
|
+
scheduler: capSched,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// ---- Produce a real, signed backup bundle --------------------------
|
|
102
|
+
var runSummary = await engine.run({ metadata: { reason: "drill-fixture" } });
|
|
103
|
+
check("backup.run produced a bundle id", typeof runSummary.bundleId === "string" && runSummary.bundleId.length > 0);
|
|
104
|
+
var bundles = await engine.list();
|
|
105
|
+
check("one bundle is in storage after run", bundles.length === 1);
|
|
106
|
+
|
|
107
|
+
// Sanity: the stored bundle's manifest is genuinely signed (the drill's
|
|
108
|
+
// signature-verify step is only meaningful if there is a signature).
|
|
109
|
+
var storedBundleId = bundles[0].bundleId;
|
|
110
|
+
var manifestOnDisk = JSON.parse(
|
|
111
|
+
fs.readFileSync(path.join(storageRoot, storedBundleId, "manifest.json"), "utf8"));
|
|
112
|
+
check("stored manifest carries a signature block",
|
|
113
|
+
manifestOnDisk.signature && typeof manifestOnDisk.signature.value === "string" &&
|
|
114
|
+
manifestOnDisk.signature.value.length > 0);
|
|
115
|
+
var signedFingerprint = manifestOnDisk.signature.fingerprint;
|
|
116
|
+
|
|
117
|
+
// =====================================================================
|
|
118
|
+
// PART 1 — PASSING DRILL via the captured closure (awaited to done)
|
|
119
|
+
// =====================================================================
|
|
120
|
+
var verifyCall = null;
|
|
121
|
+
var notifyCalls = [];
|
|
122
|
+
var schedResult = engine.scheduleTest({
|
|
123
|
+
cron: "0 3 * * 0",
|
|
124
|
+
restoreTo: restoreRoot,
|
|
125
|
+
posture: "hipaa",
|
|
126
|
+
verify: async function (ctx) {
|
|
127
|
+
// Operator-side verification: confirm the restored payload matches
|
|
128
|
+
// the original sealed bytes AND the framework handed us a parsed,
|
|
129
|
+
// signature-verified manifest + the outDir it restored into.
|
|
130
|
+
verifyCall = {
|
|
131
|
+
outDir: ctx.outDir,
|
|
132
|
+
bundleId: ctx.bundleId,
|
|
133
|
+
sigFingerprint: ctx.sigFingerprint,
|
|
134
|
+
manifestVersion: ctx.manifest && ctx.manifest.version,
|
|
135
|
+
dbEncMatch: false,
|
|
136
|
+
keyEncMatch: false,
|
|
137
|
+
outDirExists: fs.existsSync(ctx.outDir),
|
|
138
|
+
};
|
|
139
|
+
// The bundle stores ENCRYPTED file bytes under files/; decrypt-on-
|
|
140
|
+
// restore is the backup-crypto layer's job and is exercised by the
|
|
141
|
+
// bundle tests. Here the drill's contract is "restored bundle is
|
|
142
|
+
// present + manifest verifies"; the operator hook proves the bundle
|
|
143
|
+
// dir + manifest are real by reading back the manifest's file list
|
|
144
|
+
// and confirming the encrypted blobs exist on disk.
|
|
145
|
+
var allBlobsPresent = ctx.manifest.files.every(function (f) {
|
|
146
|
+
return fs.existsSync(path.join(ctx.outDir, f.encryptedPath));
|
|
147
|
+
});
|
|
148
|
+
verifyCall.allBlobsPresent = allBlobsPresent;
|
|
149
|
+
// Confirm the manifest describes the two files we backed up.
|
|
150
|
+
var rels = ctx.manifest.files.map(function (f) { return f.relativePath; }).sort();
|
|
151
|
+
verifyCall.dbEncMatch = rels.indexOf("db.enc") !== -1;
|
|
152
|
+
verifyCall.keyEncMatch = rels.indexOf("db.key.enc") !== -1;
|
|
153
|
+
return true;
|
|
154
|
+
},
|
|
155
|
+
notify: async function (info) { notifyCalls.push(info); },
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
check("scheduleTest returned an instance handle",
|
|
159
|
+
schedResult && typeof schedResult.name === "string" && schedResult.instance);
|
|
160
|
+
check("scheduleTest registered exactly one scheduler task",
|
|
161
|
+
capSched.captured.specs.length === 1);
|
|
162
|
+
var drillSpec = capSched.captured.specs[0];
|
|
163
|
+
check("the registered task carries the operator cron",
|
|
164
|
+
drillSpec.cron === "0 3 * * 0");
|
|
165
|
+
check("the registered task carries an async run closure",
|
|
166
|
+
typeof drillSpec.run === "function");
|
|
167
|
+
|
|
168
|
+
// Fire the real drill closure and await it to completion.
|
|
169
|
+
await drillSpec.run();
|
|
170
|
+
|
|
171
|
+
check("(1) drill restored the bundle into a staging dir under restoreTo",
|
|
172
|
+
verifyCall !== null && verifyCall.outDirExists === true &&
|
|
173
|
+
verifyCall.outDir.indexOf(restoreRoot) === 0);
|
|
174
|
+
check("(1b) restored staging dir carried the encrypted file blobs",
|
|
175
|
+
verifyCall.allBlobsPresent === true);
|
|
176
|
+
check("(2) drill verified the manifest signature before calling verify (fingerprint passed through)",
|
|
177
|
+
verifyCall.sigFingerprint === signedFingerprint &&
|
|
178
|
+
typeof verifyCall.sigFingerprint === "string" && verifyCall.sigFingerprint.length > 0);
|
|
179
|
+
check("(3) drill handed the operator a parsed manifest covering the backed-up files",
|
|
180
|
+
verifyCall.manifestVersion === 1 && verifyCall.dbEncMatch && verifyCall.keyEncMatch);
|
|
181
|
+
check("(3b) drill targeted the newest stored bundle",
|
|
182
|
+
verifyCall.bundleId === storedBundleId);
|
|
183
|
+
|
|
184
|
+
// (4) pass/fail recorded in the signed audit chain.
|
|
185
|
+
await b.audit.flush();
|
|
186
|
+
var passRows = await b.audit.query({ action: "backup.test.passed" });
|
|
187
|
+
check("(4) drill emitted exactly one backup.test.passed audit row", passRows.length === 1);
|
|
188
|
+
var passMeta = _meta(passRows[0]);
|
|
189
|
+
check("(4b) passed row records the bundle id + posture + fingerprint",
|
|
190
|
+
passMeta.bundleId === storedBundleId &&
|
|
191
|
+
passMeta.posture === "hipaa" &&
|
|
192
|
+
passMeta.fingerprint === signedFingerprint);
|
|
193
|
+
check("(4c) passed row outcome is success", passRows[0].outcome === "success");
|
|
194
|
+
|
|
195
|
+
check("notify hook fired with success outcome on a passing drill",
|
|
196
|
+
notifyCalls.length === 1 && notifyCalls[0].outcome === "success" &&
|
|
197
|
+
notifyCalls[0].bundleId === storedBundleId);
|
|
198
|
+
|
|
199
|
+
// The drill cleans staging by default — confirm it didn't leave the
|
|
200
|
+
// restore dir behind (regulator drills must not accumulate plaintext-
|
|
201
|
+
// restored bundles).
|
|
202
|
+
check("drill cleaned its staging dir after a passing run",
|
|
203
|
+
!fs.existsSync(verifyCall.outDir));
|
|
204
|
+
|
|
205
|
+
// =====================================================================
|
|
206
|
+
// PART 2 — PASSING DRILL via the GENUINE scheduler dispatch path
|
|
207
|
+
// =====================================================================
|
|
208
|
+
// Re-run the drill through a real b.scheduler instance + its _fireOnce
|
|
209
|
+
// test hook so the genuine schedule()/fire path is exercised, not just
|
|
210
|
+
// the captured closure.
|
|
211
|
+
b.scheduler._resetForTest && b.scheduler._resetForTest();
|
|
212
|
+
var realVerifyHit = false;
|
|
213
|
+
var realEngine = b.backup.create({
|
|
214
|
+
dataDir: seeded.dataDir,
|
|
215
|
+
storage: storage,
|
|
216
|
+
passphrase: Buffer.from("operator-backup-passphrase-256bit-entropy-here"),
|
|
217
|
+
files: [
|
|
218
|
+
{ relativePath: "db.enc", kind: "raw", required: true },
|
|
219
|
+
{ relativePath: "db.key.enc", kind: "raw", required: true },
|
|
220
|
+
],
|
|
221
|
+
vaultKeyJson: '{"version":1,"kid":"k1"}',
|
|
222
|
+
scheduler: b.scheduler,
|
|
223
|
+
});
|
|
224
|
+
var realDrill = realEngine.scheduleTest({
|
|
225
|
+
name: "drill.real.path",
|
|
226
|
+
cron: "0 4 * * 0",
|
|
227
|
+
restoreTo: restoreRoot,
|
|
228
|
+
posture: "hipaa",
|
|
229
|
+
verify: async function (ctx) {
|
|
230
|
+
realVerifyHit = ctx && typeof ctx.outDir === "string" &&
|
|
231
|
+
ctx.sigFingerprint === signedFingerprint;
|
|
232
|
+
return true;
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
// Fire via the real scheduler dispatch. _fireOnce is fire-and-forget;
|
|
236
|
+
// wait for the drill's audit row to appear.
|
|
237
|
+
realDrill.instance._fireOnce("drill.real.path");
|
|
238
|
+
await waitUntil(async function () {
|
|
239
|
+
await b.audit.flush();
|
|
240
|
+
var rows = await b.audit.query({ action: "backup.test.passed" });
|
|
241
|
+
return rows.length >= 2;
|
|
242
|
+
}, { timeoutMs: 5000, label: "real-scheduler drill: backup.test.passed lands" });
|
|
243
|
+
check("genuine scheduler dispatch ran the drill verify hook", realVerifyHit === true);
|
|
244
|
+
|
|
245
|
+
// =====================================================================
|
|
246
|
+
// PART 3 — FAILING DRILL: tamper the stored bundle's manifest signature
|
|
247
|
+
// =====================================================================
|
|
248
|
+
// Flip a byte in the signature value of the stored manifest so the
|
|
249
|
+
// signature no longer verifies. The drill MUST report failure — not a
|
|
250
|
+
// false pass — and MUST NOT call the operator verify hook (verification
|
|
251
|
+
// gates the hand-off).
|
|
252
|
+
var manifestPath = path.join(storageRoot, storedBundleId, "manifest.json");
|
|
253
|
+
var tampered = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
254
|
+
var sigBuf = Buffer.from(tampered.signature.value, "base64");
|
|
255
|
+
sigBuf[0] = sigBuf[0] ^ 0xff; // corrupt the first signature byte
|
|
256
|
+
tampered.signature.value = sigBuf.toString("base64");
|
|
257
|
+
fs.writeFileSync(manifestPath, JSON.stringify(tampered, null, 2) + "\n");
|
|
258
|
+
|
|
259
|
+
var failVerifyCalled = false;
|
|
260
|
+
var failNotify = [];
|
|
261
|
+
var capSched2 = _makeCapturingScheduler();
|
|
262
|
+
var failEngine = b.backup.create({
|
|
263
|
+
dataDir: seeded.dataDir,
|
|
264
|
+
storage: storage,
|
|
265
|
+
passphrase: Buffer.from("operator-backup-passphrase-256bit-entropy-here"),
|
|
266
|
+
files: [
|
|
267
|
+
{ relativePath: "db.enc", kind: "raw", required: true },
|
|
268
|
+
{ relativePath: "db.key.enc", kind: "raw", required: true },
|
|
269
|
+
],
|
|
270
|
+
vaultKeyJson: '{"version":1,"kid":"k1"}',
|
|
271
|
+
scheduler: capSched2,
|
|
272
|
+
});
|
|
273
|
+
failEngine.scheduleTest({
|
|
274
|
+
cron: "0 5 * * 0",
|
|
275
|
+
restoreTo: restoreRoot,
|
|
276
|
+
posture: "hipaa",
|
|
277
|
+
verify: async function () { failVerifyCalled = true; return true; },
|
|
278
|
+
notify: async function (info) { failNotify.push(info); },
|
|
279
|
+
});
|
|
280
|
+
await capSched2.captured.specs[0].run();
|
|
281
|
+
|
|
282
|
+
check("tampered-signature drill did NOT call the operator verify hook (verify gates hand-off)",
|
|
283
|
+
failVerifyCalled === false);
|
|
284
|
+
|
|
285
|
+
await b.audit.flush();
|
|
286
|
+
var failRows = await b.audit.query({ action: "backup.test.failed" });
|
|
287
|
+
check("failing drill emitted a backup.test.failed audit row (no false pass)",
|
|
288
|
+
failRows.length === 1);
|
|
289
|
+
check("failed row outcome is failure", failRows[0].outcome === "failure");
|
|
290
|
+
var failMeta = _meta(failRows[0]);
|
|
291
|
+
check("failed row reason names the signature failure",
|
|
292
|
+
typeof failMeta.reason === "string" && /signature/i.test(failMeta.reason));
|
|
293
|
+
|
|
294
|
+
// Critically: the passing-row count must NOT have grown — a tampered
|
|
295
|
+
// bundle must never be recorded as a passed drill.
|
|
296
|
+
var passRowsAfterTamper = await b.audit.query({ action: "backup.test.passed" });
|
|
297
|
+
check("tampering did not produce an additional backup.test.passed row",
|
|
298
|
+
passRowsAfterTamper.length === 2);
|
|
299
|
+
|
|
300
|
+
check("notify hook fired with failure outcome on the tampered drill",
|
|
301
|
+
failNotify.length === 1 && failNotify[0].outcome === "failure" &&
|
|
302
|
+
/signature/i.test(failNotify[0].reason || ""));
|
|
303
|
+
|
|
304
|
+
// ---- cleanup -------------------------------------------------------
|
|
305
|
+
await teardownTestDb(tmpDir);
|
|
306
|
+
try { fs.rmSync(seeded.dataDir, { recursive: true, force: true }); } catch (_e) {}
|
|
307
|
+
try { fs.rmSync(storageRoot, { recursive: true, force: true }); } catch (_e) {}
|
|
308
|
+
try { fs.rmSync(restoreRoot, { recursive: true, force: true }); } catch (_e) {}
|
|
309
|
+
|
|
310
|
+
// check() throws on the first failed assertion, so reaching here means
|
|
311
|
+
// every assertion passed.
|
|
312
|
+
console.log("backup-scheduletest-drill: OK — " + helpers.getChecks() + " checks passed");
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
main().catch(function (e) {
|
|
316
|
+
console.error("FAIL: backup-scheduletest-drill threw:", e && e.stack || e);
|
|
317
|
+
process.exitCode = 1;
|
|
318
|
+
});
|