@blamejs/blamejs-shop 0.5.12 → 0.5.14
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/sales-tax-filings.js +143 -0
- package/lib/vendor/MANIFEST.json +632 -416
- package/lib/vendor/blamejs/.clusterfuzzlite/Dockerfile +21 -10
- package/lib/vendor/blamejs/.clusterfuzzlite/build.sh +30 -9
- package/lib/vendor/blamejs/.github/dependabot.yml +8 -0
- package/lib/vendor/blamejs/.github/workflows/actions-lint.yml +3 -3
- package/lib/vendor/blamejs/.github/workflows/cflite_batch.yml +7 -2
- package/lib/vendor/blamejs/.github/workflows/cflite_pr.yml +7 -2
- package/lib/vendor/blamejs/.github/workflows/ci.yml +21 -21
- package/lib/vendor/blamejs/.github/workflows/codeql.yml +3 -3
- package/lib/vendor/blamejs/.github/workflows/npm-publish.yml +4 -4
- package/lib/vendor/blamejs/.github/workflows/release-container.yml +7 -7
- package/lib/vendor/blamejs/.github/workflows/scorecard.yml +3 -3
- package/lib/vendor/blamejs/.github/workflows/sha-to-tag-verify.yml +1 -1
- package/lib/vendor/blamejs/.gitignore +2 -0
- package/lib/vendor/blamejs/CHANGELOG.md +122 -0
- package/lib/vendor/blamejs/NOTICE +26 -1
- package/lib/vendor/blamejs/SECURITY.md +1 -1
- package/lib/vendor/blamejs/api-snapshot.json +207 -5
- package/lib/vendor/blamejs/docker/keycloak/realm-blamejs-test.json +29 -0
- package/lib/vendor/blamejs/examples/wiki/DEPLOY.md +1 -0
- package/lib/vendor/blamejs/examples/wiki/lib/build-app.js +31 -0
- package/lib/vendor/blamejs/examples/wiki/lib/source-comment-block-validator.js +109 -0
- package/lib/vendor/blamejs/examples/wiki/nginx.conf +106 -0
- package/lib/vendor/blamejs/examples/wiki/package-lock.json +1 -1
- package/lib/vendor/blamejs/examples/wiki/test/e2e.js +92 -0
- package/lib/vendor/blamejs/fuzz/asn1-der.fuzz.js +28 -0
- package/lib/vendor/blamejs/fuzz/cms-codec.fuzz.js +17 -0
- package/lib/vendor/blamejs/fuzz/link-header.fuzz.js +18 -0
- package/lib/vendor/blamejs/index.js +6 -0
- package/lib/vendor/blamejs/lib/a2a.js +4 -4
- package/lib/vendor/blamejs/lib/agent-idempotency.js +18 -2
- package/lib/vendor/blamejs/lib/agent-saga.js +36 -8
- package/lib/vendor/blamejs/lib/agent-snapshot.js +63 -6
- package/lib/vendor/blamejs/lib/app-shutdown.js +23 -20
- package/lib/vendor/blamejs/lib/archive-read.js +8 -0
- package/lib/vendor/blamejs/lib/archive-tar.js +12 -2
- package/lib/vendor/blamejs/lib/arg-parser.js +49 -5
- package/lib/vendor/blamejs/lib/asn1-der.js +12 -0
- package/lib/vendor/blamejs/lib/audit-sign.js +47 -1
- package/lib/vendor/blamejs/lib/audit.js +71 -8
- package/lib/vendor/blamejs/lib/auth/ciba.js +14 -2
- package/lib/vendor/blamejs/lib/auth/fido-mds3.js +58 -47
- package/lib/vendor/blamejs/lib/auth/jwt.js +34 -0
- package/lib/vendor/blamejs/lib/auth/oauth.js +90 -13
- package/lib/vendor/blamejs/lib/auth/oid4vci.js +20 -17
- package/lib/vendor/blamejs/lib/auth/openid-federation.js +214 -23
- package/lib/vendor/blamejs/lib/auth/saml.js +38 -20
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc.js +10 -4
- package/lib/vendor/blamejs/lib/backup/bundle.js +13 -3
- package/lib/vendor/blamejs/lib/backup/index.js +16 -5
- package/lib/vendor/blamejs/lib/backup/manifest.js +18 -5
- package/lib/vendor/blamejs/lib/base32.js +21 -0
- package/lib/vendor/blamejs/lib/break-glass.js +19 -10
- package/lib/vendor/blamejs/lib/bundler.js +5 -1
- package/lib/vendor/blamejs/lib/cache-status.js +3 -7
- package/lib/vendor/blamejs/lib/calendar.js +21 -1
- package/lib/vendor/blamejs/lib/cbor.js +12 -1
- package/lib/vendor/blamejs/lib/cert.js +29 -11
- package/lib/vendor/blamejs/lib/cli.js +94 -24
- package/lib/vendor/blamejs/lib/cloud-events.js +2 -2
- package/lib/vendor/blamejs/lib/cluster-provider-db.js +13 -3
- package/lib/vendor/blamejs/lib/codepoint-class.js +85 -0
- package/lib/vendor/blamejs/lib/compliance-ai-act-prohibited.js +8 -4
- package/lib/vendor/blamejs/lib/compliance-ai-act-transparency.js +19 -4
- package/lib/vendor/blamejs/lib/compliance-ai-act.js +6 -8
- package/lib/vendor/blamejs/lib/content-credentials.js +1 -1
- package/lib/vendor/blamejs/lib/content-digest.js +2 -1
- package/lib/vendor/blamejs/lib/cookies.js +13 -3
- package/lib/vendor/blamejs/lib/cose.js +31 -16
- package/lib/vendor/blamejs/lib/crypto-field.js +32 -0
- package/lib/vendor/blamejs/lib/crypto-oprf.js +11 -2
- package/lib/vendor/blamejs/lib/crypto-xwing.js +22 -4
- package/lib/vendor/blamejs/lib/crypto.js +314 -19
- package/lib/vendor/blamejs/lib/csp.js +11 -3
- package/lib/vendor/blamejs/lib/daemon.js +270 -24
- package/lib/vendor/blamejs/lib/dark-patterns.js +5 -2
- package/lib/vendor/blamejs/lib/db-declare-view.js +2 -2
- package/lib/vendor/blamejs/lib/db-query.js +34 -7
- package/lib/vendor/blamejs/lib/db.js +18 -6
- package/lib/vendor/blamejs/lib/dbsc.js +17 -0
- package/lib/vendor/blamejs/lib/dev.js +21 -2
- package/lib/vendor/blamejs/lib/dsr.js +157 -102
- package/lib/vendor/blamejs/lib/external-db.js +41 -2
- package/lib/vendor/blamejs/lib/file-upload.js +25 -7
- package/lib/vendor/blamejs/lib/flag-providers.js +27 -15
- package/lib/vendor/blamejs/lib/forms.js +113 -27
- package/lib/vendor/blamejs/lib/gate-contract.js +23 -10
- package/lib/vendor/blamejs/lib/gdpr-ropa.js +8 -1
- package/lib/vendor/blamejs/lib/guard-agent-registry.js +1 -1
- package/lib/vendor/blamejs/lib/guard-archive.js +2 -2
- package/lib/vendor/blamejs/lib/guard-cidr.js +27 -1
- package/lib/vendor/blamejs/lib/guard-graphql.js +30 -11
- package/lib/vendor/blamejs/lib/guard-html.js +26 -53
- package/lib/vendor/blamejs/lib/guard-image.js +24 -5
- package/lib/vendor/blamejs/lib/guard-imap-command.js +10 -2
- package/lib/vendor/blamejs/lib/guard-jmap.js +19 -3
- package/lib/vendor/blamejs/lib/guard-jwt.js +18 -2
- package/lib/vendor/blamejs/lib/guard-list-unsubscribe.js +2 -2
- package/lib/vendor/blamejs/lib/guard-managesieve-command.js +10 -2
- package/lib/vendor/blamejs/lib/guard-markdown.js +11 -24
- package/lib/vendor/blamejs/lib/guard-pdf.js +18 -2
- package/lib/vendor/blamejs/lib/guard-pop3-command.js +10 -2
- package/lib/vendor/blamejs/lib/guard-regex.js +8 -5
- package/lib/vendor/blamejs/lib/guard-svg.js +31 -32
- package/lib/vendor/blamejs/lib/guard-time.js +1 -1
- package/lib/vendor/blamejs/lib/guard-uuid.js +2 -2
- package/lib/vendor/blamejs/lib/html-balance.js +3 -3
- package/lib/vendor/blamejs/lib/http-client-cache.js +82 -4
- package/lib/vendor/blamejs/lib/http-client-cookie-jar.js +16 -1
- package/lib/vendor/blamejs/lib/http-client.js +39 -7
- package/lib/vendor/blamejs/lib/http-message-signature.js +35 -1
- package/lib/vendor/blamejs/lib/i18n-messageformat.js +31 -6
- package/lib/vendor/blamejs/lib/i18n.js +35 -21
- package/lib/vendor/blamejs/lib/incident-report.js +8 -1
- package/lib/vendor/blamejs/lib/ip-utils.js +11 -1
- package/lib/vendor/blamejs/lib/json-path.js +23 -1
- package/lib/vendor/blamejs/lib/jwk.js +20 -3
- package/lib/vendor/blamejs/lib/link-header.js +2 -1
- package/lib/vendor/blamejs/lib/log-stream-otlp-grpc.js +5 -1
- package/lib/vendor/blamejs/lib/log-stream-otlp.js +5 -1
- package/lib/vendor/blamejs/lib/log-stream.js +8 -3
- package/lib/vendor/blamejs/lib/mail-auth.js +123 -22
- package/lib/vendor/blamejs/lib/mail-bimi.js +1 -1
- package/lib/vendor/blamejs/lib/mail-bounce.js +8 -0
- package/lib/vendor/blamejs/lib/mail-crypto-pgp.js +46 -10
- package/lib/vendor/blamejs/lib/mail-dav.js +22 -3
- package/lib/vendor/blamejs/lib/mail-dkim.js +96 -30
- package/lib/vendor/blamejs/lib/mail-helo.js +6 -1
- package/lib/vendor/blamejs/lib/mail-scan.js +23 -16
- package/lib/vendor/blamejs/lib/mail-send-deliver.js +34 -8
- package/lib/vendor/blamejs/lib/mail-server-imap.js +3 -4
- package/lib/vendor/blamejs/lib/mail-server-managesieve.js +7 -11
- package/lib/vendor/blamejs/lib/mail-server-mx.js +93 -7
- package/lib/vendor/blamejs/lib/mail-server-registry.js +8 -2
- package/lib/vendor/blamejs/lib/mail.js +15 -5
- package/lib/vendor/blamejs/lib/metrics.js +268 -63
- package/lib/vendor/blamejs/lib/middleware/age-gate.js +7 -1
- package/lib/vendor/blamejs/lib/middleware/asyncapi-serve.js +1 -1
- package/lib/vendor/blamejs/lib/middleware/body-parser.js +19 -4
- package/lib/vendor/blamejs/lib/middleware/bot-guard.js +5 -9
- package/lib/vendor/blamejs/lib/middleware/csp-report.js +40 -8
- package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +24 -10
- package/lib/vendor/blamejs/lib/middleware/dpop.js +10 -5
- package/lib/vendor/blamejs/lib/middleware/index.js +6 -1
- package/lib/vendor/blamejs/lib/middleware/nel.js +1 -1
- package/lib/vendor/blamejs/lib/middleware/openapi-serve.js +1 -1
- package/lib/vendor/blamejs/lib/middleware/rate-limit.js +12 -1
- package/lib/vendor/blamejs/lib/middleware/require-bound-key.js +1 -1
- package/lib/vendor/blamejs/lib/middleware/scim-server.js +1 -1
- package/lib/vendor/blamejs/lib/middleware/tus-upload.js +6 -2
- package/lib/vendor/blamejs/lib/mtls-ca.js +18 -2
- package/lib/vendor/blamejs/lib/network-dns-resolver.js +11 -1
- package/lib/vendor/blamejs/lib/network-dns.js +21 -2
- package/lib/vendor/blamejs/lib/network-tls.js +69 -7
- package/lib/vendor/blamejs/lib/nonce-store.js +9 -1
- package/lib/vendor/blamejs/lib/object-store/gcs.js +7 -0
- package/lib/vendor/blamejs/lib/object-store/sigv4.js +31 -0
- package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +1 -1
- package/lib/vendor/blamejs/lib/outbox.js +1 -1
- package/lib/vendor/blamejs/lib/parsers/safe-ini.js +9 -1
- package/lib/vendor/blamejs/lib/parsers/safe-toml.js +10 -1
- package/lib/vendor/blamejs/lib/permissions.js +6 -3
- package/lib/vendor/blamejs/lib/pid-probe.js +55 -0
- package/lib/vendor/blamejs/lib/pqc-agent.js +8 -1
- package/lib/vendor/blamejs/lib/pqc-software.js +3 -3
- package/lib/vendor/blamejs/lib/pubsub.js +20 -9
- package/lib/vendor/blamejs/lib/queue-local.js +8 -1
- package/lib/vendor/blamejs/lib/queue-redis.js +5 -0
- package/lib/vendor/blamejs/lib/redact.js +54 -0
- package/lib/vendor/blamejs/lib/restore-bundle.js +10 -2
- package/lib/vendor/blamejs/lib/restore.js +19 -0
- package/lib/vendor/blamejs/lib/retention.js +12 -4
- package/lib/vendor/blamejs/lib/router.js +60 -6
- package/lib/vendor/blamejs/lib/safe-buffer.js +69 -0
- package/lib/vendor/blamejs/lib/safe-decompress.js +8 -2
- package/lib/vendor/blamejs/lib/safe-dns.js +25 -20
- package/lib/vendor/blamejs/lib/safe-ical.js +7 -3
- package/lib/vendor/blamejs/lib/safe-mime.js +12 -1
- package/lib/vendor/blamejs/lib/safe-object.js +80 -0
- package/lib/vendor/blamejs/lib/safe-path.js +52 -4
- package/lib/vendor/blamejs/lib/safe-redirect.js +10 -2
- package/lib/vendor/blamejs/lib/safe-schema.js +4 -1
- package/lib/vendor/blamejs/lib/safe-sieve.js +33 -1
- package/lib/vendor/blamejs/lib/safe-sql.js +88 -0
- package/lib/vendor/blamejs/lib/safe-vcard.js +7 -3
- package/lib/vendor/blamejs/lib/scheduler.js +8 -1
- package/lib/vendor/blamejs/lib/security-assert.js +6 -6
- package/lib/vendor/blamejs/lib/self-update-standalone-verifier.js +74 -27
- package/lib/vendor/blamejs/lib/self-update.js +517 -87
- package/lib/vendor/blamejs/lib/server-timing.js +2 -4
- package/lib/vendor/blamejs/lib/session.js +34 -12
- package/lib/vendor/blamejs/lib/sql.js +15 -1
- package/lib/vendor/blamejs/lib/ssrf-guard.js +52 -0
- package/lib/vendor/blamejs/lib/static.js +55 -13
- package/lib/vendor/blamejs/lib/storage.js +8 -2
- package/lib/vendor/blamejs/lib/subject.js +6 -1
- package/lib/vendor/blamejs/lib/tenant-quota.js +52 -13
- package/lib/vendor/blamejs/lib/tls-exporter.js +3 -3
- package/lib/vendor/blamejs/lib/tsa.js +34 -13
- package/lib/vendor/blamejs/lib/uri-template.js +8 -1
- package/lib/vendor/blamejs/lib/vault/index.js +13 -0
- package/lib/vendor/blamejs/lib/vc.js +44 -5
- package/lib/vendor/blamejs/lib/vendor/MANIFEST.json +26 -26
- package/lib/vendor/blamejs/lib/vendor/noble-ciphers.cjs +792 -1
- package/lib/vendor/blamejs/lib/vendor/noble-curves.cjs +3443 -1
- package/lib/vendor/blamejs/lib/vendor/noble-post-quantum.cjs +2737 -1
- package/lib/vendor/blamejs/lib/vendor/pki.cjs +39550 -13
- package/lib/vendor/blamejs/lib/vendor/public-suffix-list.dat +55 -53
- package/lib/vendor/blamejs/lib/vendor/public-suffix-list.data.js +5551 -5554
- package/lib/vendor/blamejs/lib/vendor/simplewebauthn-server.cjs +22459 -32
- package/lib/vendor/blamejs/lib/watcher.js +89 -17
- package/lib/vendor/blamejs/lib/webhook-dispatcher.js +25 -3
- package/lib/vendor/blamejs/lib/webhook.js +12 -2
- package/lib/vendor/blamejs/lib/ws-client.js +32 -1
- package/lib/vendor/blamejs/lib/xml-c14n.js +29 -1
- package/lib/vendor/blamejs/oss-fuzz/projects/blamejs/Dockerfile +1 -1
- package/lib/vendor/blamejs/package-lock.json +2 -2
- package/lib/vendor/blamejs/package.json +1 -1
- package/lib/vendor/blamejs/release-notes/v0.16.x.json +1462 -0
- package/lib/vendor/blamejs/release-notes/v0.17.0.json +31 -0
- package/lib/vendor/blamejs/release-notes/v0.17.1.json +31 -0
- package/lib/vendor/blamejs/release-notes/v0.17.10.json +30 -0
- package/lib/vendor/blamejs/release-notes/v0.17.11.json +22 -0
- package/lib/vendor/blamejs/release-notes/v0.17.12.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.17.13.json +77 -0
- package/lib/vendor/blamejs/release-notes/v0.17.14.json +40 -0
- package/lib/vendor/blamejs/release-notes/v0.17.15.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.17.16.json +27 -0
- package/lib/vendor/blamejs/release-notes/v0.17.17.json +31 -0
- package/lib/vendor/blamejs/release-notes/v0.17.18.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.17.19.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.17.2.json +26 -0
- package/lib/vendor/blamejs/release-notes/v0.17.20.json +43 -0
- package/lib/vendor/blamejs/release-notes/v0.17.21.json +26 -0
- package/lib/vendor/blamejs/release-notes/v0.17.22.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.17.3.json +43 -0
- package/lib/vendor/blamejs/release-notes/v0.17.4.json +35 -0
- package/lib/vendor/blamejs/release-notes/v0.17.5.json +26 -0
- package/lib/vendor/blamejs/release-notes/v0.17.6.json +26 -0
- package/lib/vendor/blamejs/release-notes/v0.17.7.json +35 -0
- package/lib/vendor/blamejs/release-notes/v0.17.8.json +22 -0
- package/lib/vendor/blamejs/release-notes/v0.17.9.json +26 -0
- package/lib/vendor/blamejs/scripts/validate-source-comment-blocks.js +4 -0
- package/lib/vendor/blamejs/scripts/vendor-update.sh +293 -9
- package/lib/vendor/blamejs/socket.yml +94 -0
- package/lib/vendor/blamejs/test/00-primitives.js +605 -0
- package/lib/vendor/blamejs/test/helpers/drivers.js +12 -9
- package/lib/vendor/blamejs/test/integration/audit-stack-mysql.test.js +90 -1
- package/lib/vendor/blamejs/test/integration/audit-stack-postgres.test.js +122 -1
- package/lib/vendor/blamejs/test/integration/data-layer-mysql.test.js +30 -0
- package/lib/vendor/blamejs/test/integration/data-layer-pg.test.js +29 -0
- package/lib/vendor/blamejs/test/integration/dsr-cluster-mysql.test.js +479 -0
- package/lib/vendor/blamejs/test/integration/dsr-cluster-pg.test.js +487 -0
- package/lib/vendor/blamejs/test/integration/federation-auth.test.js +313 -5
- package/lib/vendor/blamejs/test/integration/mail-dkim.test.js +80 -0
- package/lib/vendor/blamejs/test/integration/openid-federation-chain.test.js +275 -0
- package/lib/vendor/blamejs/test/integration/webhook-dispatcher-pg.test.js +96 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/a2a-tasks.test.js +401 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/acme.test.js +1649 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/age-gate.test.js +33 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-idempotency.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-saga.test.js +60 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-snapshot.test.js +524 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/api-encrypt.test.js +891 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/app-shutdown.test.js +128 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/archive-adapters.test.js +271 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/archive-read.test.js +628 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/archive-tar-read.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/archive-wrap.test.js +227 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/arg-parser.test.js +15 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/asn1-der.test.js +73 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file.test.js +114 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-sign-anchor.test.js +141 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-tools.test.js +624 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit.test.js +769 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-jwt-defenses.test.js +436 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-oauth.test.js +1697 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-oid4vp.test.js +495 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-password.test.js +500 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-saml.test.js +1809 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-status-list.test.js +243 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/backup-index.test.js +2226 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/backup-manifest-signature.test.js +183 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/base32.test.js +105 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/break-glass.test.js +693 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cache.test.js +603 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/calendar.test.js +557 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/case-insensitive-security-token-guard.test.js +214 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cbor.test.js +25 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cert.test.js +843 -6
- package/lib/vendor/blamejs/test/layer-0-primitives/ciba-authreqid-binding.test.js +77 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/ciba.test.js +866 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/clear-site-data.test.js +18 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-erase.test.js +19 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli.test.js +2790 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cluster-lease-renewal-ttl.test.js +114 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cluster.test.js +1032 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cms-codec.test.js +575 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +739 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/codepoint-class.test.js +8 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance-ai-act.test.js +194 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance-lifecycle-proto-key.test.js +104 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance-sanctions.test.js +254 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/content-credentials.test.js +613 -3
- package/lib/vendor/blamejs/test/layer-0-primitives/cookies.test.js +140 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cose.test.js +407 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-adversarial.test.js +352 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-derived-hash.test.js +32 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field.test.js +575 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hash-files-parallel.test.js +10 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hash-stream.test.js +24 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-oprf.test.js +17 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-spki-pin.test.js +183 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-xwing.test.js +48 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto.test.js +345 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csp-report.test.js +58 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csv.test.js +48 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/daemon.test.js +459 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/dark-patterns.test.js +19 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-query.test.js +1019 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-raw-residency-gate.test.js +77 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db.test.js +1621 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ddl-change-control.test.js +462 -3
- package/lib/vendor/blamejs/test/layer-0-primitives/dnssec.test.js +323 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dpop-middleware-verify.test.js +562 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dsr.test.js +901 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/dual-control.test.js +249 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-migrate.test.js +561 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db.test.js +1214 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fal.test.js +21 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fedcm-dbsc.test.js +46 -20
- package/lib/vendor/blamejs/test/layer-0-primitives/federation-vc-suite.test.js +147 -3
- package/lib/vendor/blamejs/test/layer-0-primitives/fido-mds3.test.js +907 -38
- package/lib/vendor/blamejs/test/layer-0-primitives/file-upload-content-safety-skip-audit.test.js +29 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/file-upload.test.js +1135 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/flag.test.js +30 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/forms.test.js +747 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/gate-contract.test.js +1855 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/gdpr-ropa.test.js +30 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-archive.test.js +86 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-auth.test.js +117 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-cidr.test.js +151 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-domain.test.js +422 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-filename.test.js +424 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/{guard-gate-disposition-coverage.test.js → guard-gate-disposition.test.js} +1 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-graphql.test.js +340 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-html.test.js +29 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-idempotency-key.test.js +28 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-image.test.js +519 -45
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-imap-command.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-jmap.test.js +51 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-jsonpath.test.js +66 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-jwt.test.js +436 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-managesieve-command.test.js +25 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-markdown.test.js +16 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-mime.test.js +86 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-oauth.test.js +140 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-pdf.test.js +80 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-pop3-command.test.js +27 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-regex.test.js +99 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-shell.test.js +76 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/{guard-sql-coverage.test.js → guard-sql.test.js} +3 -3
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-svg.test.js +524 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-template.test.js +71 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-time.test.js +92 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-uuid.test.js +86 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/html-balance.test.js +38 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-client-cache-authorization.test.js +347 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-client-cache.test.js +908 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-client.test.js +2874 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-message-signature.test.js +643 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/i18n-messageformat.test.js +38 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/i18n.test.js +1153 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/idempotency-key.test.js +494 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ip-utils.test.js +30 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/jsdoc-example-execution.test.js +177 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/json-path.test.js +103 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/json-schema.test.js +214 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/jwk.test.js +41 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/jwt-external.test.js +276 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/keychain.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/log-stream.test.js +241 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/log.test.js +106 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-agent.test.js +441 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-auth.test.js +2136 -4
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-bimi.test.js +759 -12
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-bounce.test.js +811 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-pgp-experimental.test.js +401 -3
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-pgp.test.js +751 -8
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-smime.test.js +864 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto.test.js +110 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-dav.test.js +830 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-deploy.test.js +635 -86
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-dkim.test.js +818 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-helo.test.js +27 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-scan.test.js +199 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-send-deliver.test.js +686 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-imap.test.js +778 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-jmap.test.js +1438 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-managesieve.test.js +449 -6
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-mx.test.js +773 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-pop3.test.js +623 -3
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-rate-limit.test.js +55 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-registry.test.js +13 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-submission.test.js +877 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-sieve.test.js +326 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-store-fts.test.js +55 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-store.test.js +242 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail.test.js +1596 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/mcp.test.js +440 -4
- package/lib/vendor/blamejs/test/layer-0-primitives/metrics-snapshot.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/metrics.test.js +1230 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/middleware-assetlinks.test.js +113 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/middleware-body-parser.test.js +1169 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/middleware-bot-disclose.test.js +123 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/middleware-cookies.test.js +108 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/middleware-gpc.test.js +121 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/middleware-headers.test.js +163 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/middleware-host-allowlist.test.js +154 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/middleware-require-content-type.test.js +127 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/{middleware-tus-upload-coverage.test.js → middleware-tus-upload.test.js} +45 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/middleware-web-app-manifest.test.js +132 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mtls-ca-crl-fingerprint-only.test.js +92 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-dns-resolver.test.js +65 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-dns.test.js +1727 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/network-nts.test.js +699 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-smtp-policy.test.js +1181 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-tls.test.js +1876 -3
- package/lib/vendor/blamejs/test/layer-0-primitives/network.test.js +32 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/nonce-store-enforce-replay-nonboolean.test.js +81 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ntp-check.test.js +122 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/observability-tracing.test.js +378 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/observability.test.js +77 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/oid4vci.test.js +1016 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/openid-federation.test.js +946 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/outbox.test.js +818 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/own-property-membership-guards.test.js +109 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/parsers-safe-toml.test.js +543 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/{parsers-safe-yaml-coverage.test.js → parsers-safe-yaml.test.js} +2 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/parsers-standalone.test.js +73 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/passkey.test.js +649 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/permissions.test.js +524 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/pqc-agent-curve.test.js +22 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/pubsub.test.js +295 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/queue-flow-repeat.test.js +36 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/queue.test.js +83 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/rate-limit-xff-spoofing.test.js +34 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/redact.test.js +1031 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/request-helpers.test.js +94 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/require-bound-key.test.js +401 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/restore-empty-manifest-wipe.test.js +154 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/retention.test.js +600 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/router-host-path-injection.test.js +231 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/router-tls0rtt.test.js +21 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/router.test.js +1277 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-async.test.js +133 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-buffer-linear-scans.test.js +14 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-buffer.test.js +80 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-decompress.test.js +28 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-dns.test.js +245 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-ical.test.js +32 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-ini.test.js +376 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-json.test.js +175 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-mime.test.js +518 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-object.test.js +62 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-path.test.js +30 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-schema.test.js +104 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-sql.test.js +142 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-url.test.js +112 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-vcard.test.js +26 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/scheduler.test.js +157 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/scim-server.test.js +396 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc.test.js +930 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/security-assert.test.js +755 -82
- package/lib/vendor/blamejs/test/layer-0-primitives/self-update.test.js +1053 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/session-device-binding.test.js +200 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/session-strict-binding-missing-failclosed.test.js +110 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sigv4-bucket-ops.test.js +636 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sql-offset-without-limit.test.js +94 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sql.test.js +1190 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ssrf-guard.test.js +21 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/static.test.js +850 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/storage-presigned-url.test.js +146 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/storage.test.js +621 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/subject.test.js +47 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/template.test.js +582 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tenant-quota.test.js +587 -25
- package/lib/vendor/blamejs/test/layer-0-primitives/time.test.js +11 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tsa.test.js +524 -63
- package/lib/vendor/blamejs/test/layer-0-primitives/uri-template.test.js +27 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vault-rotate.test.js +866 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vault.test.js +74 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vc.test.js +215 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/watcher.test.js +550 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/webhook-dispatcher.test.js +388 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/webhook.test.js +262 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/websocket.test.js +1294 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ws-client.test.js +629 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/xml-c14n-attr-normalization.test.js +164 -0
- package/lib/vendor/blamejs/test/smoke.js +2 -0
- package/package.json +1 -1
- package/lib/vendor/blamejs/release-notes/v0.16.0.json +0 -44
- package/lib/vendor/blamejs/release-notes/v0.16.1.json +0 -36
- package/lib/vendor/blamejs/release-notes/v0.16.2.json +0 -71
- package/lib/vendor/blamejs/release-notes/v0.16.3.json +0 -71
- package/lib/vendor/blamejs/test/layer-0-primitives/acme-coverage.test.js +0 -441
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-oauth-coverage.test.js +0 -482
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-saml-coverage.test.js +0 -671
- package/lib/vendor/blamejs/test/layer-0-primitives/backup-index-coverage.test.js +0 -690
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-coverage.test.js +0 -238
- package/lib/vendor/blamejs/test/layer-0-primitives/content-credentials-coverage.test.js +0 -319
- package/lib/vendor/blamejs/test/layer-0-primitives/keychain-coverage.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-auth-coverage.test.js +0 -437
- package/lib/vendor/blamejs/test/layer-0-primitives/mcp-coverage.test.js +0 -443
- package/lib/vendor/blamejs/test/layer-0-primitives/metrics-coverage.test.js +0 -395
- package/lib/vendor/blamejs/test/layer-0-primitives/network-smtp-policy-coverage.test.js +0 -565
- package/lib/vendor/blamejs/test/layer-0-primitives/router-coverage.test.js +0 -592
- package/lib/vendor/blamejs/test/layer-0-primitives/sql-coverage.test.js +0 -422
- package/lib/vendor/blamejs/test/layer-0-primitives/test-coverage.test.js +0 -573
|
@@ -0,0 +1,866 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (c) blamejs contributors
|
|
3
|
+
"use strict";
|
|
4
|
+
/**
|
|
5
|
+
* b.vaultRotate — error / adversarial / defensive / option-default branches.
|
|
6
|
+
*
|
|
7
|
+
* Covers the diagnostic surface (validateSchemaMatch / verify /
|
|
8
|
+
* formatValidationResult) and the rotation pipeline's fail-closed guards
|
|
9
|
+
* (missing keys / dataDir / stagingDir, bad mode, wrapped-without-passphrase,
|
|
10
|
+
* the external-AAD detect-and-refuse gate) plus the plaintext + wrapped
|
|
11
|
+
* key-write paths and a full plaintext keypair rotation end-to-end.
|
|
12
|
+
*
|
|
13
|
+
* The diagnostics run against an in-memory node:sqlite handle seeded with
|
|
14
|
+
* vault-prefixed cells produced under two distinct hybrid keypairs — the
|
|
15
|
+
* exact object shape an operator holds when running a pre/post-rotation
|
|
16
|
+
* sweep on a decrypted DB. No real KMS: sealed cells come from
|
|
17
|
+
* b.crypto.encrypt under keypairs from b.crypto.generateEncryptionKeyPair.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
var helpers = require("../helpers");
|
|
21
|
+
var b = helpers.b;
|
|
22
|
+
var fs = helpers.fs;
|
|
23
|
+
var os = helpers.os;
|
|
24
|
+
var path = helpers.path;
|
|
25
|
+
var check = helpers.check;
|
|
26
|
+
|
|
27
|
+
var { DatabaseSync } = require("node:sqlite");
|
|
28
|
+
|
|
29
|
+
var VAULT_PREFIX = b.constants.VAULT_PREFIX;
|
|
30
|
+
|
|
31
|
+
// Two distinct hybrid recipient keypairs. keyA is the "old" root, keyB the
|
|
32
|
+
// "new"/foreign root. Each doubles as publicKeys (encrypt) and privateKeys
|
|
33
|
+
// (decrypt), exactly as the rotation pipeline threads oldKeys/newKeys.
|
|
34
|
+
var keyA = b.crypto.generateEncryptionKeyPair();
|
|
35
|
+
var keyB = b.crypto.generateEncryptionKeyPair();
|
|
36
|
+
|
|
37
|
+
function _seal(plain, keys) {
|
|
38
|
+
return VAULT_PREFIX + b.crypto.encrypt(plain, keys);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Build an in-memory sqlite handle with one DDL + inserted rows. The handle
|
|
42
|
+
// is the real object validateSchemaMatch/verify consume; caller closes it.
|
|
43
|
+
function _memDb(ddl, inserts) {
|
|
44
|
+
var db = new DatabaseSync(":memory:");
|
|
45
|
+
db.prepare(ddl).run();
|
|
46
|
+
for (var i = 0; i < (inserts || []).length; i++) {
|
|
47
|
+
var ins = inserts[i];
|
|
48
|
+
var stmt = db.prepare(ins.sql);
|
|
49
|
+
stmt.run.apply(stmt, ins.params);
|
|
50
|
+
}
|
|
51
|
+
return db;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
// verify — input-validation fail-closed branches
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
function testVerifyRequiresKeys() {
|
|
58
|
+
var threw = null;
|
|
59
|
+
try { b.vaultRotate.verify({}); } catch (e) { threw = e; }
|
|
60
|
+
check("verify refuses when opts.keys absent",
|
|
61
|
+
!!threw && threw.code === "vault-rotate/no-keys");
|
|
62
|
+
// No-arg call exercises the `opts = opts || {}` default.
|
|
63
|
+
var threw2 = null;
|
|
64
|
+
try { b.vaultRotate.verify(); } catch (e) { threw2 = e; }
|
|
65
|
+
check("verify with no arguments refuses (opts default)",
|
|
66
|
+
!!threw2 && threw2.code === "vault-rotate/no-keys");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function testVerifyRequiresDb() {
|
|
70
|
+
var threw = null;
|
|
71
|
+
try { b.vaultRotate.verify({ keys: keyA }); } catch (e) { threw = e; }
|
|
72
|
+
check("verify refuses when opts.db absent",
|
|
73
|
+
!!threw && threw.code === "vault-rotate/no-db");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function testVerifyRejectsNonHandleDb() {
|
|
77
|
+
var threw = null;
|
|
78
|
+
// An object without a prepare() method is not a node:sqlite handle.
|
|
79
|
+
try { b.vaultRotate.verify({ keys: keyA, db: { query: function () {} } }); }
|
|
80
|
+
catch (e) { threw = e; }
|
|
81
|
+
check("verify refuses a db without prepare()",
|
|
82
|
+
!!threw && threw.code === "vault-rotate/no-db");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function testVerifyRejectsBadSampleMin() {
|
|
86
|
+
var db = _memDb("CREATE TABLE t (_id TEXT PRIMARY KEY, secret TEXT)", []);
|
|
87
|
+
try {
|
|
88
|
+
var threw = null;
|
|
89
|
+
try { b.vaultRotate.verify({ keys: keyA, db: db, sampleMin: -3 }); }
|
|
90
|
+
catch (e) { threw = e; }
|
|
91
|
+
check("verify refuses a negative sampleMin",
|
|
92
|
+
!!threw && threw.code === "vault-rotate/bad-opt");
|
|
93
|
+
} finally { db.close(); }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function testVerifyRejectsBadSamplePercent() {
|
|
97
|
+
var db = _memDb("CREATE TABLE t (_id TEXT PRIMARY KEY, secret TEXT)", []);
|
|
98
|
+
try {
|
|
99
|
+
var bad = [0, -0.5, NaN, Infinity, "10%"];
|
|
100
|
+
for (var i = 0; i < bad.length; i++) {
|
|
101
|
+
var threw = null;
|
|
102
|
+
try { b.vaultRotate.verify({ keys: keyA, db: db, samplePercent: bad[i] }); }
|
|
103
|
+
catch (e) { threw = e; }
|
|
104
|
+
check("verify refuses samplePercent=" + String(bad[i]),
|
|
105
|
+
!!threw && threw.code === "vault-rotate/bad-opt");
|
|
106
|
+
}
|
|
107
|
+
} finally { db.close(); }
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
// verify — diagnostic behaviour
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
function _seedSecretsDb(sealedValue, extraCols) {
|
|
114
|
+
b.cryptoField.clearForTest();
|
|
115
|
+
b.cryptoField.registerTable("secrets", { sealedFields: ["secret"] });
|
|
116
|
+
var ddl = "CREATE TABLE secrets (_id TEXT PRIMARY KEY, secret TEXT" +
|
|
117
|
+
(extraCols ? ", " + extraCols : "") + ")";
|
|
118
|
+
return _memDb(ddl, [{
|
|
119
|
+
sql: "INSERT INTO secrets (_id, secret) VALUES (?, ?)",
|
|
120
|
+
params: ["r1", sealedValue],
|
|
121
|
+
}]);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function testVerifyPassesUnderMatchingKey() {
|
|
125
|
+
var db = _seedSecretsDb(_seal("hello", keyA));
|
|
126
|
+
try {
|
|
127
|
+
// Explicit valid sampleMin + samplePercent exercise the option-present
|
|
128
|
+
// ternaries (not the defaults).
|
|
129
|
+
var res = b.vaultRotate.verify({ keys: keyA, db: db, sampleMin: 3, samplePercent: 0.5 });
|
|
130
|
+
check("verify ok when the cell decrypts under keys", res.ok === true);
|
|
131
|
+
check("verify records the verified table", res.passed.length === 1 &&
|
|
132
|
+
res.passed[0].table === "secrets" && res.passed[0].verified === 1);
|
|
133
|
+
check("verify reports no failures/regressions",
|
|
134
|
+
res.failures.length === 0 && res.regressions.length === 0);
|
|
135
|
+
} finally { db.close(); b.cryptoField.clearForTest(); }
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function testVerifyFlagsDecryptFailure() {
|
|
139
|
+
var db = _seedSecretsDb(_seal("hello", keyA));
|
|
140
|
+
try {
|
|
141
|
+
// Sealed under keyA, verified under keyB — decrypt must fail.
|
|
142
|
+
var res = b.vaultRotate.verify({ keys: keyB, db: db });
|
|
143
|
+
check("verify not ok when the cell fails to decrypt under keys", res.ok === false);
|
|
144
|
+
check("verify records the decrypt failure",
|
|
145
|
+
res.failures.length === 1 && res.failures[0].table === "secrets");
|
|
146
|
+
} finally { db.close(); b.cryptoField.clearForTest(); }
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function testVerifyFlagsRegressionWhenOldKeysStillDecrypt() {
|
|
150
|
+
var db = _seedSecretsDb(_seal("hello", keyA));
|
|
151
|
+
try {
|
|
152
|
+
// keys==oldKeys==keyA: the cell decrypts under both, so the old root
|
|
153
|
+
// "still decrypts" → rotation-did-not-take-effect regression.
|
|
154
|
+
var res = b.vaultRotate.verify({ keys: keyA, db: db, oldKeys: keyA });
|
|
155
|
+
check("verify not ok when old keys still decrypt", res.ok === false);
|
|
156
|
+
check("verify records the regression",
|
|
157
|
+
res.regressions.length === 1 &&
|
|
158
|
+
/rotation did not take effect/.test(res.regressions[0].error));
|
|
159
|
+
} finally { db.close(); b.cryptoField.clearForTest(); }
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function testVerifyRotationEffectiveWithOldKeys() {
|
|
163
|
+
var db = _seedSecretsDb(_seal("hello", keyB));
|
|
164
|
+
try {
|
|
165
|
+
// Sealed under the NEW root (keyB); old root (keyA) no longer decrypts,
|
|
166
|
+
// so foundOldFail flips and no regression is recorded.
|
|
167
|
+
var res = b.vaultRotate.verify({ keys: keyB, db: db, oldKeys: keyA });
|
|
168
|
+
check("verify ok when new root decrypts and old root is rejected", res.ok === true);
|
|
169
|
+
check("verify records no regression once old root fails", res.regressions.length === 0);
|
|
170
|
+
} finally { db.close(); b.cryptoField.clearForTest(); }
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function testVerifySkipsNonStringAndUnprefixedCells() {
|
|
174
|
+
b.cryptoField.clearForTest();
|
|
175
|
+
b.cryptoField.registerTable("secrets", { sealedFields: ["secret"] });
|
|
176
|
+
var db = _memDb("CREATE TABLE secrets (_id TEXT PRIMARY KEY, secret TEXT)", [
|
|
177
|
+
{ sql: "INSERT INTO secrets (_id, secret) VALUES (?, ?)", params: ["nul", null] },
|
|
178
|
+
{ sql: "INSERT INTO secrets (_id, secret) VALUES (?, ?)", params: ["plain", "not-a-vault-value"] },
|
|
179
|
+
]);
|
|
180
|
+
try {
|
|
181
|
+
var res = b.vaultRotate.verify({ keys: keyA, db: db });
|
|
182
|
+
check("verify skips NULL / non-vault-prefixed cells with no failure",
|
|
183
|
+
res.ok === true && res.failures.length === 0);
|
|
184
|
+
check("verify counts the sampled rows as verified", res.passed.length === 1 &&
|
|
185
|
+
res.passed[0].verified === 2);
|
|
186
|
+
} finally { db.close(); b.cryptoField.clearForTest(); }
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function testVerifySkipsEmptyAndUnregisteredAndMissingTables() {
|
|
190
|
+
b.cryptoField.clearForTest();
|
|
191
|
+
b.cryptoField.registerTable("secrets", { sealedFields: ["secret"] });
|
|
192
|
+
// 'secrets' registered but empty; 'plainrows' unregistered.
|
|
193
|
+
var db = _memDb("CREATE TABLE secrets (_id TEXT PRIMARY KEY, secret TEXT)", []);
|
|
194
|
+
db.prepare("CREATE TABLE plainrows (_id TEXT PRIMARY KEY, v TEXT)").run();
|
|
195
|
+
db.prepare("INSERT INTO plainrows (_id, v) VALUES ('p1', 'x')").run();
|
|
196
|
+
try {
|
|
197
|
+
// opts.tables includes a table that does not exist in the DB → skipped.
|
|
198
|
+
var res = b.vaultRotate.verify({ keys: keyA, db: db,
|
|
199
|
+
tables: ["secrets", "plainrows", "ghost_table"] });
|
|
200
|
+
check("verify skips empty + unregistered + missing tables → ok, nothing passed",
|
|
201
|
+
res.ok === true && res.passed.length === 0);
|
|
202
|
+
} finally { db.close(); b.cryptoField.clearForTest(); }
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function testVerifyDecryptsAadCells() {
|
|
206
|
+
var dir = fs.mkdtempSync(path.join(os.tmpdir(), "vr-aadverify-"));
|
|
207
|
+
var db1 = null, db2 = null;
|
|
208
|
+
return helpers.setupVaultOnly(dir).then(function () {
|
|
209
|
+
b.cryptoField.clearForTest();
|
|
210
|
+
b.cryptoField.registerTable("aadsecrets", { sealedFields: ["secret"], aad: true, rowIdField: "_id" });
|
|
211
|
+
var sealedRow = b.cryptoField.sealRow("aadsecrets", { _id: "a1", secret: "aad-secret-value" });
|
|
212
|
+
check("cryptoField.sealRow produced a vault.aad: cell",
|
|
213
|
+
typeof sealedRow.secret === "string" && sealedRow.secret.indexOf("vault.aad:") === 0);
|
|
214
|
+
var keys = JSON.parse(b.vault.getKeysJson());
|
|
215
|
+
|
|
216
|
+
// DB1 — one good AAD cell: decrypts under the matching root, and with
|
|
217
|
+
// oldKeys==keys the old root "still opens" it → regression.
|
|
218
|
+
db1 = _memDb("CREATE TABLE aadsecrets (_id TEXT PRIMARY KEY, secret TEXT)", [
|
|
219
|
+
{ sql: "INSERT INTO aadsecrets (_id, secret) VALUES (?, ?)", params: ["a1", sealedRow.secret] },
|
|
220
|
+
]);
|
|
221
|
+
var good = b.vaultRotate.verify({ keys: keys, db: db1 });
|
|
222
|
+
check("verify decrypts an AAD cell under the matching root",
|
|
223
|
+
good.ok === true && good.passed.length === 1 && good.passed[0].verified === 1);
|
|
224
|
+
var regr = b.vaultRotate.verify({ keys: keys, db: db1, oldKeys: keys });
|
|
225
|
+
check("verify flags an AAD regression when the old root still opens the cell",
|
|
226
|
+
regr.ok === false && regr.regressions.length === 1);
|
|
227
|
+
|
|
228
|
+
// DB2 — a corrupted AAD cell (prefix intact, tail mangled) fails to open.
|
|
229
|
+
var corrupt = sealedRow.secret.slice(0, -8) + "AAAAAAAA";
|
|
230
|
+
db2 = _memDb("CREATE TABLE aadsecrets (_id TEXT PRIMARY KEY, secret TEXT)", [
|
|
231
|
+
{ sql: "INSERT INTO aadsecrets (_id, secret) VALUES (?, ?)", params: ["a2", corrupt] },
|
|
232
|
+
]);
|
|
233
|
+
var bad = b.vaultRotate.verify({ keys: keys, db: db2 });
|
|
234
|
+
check("verify flags an AAD decrypt failure for a corrupted cell",
|
|
235
|
+
bad.ok === false && bad.failures.length === 1);
|
|
236
|
+
}).then(function () {
|
|
237
|
+
if (db1) db1.close();
|
|
238
|
+
if (db2) db2.close();
|
|
239
|
+
b.cryptoField.clearForTest();
|
|
240
|
+
helpers.teardownVaultOnly(dir);
|
|
241
|
+
}, function (e) {
|
|
242
|
+
if (db1) db1.close();
|
|
243
|
+
if (db2) db2.close();
|
|
244
|
+
b.cryptoField.clearForTest();
|
|
245
|
+
helpers.teardownVaultOnly(dir);
|
|
246
|
+
throw e;
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ---------------------------------------------------------------------------
|
|
251
|
+
// validateSchemaMatch — option-default, defensive + warning/error branches
|
|
252
|
+
// ---------------------------------------------------------------------------
|
|
253
|
+
function testValidateRejectsBadDriftSampleLimit() {
|
|
254
|
+
var db = _memDb("CREATE TABLE secrets (_id TEXT PRIMARY KEY, secret TEXT)", []);
|
|
255
|
+
try {
|
|
256
|
+
var threw = null;
|
|
257
|
+
try { b.vaultRotate.validateSchemaMatch(db, { driftSampleLimit: 0 }); }
|
|
258
|
+
catch (e) { threw = e; }
|
|
259
|
+
check("validateSchemaMatch refuses a non-positive driftSampleLimit",
|
|
260
|
+
!!threw && threw.code === "vault-rotate/bad-opt");
|
|
261
|
+
} finally { db.close(); }
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function testValidateCleanSchemaHasNoWarningsOrErrors() {
|
|
265
|
+
b.cryptoField.clearForTest();
|
|
266
|
+
b.cryptoField.registerTable("secrets", { sealedFields: ["secret"] });
|
|
267
|
+
var db = _memDb("CREATE TABLE secrets (_id TEXT PRIMARY KEY, secret TEXT)", [
|
|
268
|
+
{ sql: "INSERT INTO secrets (_id, secret) VALUES (?, ?)", params: ["r1", _seal("x", keyA)] },
|
|
269
|
+
]);
|
|
270
|
+
try {
|
|
271
|
+
// secret is declared sealed; _id is the primary key (unknown column) but
|
|
272
|
+
// holds no vault-prefixed value → no drift.
|
|
273
|
+
var res = b.vaultRotate.validateSchemaMatch(db);
|
|
274
|
+
check("validateSchemaMatch clean → no warnings", res.warnings.length === 0);
|
|
275
|
+
check("validateSchemaMatch clean → no errors", res.errors.length === 0);
|
|
276
|
+
} finally { db.close(); b.cryptoField.clearForTest(); }
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function testValidateTreatsDerivedHashColumnsAsKnown() {
|
|
280
|
+
b.cryptoField.clearForTest();
|
|
281
|
+
// A derived-hash column (emailHash from email) + its source column are both
|
|
282
|
+
// "known" to the drift detector even though neither is a sealedField.
|
|
283
|
+
b.cryptoField.registerTable("people", {
|
|
284
|
+
sealedFields: ["email"],
|
|
285
|
+
derivedHashes: { emailHash: { from: "email", normalize: function (v) { return String(v).toLowerCase(); } } },
|
|
286
|
+
});
|
|
287
|
+
// emailHash holds a hash (not vault-prefixed) and email holds a sealed cell;
|
|
288
|
+
// a genuinely-undeclared 'rogue' column holds a vault-prefixed value.
|
|
289
|
+
var db = _memDb(
|
|
290
|
+
"CREATE TABLE people (_id TEXT PRIMARY KEY, email TEXT, emailHash TEXT, rogue TEXT)", [
|
|
291
|
+
{ sql: "INSERT INTO people (_id, email, emailHash, rogue) VALUES (?, ?, ?, ?)",
|
|
292
|
+
params: ["p1", _seal("a@b.c", keyA), "deadbeef", _seal("leak", keyA)] },
|
|
293
|
+
]);
|
|
294
|
+
try {
|
|
295
|
+
var res = b.vaultRotate.validateSchemaMatch(db);
|
|
296
|
+
// emailHash + email are known (no drift); only 'rogue' is flagged.
|
|
297
|
+
check("validateSchemaMatch treats derivedHash + source columns as known",
|
|
298
|
+
res.errors.length === 1 && res.errors[0].column === "rogue");
|
|
299
|
+
} finally { db.close(); b.cryptoField.clearForTest(); }
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function testValidateWarnsOnMissingTable() {
|
|
303
|
+
b.cryptoField.clearForTest();
|
|
304
|
+
var db = _memDb("CREATE TABLE secrets (_id TEXT PRIMARY KEY, secret TEXT)", []);
|
|
305
|
+
try {
|
|
306
|
+
var res = b.vaultRotate.validateSchemaMatch(db, { tables: ["ghost"] });
|
|
307
|
+
check("validateSchemaMatch warns table_missing for a schema-only table",
|
|
308
|
+
res.warnings.length === 1 && res.warnings[0].kind === "table_missing" &&
|
|
309
|
+
res.warnings[0].table === "ghost");
|
|
310
|
+
check("validateSchemaMatch missing table is non-fatal (no errors)", res.errors.length === 0);
|
|
311
|
+
} finally { db.close(); b.cryptoField.clearForTest(); }
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function testValidateWarnsOnMissingSealedColumn() {
|
|
315
|
+
b.cryptoField.clearForTest();
|
|
316
|
+
// Declare a sealed column ('ssn') the live table lacks.
|
|
317
|
+
b.cryptoField.registerTable("secrets", { sealedFields: ["secret", "ssn"] });
|
|
318
|
+
var db = _memDb("CREATE TABLE secrets (_id TEXT PRIMARY KEY, secret TEXT)", []);
|
|
319
|
+
try {
|
|
320
|
+
var res = b.vaultRotate.validateSchemaMatch(db, { tables: ["secrets"] });
|
|
321
|
+
check("validateSchemaMatch warns sealed_col_missing for an absent sealed column",
|
|
322
|
+
res.warnings.some(function (w) {
|
|
323
|
+
return w.kind === "sealed_col_missing" && w.column === "ssn";
|
|
324
|
+
}));
|
|
325
|
+
} finally { db.close(); b.cryptoField.clearForTest(); }
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function testValidateDetectsDriftInUndeclaredColumn() {
|
|
329
|
+
b.cryptoField.clearForTest();
|
|
330
|
+
b.cryptoField.registerTable("secrets", { sealedFields: ["secret"] });
|
|
331
|
+
// 'rogue' is not declared sealed but holds a vault-prefixed value. Two
|
|
332
|
+
// drifting rows exercise the "column already flagged" fast-path continue.
|
|
333
|
+
var db = _memDb("CREATE TABLE secrets (_id TEXT PRIMARY KEY, secret TEXT, rogue TEXT)", [
|
|
334
|
+
{ sql: "INSERT INTO secrets (_id, secret, rogue) VALUES (?, ?, ?)",
|
|
335
|
+
params: ["r1", _seal("x", keyA), _seal("leak", keyA)] },
|
|
336
|
+
{ sql: "INSERT INTO secrets (_id, secret, rogue) VALUES (?, ?, ?)",
|
|
337
|
+
params: ["r2", _seal("y", keyA), _seal("leak2", keyA)] },
|
|
338
|
+
]);
|
|
339
|
+
try {
|
|
340
|
+
// A valid driftSampleLimit exercises the option-present ternary.
|
|
341
|
+
var res = b.vaultRotate.validateSchemaMatch(db, { driftSampleLimit: 50 });
|
|
342
|
+
check("validateSchemaMatch flags drift once per undeclared vault-prefixed column",
|
|
343
|
+
res.errors.length === 1 && res.errors[0].kind === "drift" &&
|
|
344
|
+
res.errors[0].column === "rogue");
|
|
345
|
+
} finally { db.close(); b.cryptoField.clearForTest(); }
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function testValidateInfraColumnsSuppressesDrift() {
|
|
349
|
+
b.cryptoField.clearForTest();
|
|
350
|
+
b.cryptoField.registerTable("secrets", { sealedFields: ["secret"] });
|
|
351
|
+
var db = _memDb("CREATE TABLE secrets (_id TEXT PRIMARY KEY, secret TEXT, rogue TEXT)", [
|
|
352
|
+
{ sql: "INSERT INTO secrets (_id, secret, rogue) VALUES (?, ?, ?)",
|
|
353
|
+
params: ["r1", _seal("x", keyA), _seal("leak", keyA)] },
|
|
354
|
+
]);
|
|
355
|
+
try {
|
|
356
|
+
// Operator declares 'rogue' as an intentionally-unsealed infra column.
|
|
357
|
+
var res = b.vaultRotate.validateSchemaMatch(db, { infraColumns: ["rogue", "_id"] });
|
|
358
|
+
check("validateSchemaMatch infraColumns allowlist suppresses the drift error",
|
|
359
|
+
res.errors.length === 0);
|
|
360
|
+
} finally { db.close(); b.cryptoField.clearForTest(); }
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function testValidateIgnoresNonArrayInfraColumns() {
|
|
364
|
+
b.cryptoField.clearForTest();
|
|
365
|
+
b.cryptoField.registerTable("secrets", { sealedFields: ["secret"] });
|
|
366
|
+
var db = _memDb("CREATE TABLE secrets (_id TEXT PRIMARY KEY, secret TEXT, rogue TEXT)", [
|
|
367
|
+
{ sql: "INSERT INTO secrets (_id, secret, rogue) VALUES (?, ?, ?)",
|
|
368
|
+
params: ["r1", _seal("x", keyA), _seal("leak", keyA)] },
|
|
369
|
+
]);
|
|
370
|
+
try {
|
|
371
|
+
// A non-array infraColumns is coerced to [] (defensive) — drift still fires.
|
|
372
|
+
var res = b.vaultRotate.validateSchemaMatch(db, { infraColumns: "rogue" });
|
|
373
|
+
check("validateSchemaMatch coerces a non-array infraColumns to [] and still flags drift",
|
|
374
|
+
res.errors.length === 1 && res.errors[0].column === "rogue");
|
|
375
|
+
} finally { db.close(); b.cryptoField.clearForTest(); }
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// ---------------------------------------------------------------------------
|
|
379
|
+
// formatValidationResult
|
|
380
|
+
// ---------------------------------------------------------------------------
|
|
381
|
+
function testFormatValidationResultRenders() {
|
|
382
|
+
var okLine = b.vaultRotate.formatValidationResult({ warnings: [], errors: [] });
|
|
383
|
+
check("formatValidationResult renders the OK line for a clean result",
|
|
384
|
+
okLine === "[vault-rotate] schema match: OK");
|
|
385
|
+
|
|
386
|
+
var warnOnly = b.vaultRotate.formatValidationResult({
|
|
387
|
+
warnings: [{ message: "w1" }], errors: [],
|
|
388
|
+
});
|
|
389
|
+
check("formatValidationResult renders warnings-only",
|
|
390
|
+
/schema warnings \(1/.test(warnOnly) && /- w1/.test(warnOnly));
|
|
391
|
+
|
|
392
|
+
var withErrors = b.vaultRotate.formatValidationResult({
|
|
393
|
+
warnings: [{ message: "w1" }], errors: [{ message: "e1" }],
|
|
394
|
+
});
|
|
395
|
+
check("formatValidationResult renders the FATAL error section",
|
|
396
|
+
/FATAL/.test(withErrors) && /- e1/.test(withErrors));
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// ---------------------------------------------------------------------------
|
|
400
|
+
// rotate — fail-closed guards (throw BEFORE any filesystem work)
|
|
401
|
+
// ---------------------------------------------------------------------------
|
|
402
|
+
async function _expectRotateThrow(label, opts, code) {
|
|
403
|
+
var threw = null;
|
|
404
|
+
try { await b.vaultRotate.rotate(opts); } catch (e) { threw = e; }
|
|
405
|
+
check(label + " (code " + code + ")", !!threw && threw.code === code);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
async function testRotateGuardsRequiredArgs() {
|
|
409
|
+
var dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "vr-guard-"));
|
|
410
|
+
try {
|
|
411
|
+
// No-arg call exercises the `opts = opts || {}` default before the keys guard.
|
|
412
|
+
var threwNoArg = null;
|
|
413
|
+
try { await b.vaultRotate.rotate(); } catch (e) { threwNoArg = e; }
|
|
414
|
+
check("rotate with no arguments refuses (opts default → no-keys)",
|
|
415
|
+
!!threwNoArg && threwNoArg.code === "vault-rotate/no-keys");
|
|
416
|
+
await _expectRotateThrow("rotate refuses missing keys",
|
|
417
|
+
{ dataDir: dataDir }, "vault-rotate/no-keys");
|
|
418
|
+
await _expectRotateThrow("rotate refuses a non-existent dataDir",
|
|
419
|
+
{ oldKeys: keyA, newKeys: keyB, dataDir: path.join(dataDir, "nope") },
|
|
420
|
+
"vault-rotate/no-datadir");
|
|
421
|
+
await _expectRotateThrow("rotate refuses a missing stagingDir",
|
|
422
|
+
{ oldKeys: keyA, newKeys: keyB, dataDir: dataDir },
|
|
423
|
+
"vault-rotate/no-staging");
|
|
424
|
+
await _expectRotateThrow("rotate refuses a pre-existing stagingDir",
|
|
425
|
+
{ oldKeys: keyA, newKeys: keyB, dataDir: dataDir, stagingDir: dataDir },
|
|
426
|
+
"vault-rotate/staging-exists");
|
|
427
|
+
await _expectRotateThrow("rotate refuses an unknown mode",
|
|
428
|
+
{ oldKeys: keyA, newKeys: keyB, dataDir: dataDir,
|
|
429
|
+
stagingDir: path.join(dataDir, "stg1"), mode: "sideways" },
|
|
430
|
+
"vault-rotate/bad-mode");
|
|
431
|
+
await _expectRotateThrow("rotate refuses wrapped mode without a passphrase Buffer",
|
|
432
|
+
{ oldKeys: keyA, newKeys: keyB, dataDir: dataDir,
|
|
433
|
+
stagingDir: path.join(dataDir, "stg2"), mode: "wrapped" },
|
|
434
|
+
"vault-rotate/no-passphrase");
|
|
435
|
+
} finally {
|
|
436
|
+
try { fs.rmSync(dataDir, { recursive: true, force: true }); } catch (_e) { /* cleanup */ }
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
async function testRotateRefusesUnacknowledgedExternalAad() {
|
|
441
|
+
var dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "vr-ext-"));
|
|
442
|
+
var staging = path.join(os.tmpdir(), "vr-ext-stg-" + process.pid + "-" + Date.now());
|
|
443
|
+
try {
|
|
444
|
+
// This process can load the agent AAD modules, so the external-AAD gate
|
|
445
|
+
// has tables to protect. Without acknowledgement rotate must refuse.
|
|
446
|
+
var gated = b.vaultRotate._externalAadTables();
|
|
447
|
+
check("external-AAD gate lists operator-supplied stores", gated.length > 0);
|
|
448
|
+
await _expectRotateThrow("rotate refuses unacknowledged external-AAD stores",
|
|
449
|
+
{ oldKeys: keyA, newKeys: keyB, dataDir: dataDir, stagingDir: staging, mode: "plaintext" },
|
|
450
|
+
"vault-rotate/external-aad-unresealed");
|
|
451
|
+
check("external-AAD refusal left no staging dir behind", !fs.existsSync(staging));
|
|
452
|
+
} finally {
|
|
453
|
+
try { fs.rmSync(dataDir, { recursive: true, force: true }); } catch (_e) { /* cleanup */ }
|
|
454
|
+
try { fs.rmSync(staging, { recursive: true, force: true }); } catch (_e) { /* cleanup */ }
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// ---------------------------------------------------------------------------
|
|
459
|
+
// rotate — key-write pipeline on a dataDir with no db.enc (plaintext + wrapped)
|
|
460
|
+
// ---------------------------------------------------------------------------
|
|
461
|
+
async function testRotatePlaintextKeyWriteNoDb() {
|
|
462
|
+
var dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "vr-pt-"));
|
|
463
|
+
var staging = path.join(os.tmpdir(), "vr-pt-stg-" + process.pid + "-" + Date.now());
|
|
464
|
+
var phases = [];
|
|
465
|
+
try {
|
|
466
|
+
// Acknowledge via the explicit table array (exercises the array-ack branch)
|
|
467
|
+
// and pass a progress callback that always throws to prove _emit swallows
|
|
468
|
+
// callback errors.
|
|
469
|
+
var result = await b.vaultRotate.rotate({
|
|
470
|
+
oldKeys: keyA, newKeys: keyB, dataDir: dataDir, stagingDir: staging,
|
|
471
|
+
mode: "plaintext",
|
|
472
|
+
externalAadResealed: b.vaultRotate._externalAadTables(),
|
|
473
|
+
progressCallback: function (ev) { phases.push(ev.phase); throw new Error("cb boom"); },
|
|
474
|
+
});
|
|
475
|
+
check("rotate with no db.enc processes zero tables", result.tablesProcessed === 0);
|
|
476
|
+
check("rotate emitted the init + done progress phases despite throwing callback",
|
|
477
|
+
phases.indexOf("init") !== -1 && phases.indexOf("done") !== -1);
|
|
478
|
+
check("rotate wrote the plaintext vault.key into staging",
|
|
479
|
+
fs.existsSync(path.join(staging, "vault.key")));
|
|
480
|
+
check("rotate did not produce a verifyResult with no db.enc",
|
|
481
|
+
result.verifyResult === null);
|
|
482
|
+
} finally {
|
|
483
|
+
try { fs.rmSync(dataDir, { recursive: true, force: true }); } catch (_e) { /* cleanup */ }
|
|
484
|
+
try { fs.rmSync(staging, { recursive: true, force: true }); } catch (_e) { /* cleanup */ }
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
async function testRotateWrappedKeyWriteNoDb() {
|
|
489
|
+
var dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "vr-wr-"));
|
|
490
|
+
var staging = path.join(os.tmpdir(), "vr-wr-stg-" + process.pid + "-" + Date.now());
|
|
491
|
+
try {
|
|
492
|
+
var result = await b.vaultRotate.rotate({
|
|
493
|
+
oldKeys: keyA, newKeys: keyB, dataDir: dataDir, stagingDir: staging,
|
|
494
|
+
mode: "wrapped", newPassphrase: Buffer.from("rotate-test-passphrase-not-secret"),
|
|
495
|
+
externalAadResealed: true,
|
|
496
|
+
});
|
|
497
|
+
check("rotate wrapped mode writes vault.key.sealed into staging",
|
|
498
|
+
fs.existsSync(path.join(staging, "vault.key.sealed")));
|
|
499
|
+
var sealedBytes = fs.readFileSync(path.join(staging, "vault.key.sealed"), "utf8");
|
|
500
|
+
check("rotate wrapped-mode key file is not the plaintext keypair JSON",
|
|
501
|
+
sealedBytes.indexOf("privateKey") === -1);
|
|
502
|
+
check("rotate wrapped mode processed zero tables", result.tablesProcessed === 0);
|
|
503
|
+
} finally {
|
|
504
|
+
try { fs.rmSync(dataDir, { recursive: true, force: true }); } catch (_e) { /* cleanup */ }
|
|
505
|
+
try { fs.rmSync(staging, { recursive: true, force: true }); } catch (_e) { /* cleanup */ }
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// ---------------------------------------------------------------------------
|
|
510
|
+
// rotate — full plaintext keypair rotation of a live encrypted deployment
|
|
511
|
+
// with plain (non-AAD) sealed cells + an overflow `data` JSON column.
|
|
512
|
+
// ---------------------------------------------------------------------------
|
|
513
|
+
var PLAIN_SCHEMA = [
|
|
514
|
+
{
|
|
515
|
+
name: "notes",
|
|
516
|
+
columns: { _id: "TEXT PRIMARY KEY", title: "TEXT", subtitle: "TEXT", data: "TEXT" },
|
|
517
|
+
// 'subtitle' is a real column left entirely NULL (a sealed column with
|
|
518
|
+
// zero non-null rows → _rotateColumn short-circuits); 'phantom' is
|
|
519
|
+
// declared sealed but is NOT a live column (skipped by the liveColSet gate).
|
|
520
|
+
sealedFields: ["title", "subtitle", "phantom"],
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
// AAD-bound table with a non-_id rowIdField exercises _rotateColumn's
|
|
524
|
+
// AAD reseal path when rowIdField must be projected separately.
|
|
525
|
+
name: "secrets",
|
|
526
|
+
columns: { _id: "TEXT PRIMARY KEY", entityId: "TEXT", secret: "TEXT" },
|
|
527
|
+
sealedFields: ["secret"],
|
|
528
|
+
aad: true,
|
|
529
|
+
rowIdField: "entityId",
|
|
530
|
+
schemaVersion: "1",
|
|
531
|
+
},
|
|
532
|
+
{
|
|
533
|
+
// AAD-bound table keyed on _id (the common config) exercises the AAD
|
|
534
|
+
// reseal path where the row-id IS the _id cursor (no separate projection).
|
|
535
|
+
name: "sessions",
|
|
536
|
+
columns: { _id: "TEXT PRIMARY KEY", token: "TEXT" },
|
|
537
|
+
sealedFields: ["token"],
|
|
538
|
+
aad: true,
|
|
539
|
+
rowIdField: "_id",
|
|
540
|
+
schemaVersion: "1",
|
|
541
|
+
},
|
|
542
|
+
];
|
|
543
|
+
|
|
544
|
+
async function _reset() {
|
|
545
|
+
process.env.BLAMEJS_SKIP_NTP_CHECK = "1";
|
|
546
|
+
b.cluster._resetForTest();
|
|
547
|
+
b.audit._resetForTest();
|
|
548
|
+
b.vault._resetForTest();
|
|
549
|
+
b.db._resetForTest();
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
async function testRotateFullPlaintextRotation() {
|
|
553
|
+
var dirNew = fs.mkdtempSync(path.join(os.tmpdir(), "vr-full-new-"));
|
|
554
|
+
var dirA = fs.mkdtempSync(path.join(os.tmpdir(), "vr-full-a-"));
|
|
555
|
+
var staging = path.join(os.tmpdir(), "vr-full-stg-" + process.pid + "-" + Date.now());
|
|
556
|
+
try {
|
|
557
|
+
// 1. Fresh keypair to rotate INTO.
|
|
558
|
+
await _reset();
|
|
559
|
+
await b.vault.init({ dataDir: dirNew, mode: "plaintext" });
|
|
560
|
+
var newKeys = JSON.parse(b.vault.getKeysJson());
|
|
561
|
+
b.vault._resetForTest();
|
|
562
|
+
|
|
563
|
+
// 2. Live deployment under the OLD keypair: encrypted db + a plain sealed
|
|
564
|
+
// cell + an overflow `data` JSON blob carrying a vault-prefixed string.
|
|
565
|
+
await _reset();
|
|
566
|
+
await b.vault.init({ dataDir: dirA, mode: "plaintext" });
|
|
567
|
+
var oldKeys = JSON.parse(b.vault.getKeysJson());
|
|
568
|
+
check("full-rotation old and new keypairs differ",
|
|
569
|
+
JSON.stringify(oldKeys) !== JSON.stringify(newKeys));
|
|
570
|
+
await b.db.init({ dataDir: dirA, tmpDir: path.join(dirA, "tmpfs"), atRest: "encrypted",
|
|
571
|
+
auditSigning: false, frameworkTables: false, schema: PLAIN_SCHEMA });
|
|
572
|
+
|
|
573
|
+
var sealed = b.cryptoField.sealRow("notes", { _id: "n1", title: "confidential-title" });
|
|
574
|
+
check("plain seal produced a vault: cell",
|
|
575
|
+
typeof sealed.title === "string" && sealed.title.indexOf(VAULT_PREFIX) === 0);
|
|
576
|
+
// Overflow `data` JSON exercises _rotateOverflow / _walkAndReSeal across
|
|
577
|
+
// every node kind: a nested vault-prefixed string, an array of mixed
|
|
578
|
+
// members, and primitives (number / bool / null) that pass through.
|
|
579
|
+
sealed.data = JSON.stringify({
|
|
580
|
+
note: b.vault.seal("overflow-secret"),
|
|
581
|
+
list: [b.vault.seal("in-array"), "plain-elem", 7],
|
|
582
|
+
plain: "kept",
|
|
583
|
+
count: 5,
|
|
584
|
+
flag: true,
|
|
585
|
+
nul: null,
|
|
586
|
+
});
|
|
587
|
+
b.db.from("notes").insertOne(sealed);
|
|
588
|
+
// A row whose sealed column is NULL (skipped by _rotateColumn) and whose
|
|
589
|
+
// overflow `data` is malformed JSON (left unrotated with a warning).
|
|
590
|
+
b.db.from("notes").insertOne({ _id: "n2", title: null, data: "{not valid json" });
|
|
591
|
+
|
|
592
|
+
// AAD-bound row: sealed under a (table, rowId=entityId, column) tuple.
|
|
593
|
+
var sealedAad = b.cryptoField.sealRow("secrets", { _id: "s1", entityId: "ent-1", secret: "aad-cell-value" });
|
|
594
|
+
check("AAD seal produced a vault.aad: cell",
|
|
595
|
+
typeof sealedAad.secret === "string" && sealedAad.secret.indexOf("vault.aad:") === 0);
|
|
596
|
+
b.db.from("secrets").insertOne(sealedAad);
|
|
597
|
+
var sealedSession = b.cryptoField.sealRow("sessions", { _id: "sess-1", token: "session-token-value" });
|
|
598
|
+
b.db.from("sessions").insertOne(sealedSession);
|
|
599
|
+
await b.db.flushToDisk();
|
|
600
|
+
await b.db.close();
|
|
601
|
+
|
|
602
|
+
// 3. Rotate old -> new. rowBatchSize forces the keyset cursor to page;
|
|
603
|
+
// opts.tables scopes the walk and includes a non-existent table that
|
|
604
|
+
// the tableExists gate skips.
|
|
605
|
+
var result = await b.vaultRotate.rotate({
|
|
606
|
+
dataDir: dirA, stagingDir: staging, oldKeys: oldKeys, newKeys: newKeys,
|
|
607
|
+
mode: "plaintext", externalAadResealed: true, rowBatchSize: 1,
|
|
608
|
+
tables: ["notes", "secrets", "sessions", "ghost_table"],
|
|
609
|
+
});
|
|
610
|
+
check("full rotation internal round-trip verify ok",
|
|
611
|
+
!!result.verifyResult && result.verifyResult.ok === true);
|
|
612
|
+
check("full rotation processed at least one row", result.totalRowsProcessed >= 1);
|
|
613
|
+
check("full rotation warns about the malformed overflow JSON row",
|
|
614
|
+
result.warnings.some(function (w) { return /malformed overflow JSON/.test(w); }));
|
|
615
|
+
|
|
616
|
+
// 4. Swap staging -> dataDir and re-open under the NEW keypair.
|
|
617
|
+
["db.enc", "db.key.enc", "vault.key"].forEach(function (f) {
|
|
618
|
+
var s = path.join(staging, f);
|
|
619
|
+
if (fs.existsSync(s)) fs.copyFileSync(s, path.join(dirA, f));
|
|
620
|
+
});
|
|
621
|
+
try { fs.rmSync(path.join(dirA, "tmpfs"), { recursive: true, force: true }); } catch (_e) { /* fresh decrypt */ }
|
|
622
|
+
|
|
623
|
+
await _reset();
|
|
624
|
+
await b.vault.init({ dataDir: dirA, mode: "plaintext" });
|
|
625
|
+
check("vault now live under the NEW keypair",
|
|
626
|
+
JSON.stringify(JSON.parse(b.vault.getKeysJson())) === JSON.stringify(newKeys));
|
|
627
|
+
await b.db.init({ dataDir: dirA, tmpDir: path.join(dirA, "tmpfs"), atRest: "encrypted",
|
|
628
|
+
auditSigning: false, frameworkTables: false, schema: PLAIN_SCHEMA });
|
|
629
|
+
|
|
630
|
+
var got = b.cryptoField.unsealRow("notes", b.db.from("notes").where({ _id: "n1" }).first());
|
|
631
|
+
check("plain sealed cell decrypts after rotation under the new keypair",
|
|
632
|
+
!!got && got.title === "confidential-title");
|
|
633
|
+
var overflow = JSON.parse(got.data);
|
|
634
|
+
check("overflow vault-prefixed value re-sealed and decrypts under the new keypair",
|
|
635
|
+
b.vault.unseal(overflow.note) === "overflow-secret" && overflow.plain === "kept");
|
|
636
|
+
check("overflow array member re-sealed and decrypts; primitives preserved",
|
|
637
|
+
b.vault.unseal(overflow.list[0]) === "in-array" && overflow.list[1] === "plain-elem" &&
|
|
638
|
+
overflow.list[2] === 7 && overflow.count === 5 && overflow.flag === true && overflow.nul === null);
|
|
639
|
+
|
|
640
|
+
var gotAad = b.cryptoField.unsealRow("secrets", b.db.from("secrets").where({ _id: "s1" }).first());
|
|
641
|
+
check("AAD-bound cell (entityId rowId) decrypts after rotation under the new keypair",
|
|
642
|
+
!!gotAad && gotAad.secret === "aad-cell-value");
|
|
643
|
+
var gotSess = b.cryptoField.unsealRow("sessions", b.db.from("sessions").where({ _id: "sess-1" }).first());
|
|
644
|
+
check("AAD-bound cell (_id rowId) decrypts after rotation under the new keypair",
|
|
645
|
+
!!gotSess && gotSess.token === "session-token-value");
|
|
646
|
+
await b.db.close();
|
|
647
|
+
} finally {
|
|
648
|
+
await _reset();
|
|
649
|
+
b.cryptoField.clearForTest();
|
|
650
|
+
[dirNew, dirA, staging].forEach(function (d) {
|
|
651
|
+
try { fs.rmSync(d, { recursive: true, force: true }); } catch (_e) { /* cleanup */ }
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// ---------------------------------------------------------------------------
|
|
657
|
+
// rotate — auxiliary sealed / verbatim file rotation (legacy plain db.key.enc,
|
|
658
|
+
// additionalSealed, derived-hash salt + MAC, verbatim files + dirs). No db.enc,
|
|
659
|
+
// so the pipeline exercises only the file-reseal + copy surface.
|
|
660
|
+
// ---------------------------------------------------------------------------
|
|
661
|
+
function _b64Key() { return Buffer.from("0123456789abcdef0123456789abcdef").toString("base64"); }
|
|
662
|
+
|
|
663
|
+
async function testRotateAuxiliaryFilesRotation() {
|
|
664
|
+
var dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "vr-aux-"));
|
|
665
|
+
var staging = path.join(os.tmpdir(), "vr-aux-stg-" + process.pid + "-" + Date.now());
|
|
666
|
+
try {
|
|
667
|
+
// Legacy plain-sealed db.key.enc (pre-AAD `vault:` prefix), an operator
|
|
668
|
+
// additionalSealed blob, the derived-hash salt (verbatim) + MAC (resealed),
|
|
669
|
+
// and verbatim file/dir trees.
|
|
670
|
+
fs.writeFileSync(path.join(dataDir, "db.key.enc"), _seal(_b64Key(), keyA));
|
|
671
|
+
fs.writeFileSync(path.join(dataDir, "extra.sealed"), _seal("operator-secret-blob", keyA));
|
|
672
|
+
// A nested additionalSealed forces the staging dest-dir ensureDir branch.
|
|
673
|
+
fs.mkdirSync(path.join(dataDir, "nested"));
|
|
674
|
+
fs.writeFileSync(path.join(dataDir, "nested", "deep.sealed"), _seal("deep-blob", keyA));
|
|
675
|
+
fs.writeFileSync(path.join(dataDir, "vault.derived-hash-salt"), Buffer.from("salt-bytes-verbatim"));
|
|
676
|
+
fs.writeFileSync(path.join(dataDir, "vault.derived-hash-mac.sealed"), _seal(_b64Key(), keyA));
|
|
677
|
+
fs.writeFileSync(path.join(dataDir, "keep.txt"), "verbatim-contents");
|
|
678
|
+
fs.mkdirSync(path.join(dataDir, "subdir"));
|
|
679
|
+
fs.writeFileSync(path.join(dataDir, "subdir", "inner.txt"), "inner-verbatim");
|
|
680
|
+
|
|
681
|
+
// mode omitted → defaults to "plaintext".
|
|
682
|
+
var result = await b.vaultRotate.rotate({
|
|
683
|
+
oldKeys: keyA, newKeys: keyB, dataDir: dataDir, stagingDir: staging,
|
|
684
|
+
externalAadResealed: true,
|
|
685
|
+
paths: {
|
|
686
|
+
additionalSealed: [
|
|
687
|
+
{ relativePath: "extra.sealed", required: true },
|
|
688
|
+
{ relativePath: "nested/deep.sealed", required: true },
|
|
689
|
+
{ relativePath: "missing-optional.sealed", required: false },
|
|
690
|
+
],
|
|
691
|
+
verbatimFiles: [
|
|
692
|
+
{ relativePath: "keep.txt" },
|
|
693
|
+
{ relativePath: "missing-optional.txt", required: false },
|
|
694
|
+
],
|
|
695
|
+
verbatimDirs: [
|
|
696
|
+
{ relativePath: "subdir" },
|
|
697
|
+
{ relativePath: "missing-optional-dir", required: false },
|
|
698
|
+
],
|
|
699
|
+
},
|
|
700
|
+
});
|
|
701
|
+
check("aux rotation processed zero DB tables (no db.enc)", result.tablesProcessed === 0);
|
|
702
|
+
check("aux rotation wrote the plaintext vault.key", fs.existsSync(path.join(staging, "vault.key")));
|
|
703
|
+
|
|
704
|
+
var stagedDbKey = fs.readFileSync(path.join(staging, "db.key.enc"), "utf8");
|
|
705
|
+
check("legacy plain db.key.enc re-sealed under the new keypair",
|
|
706
|
+
stagedDbKey.indexOf(VAULT_PREFIX) === 0 && stagedDbKey !== _seal(_b64Key(), keyA) &&
|
|
707
|
+
b.crypto.decrypt(stagedDbKey.substring(VAULT_PREFIX.length), keyB) === _b64Key());
|
|
708
|
+
|
|
709
|
+
var stagedExtra = fs.readFileSync(path.join(staging, "extra.sealed"), "utf8");
|
|
710
|
+
check("additionalSealed blob re-sealed and decrypts under the new keypair",
|
|
711
|
+
stagedExtra.indexOf(VAULT_PREFIX) === 0 &&
|
|
712
|
+
b.crypto.decrypt(stagedExtra.substring(VAULT_PREFIX.length), keyB) === "operator-secret-blob");
|
|
713
|
+
|
|
714
|
+
check("derived-hash salt copied verbatim",
|
|
715
|
+
fs.existsSync(path.join(staging, "vault.derived-hash-salt")) &&
|
|
716
|
+
fs.readFileSync(path.join(staging, "vault.derived-hash-salt"), "utf8") === "salt-bytes-verbatim");
|
|
717
|
+
var stagedMac = fs.readFileSync(path.join(staging, "vault.derived-hash-mac.sealed"), "utf8");
|
|
718
|
+
check("derived-hash MAC re-sealed under the new keypair",
|
|
719
|
+
stagedMac.indexOf(VAULT_PREFIX) === 0 &&
|
|
720
|
+
b.crypto.decrypt(stagedMac.substring(VAULT_PREFIX.length), keyB) === _b64Key());
|
|
721
|
+
|
|
722
|
+
check("verbatim file copied into staging",
|
|
723
|
+
fs.existsSync(path.join(staging, "keep.txt")) &&
|
|
724
|
+
fs.readFileSync(path.join(staging, "keep.txt"), "utf8") === "verbatim-contents");
|
|
725
|
+
check("verbatim dir copied recursively into staging",
|
|
726
|
+
fs.existsSync(path.join(staging, "subdir", "inner.txt")));
|
|
727
|
+
check("nested additionalSealed re-sealed into a created staging subdir",
|
|
728
|
+
fs.existsSync(path.join(staging, "nested", "deep.sealed")) &&
|
|
729
|
+
b.crypto.decrypt(fs.readFileSync(path.join(staging, "nested", "deep.sealed"), "utf8")
|
|
730
|
+
.substring(VAULT_PREFIX.length), keyB) === "deep-blob");
|
|
731
|
+
} finally {
|
|
732
|
+
try { fs.rmSync(dataDir, { recursive: true, force: true }); } catch (_e) { /* cleanup */ }
|
|
733
|
+
try { fs.rmSync(staging, { recursive: true, force: true }); } catch (_e) { /* cleanup */ }
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// rotate — fail-closed guards on missing/malformed auxiliary files.
|
|
738
|
+
async function testRotateAuxiliaryFileGuards() {
|
|
739
|
+
async function withDataDir(build, opts, code, label) {
|
|
740
|
+
var dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "vr-auxg-"));
|
|
741
|
+
var staging = path.join(os.tmpdir(), "vr-auxg-stg-" + process.pid + "-" + Date.now() + "-" + Math.random().toString(36).slice(2));
|
|
742
|
+
try {
|
|
743
|
+
build(dataDir);
|
|
744
|
+
var full = Object.assign({ oldKeys: keyA, newKeys: keyB, dataDir: dataDir,
|
|
745
|
+
stagingDir: staging, mode: "plaintext", externalAadResealed: true }, opts);
|
|
746
|
+
await _expectRotateThrow(label, full, code);
|
|
747
|
+
} finally {
|
|
748
|
+
try { fs.rmSync(dataDir, { recursive: true, force: true }); } catch (_e) { /* cleanup */ }
|
|
749
|
+
try { fs.rmSync(staging, { recursive: true, force: true }); } catch (_e) { /* cleanup */ }
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
await withDataDir(function () { /* no keep.txt */ },
|
|
754
|
+
{ paths: { verbatimFiles: [{ relativePath: "keep.txt", required: true }] } },
|
|
755
|
+
"vault-rotate/missing-verbatim", "rotate refuses a missing required verbatim file");
|
|
756
|
+
|
|
757
|
+
await withDataDir(function () { /* no subdir */ },
|
|
758
|
+
{ paths: { verbatimDirs: [{ relativePath: "subdir", required: true }] } },
|
|
759
|
+
"vault-rotate/missing-verbatim-dir", "rotate refuses a missing required verbatim dir");
|
|
760
|
+
|
|
761
|
+
await withDataDir(function () { /* no extra.sealed */ },
|
|
762
|
+
{ paths: { additionalSealed: [{ relativePath: "extra.sealed", required: true }] } },
|
|
763
|
+
"vault-rotate/missing-sealed", "rotate refuses a missing required sealed file");
|
|
764
|
+
|
|
765
|
+
await withDataDir(function (dir) { fs.writeFileSync(path.join(dir, "extra.sealed"), "not-vault-prefixed"); },
|
|
766
|
+
{ paths: { additionalSealed: [{ relativePath: "extra.sealed", required: true }] } },
|
|
767
|
+
"vault-rotate/bad-sealed", "rotate refuses an additionalSealed file lacking the vault prefix");
|
|
768
|
+
|
|
769
|
+
await withDataDir(function (dir) { fs.writeFileSync(path.join(dir, "db.key.enc"), "garbage-no-prefix"); },
|
|
770
|
+
{}, "vault-rotate/bad-dbkey", "rotate refuses a db.key.enc without a vault prefix");
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// rotate — round-trip verify fail-closed: rotating to the SAME keypair leaves
|
|
774
|
+
// every cell decryptable under the "old" root, which the internal verify flags
|
|
775
|
+
// as a non-effective rotation → the pipeline refuses rather than reporting ok.
|
|
776
|
+
async function testRotateVerifyFailedOnSameKeypair() {
|
|
777
|
+
var dirA = fs.mkdtempSync(path.join(os.tmpdir(), "vr-vf-a-"));
|
|
778
|
+
var staging = path.join(os.tmpdir(), "vr-vf-stg-" + process.pid + "-" + Date.now());
|
|
779
|
+
try {
|
|
780
|
+
await _reset();
|
|
781
|
+
await b.vault.init({ dataDir: dirA, mode: "plaintext" });
|
|
782
|
+
var liveKeys = JSON.parse(b.vault.getKeysJson());
|
|
783
|
+
await b.db.init({ dataDir: dirA, tmpDir: path.join(dirA, "tmpfs"), atRest: "encrypted",
|
|
784
|
+
auditSigning: false, frameworkTables: false, schema: [PLAIN_SCHEMA[0]] });
|
|
785
|
+
b.db.from("notes").insertOne(b.cryptoField.sealRow("notes", { _id: "n1", title: "x" }));
|
|
786
|
+
await b.db.flushToDisk();
|
|
787
|
+
await b.db.close();
|
|
788
|
+
|
|
789
|
+
// oldKeys === newKeys: the "old" root still decrypts every rotated cell.
|
|
790
|
+
await _expectRotateThrow("rotate refuses when the round-trip verify detects a non-effective rotation",
|
|
791
|
+
{ dataDir: dirA, stagingDir: staging, oldKeys: liveKeys, newKeys: liveKeys,
|
|
792
|
+
mode: "plaintext", externalAadResealed: true },
|
|
793
|
+
"vault-rotate/verify-failed");
|
|
794
|
+
} finally {
|
|
795
|
+
await _reset();
|
|
796
|
+
b.cryptoField.clearForTest();
|
|
797
|
+
try { fs.rmSync(dirA, { recursive: true, force: true }); } catch (_e) { /* cleanup */ }
|
|
798
|
+
try { fs.rmSync(staging, { recursive: true, force: true }); } catch (_e) { /* cleanup */ }
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
// ---------------------------------------------------------------------------
|
|
803
|
+
// Exposed constants — referenced by their verbatim dotted form (the static
|
|
804
|
+
// gate has no method-invocation fallback for constants).
|
|
805
|
+
// ---------------------------------------------------------------------------
|
|
806
|
+
function testExposedConstants() {
|
|
807
|
+
check("DEFAULT_DRIFT_SAMPLE_LIMIT exposed",
|
|
808
|
+
b.vaultRotate.DEFAULT_DRIFT_SAMPLE_LIMIT === 100);
|
|
809
|
+
check("DEFAULT_VERIFY_SAMPLE_MIN exposed",
|
|
810
|
+
b.vaultRotate.DEFAULT_VERIFY_SAMPLE_MIN === 5);
|
|
811
|
+
check("DEFAULT_VERIFY_SAMPLE_FRAC exposed",
|
|
812
|
+
b.vaultRotate.DEFAULT_VERIFY_SAMPLE_FRAC === 0.01);
|
|
813
|
+
check("ROW_BATCH_SIZE_DEFAULT exposed",
|
|
814
|
+
b.vaultRotate.ROW_BATCH_SIZE_DEFAULT === 1000);
|
|
815
|
+
check("VaultRotateError class exposed",
|
|
816
|
+
typeof b.vaultRotate.VaultRotateError === "function");
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
async function run() {
|
|
820
|
+
testVerifyRequiresKeys();
|
|
821
|
+
testVerifyRequiresDb();
|
|
822
|
+
testVerifyRejectsNonHandleDb();
|
|
823
|
+
testVerifyRejectsBadSampleMin();
|
|
824
|
+
testVerifyRejectsBadSamplePercent();
|
|
825
|
+
|
|
826
|
+
testVerifyPassesUnderMatchingKey();
|
|
827
|
+
testVerifyFlagsDecryptFailure();
|
|
828
|
+
testVerifyFlagsRegressionWhenOldKeysStillDecrypt();
|
|
829
|
+
testVerifyRotationEffectiveWithOldKeys();
|
|
830
|
+
testVerifySkipsNonStringAndUnprefixedCells();
|
|
831
|
+
testVerifySkipsEmptyAndUnregisteredAndMissingTables();
|
|
832
|
+
await testVerifyDecryptsAadCells();
|
|
833
|
+
|
|
834
|
+
testValidateRejectsBadDriftSampleLimit();
|
|
835
|
+
testValidateCleanSchemaHasNoWarningsOrErrors();
|
|
836
|
+
testValidateTreatsDerivedHashColumnsAsKnown();
|
|
837
|
+
testValidateWarnsOnMissingTable();
|
|
838
|
+
testValidateWarnsOnMissingSealedColumn();
|
|
839
|
+
testValidateDetectsDriftInUndeclaredColumn();
|
|
840
|
+
testValidateInfraColumnsSuppressesDrift();
|
|
841
|
+
testValidateIgnoresNonArrayInfraColumns();
|
|
842
|
+
|
|
843
|
+
testFormatValidationResultRenders();
|
|
844
|
+
|
|
845
|
+
await testRotateGuardsRequiredArgs();
|
|
846
|
+
await testRotateRefusesUnacknowledgedExternalAad();
|
|
847
|
+
await testRotatePlaintextKeyWriteNoDb();
|
|
848
|
+
await testRotateWrappedKeyWriteNoDb();
|
|
849
|
+
await testRotateAuxiliaryFilesRotation();
|
|
850
|
+
await testRotateAuxiliaryFileGuards();
|
|
851
|
+
await testRotateFullPlaintextRotation();
|
|
852
|
+
await testRotateVerifyFailedOnSameKeypair();
|
|
853
|
+
|
|
854
|
+
testExposedConstants();
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
module.exports = { run: run };
|
|
858
|
+
|
|
859
|
+
if (require.main === module) {
|
|
860
|
+
run().then(
|
|
861
|
+
function () { console.log("[vault-rotate] OK — " + helpers.getChecks() + " checks passed"); },
|
|
862
|
+
// Rethrow rather than log the error: the fixtures seed vault passphrases,
|
|
863
|
+
// and logging the error object trips CodeQL's clear-text-logging taint.
|
|
864
|
+
function (e) { process.exitCode = 1; throw e; }
|
|
865
|
+
);
|
|
866
|
+
}
|