@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
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
* - downloadStream hash-mismatch refuses, deletes tmp, throws
|
|
9
9
|
* httpclient/hash-mismatch, audits .refused
|
|
10
10
|
* - downloadStream HTTP error surfaces without dest write
|
|
11
|
+
* - downloadStream stages into an exclusive, no-follow temp file: the
|
|
12
|
+
* happy path round-trips and a symlink at the dest is replaced (not
|
|
13
|
+
* followed) so the victim it pointed at is left untouched
|
|
11
14
|
* - uploadMultipartStream POSTs file body via multipart/form-data,
|
|
12
15
|
* server receives the bytes + the operator-supplied field
|
|
13
16
|
* - uploadMultipartStream missing-file refuses at config time
|
|
@@ -216,6 +219,87 @@ async function testStreamErrorBodyPreserved() {
|
|
|
216
219
|
});
|
|
217
220
|
}
|
|
218
221
|
|
|
222
|
+
async function testDownloadTempCreateIsExclusiveNoFollow() {
|
|
223
|
+
// CWE-377 / CWE-59: downloadStream stages the body into a sibling temp
|
|
224
|
+
// file created with O_EXCL | O_NOFOLLOW before the atomic rename. Two
|
|
225
|
+
// properties to assert through the public API:
|
|
226
|
+
// (a) the happy path still round-trips (the exclusive create didn't
|
|
227
|
+
// break the streaming/rename contract);
|
|
228
|
+
// (b) a symlink planted at the DESTINATION is replaced by the rename,
|
|
229
|
+
// not followed — the victim the symlink pointed at stays untouched.
|
|
230
|
+
await _withServer(function (req, res) {
|
|
231
|
+
res.writeHead(200, { "Content-Type": "application/octet-stream" });
|
|
232
|
+
res.end(FIXTURE_BYTES);
|
|
233
|
+
}, async function (baseUrl) {
|
|
234
|
+
// (a) round-trip into a fresh dest, confirm exactly one final file and
|
|
235
|
+
// no leaked temp.
|
|
236
|
+
var dir = b.testing.tempDir("httpclient-stream-excl");
|
|
237
|
+
try {
|
|
238
|
+
var dest = path.join(dir.path, "release.bin");
|
|
239
|
+
var result = await b.httpClient.downloadStream({
|
|
240
|
+
url: baseUrl + "/payload",
|
|
241
|
+
dest: dest,
|
|
242
|
+
allowedProtocols: b.safeUrl.ALLOW_HTTP_ALL,
|
|
243
|
+
allowInternal: true,
|
|
244
|
+
});
|
|
245
|
+
check("downloadStream(excl): round-trips bytes",
|
|
246
|
+
result.bytesWritten === FIXTURE_BYTES.length &&
|
|
247
|
+
fs.readFileSync(dest).equals(FIXTURE_BYTES));
|
|
248
|
+
var leftovers = fs.readdirSync(dir.path).filter(function (n) {
|
|
249
|
+
return n.indexOf("release.bin.tmp-") === 0;
|
|
250
|
+
});
|
|
251
|
+
check("downloadStream(excl): no temp file leaked on success",
|
|
252
|
+
leftovers.length === 0);
|
|
253
|
+
|
|
254
|
+
// (b) symlink-at-destination — replaced by the rename, victim safe.
|
|
255
|
+
var victim = path.join(dir.path, "victim.bin");
|
|
256
|
+
fs.writeFileSync(victim, "DO NOT OVERWRITE", { mode: 0o600 });
|
|
257
|
+
var linkDest = path.join(dir.path, "link-dest.bin");
|
|
258
|
+
var symlinkOk = true;
|
|
259
|
+
try { fs.symlinkSync(victim, linkDest); }
|
|
260
|
+
catch (_e) { symlinkOk = false; } // Windows w/o symlink privilege
|
|
261
|
+
|
|
262
|
+
if (symlinkOk) {
|
|
263
|
+
await b.httpClient.downloadStream({
|
|
264
|
+
url: baseUrl + "/payload",
|
|
265
|
+
dest: linkDest,
|
|
266
|
+
allowedProtocols: b.safeUrl.ALLOW_HTTP_ALL,
|
|
267
|
+
allowInternal: true,
|
|
268
|
+
});
|
|
269
|
+
// Open ONE no-follow fd and take both the type check (fstat) and
|
|
270
|
+
// the byte read from that same descriptor — no lstat-then-read
|
|
271
|
+
// against the path, which would be a check-then-use file-system
|
|
272
|
+
// race (CWE-367). O_NOFOLLOW makes the open fail if linkDest were
|
|
273
|
+
// still a symlink, so a successful open already proves the rename
|
|
274
|
+
// replaced the link with a regular file.
|
|
275
|
+
var linkFd = fs.openSync(linkDest, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW || 0));
|
|
276
|
+
try {
|
|
277
|
+
var linkStat = fs.fstatSync(linkFd);
|
|
278
|
+
check("downloadStream(excl): symlink dest replaced by regular file",
|
|
279
|
+
linkStat.isFile() && !linkStat.isSymbolicLink());
|
|
280
|
+
var linkBytes = Buffer.alloc(linkStat.size);
|
|
281
|
+
var linkGot = 0;
|
|
282
|
+
while (linkGot < linkStat.size) {
|
|
283
|
+
var ln = fs.readSync(linkFd, linkBytes, linkGot, linkStat.size - linkGot, null);
|
|
284
|
+
if (ln === 0) break;
|
|
285
|
+
linkGot += ln;
|
|
286
|
+
}
|
|
287
|
+
check("downloadStream(excl): symlink dest holds downloaded bytes",
|
|
288
|
+
linkGot === FIXTURE_BYTES.length && linkBytes.equals(FIXTURE_BYTES));
|
|
289
|
+
} finally {
|
|
290
|
+
fs.closeSync(linkFd);
|
|
291
|
+
}
|
|
292
|
+
check("downloadStream(excl): symlink target (victim) untouched",
|
|
293
|
+
fs.readFileSync(victim, "utf8") === "DO NOT OVERWRITE");
|
|
294
|
+
} else {
|
|
295
|
+
check("downloadStream(excl): symlink-dest case skipped (no privilege)", true);
|
|
296
|
+
}
|
|
297
|
+
} finally {
|
|
298
|
+
dir.cleanup();
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
219
303
|
async function testDownloadBadOpts() {
|
|
220
304
|
var thrown = null;
|
|
221
305
|
try {
|
|
@@ -319,6 +403,7 @@ async function run() {
|
|
|
319
403
|
await testDownloadHashMismatch();
|
|
320
404
|
await testDownloadHttpError();
|
|
321
405
|
await testStreamErrorBodyPreserved();
|
|
406
|
+
await testDownloadTempCreateIsExclusiveNoFollow();
|
|
322
407
|
await testDownloadBadOpts();
|
|
323
408
|
await testUploadHappyPath();
|
|
324
409
|
await testUploadMissingFile();
|
|
@@ -260,14 +260,18 @@ function _mockDb() {
|
|
|
260
260
|
if (/^CREATE (TABLE|INDEX)/i.test(sql)) {
|
|
261
261
|
return { run: function () { return { changes: 0 }; } };
|
|
262
262
|
}
|
|
263
|
-
|
|
263
|
+
// b.sql emits double-quoted identifiers ("k", "fingerprint", ...);
|
|
264
|
+
// the patterns tolerate optional quotes so the mock matches the
|
|
265
|
+
// builder's quote-by-construction output as well as the legacy bare
|
|
266
|
+
// form.
|
|
267
|
+
if (/^SELECT "?k"?(, "?fingerprint"?)?.* FROM /i.test(sql)) {
|
|
264
268
|
return {
|
|
265
269
|
get: function (k) {
|
|
266
270
|
var row = data.get(k);
|
|
267
271
|
return row ? Object.assign({ k: k }, row) : undefined;
|
|
268
272
|
},
|
|
269
|
-
// resealMigrate() also issues `SELECT k, ... FROM <table>`
|
|
270
|
-
// without a WHERE k = ? clause — walk all rows.
|
|
273
|
+
// resealMigrate() also issues `SELECT "k", ... FROM <table>`
|
|
274
|
+
// without a WHERE "k" = ? clause — walk all rows.
|
|
271
275
|
all: function () {
|
|
272
276
|
var out = [];
|
|
273
277
|
data.forEach(function (row, k) {
|
|
@@ -277,7 +281,7 @@ function _mockDb() {
|
|
|
277
281
|
},
|
|
278
282
|
};
|
|
279
283
|
}
|
|
280
|
-
if (/^INSERT INTO
|
|
284
|
+
if (/^INSERT INTO .*\(\s*"?k"?, "?fingerprint"?, "?status_code"?, "?headers"?, "?body"?, "?expires_at"?\s*\)/i.test(sql)) {
|
|
281
285
|
return {
|
|
282
286
|
run: function (k, fingerprint, statusCode, headers, body, expiresAt) {
|
|
283
287
|
data.set(k, {
|
|
@@ -291,7 +295,7 @@ function _mockDb() {
|
|
|
291
295
|
},
|
|
292
296
|
};
|
|
293
297
|
}
|
|
294
|
-
if (/^DELETE FROM
|
|
298
|
+
if (/^DELETE FROM .* WHERE "?k"? = \? AND "?expires_at"? <= \?/i.test(sql)) {
|
|
295
299
|
return {
|
|
296
300
|
run: function (k, expiresAt) {
|
|
297
301
|
var row = data.get(k);
|
|
@@ -407,7 +411,7 @@ function testDbStoreExpiredRaceNoFreshClobber() {
|
|
|
407
411
|
var origGet = db.prepare;
|
|
408
412
|
db.prepare = function (sql) {
|
|
409
413
|
var stmt = origGet.call(db, sql);
|
|
410
|
-
if (/^SELECT (
|
|
414
|
+
if (/^SELECT "?k"?(, "?fingerprint"?)?.* FROM /i.test(sql)) {
|
|
411
415
|
var realGet = stmt.get;
|
|
412
416
|
return {
|
|
413
417
|
get: function (k) {
|
|
@@ -23,16 +23,27 @@ function _makeFakeExternalDb() {
|
|
|
23
23
|
dialect: "sqlite",
|
|
24
24
|
query: async function (sql, args) {
|
|
25
25
|
var sqlLower = sql.toLowerCase();
|
|
26
|
-
|
|
26
|
+
// recordReceive composes through b.sql.upsert(...).doNothing()
|
|
27
|
+
// .returning(...) -> `INSERT INTO ... ON CONFLICT (...) DO NOTHING
|
|
28
|
+
// RETURNING "message_id"`. Match either that or the legacy
|
|
29
|
+
// `INSERT OR IGNORE ... RETURNING` shape. The b.sql column order is
|
|
30
|
+
// [message_id, source, ...] so args[0] = mid, args[1] = src.
|
|
31
|
+
var isDedupeInsert = sqlLower.indexOf("insert or ignore") !== -1 ||
|
|
32
|
+
(sqlLower.indexOf("insert into") !== -1 &&
|
|
33
|
+
sqlLower.indexOf("on conflict") !== -1 &&
|
|
34
|
+
sqlLower.indexOf("do nothing") !== -1);
|
|
35
|
+
if (isDedupeInsert) {
|
|
27
36
|
var src = args[1], mid = args[0];
|
|
28
37
|
var existing = rows.filter(function (r) { return r.source === src && r.message_id === mid; });
|
|
29
38
|
if (existing.length === 0) {
|
|
30
39
|
rows.push({ message_id: mid, source: src, received_at: new Date().toISOString(), processed_at: null, metadata_json: args[2] });
|
|
31
40
|
lastChanges = 1;
|
|
32
|
-
// RETURNING
|
|
33
|
-
//
|
|
41
|
+
// RETURNING — DO NOTHING returns one row on a fresh insert, none
|
|
42
|
+
// on a duplicate. The migrated path RETURNs "message_id" (the
|
|
43
|
+
// portable presence sentinel); the freshness check only reads
|
|
44
|
+
// rows.length, so return the inserted message_id row.
|
|
34
45
|
if (sqlLower.indexOf("returning") !== -1) {
|
|
35
|
-
return { rows: [{
|
|
46
|
+
return { rows: [{ message_id: mid }] };
|
|
36
47
|
}
|
|
37
48
|
} else {
|
|
38
49
|
lastChanges = 0;
|
|
@@ -112,7 +112,153 @@ async function run() {
|
|
|
112
112
|
check("cryptoField.clearResidencyForTest is fn",
|
|
113
113
|
typeof b.cryptoField.clearResidencyForTest === "function");
|
|
114
114
|
|
|
115
|
+
runPerRowResidencyUnit();
|
|
116
|
+
|
|
117
|
+
// ---- #114: legal-hold + subject-restriction PII must be SEALED at rest ----
|
|
118
|
+
// These local tables hold legal-basis / custodian / ticket-reference free
|
|
119
|
+
// text that links a data subject to a legal matter — PII at rest. The raw
|
|
120
|
+
// write path (sql.insert + db.prepare().run()) bypassed the structured
|
|
121
|
+
// builder's auto-seal, so the values landed in clear despite the schema.
|
|
122
|
+
var LH_SECRET = "TOPSECRET-legal-reason-9f3a";
|
|
123
|
+
var LH_CUSTODIAN = "custodian-secret-7b2c@example.com";
|
|
124
|
+
holds.place("seal-subj-1", { reason: LH_SECRET, custodian: LH_CUSTODIAN, citation: "SEC-Rule-17a-4" });
|
|
125
|
+
var lhRaw = JSON.stringify(b.db.prepare("SELECT reason, custodian, citation FROM \"_blamejs_legal_hold\"").all());
|
|
126
|
+
check("#114 legal-hold reason is sealed at rest (not plaintext)", lhRaw.indexOf(LH_SECRET) === -1);
|
|
127
|
+
check("#114 legal-hold custodian is sealed at rest (not plaintext)", lhRaw.indexOf(LH_CUSTODIAN) === -1);
|
|
128
|
+
var lhGet = holds.get("seal-subj-1");
|
|
129
|
+
check("#114 legal-hold get() unseals on the consumer path",
|
|
130
|
+
!!(lhGet && lhGet.reason === LH_SECRET && lhGet.custodian === LH_CUSTODIAN));
|
|
131
|
+
var lhList = holds.list();
|
|
132
|
+
check("#114 legal-hold list() unseals on the consumer path",
|
|
133
|
+
lhList.some(function (h) { return h.reason === LH_SECRET; }));
|
|
134
|
+
|
|
135
|
+
var RES_SECRET = "TOPSECRET-restrict-reason-4e1d";
|
|
136
|
+
b.subject.restrict("seal-subj-2", { on: true, reason: RES_SECRET });
|
|
137
|
+
var resRaw = JSON.stringify(b.db.prepare("SELECT reason FROM \"_blamejs_subject_restrictions\"").all());
|
|
138
|
+
check("#114 subject-restriction reason is sealed at rest (not plaintext)", resRaw.indexOf(RES_SECRET) === -1);
|
|
139
|
+
|
|
115
140
|
await dbHelper.teardownTestDb(tmpDir);
|
|
116
141
|
}
|
|
117
142
|
|
|
143
|
+
// Returns the thrown error's .code when fn() throws, else null. Mirrors
|
|
144
|
+
// the threw-matching pattern used elsewhere in the layer-0 suite.
|
|
145
|
+
function codeFromThrow(fn) {
|
|
146
|
+
try { fn(); } catch (e) { return e && e.code; }
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Per-row residency unit surface (declare/get/clear). The cryptoField
|
|
151
|
+
// residency registry is in-process global state, so the block restores
|
|
152
|
+
// it via clearResidencyForTest in a finally so a parallel smoke file
|
|
153
|
+
// running another residency case isn't poisoned.
|
|
154
|
+
function runPerRowResidencyUnit() {
|
|
155
|
+
b.cryptoField.clearResidencyForTest();
|
|
156
|
+
try {
|
|
157
|
+
// ---- valid declare → return shape + getPerRowResidency round-trip ----
|
|
158
|
+
var decl = b.cryptoField.declarePerRowResidency("residents", {
|
|
159
|
+
residencyColumn: "dataRegion",
|
|
160
|
+
allowedTags: ["eu-west-1", "us-east-1", "global"],
|
|
161
|
+
});
|
|
162
|
+
check("declarePerRowResidency returns table",
|
|
163
|
+
decl.table === "residents");
|
|
164
|
+
check("declarePerRowResidency returns residencyColumn",
|
|
165
|
+
decl.residencyColumn === "dataRegion");
|
|
166
|
+
check("declarePerRowResidency returns allowedTags copy",
|
|
167
|
+
Array.isArray(decl.allowedTags) && decl.allowedTags.length === 3 &&
|
|
168
|
+
decl.allowedTags.indexOf("eu-west-1") !== -1);
|
|
169
|
+
|
|
170
|
+
var got = b.cryptoField.getPerRowResidency("residents");
|
|
171
|
+
check("getPerRowResidency round-trips residencyColumn",
|
|
172
|
+
got && got.residencyColumn === "dataRegion");
|
|
173
|
+
check("getPerRowResidency round-trips allowedTags",
|
|
174
|
+
got && got.allowedTags.join(",") === "eu-west-1,us-east-1,global");
|
|
175
|
+
check("getPerRowResidency returns a defensive allowedTags copy",
|
|
176
|
+
got.allowedTags !== decl.allowedTags);
|
|
177
|
+
check("getPerRowResidency on undeclared table → null",
|
|
178
|
+
b.cryptoField.getPerRowResidency("never-declared") === null);
|
|
179
|
+
|
|
180
|
+
// ---- refusals: each asserts the thrown e.code ----
|
|
181
|
+
check("declarePerRowResidency empty table → table-empty code",
|
|
182
|
+
codeFromThrow(function () {
|
|
183
|
+
b.cryptoField.declarePerRowResidency("", {
|
|
184
|
+
residencyColumn: "dataRegion", allowedTags: ["eu-west-1"],
|
|
185
|
+
});
|
|
186
|
+
}) === "crypto-field/per-row-residency-table-empty");
|
|
187
|
+
|
|
188
|
+
check("declarePerRowResidency null opts → opts-not-object code",
|
|
189
|
+
codeFromThrow(function () {
|
|
190
|
+
b.cryptoField.declarePerRowResidency("residents", null);
|
|
191
|
+
}) === "crypto-field/per-row-residency-opts-not-object");
|
|
192
|
+
|
|
193
|
+
check("declarePerRowResidency bad residencyColumn → column-invalid code",
|
|
194
|
+
codeFromThrow(function () {
|
|
195
|
+
b.cryptoField.declarePerRowResidency("residents", {
|
|
196
|
+
residencyColumn: "", allowedTags: ["eu-west-1"],
|
|
197
|
+
});
|
|
198
|
+
}) === "crypto-field/per-row-residency-column-invalid");
|
|
199
|
+
|
|
200
|
+
check("declarePerRowResidency empty allowedTags → tags-invalid code",
|
|
201
|
+
codeFromThrow(function () {
|
|
202
|
+
b.cryptoField.declarePerRowResidency("residents", {
|
|
203
|
+
residencyColumn: "dataRegion", allowedTags: [],
|
|
204
|
+
});
|
|
205
|
+
}) === "crypto-field/per-row-residency-tags-invalid");
|
|
206
|
+
|
|
207
|
+
check("declarePerRowResidency non-array allowedTags → tags-invalid code",
|
|
208
|
+
codeFromThrow(function () {
|
|
209
|
+
b.cryptoField.declarePerRowResidency("residents", {
|
|
210
|
+
residencyColumn: "dataRegion", allowedTags: "eu-west-1",
|
|
211
|
+
});
|
|
212
|
+
}) === "crypto-field/per-row-residency-tags-invalid");
|
|
213
|
+
|
|
214
|
+
check("declarePerRowResidency non-string tag → tag-empty code",
|
|
215
|
+
codeFromThrow(function () {
|
|
216
|
+
b.cryptoField.declarePerRowResidency("residents", {
|
|
217
|
+
residencyColumn: "dataRegion", allowedTags: ["eu-west-1", 42],
|
|
218
|
+
});
|
|
219
|
+
}) === "crypto-field/per-row-residency-tag-empty");
|
|
220
|
+
|
|
221
|
+
// Unknown opt key throws via validateOpts (plain Error, no .code) —
|
|
222
|
+
// assert the message names the unknown key + the primitive.
|
|
223
|
+
var unknownKeyErr = null;
|
|
224
|
+
try {
|
|
225
|
+
b.cryptoField.declarePerRowResidency("residents", {
|
|
226
|
+
residencyColumn: "dataRegion", allowedTags: ["eu-west-1"], bogusKey: 1,
|
|
227
|
+
});
|
|
228
|
+
} catch (e) { unknownKeyErr = e; }
|
|
229
|
+
check("declarePerRowResidency unknown opt key throws",
|
|
230
|
+
unknownKeyErr !== null);
|
|
231
|
+
check("declarePerRowResidency unknown opt key message names the key",
|
|
232
|
+
unknownKeyErr && /unknown option 'bogusKey'/.test(unknownKeyErr.message) &&
|
|
233
|
+
/declarePerRowResidency/.test(unknownKeyErr.message));
|
|
234
|
+
|
|
235
|
+
// A sealed column can't be the residency tag column — the gate
|
|
236
|
+
// reads the tag as plaintext before sealRow, and reads return it
|
|
237
|
+
// verbatim. Declaring a sealed column refuses at declaration time.
|
|
238
|
+
b.cryptoField.registerTable("sealed_residents", { sealedFields: ["dataRegion"] });
|
|
239
|
+
check("declarePerRowResidency rejects a sealed column as the tag column",
|
|
240
|
+
codeFromThrow(function () {
|
|
241
|
+
b.cryptoField.declarePerRowResidency("sealed_residents", {
|
|
242
|
+
residencyColumn: "dataRegion", allowedTags: ["eu-west-1"],
|
|
243
|
+
});
|
|
244
|
+
}) === "crypto-field/per-row-residency-sealed-conflict");
|
|
245
|
+
check("declarePerRowResidency accepts a non-sealed column on the same table",
|
|
246
|
+
b.cryptoField.declarePerRowResidency("sealed_residents", {
|
|
247
|
+
residencyColumn: "region_tag", allowedTags: ["eu-west-1"],
|
|
248
|
+
}).residencyColumn === "region_tag");
|
|
249
|
+
|
|
250
|
+
// ---- clearResidencyForTest clears the per-row registry too ----
|
|
251
|
+
b.cryptoField.declarePerRowResidency("residents", {
|
|
252
|
+
residencyColumn: "dataRegion", allowedTags: ["eu-west-1"],
|
|
253
|
+
});
|
|
254
|
+
check("per-row residency present before clear",
|
|
255
|
+
b.cryptoField.getPerRowResidency("residents") !== null);
|
|
256
|
+
b.cryptoField.clearResidencyForTest();
|
|
257
|
+
check("clearResidencyForTest drops the per-row residency registry",
|
|
258
|
+
b.cryptoField.getPerRowResidency("residents") === null);
|
|
259
|
+
} finally {
|
|
260
|
+
b.cryptoField.clearResidencyForTest();
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
118
264
|
module.exports = { run: run };
|
|
@@ -108,6 +108,190 @@ async function testDmarcEvaluateUnaligned() {
|
|
|
108
108
|
rv.result === "fail" && rv.recommendedAction === "quarantine");
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
// ---- b.mail.inbound.verify — receiver pipeline (RFC 7489 §6.6) ----
|
|
112
|
+
|
|
113
|
+
function _inboundDns(records) {
|
|
114
|
+
return async function (host, type) {
|
|
115
|
+
if (records[host + "/" + type]) return records[host + "/" + type];
|
|
116
|
+
var err = new Error("ENOTFOUND"); err.code = "ENOTFOUND"; throw err;
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function testInboundVerifyAlignedPass() {
|
|
121
|
+
var dnsLookup = _inboundDns({
|
|
122
|
+
"example.com/TXT": [["v=spf1 ip4:192.0.2.0/24 -all"]],
|
|
123
|
+
"_dmarc.example.com/TXT": [["v=DMARC1; p=reject"]],
|
|
124
|
+
});
|
|
125
|
+
var msg = "From: Alice <alice@example.com>\r\nSubject: hi\r\n\r\nhello\r\n";
|
|
126
|
+
var v = await b.mail.inbound.verify({
|
|
127
|
+
ip: "192.0.2.5",
|
|
128
|
+
helo: "mail.example.com",
|
|
129
|
+
mailFrom: "alice@example.com",
|
|
130
|
+
message: Buffer.from(msg),
|
|
131
|
+
dnsLookup: dnsLookup,
|
|
132
|
+
authservId: "mx.local.test",
|
|
133
|
+
});
|
|
134
|
+
check("inbound.verify: aligned SPF → dmarc pass + deliver",
|
|
135
|
+
v.spf.result === "pass" && v.dmarc.result === "pass" &&
|
|
136
|
+
v.dmarc.recommendedAction === "deliver");
|
|
137
|
+
check("inbound.verify: From extracted from Buffer message",
|
|
138
|
+
v.from.count === 1 && v.from.address === "alice@example.com" &&
|
|
139
|
+
v.from.domain === "example.com");
|
|
140
|
+
check("inbound.verify: A-R header emitted with authserv-id first",
|
|
141
|
+
typeof v.authResults === "string" &&
|
|
142
|
+
v.authResults.indexOf("Authentication-Results: mx.local.test") === 0 &&
|
|
143
|
+
/spf=pass/.test(v.authResults) && /dmarc=pass/.test(v.authResults));
|
|
144
|
+
check("inbound.verify: unsigned message verifies dkim none",
|
|
145
|
+
Array.isArray(v.dkim) && v.dkim[0] && v.dkim[0].result === "none");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function testInboundVerifySpoofRejected() {
|
|
149
|
+
var dnsLookup = _inboundDns({
|
|
150
|
+
"spoofed.example/TXT": [["v=spf1 -all"]],
|
|
151
|
+
"_dmarc.spoofed.example/TXT": [["v=DMARC1; p=reject"]],
|
|
152
|
+
});
|
|
153
|
+
var msg = "From: ceo@spoofed.example\r\nSubject: urgent\r\n\r\nwire money\r\n";
|
|
154
|
+
var v = await b.mail.inbound.verify({
|
|
155
|
+
ip: "203.0.113.9",
|
|
156
|
+
helo: "evil.host",
|
|
157
|
+
mailFrom: "ceo@spoofed.example",
|
|
158
|
+
message: msg,
|
|
159
|
+
dnsLookup: dnsLookup,
|
|
160
|
+
});
|
|
161
|
+
check("inbound.verify: spoofed sender → spf fail + dmarc fail + reject",
|
|
162
|
+
v.spf.result === "fail" && v.dmarc.result === "fail" &&
|
|
163
|
+
v.dmarc.recommendedAction === "reject");
|
|
164
|
+
check("inbound.verify: no authservId → no A-R header", v.authResults === null);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function testInboundVerifyFromHeaderDiscipline() {
|
|
168
|
+
var dnsLookup = _inboundDns({});
|
|
169
|
+
// Two From fields — the header-duplication spoofing shape.
|
|
170
|
+
var dup = await b.mail.inbound.verify({
|
|
171
|
+
ip: "203.0.113.9", helo: "evil.host", mailFrom: "safe@aligned.example",
|
|
172
|
+
message: "From: safe@aligned.example\r\nFrom: ceo@victim.example\r\n\r\nbody\r\n",
|
|
173
|
+
dnsLookup: dnsLookup,
|
|
174
|
+
});
|
|
175
|
+
check("inbound.verify: duplicated From → permerror + reject (RFC 7489 §6.6.1)",
|
|
176
|
+
dup.from.count === 2 && dup.dmarc.result === "permerror" &&
|
|
177
|
+
dup.dmarc.recommendedAction === "reject");
|
|
178
|
+
// Two angle-addr authors inside one field.
|
|
179
|
+
var multi = await b.mail.inbound.verify({
|
|
180
|
+
ip: "203.0.113.9", helo: "evil.host", mailFrom: "safe@aligned.example",
|
|
181
|
+
message: "From: <safe@aligned.example> <ceo@victim.example>\r\n\r\nbody\r\n",
|
|
182
|
+
dnsLookup: dnsLookup,
|
|
183
|
+
});
|
|
184
|
+
check("inbound.verify: two angle-addrs in one From → permerror + reject",
|
|
185
|
+
multi.from.count === 2 && multi.dmarc.recommendedAction === "reject");
|
|
186
|
+
// Bare address list (no angle-addrs) — unparsable rather than
|
|
187
|
+
// picking one of the authors.
|
|
188
|
+
var bareList = await b.mail.inbound.verify({
|
|
189
|
+
ip: "203.0.113.9", helo: "evil.host", mailFrom: "safe@aligned.example",
|
|
190
|
+
message: "From: a@aligned.example, b@victim.example\r\n\r\nbody\r\n",
|
|
191
|
+
dnsLookup: dnsLookup,
|
|
192
|
+
});
|
|
193
|
+
check("inbound.verify: bare From address-list → no author domain picked + reject",
|
|
194
|
+
bareList.from.domain === null && bareList.dmarc.recommendedAction === "reject");
|
|
195
|
+
// No From header at all.
|
|
196
|
+
var none = await b.mail.inbound.verify({
|
|
197
|
+
ip: "203.0.113.9", helo: "evil.host", mailFrom: "safe@aligned.example",
|
|
198
|
+
message: "Subject: headless\r\n\r\nbody\r\n",
|
|
199
|
+
dnsLookup: dnsLookup,
|
|
200
|
+
});
|
|
201
|
+
check("inbound.verify: missing From → permerror + reject",
|
|
202
|
+
none.from.count === 0 && none.dmarc.result === "permerror" &&
|
|
203
|
+
none.dmarc.recommendedAction === "reject");
|
|
204
|
+
// Folded From header unfolds before extraction.
|
|
205
|
+
var folded = await b.mail.inbound.verify({
|
|
206
|
+
ip: "203.0.113.9", helo: "evil.host", mailFrom: "x@folded.example",
|
|
207
|
+
message: "From: Folded Name\r\n <x@folded.example>\r\nSubject: f\r\n\r\nbody\r\n",
|
|
208
|
+
dnsLookup: dnsLookup,
|
|
209
|
+
});
|
|
210
|
+
check("inbound.verify: folded From unfolds (RFC 5322 §2.2.3)",
|
|
211
|
+
folded.from.count === 1 && folded.from.domain === "folded.example");
|
|
212
|
+
// Quoted display-names: a literal `<` or comma inside a
|
|
213
|
+
// quoted-string is display-name text, not a second author — valid
|
|
214
|
+
// single-author mail must not false-positive into permerror.
|
|
215
|
+
var quotedLt = await b.mail.inbound.verify({
|
|
216
|
+
ip: "203.0.113.9", helo: "h.example", mailFrom: "u@quoted.example",
|
|
217
|
+
message: "From: \"John <Jr.> Smith\" <u@quoted.example>\r\n\r\nbody\r\n",
|
|
218
|
+
dnsLookup: dnsLookup,
|
|
219
|
+
});
|
|
220
|
+
check("inbound.verify: quoted display-name with literal < is one author",
|
|
221
|
+
quotedLt.from.count === 1 && quotedLt.from.domain === "quoted.example");
|
|
222
|
+
var quotedComma = await b.mail.inbound.verify({
|
|
223
|
+
ip: "203.0.113.9", helo: "h.example", mailFrom: "j@comma.example",
|
|
224
|
+
message: "From: \"Doe, John\" <j@comma.example>\r\n\r\nbody\r\n",
|
|
225
|
+
dnsLookup: dnsLookup,
|
|
226
|
+
});
|
|
227
|
+
check("inbound.verify: quoted display-name with comma is one author",
|
|
228
|
+
quotedComma.from.count === 1 && quotedComma.from.domain === "comma.example");
|
|
229
|
+
// Comma-separated angle-addr list — multiple authors refused.
|
|
230
|
+
var twoAngle = await b.mail.inbound.verify({
|
|
231
|
+
ip: "203.0.113.9", helo: "evil.host", mailFrom: "safe@aligned.example",
|
|
232
|
+
message: "From: <safe@aligned.example>, Boss <ceo@victim.example>\r\n\r\nbody\r\n",
|
|
233
|
+
dnsLookup: dnsLookup,
|
|
234
|
+
});
|
|
235
|
+
check("inbound.verify: comma-separated angle-addr list → multiple authors refused",
|
|
236
|
+
twoAngle.from.count === 2 && twoAngle.dmarc.recommendedAction === "reject");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// RFC 7489 §6.6.2 — a fail verdict computed while SPF or DKIM returned
|
|
240
|
+
// temperror must surface as temperror (the transiently-failed lookup
|
|
241
|
+
// could have produced the aligned pass), so the MX gate defers with
|
|
242
|
+
// 451 instead of permanently refusing a legitimate sender mid-DNS-blip.
|
|
243
|
+
async function testInboundVerifyTemperrorPrecedence() {
|
|
244
|
+
// SPF TXT lookup fails transiently (SERVFAIL — no ENOTFOUND code);
|
|
245
|
+
// the DMARC policy lookup itself succeeds with p=reject.
|
|
246
|
+
var dnsLookup = async function (host, type) {
|
|
247
|
+
if (host === "_dmarc.blip.example" && type === "TXT") {
|
|
248
|
+
return [["v=DMARC1; p=reject"]];
|
|
249
|
+
}
|
|
250
|
+
throw new Error("SERVFAIL");
|
|
251
|
+
};
|
|
252
|
+
var v = await b.mail.inbound.verify({
|
|
253
|
+
ip: "203.0.113.9",
|
|
254
|
+
helo: "mail.blip.example",
|
|
255
|
+
mailFrom: "news@blip.example",
|
|
256
|
+
message: "From: news@blip.example\r\nSubject: hi\r\n\r\nhello\r\n",
|
|
257
|
+
dnsLookup: dnsLookup,
|
|
258
|
+
});
|
|
259
|
+
check("inbound.verify: SPF temperror under p=reject → dmarc temperror, not fail",
|
|
260
|
+
v.spf.result === "temperror" && v.dmarc.result === "temperror" &&
|
|
261
|
+
v.dmarc.recommendedAction !== "reject");
|
|
262
|
+
// A pass stands regardless of the other authenticator's temperror:
|
|
263
|
+
// aligned DKIM pass + SPF temperror is still a DMARC pass.
|
|
264
|
+
var dnsLookup2 = async function (host, type) {
|
|
265
|
+
if (host === "_dmarc.blip.example" && type === "TXT") {
|
|
266
|
+
return [["v=DMARC1; p=reject"]];
|
|
267
|
+
}
|
|
268
|
+
throw new Error("SERVFAIL");
|
|
269
|
+
};
|
|
270
|
+
var v2 = await b.mail.dmarc.evaluate({
|
|
271
|
+
from: "news@blip.example",
|
|
272
|
+
spf: { result: "temperror", domain: "blip.example" },
|
|
273
|
+
dkim: [{ result: "pass", domain: "blip.example" }],
|
|
274
|
+
dnsLookup: dnsLookup2,
|
|
275
|
+
});
|
|
276
|
+
check("dmarc.evaluate: aligned DKIM pass beats SPF temperror (pass stands)",
|
|
277
|
+
v2.result === "pass" && v2.recommendedAction === "deliver");
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async function testInboundVerifyValidation() {
|
|
281
|
+
var e1 = null;
|
|
282
|
+
try { await b.mail.inbound.verify({ message: "x" }); } catch (e) { e1 = e; }
|
|
283
|
+
check("inbound.verify: missing ip refused", e1 !== null);
|
|
284
|
+
var e2 = null;
|
|
285
|
+
try { await b.mail.inbound.verify({ ip: "203.0.113.9", message: "" }); } catch (e) { e2 = e; }
|
|
286
|
+
check("inbound.verify: empty message refused",
|
|
287
|
+
e2 && /inbound-bad-message/.test(e2.code || ""));
|
|
288
|
+
var e3 = null;
|
|
289
|
+
try {
|
|
290
|
+
await b.mail.inbound.verify({ ip: "203.0.113.9", message: "From: a@b.c\r\n\r\nx", bogus: 1 });
|
|
291
|
+
} catch (e) { e3 = e; }
|
|
292
|
+
check("inbound.verify: unknown opt refused (config-time)", e3 !== null);
|
|
293
|
+
}
|
|
294
|
+
|
|
111
295
|
async function testArcVerifyMissing() {
|
|
112
296
|
var msg = "ARC-Seal: i=1; a=rsa-sha256; cv=none; d=example.com; s=arc; b=AAAA\r\n" +
|
|
113
297
|
"From: alice@example.com\r\n\r\nbody\r\n";
|
|
@@ -1214,6 +1398,11 @@ async function run() {
|
|
|
1214
1398
|
await testDmarcEvaluateUnaligned();
|
|
1215
1399
|
await testDmarcEvaluateOrgDomainViaPsl();
|
|
1216
1400
|
await testDmarcEvaluateNpPolicy();
|
|
1401
|
+
await testInboundVerifyAlignedPass();
|
|
1402
|
+
await testInboundVerifySpoofRejected();
|
|
1403
|
+
await testInboundVerifyFromHeaderDiscipline();
|
|
1404
|
+
await testInboundVerifyTemperrorPrecedence();
|
|
1405
|
+
await testInboundVerifyValidation();
|
|
1217
1406
|
await testArcVerifyMissing();
|
|
1218
1407
|
await testArcVerifyNone();
|
|
1219
1408
|
await testArcVerifyBadSignatures();
|
|
@@ -162,7 +162,9 @@ function testSealUnsealRoundTrip() {
|
|
|
162
162
|
};
|
|
163
163
|
return [];
|
|
164
164
|
}
|
|
165
|
-
|
|
165
|
+
// b.sql quotes identifiers ("journal_id"); match the quoted or
|
|
166
|
+
// bare column form so this fake works against the composed SQL.
|
|
167
|
+
if (/SELECT[\s\S]*FROM[\s\S]*WHERE "?journal_id"? = \?/.test(sql)) {
|
|
166
168
|
return insertedRow ? [insertedRow] : [];
|
|
167
169
|
}
|
|
168
170
|
return [];
|