@blamejs/blamejs-shop 0.4.31 → 0.4.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2 -0
- package/lib/asset-manifest.json +1 -1
- package/lib/vendor/MANIFEST.json +392 -278
- package/lib/vendor/blamejs/.github/workflows/ci.yml +34 -3
- package/lib/vendor/blamejs/.github/workflows/npm-publish.yml +21 -4
- package/lib/vendor/blamejs/.gitignore +6 -0
- package/lib/vendor/blamejs/CHANGELOG.md +26 -0
- package/lib/vendor/blamejs/MIGRATING.md +43 -0
- package/lib/vendor/blamejs/README.md +8 -6
- package/lib/vendor/blamejs/SECURITY.md +19 -3
- package/lib/vendor/blamejs/api-snapshot.json +2190 -664
- package/lib/vendor/blamejs/docker/caddy/localstack.Caddyfile +19 -0
- package/lib/vendor/blamejs/docker/init/generate-certs.sh +1 -1
- package/lib/vendor/blamejs/docker/otel/config.yaml +42 -0
- package/lib/vendor/blamejs/docker/otel/export/.gitkeep +0 -0
- package/lib/vendor/blamejs/docker/postgres/initdb/10-replication.sh +15 -0
- package/lib/vendor/blamejs/docker/postgres/replica-entrypoint.sh +38 -0
- package/lib/vendor/blamejs/docker/toxiproxy/toxiproxy.json +14 -0
- package/lib/vendor/blamejs/docker-compose.test.yml +209 -0
- package/lib/vendor/blamejs/examples/wiki/lib/page-generator.js +132 -0
- package/lib/vendor/blamejs/examples/wiki/lib/source-comment-block-validator.js +221 -61
- package/lib/vendor/blamejs/examples/wiki/lib/source-doc-parser.js +144 -9
- package/lib/vendor/blamejs/examples/wiki/test/e2e.js +99 -0
- package/lib/vendor/blamejs/fuzz/guard-sql.fuzz.js +36 -0
- package/lib/vendor/blamejs/index.js +4 -0
- package/lib/vendor/blamejs/lib/agent-envelope-mac.js +104 -0
- package/lib/vendor/blamejs/lib/agent-event-bus.js +105 -4
- package/lib/vendor/blamejs/lib/agent-posture-chain.js +8 -42
- package/lib/vendor/blamejs/lib/ai-content-detect.js +9 -10
- package/lib/vendor/blamejs/lib/api-key.js +158 -77
- package/lib/vendor/blamejs/lib/atomic-file.js +62 -4
- package/lib/vendor/blamejs/lib/audit-chain.js +47 -11
- package/lib/vendor/blamejs/lib/audit-sign.js +77 -2
- package/lib/vendor/blamejs/lib/audit-tools.js +79 -51
- package/lib/vendor/blamejs/lib/audit.js +259 -123
- package/lib/vendor/blamejs/lib/auth/oauth.js +53 -9
- package/lib/vendor/blamejs/lib/auth/openid-federation.js +108 -47
- package/lib/vendor/blamejs/lib/auth/saml.js +6 -8
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc.js +31 -5
- package/lib/vendor/blamejs/lib/backup/index.js +45 -10
- package/lib/vendor/blamejs/lib/break-glass.js +355 -147
- package/lib/vendor/blamejs/lib/cache.js +174 -105
- package/lib/vendor/blamejs/lib/chain-writer.js +38 -16
- package/lib/vendor/blamejs/lib/cli.js +19 -14
- package/lib/vendor/blamejs/lib/cluster-provider-db.js +130 -104
- package/lib/vendor/blamejs/lib/cluster-storage.js +119 -22
- package/lib/vendor/blamejs/lib/cluster.js +119 -71
- package/lib/vendor/blamejs/lib/codepoint-class.js +23 -0
- package/lib/vendor/blamejs/lib/compliance.js +206 -4
- package/lib/vendor/blamejs/lib/consent.js +82 -29
- package/lib/vendor/blamejs/lib/constants.js +27 -11
- package/lib/vendor/blamejs/lib/crypto-field.js +916 -156
- package/lib/vendor/blamejs/lib/db-declare-row-policy.js +35 -22
- package/lib/vendor/blamejs/lib/db-file-lifecycle.js +3 -2
- package/lib/vendor/blamejs/lib/db-query.js +882 -260
- package/lib/vendor/blamejs/lib/db-schema.js +228 -44
- package/lib/vendor/blamejs/lib/db.js +249 -99
- package/lib/vendor/blamejs/lib/dsr.js +385 -55
- package/lib/vendor/blamejs/lib/error-page.js +14 -1
- package/lib/vendor/blamejs/lib/external-db-migrate.js +239 -137
- package/lib/vendor/blamejs/lib/external-db.js +549 -34
- package/lib/vendor/blamejs/lib/file-upload.js +52 -7
- package/lib/vendor/blamejs/lib/framework-error.js +20 -1
- package/lib/vendor/blamejs/lib/framework-files.js +73 -0
- package/lib/vendor/blamejs/lib/framework-schema.js +695 -394
- package/lib/vendor/blamejs/lib/gate-contract.js +659 -1
- package/lib/vendor/blamejs/lib/guard-agent-registry.js +26 -44
- package/lib/vendor/blamejs/lib/guard-all.js +1 -0
- package/lib/vendor/blamejs/lib/guard-auth.js +42 -112
- package/lib/vendor/blamejs/lib/guard-cidr.js +33 -154
- package/lib/vendor/blamejs/lib/guard-csv.js +46 -113
- package/lib/vendor/blamejs/lib/guard-domain.js +34 -157
- package/lib/vendor/blamejs/lib/guard-dsn.js +27 -43
- package/lib/vendor/blamejs/lib/guard-email.js +47 -69
- package/lib/vendor/blamejs/lib/guard-envelope.js +19 -32
- package/lib/vendor/blamejs/lib/guard-event-bus-payload.js +24 -42
- package/lib/vendor/blamejs/lib/guard-event-bus-topic.js +25 -43
- package/lib/vendor/blamejs/lib/guard-filename.js +42 -106
- package/lib/vendor/blamejs/lib/guard-graphql.js +42 -123
- package/lib/vendor/blamejs/lib/guard-html.js +53 -108
- package/lib/vendor/blamejs/lib/guard-idempotency-key.js +24 -42
- package/lib/vendor/blamejs/lib/guard-image.js +46 -103
- package/lib/vendor/blamejs/lib/guard-imap-command.js +18 -32
- package/lib/vendor/blamejs/lib/guard-jmap.js +16 -30
- package/lib/vendor/blamejs/lib/guard-json.js +38 -108
- package/lib/vendor/blamejs/lib/guard-jsonpath.js +38 -171
- package/lib/vendor/blamejs/lib/guard-jwt.js +49 -179
- package/lib/vendor/blamejs/lib/guard-list-id.js +25 -41
- package/lib/vendor/blamejs/lib/guard-list-unsubscribe.js +27 -43
- package/lib/vendor/blamejs/lib/guard-mail-compose.js +24 -42
- package/lib/vendor/blamejs/lib/guard-mail-move.js +26 -44
- package/lib/vendor/blamejs/lib/guard-mail-query.js +28 -46
- package/lib/vendor/blamejs/lib/guard-mail-reply.js +24 -42
- package/lib/vendor/blamejs/lib/guard-mail-sieve.js +24 -42
- package/lib/vendor/blamejs/lib/guard-managesieve-command.js +17 -31
- package/lib/vendor/blamejs/lib/guard-markdown.js +37 -104
- package/lib/vendor/blamejs/lib/guard-message-id.js +26 -45
- package/lib/vendor/blamejs/lib/guard-mime.js +39 -151
- package/lib/vendor/blamejs/lib/guard-oauth.js +54 -135
- package/lib/vendor/blamejs/lib/guard-pdf.js +45 -101
- package/lib/vendor/blamejs/lib/guard-pop3-command.js +21 -31
- package/lib/vendor/blamejs/lib/guard-posture-chain.js +24 -42
- package/lib/vendor/blamejs/lib/guard-regex.js +33 -107
- package/lib/vendor/blamejs/lib/guard-saga-config.js +24 -42
- package/lib/vendor/blamejs/lib/guard-shell.js +42 -172
- package/lib/vendor/blamejs/lib/guard-smtp-command.js +48 -54
- package/lib/vendor/blamejs/lib/guard-snapshot-envelope.js +24 -42
- package/lib/vendor/blamejs/lib/guard-sql.js +1491 -0
- package/lib/vendor/blamejs/lib/guard-stream-args.js +24 -43
- package/lib/vendor/blamejs/lib/guard-svg.js +47 -65
- package/lib/vendor/blamejs/lib/guard-template.js +35 -172
- package/lib/vendor/blamejs/lib/guard-tenant-id.js +26 -45
- package/lib/vendor/blamejs/lib/guard-time.js +32 -154
- package/lib/vendor/blamejs/lib/guard-trace-context.js +25 -44
- package/lib/vendor/blamejs/lib/guard-uuid.js +32 -153
- package/lib/vendor/blamejs/lib/guard-xml.js +38 -113
- package/lib/vendor/blamejs/lib/guard-yaml.js +51 -163
- package/lib/vendor/blamejs/lib/http-client.js +37 -9
- package/lib/vendor/blamejs/lib/inbox.js +120 -107
- package/lib/vendor/blamejs/lib/legal-hold.js +121 -50
- package/lib/vendor/blamejs/lib/log-stream-cloudwatch.js +47 -31
- package/lib/vendor/blamejs/lib/log-stream-otlp.js +32 -18
- package/lib/vendor/blamejs/lib/mail-auth.js +236 -0
- package/lib/vendor/blamejs/lib/mail-crypto-smime.js +2 -6
- package/lib/vendor/blamejs/lib/mail-dkim.js +1 -0
- package/lib/vendor/blamejs/lib/mail-greylist.js +2 -6
- package/lib/vendor/blamejs/lib/mail-helo.js +2 -6
- package/lib/vendor/blamejs/lib/mail-journal.js +85 -64
- package/lib/vendor/blamejs/lib/mail-rbl.js +2 -6
- package/lib/vendor/blamejs/lib/mail-scan.js +2 -6
- package/lib/vendor/blamejs/lib/mail-server-jmap.js +117 -12
- package/lib/vendor/blamejs/lib/mail-server-mx.js +276 -7
- package/lib/vendor/blamejs/lib/mail-spam-score.js +2 -6
- package/lib/vendor/blamejs/lib/mail-store.js +293 -154
- package/lib/vendor/blamejs/lib/mail.js +8 -4
- package/lib/vendor/blamejs/lib/middleware/body-parser.js +71 -25
- package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +19 -8
- package/lib/vendor/blamejs/lib/middleware/dpop.js +10 -1
- package/lib/vendor/blamejs/lib/middleware/fetch-metadata.js +17 -7
- package/lib/vendor/blamejs/lib/middleware/idempotency-key.js +75 -51
- package/lib/vendor/blamejs/lib/middleware/rate-limit.js +102 -32
- package/lib/vendor/blamejs/lib/middleware/security-headers.js +21 -5
- package/lib/vendor/blamejs/lib/migrations.js +108 -66
- package/lib/vendor/blamejs/lib/network-heartbeat.js +7 -0
- package/lib/vendor/blamejs/lib/network-proxy.js +24 -1
- package/lib/vendor/blamejs/lib/nonce-store.js +31 -9
- package/lib/vendor/blamejs/lib/object-store/azure-blob-bucket-ops.js +9 -4
- package/lib/vendor/blamejs/lib/object-store/azure-blob.js +57 -3
- package/lib/vendor/blamejs/lib/object-store/gcs.js +4 -1
- package/lib/vendor/blamejs/lib/object-store/sigv4-bucket-ops.js +5 -2
- package/lib/vendor/blamejs/lib/object-store/sigv4.js +38 -6
- package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +9 -1
- package/lib/vendor/blamejs/lib/observability.js +124 -0
- package/lib/vendor/blamejs/lib/otel-export.js +12 -3
- package/lib/vendor/blamejs/lib/outbox.js +184 -83
- package/lib/vendor/blamejs/lib/parsers/safe-xml.js +47 -7
- package/lib/vendor/blamejs/lib/pqc-agent.js +44 -0
- package/lib/vendor/blamejs/lib/pubsub-cluster.js +42 -20
- package/lib/vendor/blamejs/lib/queue-local.js +225 -140
- package/lib/vendor/blamejs/lib/queue-redis.js +9 -1
- package/lib/vendor/blamejs/lib/queue-sqs.js +6 -0
- package/lib/vendor/blamejs/lib/queue.js +7 -0
- package/lib/vendor/blamejs/lib/redact.js +68 -11
- package/lib/vendor/blamejs/lib/redis-client.js +160 -31
- package/lib/vendor/blamejs/lib/request-helpers.js +7 -0
- package/lib/vendor/blamejs/lib/retention.js +101 -40
- package/lib/vendor/blamejs/lib/router.js +212 -5
- package/lib/vendor/blamejs/lib/safe-dns.js +29 -45
- package/lib/vendor/blamejs/lib/safe-ical.js +18 -33
- package/lib/vendor/blamejs/lib/safe-icap.js +27 -43
- package/lib/vendor/blamejs/lib/safe-sieve.js +21 -40
- package/lib/vendor/blamejs/lib/safe-sql.js +212 -3
- package/lib/vendor/blamejs/lib/safe-url.js +170 -3
- package/lib/vendor/blamejs/lib/safe-vcard.js +18 -33
- package/lib/vendor/blamejs/lib/scheduler.js +35 -12
- package/lib/vendor/blamejs/lib/seeders.js +122 -74
- package/lib/vendor/blamejs/lib/session-stores.js +42 -14
- package/lib/vendor/blamejs/lib/session.js +175 -77
- package/lib/vendor/blamejs/lib/sql.js +3842 -0
- package/lib/vendor/blamejs/lib/sse.js +26 -0
- package/lib/vendor/blamejs/lib/ssrf-guard.js +151 -4
- package/lib/vendor/blamejs/lib/static.js +177 -34
- package/lib/vendor/blamejs/lib/subject.js +96 -49
- package/lib/vendor/blamejs/lib/vault/index.js +3 -2
- package/lib/vendor/blamejs/lib/vault/passphrase-ops.js +3 -2
- package/lib/vendor/blamejs/lib/vault/rotate.js +168 -108
- package/lib/vendor/blamejs/lib/vault-aad.js +6 -0
- package/lib/vendor/blamejs/lib/vendor-data.js +2 -0
- package/lib/vendor/blamejs/lib/websocket.js +35 -5
- package/lib/vendor/blamejs/lib/worker-pool.js +11 -0
- package/lib/vendor/blamejs/package.json +2 -2
- package/lib/vendor/blamejs/release-notes/v0.14.x.json +1503 -0
- package/lib/vendor/blamejs/release-notes/v0.15.0.json +77 -0
- package/lib/vendor/blamejs/release-notes/v0.15.1.json +22 -0
- package/lib/vendor/blamejs/release-notes/v0.15.2.json +22 -0
- package/lib/vendor/blamejs/release-notes/v0.15.3.json +39 -0
- package/lib/vendor/blamejs/release-notes/v0.15.4.json +39 -0
- package/lib/vendor/blamejs/release-notes/v0.15.5.json +22 -0
- package/lib/vendor/blamejs/release-notes/v0.15.6.json +59 -0
- package/lib/vendor/blamejs/scripts/check-services.js +21 -0
- package/lib/vendor/blamejs/scripts/gen-migrating.js +51 -0
- package/lib/vendor/blamejs/scripts/release.js +398 -38
- package/lib/vendor/blamejs/test/00-primitives.js +117 -0
- package/lib/vendor/blamejs/test/10-state.js +140 -14
- package/lib/vendor/blamejs/test/20-db.js +65 -2
- package/lib/vendor/blamejs/test/helpers/db.js +9 -0
- package/lib/vendor/blamejs/test/helpers/drivers.js +27 -15
- package/lib/vendor/blamejs/test/helpers/services.js +21 -0
- package/lib/vendor/blamejs/test/integration/audit-actor-binding-pg.test.js +246 -0
- package/lib/vendor/blamejs/test/integration/audit-chain-external-db.test.js +517 -0
- package/lib/vendor/blamejs/test/integration/audit-stack-mysql.test.js +639 -0
- package/lib/vendor/blamejs/test/integration/audit-stack-postgres.test.js +832 -0
- package/lib/vendor/blamejs/test/integration/backup-restore-objectstore.test.js +453 -0
- package/lib/vendor/blamejs/test/integration/data-layer-cluster-mysql.test.js +649 -0
- package/lib/vendor/blamejs/test/integration/data-layer-cluster-pg.test.js +770 -0
- package/lib/vendor/blamejs/test/integration/data-layer-mysql-privacy.test.js +630 -0
- package/lib/vendor/blamejs/test/integration/data-layer-mysql.test.js +610 -0
- package/lib/vendor/blamejs/test/integration/data-layer-pg.test.js +577 -0
- package/lib/vendor/blamejs/test/integration/data-layer-postgres.test.js +771 -0
- package/lib/vendor/blamejs/test/integration/db-layer-mysql.test.js +549 -0
- package/lib/vendor/blamejs/test/integration/db-layer-postgres.test.js +598 -0
- package/lib/vendor/blamejs/test/integration/distributed-scheduler-fencing-pg.test.js +602 -0
- package/lib/vendor/blamejs/test/integration/external-db-postgres.test.js +576 -0
- package/lib/vendor/blamejs/test/integration/framework-schema-mysql.test.js +353 -0
- package/lib/vendor/blamejs/test/integration/log-stream-cloudwatch.test.js +224 -0
- package/lib/vendor/blamejs/test/integration/mail-crypto-smime.test.js +142 -17
- package/lib/vendor/blamejs/test/integration/network-heartbeat.test.js +25 -10
- package/lib/vendor/blamejs/test/integration/object-store-azure.test.js +101 -0
- package/lib/vendor/blamejs/test/integration/object-store-gcs.test.js +239 -0
- package/lib/vendor/blamejs/test/integration/object-store-sigv4.test.js +35 -16
- package/lib/vendor/blamejs/test/integration/object-store-worm-lock.test.js +291 -0
- package/lib/vendor/blamejs/test/integration/pubsub.test.js +14 -0
- package/lib/vendor/blamejs/test/integration/queue-sqs.test.js +322 -0
- package/lib/vendor/blamejs/test/integration/redis-reconnect-toxiproxy.test.js +300 -0
- package/lib/vendor/blamejs/test/integration/sql-fts5-catalog-sqlite.test.js +154 -0
- package/lib/vendor/blamejs/test/integration/tls-classical-downgrade-audit.test.js +71 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-event-bus.test.js +175 -12
- package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file-exclusive-temp.test.js +216 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-checkpoint-false-rollback.test.js +203 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-query-self-log.test.js +126 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-safeemit-redacts-secrets.test.js +196 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-signing-key-rotation.test.js +197 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-verifybundle-tamper.test.js +209 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/azure-blob-key-encoding.test.js +121 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/backup-residency-posture.test.js +168 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/backup-scheduletest-drill.test.js +318 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/break-glass.test.js +233 -7
- package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +1120 -14
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance.test.js +229 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-derived-hash.test.js +24 -7
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-dual-read-migrate.test.js +165 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-per-row-key.test.js +350 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-unseal-rate-cap.test.js +27 -9
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-upgrade-dialect.test.js +76 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-interop-oracles.test.js +392 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csrf-protect.test.js +159 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-column-gate.test.js +180 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/db-query-cross-schema.test.js +5 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/db-query-sealed-field-in.test.js +101 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-raw-residency-gate.test.js +128 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-schema-drift.test.js +38 -5
- package/lib/vendor/blamejs/test/layer-0-primitives/db-schema-reconcile-emittable.test.js +127 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-stream-and-payload-shape.test.js +267 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-worm.test.js +150 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/defineguard-default-gate-posture-caps.test.js +30 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dpop-middleware-replaystore-required.test.js +46 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dsr.test.js +218 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/erase-posture-vacuum.test.js +210 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-hardening.test.js +4 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-migrate.test.js +48 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/federation-vc-suite.test.js +237 -5
- package/lib/vendor/blamejs/test/layer-0-primitives/fetch-metadata.test.js +20 -9
- package/lib/vendor/blamejs/test/layer-0-primitives/file-upload-content-safety-skip-audit.test.js +193 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-csv.test.js +90 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-client-stream.test.js +85 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/idempotency-key.test.js +10 -6
- package/lib/vendor/blamejs/test/layer-0-primitives/inbox.test.js +15 -4
- package/lib/vendor/blamejs/test/layer-0-primitives/legal-hold.test.js +146 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-auth.test.js +189 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-journal.test.js +3 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-jmap.test.js +123 -4
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-mx.test.js +207 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-store.test.js +74 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/oauth-callback.test.js +43 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/otel-export.test.js +133 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/otlp-attr-redaction.test.js +101 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/outbox-inflight-reaper.test.js +136 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/parsers-standalone.test.js +83 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/passkey-real-vectors.test.js +429 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/pqc-agent-curve.test.js +21 -11
- package/lib/vendor/blamejs/test/layer-0-primitives/queue-byo-db.test.js +40 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/redact-dlp.test.js +83 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/redis-client.test.js +113 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/retention-dryrun-no-vacuum.test.js +99 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/router-use-path-scope.test.js +255 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-url-canonicalize.test.js +309 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-xml.test.js +143 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/saml-subjectconfirmation-notonorafter.test.js +287 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc-ecdsa-p1363.test.js +79 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc.test.js +50 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/security-headers.test.js +31 -4
- package/lib/vendor/blamejs/test/layer-0-primitives/session-extensions.test.js +45 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sigv4-bucket-ops.test.js +49 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sql.test.js +595 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sse-backpressure.test.js +91 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ssrf-guard.test.js +69 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/static.test.js +194 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/websocket-extension-header.test.js +88 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/worker-pool-recycle-race.test.js +66 -0
- package/lib/vendor/blamejs/test/layer-1-state/api-key.test.js +84 -0
- package/lib/vendor/blamejs/test/layer-5-integration/external-db-residency.test.js +638 -0
- package/lib/vendor/blamejs/test/layer-5-integration/guard-host-integration.test.js +21 -0
- package/lib/vendor/blamejs/test/smoke.js +79 -21
- package/package.json +1 -1
- package/lib/vendor/blamejs/release-notes/v0.14.0.json +0 -43
- package/lib/vendor/blamejs/release-notes/v0.14.1.json +0 -60
- package/lib/vendor/blamejs/release-notes/v0.14.10.json +0 -54
- package/lib/vendor/blamejs/release-notes/v0.14.11.json +0 -72
- package/lib/vendor/blamejs/release-notes/v0.14.12.json +0 -95
- package/lib/vendor/blamejs/release-notes/v0.14.13.json +0 -52
- package/lib/vendor/blamejs/release-notes/v0.14.14.json +0 -31
- package/lib/vendor/blamejs/release-notes/v0.14.16.json +0 -45
- package/lib/vendor/blamejs/release-notes/v0.14.17.json +0 -57
- package/lib/vendor/blamejs/release-notes/v0.14.18.json +0 -127
- package/lib/vendor/blamejs/release-notes/v0.14.19.json +0 -61
- package/lib/vendor/blamejs/release-notes/v0.14.2.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.14.20.json +0 -73
- package/lib/vendor/blamejs/release-notes/v0.14.21.json +0 -98
- package/lib/vendor/blamejs/release-notes/v0.14.22.json +0 -91
- package/lib/vendor/blamejs/release-notes/v0.14.3.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.14.4.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.14.5.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.14.6.json +0 -60
- package/lib/vendor/blamejs/release-notes/v0.14.7.json +0 -77
- package/lib/vendor/blamejs/release-notes/v0.14.8.json +0 -27
- package/lib/vendor/blamejs/release-notes/v0.14.9.json +0 -40
|
@@ -316,6 +316,33 @@ function create(opts) {
|
|
|
316
316
|
return cur;
|
|
317
317
|
}
|
|
318
318
|
|
|
319
|
+
// ---- Account authorization (RFC 8620 §3.6.1 accountNotFound) ------------
|
|
320
|
+
//
|
|
321
|
+
// The set of accountIds an actor may touch is whatever
|
|
322
|
+
// `opts.accountsFor(actor)` enumerates in its `accounts` map — the same
|
|
323
|
+
// source the session resource advertises. Resolving it ONCE per request
|
|
324
|
+
// and rejecting any client-supplied accountId outside that set is the
|
|
325
|
+
// cross-tenant authorization control: without it, a tenant's request can
|
|
326
|
+
// name another tenant's accountId and reach the operator's method/blob
|
|
327
|
+
// handler, which must then independently re-check or leak. The listener
|
|
328
|
+
// owns this gate so every account-scoped op (method dispatch + blob
|
|
329
|
+
// upload/download) is covered uniformly.
|
|
330
|
+
async function _permittedAccountIds(actor) {
|
|
331
|
+
var info = await opts.accountsFor(actor);
|
|
332
|
+
info = info || {};
|
|
333
|
+
var accounts = info.accounts || {};
|
|
334
|
+
// A Set of the accountIds the actor is enumerated for. An empty/garbage
|
|
335
|
+
// accounts map yields an empty set → every account-scoped reference is
|
|
336
|
+
// rejected (fail-closed), which is the correct posture for an actor the
|
|
337
|
+
// operator declined to grant any account.
|
|
338
|
+
var set = Object.create(null);
|
|
339
|
+
if (accounts && typeof accounts === "object") {
|
|
340
|
+
var ids = Object.keys(accounts);
|
|
341
|
+
for (var i = 0; i < ids.length; i += 1) set[ids[i]] = true;
|
|
342
|
+
}
|
|
343
|
+
return set;
|
|
344
|
+
}
|
|
345
|
+
|
|
319
346
|
// ---- Dispatch ------------------------------------------------------------
|
|
320
347
|
//
|
|
321
348
|
// `dispatch(actor, body)` is the operator-callable form — accepts a
|
|
@@ -340,6 +367,20 @@ function create(opts) {
|
|
|
340
367
|
return _refusalResponse(errType, (e && e.message) || "request refused");
|
|
341
368
|
}
|
|
342
369
|
|
|
370
|
+
// Resolve the actor's permitted accountId set ONCE for the whole
|
|
371
|
+
// request (RFC 8620 §3.6.1). Every account-scoped method call is gated
|
|
372
|
+
// against it below, BEFORE the operator handler runs, so a client can't
|
|
373
|
+
// name another tenant's accountId and reach the backend.
|
|
374
|
+
var permittedAccounts;
|
|
375
|
+
try {
|
|
376
|
+
permittedAccounts = await _permittedAccountIds(actor);
|
|
377
|
+
} catch (e) {
|
|
378
|
+
_emit("mail.server.jmap.accounts_for_threw",
|
|
379
|
+
{ error: (e && e.message) || String(e) }, "failure");
|
|
380
|
+
return _refusalResponse("urn:ietf:params:jmap:error:serverFail",
|
|
381
|
+
"account authorization unavailable");
|
|
382
|
+
}
|
|
383
|
+
|
|
343
384
|
var methodResponses = [];
|
|
344
385
|
var byClientId = Object.create(null);
|
|
345
386
|
for (var i = 0; i < parsed.methodCalls.length; i += 1) {
|
|
@@ -361,6 +402,24 @@ function create(opts) {
|
|
|
361
402
|
description: "Method '" + methodName + "' not implemented on this server" }, clientId]);
|
|
362
403
|
continue;
|
|
363
404
|
}
|
|
405
|
+
// Cross-tenant gate (RFC 8620 §3.6.1): if the call names an accountId,
|
|
406
|
+
// it MUST be one the actor is enumerated for. Rejected BEFORE the
|
|
407
|
+
// operator handler runs so a forged/foreign accountId never reaches
|
|
408
|
+
// the backend. Calls without an accountId (account-agnostic methods)
|
|
409
|
+
// pass through unchanged.
|
|
410
|
+
if (resolvedArgs && typeof resolvedArgs === "object" &&
|
|
411
|
+
resolvedArgs.accountId !== undefined && resolvedArgs.accountId !== null) {
|
|
412
|
+
var callAccountId = resolvedArgs.accountId;
|
|
413
|
+
if (typeof callAccountId !== "string" || !permittedAccounts[callAccountId]) {
|
|
414
|
+
_emit("mail.server.jmap.account_not_found",
|
|
415
|
+
{ method: methodName, accountId: typeof callAccountId === "string" ? callAccountId : null,
|
|
416
|
+
clientId: clientId }, "denied");
|
|
417
|
+
methodResponses.push(["error",
|
|
418
|
+
{ type: "urn:ietf:params:jmap:error:accountNotFound",
|
|
419
|
+
description: "accountId is not accessible to this actor" }, clientId]);
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
364
423
|
if (!_legacyDeprecationEmitted && registry.source(methodName) === "builtin") {
|
|
365
424
|
_legacyDeprecationEmitted = true;
|
|
366
425
|
_emit("mail.server.jmap.methods_opt_deprecated",
|
|
@@ -813,6 +872,40 @@ function create(opts) {
|
|
|
813
872
|
if (refused) return;
|
|
814
873
|
var bytes = collector.result();
|
|
815
874
|
Promise.resolve()
|
|
875
|
+
// Cross-tenant gate (RFC 8620 §3.6.1): the accountId in the upload
|
|
876
|
+
// URL must be one the actor is enumerated for, else accountNotFound
|
|
877
|
+
// — the foreign accountId never reaches uploadBlob.
|
|
878
|
+
.then(function () { return _permittedAccountIds(actor); })
|
|
879
|
+
.then(function (permitted) {
|
|
880
|
+
if (!permitted[accountId]) {
|
|
881
|
+
_emit("mail.server.jmap.account_not_found",
|
|
882
|
+
{ op: "upload", accountId: accountId }, "denied");
|
|
883
|
+
res.statusCode = 404;
|
|
884
|
+
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
885
|
+
res.end(JSON.stringify({
|
|
886
|
+
type: "urn:ietf:params:jmap:error:accountNotFound",
|
|
887
|
+
description: "accountId is not accessible to this actor",
|
|
888
|
+
}));
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
return _completeUpload(bytes);
|
|
892
|
+
})
|
|
893
|
+
.catch(function (err) {
|
|
894
|
+
_emit("mail.server.jmap.upload_threw",
|
|
895
|
+
{ accountId: accountId, error: (err && err.message) || String(err) }, "failure");
|
|
896
|
+
if (!res.headersSent) {
|
|
897
|
+
res.statusCode = 500;
|
|
898
|
+
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
899
|
+
res.end(JSON.stringify({
|
|
900
|
+
type: "urn:ietf:params:jmap:error:serverFail",
|
|
901
|
+
description: "Upload failed",
|
|
902
|
+
}));
|
|
903
|
+
}
|
|
904
|
+
});
|
|
905
|
+
});
|
|
906
|
+
|
|
907
|
+
function _completeUpload(bytes) {
|
|
908
|
+
return Promise.resolve()
|
|
816
909
|
.then(function () { return opts.mailStore.uploadBlob(actor, accountId, contentType, bytes); })
|
|
817
910
|
.then(function (meta) {
|
|
818
911
|
if (!meta || typeof meta !== "object" || typeof meta.blobId !== "string") {
|
|
@@ -827,18 +920,10 @@ function create(opts) {
|
|
|
827
920
|
type: meta.type || contentType,
|
|
828
921
|
size: typeof meta.size === "number" ? meta.size : bytes.length,
|
|
829
922
|
}));
|
|
830
|
-
})
|
|
831
|
-
.catch(function (err) {
|
|
832
|
-
_emit("mail.server.jmap.upload_threw",
|
|
833
|
-
{ accountId: accountId, error: (err && err.message) || String(err) }, "failure");
|
|
834
|
-
res.statusCode = 500;
|
|
835
|
-
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
836
|
-
res.end(JSON.stringify({
|
|
837
|
-
type: "urn:ietf:params:jmap:error:serverFail",
|
|
838
|
-
description: "Upload failed",
|
|
839
|
-
}));
|
|
840
923
|
});
|
|
841
|
-
|
|
924
|
+
// Errors from uploadBlob propagate to the req.on("end") chain's
|
|
925
|
+
// .catch (single serverFail responder, headersSent-guarded).
|
|
926
|
+
}
|
|
842
927
|
req.on("error", function () {
|
|
843
928
|
if (!refused) {
|
|
844
929
|
refused = true;
|
|
@@ -944,9 +1029,29 @@ function create(opts) {
|
|
|
944
1029
|
}));
|
|
945
1030
|
return;
|
|
946
1031
|
}
|
|
1032
|
+
var downloadDenied = false;
|
|
947
1033
|
Promise.resolve()
|
|
948
|
-
|
|
1034
|
+
// Cross-tenant gate (RFC 8620 §3.6.1): the accountId in the download
|
|
1035
|
+
// URL must be one the actor is enumerated for, else accountNotFound —
|
|
1036
|
+
// the foreign accountId never reaches downloadBlob.
|
|
1037
|
+
.then(function () { return _permittedAccountIds(actor); })
|
|
1038
|
+
.then(function (permitted) {
|
|
1039
|
+
if (!permitted[accountId]) {
|
|
1040
|
+
downloadDenied = true;
|
|
1041
|
+
_emit("mail.server.jmap.account_not_found",
|
|
1042
|
+
{ op: "download", accountId: accountId, blobId: blobId }, "denied");
|
|
1043
|
+
res.statusCode = 404;
|
|
1044
|
+
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
1045
|
+
res.end(JSON.stringify({
|
|
1046
|
+
type: "urn:ietf:params:jmap:error:accountNotFound",
|
|
1047
|
+
description: "accountId is not accessible to this actor",
|
|
1048
|
+
}));
|
|
1049
|
+
return undefined;
|
|
1050
|
+
}
|
|
1051
|
+
return opts.mailStore.downloadBlob(actor, accountId, blobId);
|
|
1052
|
+
})
|
|
949
1053
|
.then(function (result) {
|
|
1054
|
+
if (downloadDenied) return;
|
|
950
1055
|
if (!result || (typeof result !== "object" && !Buffer.isBuffer(result))) {
|
|
951
1056
|
res.statusCode = 404;
|
|
952
1057
|
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
@@ -73,6 +73,8 @@
|
|
|
73
73
|
* - `mail.server.mx.rbl_refused` — connecting IP on a DNS blocklist (zones)
|
|
74
74
|
* - `mail.server.mx.greylist_deferred` — (ip, from, rcpt) first-seen 450 deferral
|
|
75
75
|
* - `mail.server.mx.data_refused` — refusal reason + SMTP code (5xx vs 4xx)
|
|
76
|
+
* - `mail.server.mx.envelope_verdict` — DATA-phase SPF/DKIM/DMARC results + action (accept / quarantine / reject / defer) + gate mode
|
|
77
|
+
* - `mail.server.mx.envelope_error` — DATA-phase authentication pipeline failure or timeout (disposition follows onTemperror)
|
|
76
78
|
* - `mail.server.mx.delivered` — agent.handoff ack
|
|
77
79
|
* - `mail.server.mx.tls_handshake_failed` — handshake error
|
|
78
80
|
* - `mail.server.mx.smtp_smuggling_detected` — CRLF.CRLF injection class
|
|
@@ -111,13 +113,20 @@
|
|
|
111
113
|
* (connecting-IP DNS blocklist, evaluated once per connection) and
|
|
112
114
|
* `opts.greylist` ((ip, from, rcpt) first-seen deferral) evaluate at
|
|
113
115
|
* RCPT TO and surface their verdicts on the `rcpt_to` event. The
|
|
114
|
-
* message-authentication
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
116
|
+
* message-authentication gate (`opts.guardEnvelope`) runs at DATA
|
|
117
|
+
* completion through `b.mail.inbound.verify` — SPF (RFC 7208) on the
|
|
118
|
+
* envelope identity, DKIM (RFC 6376) on the message bytes, DMARC
|
|
119
|
+
* (RFC 7489) policy + alignment on the From-header domain — and in
|
|
120
|
+
* enforce mode refuses before the agent handoff: 550 5.7.26
|
|
121
|
+
* (RFC 7372) when the sender's published policy says reject, 550
|
|
122
|
+
* 5.7.1 on the RFC 7489 §6.6.1 multi-From spoofing shape, 451 4.7.0
|
|
123
|
+
* on DNS temperror or pipeline timeout (operator-tunable via
|
|
124
|
+
* `onTemperror` / `timeoutMs`). Accepted messages carry the verdict
|
|
125
|
+
* to the agent handoff as `auth` and gain the receiver's RFC 8601
|
|
126
|
+
* Authentication-Results header — any sender-attached header forging
|
|
127
|
+
* this receiver's authserv-id is stripped first (§5) — so downstream
|
|
128
|
+
* consumers act on authenticated results instead of re-verifying;
|
|
129
|
+
* monitor mode annotates without refusing.
|
|
121
130
|
*
|
|
122
131
|
* @card
|
|
123
132
|
* Inbound SMTP / MX listener. RFC 5321 state machine with SMTP-
|
|
@@ -144,6 +153,12 @@ var mailServerTls = require("./mail-server-tls");
|
|
|
144
153
|
var { defineClass } = require("./framework-error");
|
|
145
154
|
|
|
146
155
|
var audit = lazyRequire(function () { return require("./audit"); });
|
|
156
|
+
// Lazy like the sibling host primitives' guard loads — the inbound
|
|
157
|
+
// authentication pipeline (and the DKIM verifier whose range
|
|
158
|
+
// constants the boot validation mirrors) only loads when an operator
|
|
159
|
+
// wires opts.guardEnvelope.
|
|
160
|
+
var mailAuth = lazyRequire(function () { return require("./mail-auth"); });
|
|
161
|
+
var dkim = lazyRequire(function () { return require("./mail-dkim"); });
|
|
147
162
|
|
|
148
163
|
var MailServerMxError = defineClass("MailServerMxError", { alwaysPermanent: true });
|
|
149
164
|
|
|
@@ -178,6 +193,59 @@ var RE_MAIL_FROM = /^MAIL\s+FROM:\s*<([^>]*)>(?:\s+(.*))?$/i;
|
|
|
178
193
|
var RE_RCPT_TO = /^RCPT\s+TO:\s*<([^>]+)>(?:\s+.*)?$/i;
|
|
179
194
|
var RE_SIZE = /SIZE=(\d+)/i;
|
|
180
195
|
|
|
196
|
+
// Map the b.mail.inbound.verify verdict to the DATA-phase gate action.
|
|
197
|
+
// The sender's published DMARC policy drives it (RFC 7489 §6.3 p= /
|
|
198
|
+
// §6.6.2 disposition): reject → refuse at the wire; quarantine →
|
|
199
|
+
// deliver annotated (an MX cannot spam-folder — the downstream agent
|
|
200
|
+
// owns disposition); none / pass → accept. DNS temperror defers or
|
|
201
|
+
// accepts per the operator's onTemperror choice. permerror carries a
|
|
202
|
+
// reject recommendation only for the multi-From spoofing shape
|
|
203
|
+
// (RFC 7489 §6.6.1), set by the pipeline itself.
|
|
204
|
+
function _envelopeActionFor(inbound, gate) {
|
|
205
|
+
var dmarc = inbound.dmarc || {};
|
|
206
|
+
if (dmarc.result === "temperror") {
|
|
207
|
+
return gate.onTemperror === "accept" ? "accept" : "defer";
|
|
208
|
+
}
|
|
209
|
+
if (dmarc.recommendedAction === "reject") return "reject";
|
|
210
|
+
if (dmarc.recommendedAction === "quarantine") return "quarantine";
|
|
211
|
+
return "accept";
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// RFC 8601 §5 — an MTA adding its own Authentication-Results header
|
|
215
|
+
// MUST first remove any existing instance claiming its authserv-id: a
|
|
216
|
+
// sender can pre-attach a forged header carrying the receiver's name
|
|
217
|
+
// ("Authentication-Results: mx.example.com; dmarc=pass") and downstream
|
|
218
|
+
// consumers that trust the receiver's A-R header would read the forged
|
|
219
|
+
// verdict instead of the computed one. Headers naming OTHER
|
|
220
|
+
// authserv-ids are prior-hop information and stay. Operates on the
|
|
221
|
+
// header block only — the block is decoded as latin1 (byte-preserving
|
|
222
|
+
// round-trip) and the body bytes are never decoded at all, so 8-bit
|
|
223
|
+
// content is untouched.
|
|
224
|
+
function _stripForgedAuthResults(messageBuf, authservId) {
|
|
225
|
+
if (!authservId) return messageBuf;
|
|
226
|
+
var sepIdx = messageBuf.indexOf("\r\n\r\n");
|
|
227
|
+
var headerEnd = sepIdx === -1 ? messageBuf.length : sepIdx + 2;
|
|
228
|
+
var head = messageBuf.slice(0, headerEnd).toString("latin1");
|
|
229
|
+
var rest = messageBuf.slice(headerEnd);
|
|
230
|
+
if (head.toLowerCase().indexOf("authentication-results:") === -1) return messageBuf;
|
|
231
|
+
var lines = head.split("\r\n");
|
|
232
|
+
var out = [];
|
|
233
|
+
var skipping = false;
|
|
234
|
+
var prefix = "authentication-results:";
|
|
235
|
+
var wantId = authservId.toLowerCase();
|
|
236
|
+
for (var i = 0; i < lines.length; i += 1) {
|
|
237
|
+
var line = lines[i];
|
|
238
|
+
if (skipping && (line.charAt(0) === " " || line.charAt(0) === "\t")) continue; // folded continuation
|
|
239
|
+
skipping = false;
|
|
240
|
+
if (line.slice(0, prefix.length).toLowerCase() === prefix) {
|
|
241
|
+
var idTok = line.slice(prefix.length).trim().split(/[;\s]/)[0].toLowerCase();
|
|
242
|
+
if (idTok === wantId) { skipping = true; continue; }
|
|
243
|
+
}
|
|
244
|
+
out.push(line);
|
|
245
|
+
}
|
|
246
|
+
return Buffer.concat([Buffer.from(out.join("\r\n"), "latin1"), rest]);
|
|
247
|
+
}
|
|
248
|
+
|
|
181
249
|
/**
|
|
182
250
|
* @primitive b.mail.server.mx.create
|
|
183
251
|
* @signature b.mail.server.mx.create(opts)
|
|
@@ -202,6 +270,16 @@ var RE_SIZE = /SIZE=(\d+)/i;
|
|
|
202
270
|
* maxRcptsPerMessage: number, // default 100 — per RFC 5321 §4.5.3.1.8
|
|
203
271
|
* idleTimeoutMs: number, // default 5 minutes — RFC 5321 §4.5.3.2.7
|
|
204
272
|
* profile: "strict" | "balanced" | "permissive", // gate posture cascade
|
|
273
|
+
* guardEnvelope: true | { // optional gate — DATA-phase SPF/DKIM/DMARC via b.mail.inbound.verify
|
|
274
|
+
* mode?: "enforce" | "monitor", // default: enforce (monitor when profile is permissive)
|
|
275
|
+
* onTemperror?: "defer" | "accept", // DNS temperror disposition; default "defer" (451 4.7.5)
|
|
276
|
+
* authservId?: string, // RFC 8601 authserv-id; default localDomains[0]
|
|
277
|
+
* dnsLookup?: function, // async (qname, type) override for SPF/DKIM/DMARC lookups
|
|
278
|
+
* maxSignatures?: number, // DKIM verify cap (1-16)
|
|
279
|
+
* clockSkewMs?: number, // DKIM timestamp skew tolerance
|
|
280
|
+
* minRsaBits?: number, // DKIM minimum RSA key size
|
|
281
|
+
* timeoutMs?: number, // pipeline wall-clock ceiling; default 20s (timeout → temperror disposition)
|
|
282
|
+
* },
|
|
205
283
|
*
|
|
206
284
|
* @example
|
|
207
285
|
* var tls = b.network.tls.context({ cert: certPem, key: keyPem });
|
|
@@ -269,6 +347,92 @@ function create(opts) {
|
|
|
269
347
|
rateLimit = mailServerRateLimit.create(opts.rateLimit || {});
|
|
270
348
|
}
|
|
271
349
|
|
|
350
|
+
// DATA-phase message-authentication gate. `guardEnvelope: true`
|
|
351
|
+
// gates with defaults; an object tunes it. Like the sibling gates
|
|
352
|
+
// (helo / rbl / greylist) the phase is skipped when the operator
|
|
353
|
+
// doesn't wire it — the gate needs live DNS to evaluate the
|
|
354
|
+
// sender's published policy, which closed-network deployments may
|
|
355
|
+
// not have.
|
|
356
|
+
var envelopeGate = null;
|
|
357
|
+
if (opts.guardEnvelope !== undefined && opts.guardEnvelope !== false) {
|
|
358
|
+
if (opts.guardEnvelope !== true &&
|
|
359
|
+
(typeof opts.guardEnvelope !== "object" || opts.guardEnvelope === null ||
|
|
360
|
+
Array.isArray(opts.guardEnvelope))) {
|
|
361
|
+
throw new MailServerMxError("mail-server-mx/bad-opts",
|
|
362
|
+
"mail.server.mx.create: guardEnvelope must be true, false, or a config object");
|
|
363
|
+
}
|
|
364
|
+
var ge = opts.guardEnvelope === true ? {} : opts.guardEnvelope;
|
|
365
|
+
validateOpts(ge, ["mode", "onTemperror", "authservId", "dnsLookup",
|
|
366
|
+
"maxSignatures", "clockSkewMs", "minRsaBits", "timeoutMs"],
|
|
367
|
+
"mail.server.mx.guardEnvelope");
|
|
368
|
+
var geMode = (ge.mode === undefined || ge.mode === null)
|
|
369
|
+
? (profile === "permissive" ? "monitor" : "enforce")
|
|
370
|
+
: ge.mode;
|
|
371
|
+
if (geMode !== "enforce" && geMode !== "monitor") {
|
|
372
|
+
throw new MailServerMxError("mail-server-mx/bad-opts",
|
|
373
|
+
"mail.server.mx.create: guardEnvelope.mode must be 'enforce' or 'monitor'");
|
|
374
|
+
}
|
|
375
|
+
var geOnTemperror = (ge.onTemperror === undefined || ge.onTemperror === null)
|
|
376
|
+
? "defer" : ge.onTemperror;
|
|
377
|
+
if (geOnTemperror !== "defer" && geOnTemperror !== "accept") {
|
|
378
|
+
throw new MailServerMxError("mail-server-mx/bad-opts",
|
|
379
|
+
"mail.server.mx.create: guardEnvelope.onTemperror must be 'defer' or 'accept'");
|
|
380
|
+
}
|
|
381
|
+
if (ge.authservId !== undefined && ge.authservId !== null) {
|
|
382
|
+
validateOpts.requireNonEmptyString(ge.authservId,
|
|
383
|
+
"mail.server.mx.create: guardEnvelope.authservId",
|
|
384
|
+
MailServerMxError, "mail-server-mx/bad-opts");
|
|
385
|
+
}
|
|
386
|
+
if (ge.dnsLookup !== undefined && ge.dnsLookup !== null &&
|
|
387
|
+
typeof ge.dnsLookup !== "function") {
|
|
388
|
+
throw new MailServerMxError("mail-server-mx/bad-opts",
|
|
389
|
+
"mail.server.mx.create: guardEnvelope.dnsLookup must be a function");
|
|
390
|
+
}
|
|
391
|
+
// DKIM bounds caught at boot, not at the first DATA — mirroring
|
|
392
|
+
// the exact ranges b.mail.dkim.verify enforces per call, so an
|
|
393
|
+
// operator typo fails startup instead of turning every live
|
|
394
|
+
// message into an envelope_error + temperror disposition.
|
|
395
|
+
numericBounds.requireAllPositiveFiniteIntIfPresent(ge,
|
|
396
|
+
["maxSignatures", "clockSkewMs", "minRsaBits", "timeoutMs"],
|
|
397
|
+
"mail.server.mx.guardEnvelope.", MailServerMxError, "mail-server-mx/bad-bound");
|
|
398
|
+
if (ge.maxSignatures !== undefined && ge.maxSignatures !== null &&
|
|
399
|
+
ge.maxSignatures > dkim().DKIM_MAX_SIGNATURES_PER_MESSAGE_CEILING) {
|
|
400
|
+
throw new MailServerMxError("mail-server-mx/bad-bound",
|
|
401
|
+
"mail.server.mx.create: guardEnvelope.maxSignatures " + ge.maxSignatures +
|
|
402
|
+
" exceeds the DKIM verifier ceiling " +
|
|
403
|
+
dkim().DKIM_MAX_SIGNATURES_PER_MESSAGE_CEILING +
|
|
404
|
+
" (RFC 6376 §6.1 fan-out DoS bound)");
|
|
405
|
+
}
|
|
406
|
+
if (ge.clockSkewMs !== undefined && ge.clockSkewMs !== null &&
|
|
407
|
+
ge.clockSkewMs > dkim().DKIM_CLOCK_SKEW_MS_MAX) {
|
|
408
|
+
throw new MailServerMxError("mail-server-mx/bad-bound",
|
|
409
|
+
"mail.server.mx.create: guardEnvelope.clockSkewMs " + ge.clockSkewMs +
|
|
410
|
+
" exceeds the DKIM verifier ceiling " + dkim().DKIM_CLOCK_SKEW_MS_MAX +
|
|
411
|
+
" (RFC 6376 §3.5 back-dating replay defense)");
|
|
412
|
+
}
|
|
413
|
+
envelopeGate = Object.freeze({
|
|
414
|
+
mode: geMode,
|
|
415
|
+
onTemperror: geOnTemperror,
|
|
416
|
+
// RFC 8601 authserv-id — the receiver's own name on the
|
|
417
|
+
// Authentication-Results header. Defaults to the first local
|
|
418
|
+
// domain; with neither, the header is skipped (the verdict
|
|
419
|
+
// still reaches the agent handoff).
|
|
420
|
+
authservId: ge.authservId || localDomains[0] || null,
|
|
421
|
+
dnsLookup: ge.dnsLookup || undefined,
|
|
422
|
+
maxSignatures: ge.maxSignatures,
|
|
423
|
+
clockSkewMs: ge.clockSkewMs,
|
|
424
|
+
minRsaBits: ge.minRsaBits,
|
|
425
|
+
// Wall-clock ceiling for the whole pipeline (SPF include chains
|
|
426
|
+
// + per-signature DKIM key fetches + DMARC policy walk). A
|
|
427
|
+
// message stuffed with signatures pointing at slow resolvers
|
|
428
|
+
// must not pin the connection slot — on timeout the message
|
|
429
|
+
// takes the temperror disposition (defer / accept per
|
|
430
|
+
// onTemperror).
|
|
431
|
+
timeoutMs: (ge.timeoutMs === undefined || ge.timeoutMs === null)
|
|
432
|
+
? C.TIME.seconds(20) : ge.timeoutMs,
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
|
|
272
436
|
// Default-on operator-supplied-domain hardening. opts.localDomains
|
|
273
437
|
// and the HELO / MAIL FROM / RCPT TO domain validations all route
|
|
274
438
|
// through `b.guardDomain` for IDN homograph / Punycode-spoof defense
|
|
@@ -885,6 +1049,110 @@ function create(opts) {
|
|
|
885
1049
|
return;
|
|
886
1050
|
}
|
|
887
1051
|
}
|
|
1052
|
+
// DATA-phase message authentication (opts.guardEnvelope) — SPF /
|
|
1053
|
+
// DKIM / DMARC through b.mail.inbound.verify, refusing before the
|
|
1054
|
+
// agent handoff so a policy-failing message never reaches storage.
|
|
1055
|
+
var inboundAuth = null;
|
|
1056
|
+
if (envelopeGate) {
|
|
1057
|
+
var inboundVerdict = null;
|
|
1058
|
+
try {
|
|
1059
|
+
// Wall-clock ceiling around the whole pipeline — a message
|
|
1060
|
+
// stuffed with signatures pointing at slow resolvers must
|
|
1061
|
+
// not pin the connection slot. Timeout surfaces as
|
|
1062
|
+
// SafeAsyncError(async/timeout) into the catch below.
|
|
1063
|
+
inboundVerdict = await safeAsync.withTimeout(
|
|
1064
|
+
mailAuth().inbound.verify({
|
|
1065
|
+
ip: state.remoteAddress,
|
|
1066
|
+
helo: state.helo || undefined,
|
|
1067
|
+
mailFrom: state.mailFrom || undefined,
|
|
1068
|
+
message: dedotted,
|
|
1069
|
+
dnsLookup: envelopeGate.dnsLookup,
|
|
1070
|
+
maxSignatures: envelopeGate.maxSignatures,
|
|
1071
|
+
clockSkewMs: envelopeGate.clockSkewMs,
|
|
1072
|
+
minRsaBits: envelopeGate.minRsaBits,
|
|
1073
|
+
authservId: envelopeGate.authservId || undefined,
|
|
1074
|
+
}),
|
|
1075
|
+
envelopeGate.timeoutMs,
|
|
1076
|
+
{ name: "mail.server.mx.guardEnvelope" });
|
|
1077
|
+
} catch (err) {
|
|
1078
|
+
// Pipeline infrastructure failure or wall-clock timeout (not
|
|
1079
|
+
// an authentication verdict). Same disposition as a DNS
|
|
1080
|
+
// temperror: defer so the sender retries, or accept
|
|
1081
|
+
// unauthenticated when the operator chose availability via
|
|
1082
|
+
// onTemperror.
|
|
1083
|
+
_emit("mail.server.mx.envelope_error", {
|
|
1084
|
+
connectionId: state.id,
|
|
1085
|
+
mailFrom: state.mailFrom,
|
|
1086
|
+
error: (err && err.message) || String(err),
|
|
1087
|
+
}, "failure");
|
|
1088
|
+
if (envelopeGate.mode === "enforce" && envelopeGate.onTemperror === "defer") {
|
|
1089
|
+
_writeReply(socket, REPLY_451_LOCAL_ERROR,
|
|
1090
|
+
"4.7.0 Message authentication could not be completed; try again later");
|
|
1091
|
+
_resetTransaction(state);
|
|
1092
|
+
return;
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
if (inboundVerdict) {
|
|
1096
|
+
var envAction = _envelopeActionFor(inboundVerdict, envelopeGate);
|
|
1097
|
+
var dkimSummary = inboundVerdict.dkim.some(function (d) { return d.result === "pass"; })
|
|
1098
|
+
? "pass"
|
|
1099
|
+
: (inboundVerdict.dkim[0] ? inboundVerdict.dkim[0].result : "none");
|
|
1100
|
+
_emit("mail.server.mx.envelope_verdict", {
|
|
1101
|
+
connectionId: state.id,
|
|
1102
|
+
mailFrom: state.mailFrom,
|
|
1103
|
+
fromDomain: inboundVerdict.from.domain,
|
|
1104
|
+
spf: inboundVerdict.spf.result,
|
|
1105
|
+
dkim: dkimSummary,
|
|
1106
|
+
dmarc: inboundVerdict.dmarc.result,
|
|
1107
|
+
action: envAction,
|
|
1108
|
+
mode: envelopeGate.mode,
|
|
1109
|
+
}, (envAction === "reject" || envAction === "defer") ? "denied" : "success");
|
|
1110
|
+
if (envelopeGate.mode === "enforce" && envAction === "reject") {
|
|
1111
|
+
// RFC 7372 §3.2 — 5.7.26 ("multiple authentication checks
|
|
1112
|
+
// failed") for a DMARC evaluation that failed; the
|
|
1113
|
+
// multi-From / unparsable-author permerror shape is a
|
|
1114
|
+
// message-acceptability refusal and keeps the generic
|
|
1115
|
+
// 5.7.1.
|
|
1116
|
+
var enhanced = inboundVerdict.dmarc.result === "fail" ? "5.7.26" : "5.7.1";
|
|
1117
|
+
_writeReply(socket, REPLY_550_MAILBOX_UNAVAIL,
|
|
1118
|
+
enhanced + " Message refused by sender authentication policy (DMARC " +
|
|
1119
|
+
inboundVerdict.dmarc.result + "; SPF " + inboundVerdict.spf.result +
|
|
1120
|
+
", DKIM " + dkimSummary + ")");
|
|
1121
|
+
_resetTransaction(state);
|
|
1122
|
+
return;
|
|
1123
|
+
}
|
|
1124
|
+
if (envelopeGate.mode === "enforce" && envAction === "defer") {
|
|
1125
|
+
_writeReply(socket, REPLY_451_LOCAL_ERROR,
|
|
1126
|
+
"4.7.0 Sender authentication temporarily unavailable (DNS); try again later");
|
|
1127
|
+
_resetTransaction(state);
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
// Accept / quarantine / monitor mode: the verdict rides to
|
|
1131
|
+
// the agent handoff as `auth`, and the receiver's RFC 8601
|
|
1132
|
+
// Authentication-Results header is prepended so downstream
|
|
1133
|
+
// consumers (spam-foldering quarantined mail included) act
|
|
1134
|
+
// on authenticated results instead of re-verifying.
|
|
1135
|
+
if (inboundVerdict.authResults) {
|
|
1136
|
+
// RFC 8601 §5 — strip any sender-attached A-R header
|
|
1137
|
+
// claiming this receiver's authserv-id before prepending
|
|
1138
|
+
// the computed one (forged-verdict shadowing defense).
|
|
1139
|
+
dedotted = _stripForgedAuthResults(dedotted, envelopeGate.authservId);
|
|
1140
|
+
dedotted = Buffer.concat([
|
|
1141
|
+
Buffer.from(inboundVerdict.authResults + "\r\n", "utf8"),
|
|
1142
|
+
dedotted,
|
|
1143
|
+
]);
|
|
1144
|
+
}
|
|
1145
|
+
inboundAuth = {
|
|
1146
|
+
spf: inboundVerdict.spf,
|
|
1147
|
+
dkim: inboundVerdict.dkim,
|
|
1148
|
+
dmarc: inboundVerdict.dmarc,
|
|
1149
|
+
from: inboundVerdict.from,
|
|
1150
|
+
action: envAction,
|
|
1151
|
+
mode: envelopeGate.mode,
|
|
1152
|
+
quarantine: envAction === "quarantine",
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
888
1156
|
// operator-supplied agent handoff — when wired, persist via
|
|
889
1157
|
// agent + write the 250 reply. When not wired, accept-and-drop
|
|
890
1158
|
// (audit-only mode useful for staging deployments).
|
|
@@ -897,6 +1165,7 @@ function create(opts) {
|
|
|
897
1165
|
remote: { address: state.remoteAddress, port: state.remotePort },
|
|
898
1166
|
tls: state.tls,
|
|
899
1167
|
helo: state.helo,
|
|
1168
|
+
auth: inboundAuth,
|
|
900
1169
|
connectionId: state.id,
|
|
901
1170
|
}).then(function (ack) {
|
|
902
1171
|
_emit("mail.server.mx.delivered",
|
|
@@ -70,6 +70,7 @@
|
|
|
70
70
|
var { defineClass } = require("./framework-error");
|
|
71
71
|
var lazyRequire = require("./lazy-require");
|
|
72
72
|
var validateOpts = require("./validate-opts");
|
|
73
|
+
var gateContract = require("./gate-contract");
|
|
73
74
|
|
|
74
75
|
var audit = lazyRequire(function () { return require("./audit"); });
|
|
75
76
|
|
|
@@ -90,12 +91,7 @@ var PROFILES = Object.freeze({
|
|
|
90
91
|
permissive: { threshold: 10.0, maxReasons: MAX_REASONS, maxReasonBytes: MAX_REASON_BYTES },
|
|
91
92
|
});
|
|
92
93
|
|
|
93
|
-
var COMPLIANCE_POSTURES =
|
|
94
|
-
hipaa: "strict",
|
|
95
|
-
"pci-dss": "strict",
|
|
96
|
-
gdpr: "strict",
|
|
97
|
-
soc2: "strict",
|
|
98
|
-
});
|
|
94
|
+
var COMPLIANCE_POSTURES = gateContract.ALL_STRICT_POSTURES;
|
|
99
95
|
|
|
100
96
|
/**
|
|
101
97
|
* @primitive b.mail.spamScore.create
|