@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
|
@@ -149,6 +149,111 @@ async function testNoActorRefused() {
|
|
|
149
149
|
rv.type === "urn:ietf:params:jmap:error:forbidden");
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
+
// ---- Cross-tenant accountId gate (RFC 8620 §3.6.1 accountNotFound) ----
|
|
153
|
+
//
|
|
154
|
+
// accountsFor(actor) is the authorization source; a method call / blob op
|
|
155
|
+
// that names an accountId outside the actor's enumerated set is rejected
|
|
156
|
+
// with `accountNotFound` BEFORE the operator handler runs, so a tenant
|
|
157
|
+
// can't read/write another tenant's account.
|
|
158
|
+
|
|
159
|
+
async function testDispatchForeignAccountRefused() {
|
|
160
|
+
var reached = false;
|
|
161
|
+
var jmap = b.mail.server.jmap.create({
|
|
162
|
+
mailStore: { appendMessage: function () {} },
|
|
163
|
+
// Actor A is enumerated for A1 only.
|
|
164
|
+
accountsFor: async function () { return { primaryAccounts: { core: "A1" }, accounts: { A1: { name: "tenant-a" } } }; },
|
|
165
|
+
methods: {
|
|
166
|
+
"Mailbox/get": async function () { reached = true; return { list: [] }; },
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
// Tenant A references tenant B's accountId (B9).
|
|
170
|
+
var rv = await jmap.dispatch({ id: "actor-a", tenantId: "tenant-a" }, {
|
|
171
|
+
using: [],
|
|
172
|
+
methodCalls: [["Mailbox/get", { accountId: "B9" }, "c0"]],
|
|
173
|
+
});
|
|
174
|
+
check("foreign accountId → accountNotFound",
|
|
175
|
+
rv.methodResponses[0][0] === "error" &&
|
|
176
|
+
rv.methodResponses[0][1].type === "urn:ietf:params:jmap:error:accountNotFound");
|
|
177
|
+
check("foreign accountId → method handler NOT reached", reached === false);
|
|
178
|
+
check("response echoes clientId on the gate error",
|
|
179
|
+
rv.methodResponses[0][2] === "c0");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async function testDispatchOwnAccountAllowed() {
|
|
183
|
+
var reached = false;
|
|
184
|
+
var jmap = b.mail.server.jmap.create({
|
|
185
|
+
mailStore: { appendMessage: function () {} },
|
|
186
|
+
accountsFor: async function () { return { primaryAccounts: { core: "A1" }, accounts: { A1: { name: "tenant-a" } } }; },
|
|
187
|
+
methods: {
|
|
188
|
+
"Mailbox/get": async function (actor, args) { reached = true; return { accountId: args.accountId, list: [] }; },
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
var rv = await jmap.dispatch({ id: "actor-a" }, {
|
|
192
|
+
using: [],
|
|
193
|
+
methodCalls: [["Mailbox/get", { accountId: "A1" }, "c0"]],
|
|
194
|
+
});
|
|
195
|
+
check("own accountId → handler reached", reached === true);
|
|
196
|
+
check("own accountId → no error", rv.methodResponses[0][0] === "Mailbox/get");
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async function testDispatchAccountAgnosticMethodAllowed() {
|
|
200
|
+
// A call with no accountId (account-agnostic) passes the gate unchanged.
|
|
201
|
+
var reached = false;
|
|
202
|
+
var jmap = b.mail.server.jmap.create({
|
|
203
|
+
mailStore: { appendMessage: function () {} },
|
|
204
|
+
accountsFor: async function () { return { primaryAccounts: {}, accounts: {} }; },
|
|
205
|
+
methods: {
|
|
206
|
+
"Core/echo": async function (actor, args) { reached = true; return { hi: args.hi }; },
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
var rv = await jmap.dispatch({ id: "actor-a" }, {
|
|
210
|
+
using: [],
|
|
211
|
+
methodCalls: [["Core/echo", { hi: 7 }, "c0"]],
|
|
212
|
+
});
|
|
213
|
+
check("account-agnostic method → handler reached", reached === true);
|
|
214
|
+
check("account-agnostic method → result returned", rv.methodResponses[0][1].hi === 7);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async function testUploadForeignAccountRefused() {
|
|
218
|
+
var backendHit = false;
|
|
219
|
+
var jmap = b.mail.server.jmap.create({
|
|
220
|
+
mailStore: {
|
|
221
|
+
appendMessage: function () {},
|
|
222
|
+
uploadBlob: function () { backendHit = true; return Promise.resolve({ blobId: "x" }); },
|
|
223
|
+
},
|
|
224
|
+
accountsFor: async function () { return { accounts: { A1: { name: "tenant-a" } } }; },
|
|
225
|
+
methods: {},
|
|
226
|
+
});
|
|
227
|
+
// Upload targeting tenant B's accountId (B9).
|
|
228
|
+
var mr = _makeUploadReqRes("/jmap/upload/B9", "text/plain", [Buffer.from("hi")]);
|
|
229
|
+
jmap.uploadHandler(mr.req, mr.res);
|
|
230
|
+
await new Promise(function (r) { setImmediate(function () { setImmediate(r); }); });
|
|
231
|
+
await new Promise(function (r) { setImmediate(r); });
|
|
232
|
+
check("upload foreign accountId → 404", mr.res._status() === 404);
|
|
233
|
+
check("upload foreign accountId → accountNotFound", /jmap:error:accountNotFound/.test(mr.res._buf()));
|
|
234
|
+
check("upload foreign accountId → backend NOT hit", backendHit === false);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async function testDownloadForeignAccountRefused() {
|
|
238
|
+
var backendHit = false;
|
|
239
|
+
var jmap = b.mail.server.jmap.create({
|
|
240
|
+
mailStore: {
|
|
241
|
+
appendMessage: function () {},
|
|
242
|
+
downloadBlob: function () { backendHit = true; return Promise.resolve({ bytes: Buffer.from("x"), type: "text/plain" }); },
|
|
243
|
+
},
|
|
244
|
+
accountsFor: async function () { return { accounts: { A1: { name: "tenant-a" } } }; },
|
|
245
|
+
methods: {},
|
|
246
|
+
});
|
|
247
|
+
// Download targeting tenant B's accountId (B9).
|
|
248
|
+
var mr = _makeUploadReqRes("/jmap/download/B9/blob_42/note.txt", null, []);
|
|
249
|
+
jmap.downloadHandler(mr.req, mr.res);
|
|
250
|
+
await new Promise(function (r) { setImmediate(function () { setImmediate(r); }); });
|
|
251
|
+
await new Promise(function (r) { setImmediate(r); });
|
|
252
|
+
check("download foreign accountId → 404", mr.res._status() === 404);
|
|
253
|
+
check("download foreign accountId → accountNotFound", /jmap:error:accountNotFound/.test(mr.res._buf()));
|
|
254
|
+
check("download foreign accountId → backend NOT hit", backendHit === false);
|
|
255
|
+
}
|
|
256
|
+
|
|
152
257
|
// ---- v0.11.29 — JMAP Push (EventSource SSE per RFC 8620 §7.3) ----
|
|
153
258
|
|
|
154
259
|
function _makeMockReqRes(url) {
|
|
@@ -366,7 +471,7 @@ async function testUploadHandlerHappyPath() {
|
|
|
366
471
|
return Promise.resolve({ blobId: "blob_42", type: type, size: bytes.length });
|
|
367
472
|
},
|
|
368
473
|
},
|
|
369
|
-
accountsFor: async function () { return {}; },
|
|
474
|
+
accountsFor: async function () { return { accounts: { A1: { name: "x" } } }; },
|
|
370
475
|
methods: {},
|
|
371
476
|
});
|
|
372
477
|
var body = Buffer.from("Hello, blob world.");
|
|
@@ -450,7 +555,7 @@ async function testDownloadHandlerHappyPath() {
|
|
|
450
555
|
return Promise.resolve({ bytes: Buffer.from("hello blob"), type: "text/plain" });
|
|
451
556
|
},
|
|
452
557
|
},
|
|
453
|
-
accountsFor: async function () { return {}; },
|
|
558
|
+
accountsFor: async function () { return { accounts: { A1: { name: "x" } } }; },
|
|
454
559
|
methods: {},
|
|
455
560
|
});
|
|
456
561
|
var mr = _makeUploadReqRes("/jmap/download/A1/blob_42/note.txt", null, []);
|
|
@@ -498,7 +603,13 @@ async function testJmapIdAcceptsFullLength() {
|
|
|
498
603
|
uploadBlob: function () { return Promise.resolve({ blobId: "B" }); },
|
|
499
604
|
downloadBlob: function () { return Promise.resolve({ bytes: Buffer.from("x"), type: "text/plain" }); },
|
|
500
605
|
},
|
|
501
|
-
|
|
606
|
+
// The full-length accountId is enumerated for the actor so the gate
|
|
607
|
+
// permits it; this test exercises the JMAP Id length cap, not authz.
|
|
608
|
+
accountsFor: async function () {
|
|
609
|
+
var id = "A".repeat(200); // allow:raw-byte-literal — 200 chars, under RFC 8620 §1.2 255 cap
|
|
610
|
+
var accts = {}; accts[id] = { name: "x" };
|
|
611
|
+
return { accounts: accts };
|
|
612
|
+
},
|
|
502
613
|
methods: {},
|
|
503
614
|
});
|
|
504
615
|
var longId = "A".repeat(200); // allow:raw-byte-literal — 200 chars, under RFC 8620 §1.2 255 cap
|
|
@@ -524,13 +635,15 @@ async function testDownloadHandlerNotFound() {
|
|
|
524
635
|
appendMessage: function () {},
|
|
525
636
|
downloadBlob: function () { return Promise.resolve(null); },
|
|
526
637
|
},
|
|
527
|
-
accountsFor: async function () { return {}; },
|
|
638
|
+
accountsFor: async function () { return { accounts: { A1: { name: "x" } } }; },
|
|
528
639
|
methods: {},
|
|
529
640
|
});
|
|
530
641
|
var mr = _makeUploadReqRes("/jmap/download/A1/missing/note.txt", null, []);
|
|
531
642
|
jmap.downloadHandler(mr.req, mr.res);
|
|
532
643
|
await new Promise(function (r) { setImmediate(function () { setImmediate(r); }); });
|
|
533
644
|
check("download missing → 404", mr.res._status() === 404);
|
|
645
|
+
check("download missing → blob-not-found (not account gate)",
|
|
646
|
+
/Blob not found/.test(mr.res._buf()));
|
|
534
647
|
}
|
|
535
648
|
|
|
536
649
|
function testDownloadHandlerRefusesUnauth() {
|
|
@@ -597,6 +710,12 @@ async function run() {
|
|
|
597
710
|
await testUnknownMethod();
|
|
598
711
|
await testMethodThrewMaskedAsServerFail();
|
|
599
712
|
await testNoActorRefused();
|
|
713
|
+
// Cross-tenant accountId gate (RFC 8620 §3.6.1 accountNotFound)
|
|
714
|
+
await testDispatchForeignAccountRefused();
|
|
715
|
+
await testDispatchOwnAccountAllowed();
|
|
716
|
+
await testDispatchAccountAgnosticMethodAllowed();
|
|
717
|
+
await testUploadForeignAccountRefused();
|
|
718
|
+
await testDownloadForeignAccountRefused();
|
|
600
719
|
// v0.11.29 — JMAP Push (EventSource SSE per RFC 8620 §7.3)
|
|
601
720
|
testEventSourceHandlerExists();
|
|
602
721
|
testEventSourceRefusesUnauthenticated();
|
|
@@ -135,12 +135,17 @@ async function _sendCommand(socket, line) {
|
|
|
135
135
|
var last = lines[lines.length - 1];
|
|
136
136
|
if (/^\d{3} /.test(last)) {
|
|
137
137
|
socket.removeListener("data", onData);
|
|
138
|
+
socket.removeListener("error", onError);
|
|
138
139
|
resolve(buf);
|
|
139
140
|
}
|
|
140
141
|
}
|
|
141
142
|
}
|
|
143
|
+
// Detach on settle — a long transaction issues a dozen commands on
|
|
144
|
+
// one socket, and never-fired once("error") handlers accumulate
|
|
145
|
+
// past the MaxListeners warning threshold.
|
|
146
|
+
function onError(e) { socket.removeListener("data", onData); reject(e); }
|
|
142
147
|
socket.on("data", onData);
|
|
143
|
-
socket.once("error",
|
|
148
|
+
socket.once("error", onError);
|
|
144
149
|
socket.write(line + "\r\n");
|
|
145
150
|
});
|
|
146
151
|
}
|
|
@@ -152,11 +157,13 @@ async function _readGreeting(socket) {
|
|
|
152
157
|
buf += chunk.toString("utf8");
|
|
153
158
|
if (buf.indexOf("\r\n") !== -1) {
|
|
154
159
|
socket.removeListener("data", onData);
|
|
160
|
+
socket.removeListener("error", onError);
|
|
155
161
|
resolve(buf);
|
|
156
162
|
}
|
|
157
163
|
}
|
|
164
|
+
function onError(e) { socket.removeListener("data", onData); reject(e); }
|
|
158
165
|
socket.on("data", onData);
|
|
159
|
-
socket.once("error",
|
|
166
|
+
socket.once("error", onError);
|
|
160
167
|
});
|
|
161
168
|
}
|
|
162
169
|
|
|
@@ -426,6 +433,203 @@ async function testGateOverStartTls() {
|
|
|
426
433
|
} finally { await srv.close({ timeoutMs: 1000 }); } // allow:raw-time-literal — test-only short drain
|
|
427
434
|
}
|
|
428
435
|
|
|
436
|
+
// DATA-phase SPF/DKIM/DMARC gate (opts.guardEnvelope → b.mail.inbound
|
|
437
|
+
// .verify). Drives full SMTP transactions against a mocked DNS: the
|
|
438
|
+
// policy-reject path answers 550 5.7.1 before the agent handoff, the
|
|
439
|
+
// aligned path delivers with the verdict on the handoff ctx + the
|
|
440
|
+
// RFC 8601 Authentication-Results header prepended, quarantine
|
|
441
|
+
// delivers annotated, monitor mode never refuses, and DNS temperror
|
|
442
|
+
// defers (451) or accepts per onTemperror.
|
|
443
|
+
async function testGuardEnvelopeGate() {
|
|
444
|
+
var ctx;
|
|
445
|
+
try { ctx = await _makeTestTlsContext(); }
|
|
446
|
+
catch (_e) { check("guardEnvelope gate (skipped — test cert fixture unavailable)", true); return; }
|
|
447
|
+
var records = {
|
|
448
|
+
"external.com/TXT": [["v=spf1 ip4:127.0.0.1 -all"]],
|
|
449
|
+
"_dmarc.external.com/TXT": [["v=DMARC1; p=reject"]],
|
|
450
|
+
"spoof.example/TXT": [["v=spf1 -all"]],
|
|
451
|
+
"_dmarc.spoof.example/TXT": [["v=DMARC1; p=reject"]],
|
|
452
|
+
"spoofq.example/TXT": [["v=spf1 -all"]],
|
|
453
|
+
"_dmarc.spoofq.example/TXT": [["v=DMARC1; p=quarantine"]],
|
|
454
|
+
};
|
|
455
|
+
var dnsLookup = async function (host, type) {
|
|
456
|
+
if (records[host + "/" + type]) return records[host + "/" + type];
|
|
457
|
+
var err = new Error("ENOTFOUND"); err.code = "ENOTFOUND"; throw err;
|
|
458
|
+
};
|
|
459
|
+
async function _transact(socket, mailFrom, body) {
|
|
460
|
+
await _sendCommand(socket, "MAIL FROM:<" + mailFrom + ">");
|
|
461
|
+
await _sendCommand(socket, "RCPT TO:<alice@example.com>");
|
|
462
|
+
await _sendCommand(socket, "DATA");
|
|
463
|
+
return _sendCommand(socket, body + "\r\n.");
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Boot-time validation of the gate config.
|
|
467
|
+
var eBad = null;
|
|
468
|
+
try {
|
|
469
|
+
b.mail.server.mx.create({ tlsContext: ctx, guardEnvelope: "yes" });
|
|
470
|
+
} catch (e) { eBad = e; }
|
|
471
|
+
check("guardEnvelope: non-boolean/object config refused at boot", eBad !== null);
|
|
472
|
+
var eMode = null;
|
|
473
|
+
try {
|
|
474
|
+
b.mail.server.mx.create({ tlsContext: ctx, guardEnvelope: { mode: "loud" } });
|
|
475
|
+
} catch (e) { eMode = e; }
|
|
476
|
+
check("guardEnvelope: unknown mode refused at boot", eMode !== null);
|
|
477
|
+
// DKIM verifier ranges are mirrored at boot — a config the verifier
|
|
478
|
+
// would refuse per-message must fail startup, not break live SMTP.
|
|
479
|
+
var eSigs = null;
|
|
480
|
+
try {
|
|
481
|
+
b.mail.server.mx.create({ tlsContext: ctx, guardEnvelope: { maxSignatures: 100 } });
|
|
482
|
+
} catch (e) { eSigs = e; }
|
|
483
|
+
check("guardEnvelope: maxSignatures above the DKIM verifier ceiling refused at boot",
|
|
484
|
+
eSigs !== null && /bad-bound/.test(eSigs.code || ""));
|
|
485
|
+
var eSkew = null;
|
|
486
|
+
try {
|
|
487
|
+
b.mail.server.mx.create({ tlsContext: ctx,
|
|
488
|
+
guardEnvelope: { clockSkewMs: b.mail.dkim.DKIM_CLOCK_SKEW_MS_MAX + 1 } });
|
|
489
|
+
} catch (e) { eSkew = e; }
|
|
490
|
+
check("guardEnvelope: clockSkewMs above the DKIM verifier ceiling refused at boot",
|
|
491
|
+
eSkew !== null && /bad-bound/.test(eSkew.code || ""));
|
|
492
|
+
|
|
493
|
+
// ---- enforce mode ----
|
|
494
|
+
var handoffs = [];
|
|
495
|
+
var srv = b.mail.server.mx.create({
|
|
496
|
+
tlsContext: ctx, profile: "permissive", localDomains: ["example.com"],
|
|
497
|
+
guardEnvelope: { mode: "enforce", dnsLookup: dnsLookup },
|
|
498
|
+
agent: { handoff: async function (h) { handoffs.push(h); return { messageId: "m" + handoffs.length }; } },
|
|
499
|
+
});
|
|
500
|
+
var info = await srv.listen({ port: 0, address: "127.0.0.1" });
|
|
501
|
+
try {
|
|
502
|
+
var sock = nodeNet.connect(info.port, "127.0.0.1");
|
|
503
|
+
await new Promise(function (r) { sock.once("connect", r); });
|
|
504
|
+
await _readGreeting(sock);
|
|
505
|
+
await _sendCommand(sock, "EHLO sender.example.com");
|
|
506
|
+
|
|
507
|
+
// Policy reject: SPF fail against p=reject → refused at the wire
|
|
508
|
+
// with the RFC 7372 multiple-authentication-checks-failed code.
|
|
509
|
+
var rej = await _transact(sock, "ceo@spoof.example",
|
|
510
|
+
"From: ceo@spoof.example\r\nSubject: urgent\r\n\r\npay this invoice\r\n");
|
|
511
|
+
check("guardEnvelope enforce: DMARC p=reject + SPF fail → 550 5.7.26 (RFC 7372)", /^550 5\.7\.26/.test(rej));
|
|
512
|
+
check("guardEnvelope enforce: refused message never reaches the agent", handoffs.length === 0);
|
|
513
|
+
|
|
514
|
+
// Multi-From spoofing shape → refused.
|
|
515
|
+
var multi = await _transact(sock, "s@external.com",
|
|
516
|
+
"From: s@external.com\r\nFrom: ceo@spoof.example\r\nSubject: x\r\n\r\nbody\r\n");
|
|
517
|
+
check("guardEnvelope enforce: duplicated From → 550 5.7.1 (RFC 7489 §6.6.1)",
|
|
518
|
+
/^550 5\.7\.1/.test(multi) && handoffs.length === 0);
|
|
519
|
+
|
|
520
|
+
// Aligned pass: delivered with verdict + A-R header. The message
|
|
521
|
+
// arrives with a FORGED Authentication-Results header claiming
|
|
522
|
+
// this receiver's authserv-id — RFC 8601 §5 requires it stripped
|
|
523
|
+
// before the computed one is prepended.
|
|
524
|
+
var ok = await _transact(sock, "s@external.com",
|
|
525
|
+
"Authentication-Results: example.com;\r\n dkim=pass header.d=forged.example\r\n" +
|
|
526
|
+
"From: s@external.com\r\nSubject: hi\r\n\r\nhello\r\n");
|
|
527
|
+
check("guardEnvelope enforce: aligned SPF pass → 250 accepted", /^250 /.test(ok));
|
|
528
|
+
check("guardEnvelope: handoff carries the auth verdict",
|
|
529
|
+
handoffs.length === 1 && handoffs[0].auth &&
|
|
530
|
+
handoffs[0].auth.action === "accept" &&
|
|
531
|
+
handoffs[0].auth.spf.result === "pass" &&
|
|
532
|
+
handoffs[0].auth.dmarc.result === "pass" &&
|
|
533
|
+
handoffs[0].auth.quarantine === false);
|
|
534
|
+
var delivered = handoffs.length === 1 ? handoffs[0].body.toString("utf8") : "";
|
|
535
|
+
check("guardEnvelope: A-R header prepended with the localDomains authserv-id",
|
|
536
|
+
delivered.indexOf("Authentication-Results: example.com") === 0 &&
|
|
537
|
+
/spf=pass/.test(delivered) && /dmarc=pass/.test(delivered));
|
|
538
|
+
check("guardEnvelope: forged same-authserv-id A-R header stripped (RFC 8601 §5)",
|
|
539
|
+
delivered.indexOf("forged.example") === -1 &&
|
|
540
|
+
delivered.split("Authentication-Results: example.com").length === 2);
|
|
541
|
+
check("guardEnvelope: original message preserved after the A-R header",
|
|
542
|
+
delivered.indexOf("Subject: hi") !== -1 && delivered.indexOf("hello") !== -1);
|
|
543
|
+
|
|
544
|
+
// Quarantine policy: delivered, annotated for the downstream agent.
|
|
545
|
+
var q = await _transact(sock, "news@spoofq.example",
|
|
546
|
+
"From: news@spoofq.example\r\nSubject: promo\r\n\r\ndeal\r\n");
|
|
547
|
+
check("guardEnvelope enforce: p=quarantine → 250 delivered annotated",
|
|
548
|
+
/^250 /.test(q) && handoffs.length === 2 &&
|
|
549
|
+
handoffs[1].auth.quarantine === true &&
|
|
550
|
+
handoffs[1].auth.action === "quarantine");
|
|
551
|
+
sock.destroy();
|
|
552
|
+
} finally { await srv.close({ timeoutMs: 1000 }); } // allow:raw-time-literal — test-only short drain
|
|
553
|
+
|
|
554
|
+
// ---- monitor mode: same spoof, never refused ----
|
|
555
|
+
var monHandoffs = [];
|
|
556
|
+
var monSrv = b.mail.server.mx.create({
|
|
557
|
+
tlsContext: ctx, profile: "permissive", localDomains: ["example.com"],
|
|
558
|
+
guardEnvelope: { mode: "monitor", dnsLookup: dnsLookup },
|
|
559
|
+
agent: { handoff: async function (h) { monHandoffs.push(h); return {}; } },
|
|
560
|
+
});
|
|
561
|
+
var monInfo = await monSrv.listen({ port: 0, address: "127.0.0.1" });
|
|
562
|
+
try {
|
|
563
|
+
var monSock = nodeNet.connect(monInfo.port, "127.0.0.1");
|
|
564
|
+
await new Promise(function (r) { monSock.once("connect", r); });
|
|
565
|
+
await _readGreeting(monSock);
|
|
566
|
+
await _sendCommand(monSock, "EHLO sender.example.com");
|
|
567
|
+
var monRej = await _transact(monSock, "ceo@spoof.example",
|
|
568
|
+
"From: ceo@spoof.example\r\nSubject: urgent\r\n\r\npay\r\n");
|
|
569
|
+
check("guardEnvelope monitor: policy-reject message still delivered",
|
|
570
|
+
/^250 /.test(monRej) && monHandoffs.length === 1 &&
|
|
571
|
+
monHandoffs[0].auth.action === "reject" &&
|
|
572
|
+
monHandoffs[0].auth.mode === "monitor");
|
|
573
|
+
monSock.destroy();
|
|
574
|
+
} finally { await monSrv.close({ timeoutMs: 1000 }); } // allow:raw-time-literal — test-only short drain
|
|
575
|
+
|
|
576
|
+
// ---- DNS temperror: defer (default) vs accept ----
|
|
577
|
+
var servfail = async function () { throw new Error("SERVFAIL"); };
|
|
578
|
+
var deferSrv = b.mail.server.mx.create({
|
|
579
|
+
tlsContext: ctx, profile: "permissive", localDomains: ["example.com"],
|
|
580
|
+
guardEnvelope: { mode: "enforce", dnsLookup: servfail },
|
|
581
|
+
});
|
|
582
|
+
var deferInfo = await deferSrv.listen({ port: 0, address: "127.0.0.1" });
|
|
583
|
+
try {
|
|
584
|
+
var dSock = nodeNet.connect(deferInfo.port, "127.0.0.1");
|
|
585
|
+
await new Promise(function (r) { dSock.once("connect", r); });
|
|
586
|
+
await _readGreeting(dSock);
|
|
587
|
+
await _sendCommand(dSock, "EHLO sender.example.com");
|
|
588
|
+
var deferred = await _transact(dSock, "s@external.com",
|
|
589
|
+
"From: s@external.com\r\nSubject: hi\r\n\r\nhello\r\n");
|
|
590
|
+
check("guardEnvelope: DNS temperror defers with 451 4.7.0 (sender retries)",
|
|
591
|
+
/^451 4\.7\.0/.test(deferred));
|
|
592
|
+
dSock.destroy();
|
|
593
|
+
} finally { await deferSrv.close({ timeoutMs: 1000 }); } // allow:raw-time-literal — test-only short drain
|
|
594
|
+
|
|
595
|
+
// ---- pipeline wall-clock timeout: a hanging resolver cannot pin
|
|
596
|
+
// the connection slot — the race defers on the temperror path ----
|
|
597
|
+
var hangForever = function () { return new Promise(function () {}); };
|
|
598
|
+
var timeoutSrv = b.mail.server.mx.create({
|
|
599
|
+
tlsContext: ctx, profile: "permissive", localDomains: ["example.com"],
|
|
600
|
+
guardEnvelope: { mode: "enforce", dnsLookup: hangForever, timeoutMs: 250 }, // allow:raw-time-literal — test-only short budget
|
|
601
|
+
});
|
|
602
|
+
var timeoutInfo = await timeoutSrv.listen({ port: 0, address: "127.0.0.1" });
|
|
603
|
+
try {
|
|
604
|
+
var tSock = nodeNet.connect(timeoutInfo.port, "127.0.0.1");
|
|
605
|
+
await new Promise(function (r) { tSock.once("connect", r); });
|
|
606
|
+
await _readGreeting(tSock);
|
|
607
|
+
await _sendCommand(tSock, "EHLO sender.example.com");
|
|
608
|
+
var timedOut = await _transact(tSock, "s@external.com",
|
|
609
|
+
"From: s@external.com\r\nSubject: hi\r\n\r\nhello\r\n");
|
|
610
|
+
check("guardEnvelope: hanging resolver hits timeoutMs → 451 4.7.0",
|
|
611
|
+
/^451 4\.7\.0/.test(timedOut));
|
|
612
|
+
tSock.destroy();
|
|
613
|
+
} finally { await timeoutSrv.close({ timeoutMs: 1000 }); } // allow:raw-time-literal — test-only short drain
|
|
614
|
+
|
|
615
|
+
var acceptSrv = b.mail.server.mx.create({
|
|
616
|
+
tlsContext: ctx, profile: "permissive", localDomains: ["example.com"],
|
|
617
|
+
guardEnvelope: { mode: "enforce", onTemperror: "accept", dnsLookup: servfail },
|
|
618
|
+
});
|
|
619
|
+
var acceptInfo = await acceptSrv.listen({ port: 0, address: "127.0.0.1" });
|
|
620
|
+
try {
|
|
621
|
+
var aSock = nodeNet.connect(acceptInfo.port, "127.0.0.1");
|
|
622
|
+
await new Promise(function (r) { aSock.once("connect", r); });
|
|
623
|
+
await _readGreeting(aSock);
|
|
624
|
+
await _sendCommand(aSock, "EHLO sender.example.com");
|
|
625
|
+
var accepted = await _transact(aSock, "s@external.com",
|
|
626
|
+
"From: s@external.com\r\nSubject: hi\r\n\r\nhello\r\n");
|
|
627
|
+
check("guardEnvelope: onTemperror accept admits when DNS is down",
|
|
628
|
+
/^250 /.test(accepted));
|
|
629
|
+
aSock.destroy();
|
|
630
|
+
} finally { await acceptSrv.close({ timeoutMs: 1000 }); } // allow:raw-time-literal — test-only short drain
|
|
631
|
+
}
|
|
632
|
+
|
|
429
633
|
async function run() {
|
|
430
634
|
testSurface();
|
|
431
635
|
testCreateRequiresTlsContext();
|
|
@@ -438,6 +642,7 @@ async function run() {
|
|
|
438
642
|
await testStrictProfileRequiresStartTls();
|
|
439
643
|
await testConnectionGates();
|
|
440
644
|
await testGateOverStartTls();
|
|
645
|
+
await testGuardEnvelopeGate();
|
|
441
646
|
}
|
|
442
647
|
|
|
443
648
|
module.exports = { run: run };
|
|
@@ -291,6 +291,78 @@ async function testMoveMessages() {
|
|
|
291
291
|
} finally { _teardown(fx); }
|
|
292
292
|
}
|
|
293
293
|
|
|
294
|
+
async function testSearch() {
|
|
295
|
+
var fx = await _setupStore("search");
|
|
296
|
+
try {
|
|
297
|
+
var store = b.mailStore.create({ backend: fx.db });
|
|
298
|
+
var m1 = store.appendMessage("INBOX",
|
|
299
|
+
_msg(["From: alice@example.com", "To: bob@example.com",
|
|
300
|
+
"Subject: Kubernetes deploy notes", "Message-Id: <s1@x>"],
|
|
301
|
+
"the kubernetes cluster is healthy"));
|
|
302
|
+
var m2 = store.appendMessage("INBOX",
|
|
303
|
+
_msg(["From: carol@example.com", "To: bob@example.com",
|
|
304
|
+
"Subject: Lunch plans", "Message-Id: <s2@x>"],
|
|
305
|
+
"want to grab lunch tomorrow"));
|
|
306
|
+
|
|
307
|
+
// Body/subject text MATCH (FTS5 IN-subquery via whereMatch).
|
|
308
|
+
var r1 = store.search("INBOX", { text: "kubernetes" });
|
|
309
|
+
check("search: text=kubernetes hits only m1",
|
|
310
|
+
r1.rows.length === 1 && r1.rows[0].objectid === m1.objectid && !!r1.matchExpr);
|
|
311
|
+
|
|
312
|
+
// Subject-only column MATCH.
|
|
313
|
+
var r2 = store.search("INBOX", { subject: "lunch" });
|
|
314
|
+
check("search: subject=lunch hits only m2",
|
|
315
|
+
r2.rows.length === 1 && r2.rows[0].objectid === m2.objectid);
|
|
316
|
+
|
|
317
|
+
// Address column MATCH (from/to share addr_toks).
|
|
318
|
+
var r3 = store.search("INBOX", { from: "alice@example.com" });
|
|
319
|
+
check("search: from=alice hits only m1",
|
|
320
|
+
r3.rows.length === 1 && r3.rows[0].objectid === m1.objectid);
|
|
321
|
+
|
|
322
|
+
// No surviving tokens → empty result set, not a fallback.
|
|
323
|
+
var r4 = store.search("INBOX", { text: "nonexistentterm" });
|
|
324
|
+
check("search: no-match returns zero rows", r4.rows.length === 0);
|
|
325
|
+
|
|
326
|
+
// No text-side filter → falls through to the modseq cursor.
|
|
327
|
+
var r5 = store.search("INBOX", {});
|
|
328
|
+
check("search: no-filter falls back to modseq cursor", r5.rows.length === 2);
|
|
329
|
+
} finally { _teardown(fx); }
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
async function testHardExpunge() {
|
|
333
|
+
var fx = await _setupStore("expunge");
|
|
334
|
+
try {
|
|
335
|
+
var store = b.mailStore.create({ backend: fx.db });
|
|
336
|
+
var m1 = store.appendMessage("INBOX",
|
|
337
|
+
_msg(["From: a@x", "Subject: keep", "Message-Id: <e1@x>"], "keep me"));
|
|
338
|
+
var m2 = store.appendMessage("INBOX",
|
|
339
|
+
_msg(["From: a@x", "Subject: drop", "Message-Id: <e2@x>"], "drop me"));
|
|
340
|
+
var q0 = store.quota("INBOX");
|
|
341
|
+
check("expunge-pre: 2 messages", q0.usedCount === 2);
|
|
342
|
+
|
|
343
|
+
// hardExpunge resolves the candidate set through json_each(?).
|
|
344
|
+
var ex = store.hardExpunge("INBOX", [m2.objectid]);
|
|
345
|
+
check("expunge: m2 deleted", ex.deleted.length === 1 && ex.deleted[0] === m2.objectid);
|
|
346
|
+
check("expunge: nothing refused", ex.refused.length === 0);
|
|
347
|
+
|
|
348
|
+
// Row + FTS row + quota all reflect the delete.
|
|
349
|
+
check("expunge: m2 fetch is null", store.fetchByObjectId("INBOX", m2.objectid) === null);
|
|
350
|
+
check("expunge: m1 still present", store.fetchByObjectId("INBOX", m1.objectid) !== null);
|
|
351
|
+
check("expunge: FTS no longer matches m2",
|
|
352
|
+
store.search("INBOX", { subject: "drop" }).rows.length === 0);
|
|
353
|
+
var q1 = store.quota("INBOX");
|
|
354
|
+
check("expunge: quota decremented to 1", q1.usedCount === 1);
|
|
355
|
+
|
|
356
|
+
// A legal-held message is refused, not deleted.
|
|
357
|
+
store.setLegalHold([m1.objectid], { hold: true });
|
|
358
|
+
var ex2 = store.hardExpunge("INBOX", [m1.objectid]);
|
|
359
|
+
check("expunge: legal-held refused",
|
|
360
|
+
ex2.deleted.length === 0 && ex2.refused.length === 1 &&
|
|
361
|
+
ex2.refused[0].reason === "legal-hold");
|
|
362
|
+
check("expunge: held m1 still present", store.fetchByObjectId("INBOX", m1.objectid) !== null);
|
|
363
|
+
} finally { _teardown(fx); }
|
|
364
|
+
}
|
|
365
|
+
|
|
294
366
|
async function testRefusesBadBackend() {
|
|
295
367
|
var threw = null;
|
|
296
368
|
try { b.mailStore.create({ backend: {} }); }
|
|
@@ -312,6 +384,8 @@ async function run() {
|
|
|
312
384
|
await testRefusesBadInput();
|
|
313
385
|
await testCustomFolder();
|
|
314
386
|
await testMoveMessages();
|
|
387
|
+
await testSearch();
|
|
388
|
+
await testHardExpunge();
|
|
315
389
|
await testRefusesBadBackend();
|
|
316
390
|
}
|
|
317
391
|
|
|
@@ -455,6 +455,9 @@ async function run() {
|
|
|
455
455
|
// ---- RFC 9101 + RFC 9126 §3 — PAR with a signed request object ----
|
|
456
456
|
await _testParRequestObject();
|
|
457
457
|
await _testParPlainUnchanged();
|
|
458
|
+
|
|
459
|
+
// ---- OIDC Back-Channel Logout — bounded token + header parse ----
|
|
460
|
+
await _testBackchannelLogoutOversized();
|
|
458
461
|
}
|
|
459
462
|
|
|
460
463
|
// PAR carrying a signed request object: the form body MUST hold `request=`
|
|
@@ -481,6 +484,9 @@ async function _testParRequestObject() {
|
|
|
481
484
|
});
|
|
482
485
|
var rv = await oa.pushAuthorizationRequest({
|
|
483
486
|
signedRequestObject: { key: kp.privateKey, kid: "c1" },
|
|
487
|
+
authorizationDetails: [
|
|
488
|
+
{ type: "payment_initiation", actions: ["initiate"], locations: ["https://rs.example/pay"] },
|
|
489
|
+
],
|
|
484
490
|
});
|
|
485
491
|
check("oauth.PAR+RO: returns a request_uri", rv.requestUri === "urn:ietf:params:oauth:request_uri:abc123");
|
|
486
492
|
check("oauth.PAR+RO: flags requestObjectSent", rv.requestObjectSent === true);
|
|
@@ -506,9 +512,46 @@ async function _testParRequestObject() {
|
|
|
506
512
|
parsed.params.redirect_uri === "https://rp.example/cb" && parsed.params.scope === "openid profile" &&
|
|
507
513
|
parsed.params.code_challenge_method === "S256" && typeof parsed.params.code_challenge === "string");
|
|
508
514
|
check("oauth.PAR+RO: request object aud is the AS issuer", parsed.claims.aud === issuer);
|
|
515
|
+
// RFC 9101/9396 — authorization_details travels as a JSON ARRAY claim,
|
|
516
|
+
// not a JSON string (a conforming AS rejects the string-valued claim).
|
|
517
|
+
check("oauth.PAR+RO: authorization_details is a native array claim",
|
|
518
|
+
Array.isArray(parsed.params.authorization_details) &&
|
|
519
|
+
parsed.params.authorization_details.length === 1 &&
|
|
520
|
+
parsed.params.authorization_details[0].type === "payment_initiation");
|
|
509
521
|
} finally { server.close(); }
|
|
510
522
|
}
|
|
511
523
|
|
|
524
|
+
// RFC OIDC Back-Channel Logout — the pre-verify header parse + the token
|
|
525
|
+
// split run on an attacker-reachable, not-yet-signature-checked token, so
|
|
526
|
+
// the token must be length-bounded before either.
|
|
527
|
+
async function _testBackchannelLogoutOversized() {
|
|
528
|
+
var oa = b.auth.oauth.create({
|
|
529
|
+
issuer: "https://idp.example",
|
|
530
|
+
clientId: "rp-bcl",
|
|
531
|
+
redirectUri: "https://rp.example/cb",
|
|
532
|
+
isOidc: true,
|
|
533
|
+
allowHttp: true,
|
|
534
|
+
allowInternal: true,
|
|
535
|
+
});
|
|
536
|
+
// A logout_token far larger than the response-bytes cap must be refused
|
|
537
|
+
// BEFORE the split / base64url header decode.
|
|
538
|
+
var huge = "a".repeat(300000) + ".b.c";
|
|
539
|
+
var threw = null;
|
|
540
|
+
try { await oa.verifyBackchannelLogoutToken(huge); }
|
|
541
|
+
catch (e) { threw = e; }
|
|
542
|
+
check("oauth.backchannelLogout: oversized token refused before parse",
|
|
543
|
+
threw && threw.code === "auth-oauth/logout-token-too-large");
|
|
544
|
+
|
|
545
|
+
// A well-formed-shape but bogus header still parses through safeJson
|
|
546
|
+
// (size-bounded) and fails downstream, not with an unbounded-parse crash.
|
|
547
|
+
var bogusHeader = Buffer.from("{not json", "utf8").toString("base64url");
|
|
548
|
+
var threw2 = null;
|
|
549
|
+
try { await oa.verifyBackchannelLogoutToken(bogusHeader + ".payload.sig"); }
|
|
550
|
+
catch (e) { threw2 = e; }
|
|
551
|
+
check("oauth.backchannelLogout: malformed header refused (bounded parse)",
|
|
552
|
+
threw2 && threw2.code === "auth-oauth/bad-logout-header");
|
|
553
|
+
}
|
|
554
|
+
|
|
512
555
|
// PAR WITHOUT a signed request object: byte-for-byte the prior plain-form
|
|
513
556
|
// behavior — authorization parameters are bare form params, no `request=`.
|
|
514
557
|
async function _testParPlainUnchanged() {
|