@blamejs/blamejs-shop 0.5.12 → 0.5.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/lib/asset-manifest.json +1 -1
- package/lib/sales-tax-filings.js +143 -0
- package/lib/vendor/MANIFEST.json +632 -416
- package/lib/vendor/blamejs/.clusterfuzzlite/Dockerfile +21 -10
- package/lib/vendor/blamejs/.clusterfuzzlite/build.sh +30 -9
- package/lib/vendor/blamejs/.github/dependabot.yml +8 -0
- package/lib/vendor/blamejs/.github/workflows/actions-lint.yml +3 -3
- package/lib/vendor/blamejs/.github/workflows/cflite_batch.yml +7 -2
- package/lib/vendor/blamejs/.github/workflows/cflite_pr.yml +7 -2
- package/lib/vendor/blamejs/.github/workflows/ci.yml +21 -21
- package/lib/vendor/blamejs/.github/workflows/codeql.yml +3 -3
- package/lib/vendor/blamejs/.github/workflows/npm-publish.yml +4 -4
- package/lib/vendor/blamejs/.github/workflows/release-container.yml +7 -7
- package/lib/vendor/blamejs/.github/workflows/scorecard.yml +3 -3
- package/lib/vendor/blamejs/.github/workflows/sha-to-tag-verify.yml +1 -1
- package/lib/vendor/blamejs/.gitignore +2 -0
- package/lib/vendor/blamejs/CHANGELOG.md +122 -0
- package/lib/vendor/blamejs/NOTICE +26 -1
- package/lib/vendor/blamejs/SECURITY.md +1 -1
- package/lib/vendor/blamejs/api-snapshot.json +207 -5
- package/lib/vendor/blamejs/docker/keycloak/realm-blamejs-test.json +29 -0
- package/lib/vendor/blamejs/examples/wiki/DEPLOY.md +1 -0
- package/lib/vendor/blamejs/examples/wiki/lib/build-app.js +31 -0
- package/lib/vendor/blamejs/examples/wiki/lib/source-comment-block-validator.js +109 -0
- package/lib/vendor/blamejs/examples/wiki/nginx.conf +106 -0
- package/lib/vendor/blamejs/examples/wiki/package-lock.json +1 -1
- package/lib/vendor/blamejs/examples/wiki/test/e2e.js +92 -0
- package/lib/vendor/blamejs/fuzz/asn1-der.fuzz.js +28 -0
- package/lib/vendor/blamejs/fuzz/cms-codec.fuzz.js +17 -0
- package/lib/vendor/blamejs/fuzz/link-header.fuzz.js +18 -0
- package/lib/vendor/blamejs/index.js +6 -0
- package/lib/vendor/blamejs/lib/a2a.js +4 -4
- package/lib/vendor/blamejs/lib/agent-idempotency.js +18 -2
- package/lib/vendor/blamejs/lib/agent-saga.js +36 -8
- package/lib/vendor/blamejs/lib/agent-snapshot.js +63 -6
- package/lib/vendor/blamejs/lib/app-shutdown.js +23 -20
- package/lib/vendor/blamejs/lib/archive-read.js +8 -0
- package/lib/vendor/blamejs/lib/archive-tar.js +12 -2
- package/lib/vendor/blamejs/lib/arg-parser.js +49 -5
- package/lib/vendor/blamejs/lib/asn1-der.js +12 -0
- package/lib/vendor/blamejs/lib/audit-sign.js +47 -1
- package/lib/vendor/blamejs/lib/audit.js +71 -8
- package/lib/vendor/blamejs/lib/auth/ciba.js +14 -2
- package/lib/vendor/blamejs/lib/auth/fido-mds3.js +58 -47
- package/lib/vendor/blamejs/lib/auth/jwt.js +34 -0
- package/lib/vendor/blamejs/lib/auth/oauth.js +90 -13
- package/lib/vendor/blamejs/lib/auth/oid4vci.js +20 -17
- package/lib/vendor/blamejs/lib/auth/openid-federation.js +214 -23
- package/lib/vendor/blamejs/lib/auth/saml.js +38 -20
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc.js +10 -4
- package/lib/vendor/blamejs/lib/backup/bundle.js +13 -3
- package/lib/vendor/blamejs/lib/backup/index.js +16 -5
- package/lib/vendor/blamejs/lib/backup/manifest.js +18 -5
- package/lib/vendor/blamejs/lib/base32.js +21 -0
- package/lib/vendor/blamejs/lib/break-glass.js +19 -10
- package/lib/vendor/blamejs/lib/bundler.js +5 -1
- package/lib/vendor/blamejs/lib/cache-status.js +3 -7
- package/lib/vendor/blamejs/lib/calendar.js +21 -1
- package/lib/vendor/blamejs/lib/cbor.js +12 -1
- package/lib/vendor/blamejs/lib/cert.js +29 -11
- package/lib/vendor/blamejs/lib/cli.js +94 -24
- package/lib/vendor/blamejs/lib/cloud-events.js +2 -2
- package/lib/vendor/blamejs/lib/cluster-provider-db.js +13 -3
- package/lib/vendor/blamejs/lib/codepoint-class.js +85 -0
- package/lib/vendor/blamejs/lib/compliance-ai-act-prohibited.js +8 -4
- package/lib/vendor/blamejs/lib/compliance-ai-act-transparency.js +19 -4
- package/lib/vendor/blamejs/lib/compliance-ai-act.js +6 -8
- package/lib/vendor/blamejs/lib/content-credentials.js +1 -1
- package/lib/vendor/blamejs/lib/content-digest.js +2 -1
- package/lib/vendor/blamejs/lib/cookies.js +13 -3
- package/lib/vendor/blamejs/lib/cose.js +31 -16
- package/lib/vendor/blamejs/lib/crypto-field.js +32 -0
- package/lib/vendor/blamejs/lib/crypto-oprf.js +11 -2
- package/lib/vendor/blamejs/lib/crypto-xwing.js +22 -4
- package/lib/vendor/blamejs/lib/crypto.js +314 -19
- package/lib/vendor/blamejs/lib/csp.js +11 -3
- package/lib/vendor/blamejs/lib/daemon.js +270 -24
- package/lib/vendor/blamejs/lib/dark-patterns.js +5 -2
- package/lib/vendor/blamejs/lib/db-declare-view.js +2 -2
- package/lib/vendor/blamejs/lib/db-query.js +34 -7
- package/lib/vendor/blamejs/lib/db.js +18 -6
- package/lib/vendor/blamejs/lib/dbsc.js +17 -0
- package/lib/vendor/blamejs/lib/dev.js +21 -2
- package/lib/vendor/blamejs/lib/dsr.js +157 -102
- package/lib/vendor/blamejs/lib/external-db.js +41 -2
- package/lib/vendor/blamejs/lib/file-upload.js +25 -7
- package/lib/vendor/blamejs/lib/flag-providers.js +27 -15
- package/lib/vendor/blamejs/lib/forms.js +113 -27
- package/lib/vendor/blamejs/lib/gate-contract.js +23 -10
- package/lib/vendor/blamejs/lib/gdpr-ropa.js +8 -1
- package/lib/vendor/blamejs/lib/guard-agent-registry.js +1 -1
- package/lib/vendor/blamejs/lib/guard-archive.js +2 -2
- package/lib/vendor/blamejs/lib/guard-cidr.js +27 -1
- package/lib/vendor/blamejs/lib/guard-graphql.js +30 -11
- package/lib/vendor/blamejs/lib/guard-html.js +26 -53
- package/lib/vendor/blamejs/lib/guard-image.js +24 -5
- package/lib/vendor/blamejs/lib/guard-imap-command.js +10 -2
- package/lib/vendor/blamejs/lib/guard-jmap.js +19 -3
- package/lib/vendor/blamejs/lib/guard-jwt.js +18 -2
- package/lib/vendor/blamejs/lib/guard-list-unsubscribe.js +2 -2
- package/lib/vendor/blamejs/lib/guard-managesieve-command.js +10 -2
- package/lib/vendor/blamejs/lib/guard-markdown.js +11 -24
- package/lib/vendor/blamejs/lib/guard-pdf.js +18 -2
- package/lib/vendor/blamejs/lib/guard-pop3-command.js +10 -2
- package/lib/vendor/blamejs/lib/guard-regex.js +8 -5
- package/lib/vendor/blamejs/lib/guard-svg.js +31 -32
- package/lib/vendor/blamejs/lib/guard-time.js +1 -1
- package/lib/vendor/blamejs/lib/guard-uuid.js +2 -2
- package/lib/vendor/blamejs/lib/html-balance.js +3 -3
- package/lib/vendor/blamejs/lib/http-client-cache.js +82 -4
- package/lib/vendor/blamejs/lib/http-client-cookie-jar.js +16 -1
- package/lib/vendor/blamejs/lib/http-client.js +39 -7
- package/lib/vendor/blamejs/lib/http-message-signature.js +35 -1
- package/lib/vendor/blamejs/lib/i18n-messageformat.js +31 -6
- package/lib/vendor/blamejs/lib/i18n.js +35 -21
- package/lib/vendor/blamejs/lib/incident-report.js +8 -1
- package/lib/vendor/blamejs/lib/ip-utils.js +11 -1
- package/lib/vendor/blamejs/lib/json-path.js +23 -1
- package/lib/vendor/blamejs/lib/jwk.js +20 -3
- package/lib/vendor/blamejs/lib/link-header.js +2 -1
- package/lib/vendor/blamejs/lib/log-stream-otlp-grpc.js +5 -1
- package/lib/vendor/blamejs/lib/log-stream-otlp.js +5 -1
- package/lib/vendor/blamejs/lib/log-stream.js +8 -3
- package/lib/vendor/blamejs/lib/mail-auth.js +123 -22
- package/lib/vendor/blamejs/lib/mail-bimi.js +1 -1
- package/lib/vendor/blamejs/lib/mail-bounce.js +8 -0
- package/lib/vendor/blamejs/lib/mail-crypto-pgp.js +46 -10
- package/lib/vendor/blamejs/lib/mail-dav.js +22 -3
- package/lib/vendor/blamejs/lib/mail-dkim.js +96 -30
- package/lib/vendor/blamejs/lib/mail-helo.js +6 -1
- package/lib/vendor/blamejs/lib/mail-scan.js +23 -16
- package/lib/vendor/blamejs/lib/mail-send-deliver.js +34 -8
- package/lib/vendor/blamejs/lib/mail-server-imap.js +3 -4
- package/lib/vendor/blamejs/lib/mail-server-managesieve.js +7 -11
- package/lib/vendor/blamejs/lib/mail-server-mx.js +93 -7
- package/lib/vendor/blamejs/lib/mail-server-registry.js +8 -2
- package/lib/vendor/blamejs/lib/mail.js +15 -5
- package/lib/vendor/blamejs/lib/metrics.js +268 -63
- package/lib/vendor/blamejs/lib/middleware/age-gate.js +7 -1
- package/lib/vendor/blamejs/lib/middleware/asyncapi-serve.js +1 -1
- package/lib/vendor/blamejs/lib/middleware/body-parser.js +19 -4
- package/lib/vendor/blamejs/lib/middleware/bot-guard.js +5 -9
- package/lib/vendor/blamejs/lib/middleware/csp-report.js +40 -8
- package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +24 -10
- package/lib/vendor/blamejs/lib/middleware/dpop.js +10 -5
- package/lib/vendor/blamejs/lib/middleware/index.js +6 -1
- package/lib/vendor/blamejs/lib/middleware/nel.js +1 -1
- package/lib/vendor/blamejs/lib/middleware/openapi-serve.js +1 -1
- package/lib/vendor/blamejs/lib/middleware/rate-limit.js +12 -1
- package/lib/vendor/blamejs/lib/middleware/require-bound-key.js +1 -1
- package/lib/vendor/blamejs/lib/middleware/scim-server.js +1 -1
- package/lib/vendor/blamejs/lib/middleware/tus-upload.js +6 -2
- package/lib/vendor/blamejs/lib/mtls-ca.js +18 -2
- package/lib/vendor/blamejs/lib/network-dns-resolver.js +11 -1
- package/lib/vendor/blamejs/lib/network-dns.js +21 -2
- package/lib/vendor/blamejs/lib/network-tls.js +69 -7
- package/lib/vendor/blamejs/lib/nonce-store.js +9 -1
- package/lib/vendor/blamejs/lib/object-store/gcs.js +7 -0
- package/lib/vendor/blamejs/lib/object-store/sigv4.js +31 -0
- package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +1 -1
- package/lib/vendor/blamejs/lib/outbox.js +1 -1
- package/lib/vendor/blamejs/lib/parsers/safe-ini.js +9 -1
- package/lib/vendor/blamejs/lib/parsers/safe-toml.js +10 -1
- package/lib/vendor/blamejs/lib/permissions.js +6 -3
- package/lib/vendor/blamejs/lib/pid-probe.js +55 -0
- package/lib/vendor/blamejs/lib/pqc-agent.js +8 -1
- package/lib/vendor/blamejs/lib/pqc-software.js +3 -3
- package/lib/vendor/blamejs/lib/pubsub.js +20 -9
- package/lib/vendor/blamejs/lib/queue-local.js +8 -1
- package/lib/vendor/blamejs/lib/queue-redis.js +5 -0
- package/lib/vendor/blamejs/lib/redact.js +54 -0
- package/lib/vendor/blamejs/lib/restore-bundle.js +10 -2
- package/lib/vendor/blamejs/lib/restore.js +19 -0
- package/lib/vendor/blamejs/lib/retention.js +12 -4
- package/lib/vendor/blamejs/lib/router.js +60 -6
- package/lib/vendor/blamejs/lib/safe-buffer.js +69 -0
- package/lib/vendor/blamejs/lib/safe-decompress.js +8 -2
- package/lib/vendor/blamejs/lib/safe-dns.js +25 -20
- package/lib/vendor/blamejs/lib/safe-ical.js +7 -3
- package/lib/vendor/blamejs/lib/safe-mime.js +12 -1
- package/lib/vendor/blamejs/lib/safe-object.js +80 -0
- package/lib/vendor/blamejs/lib/safe-path.js +52 -4
- package/lib/vendor/blamejs/lib/safe-redirect.js +10 -2
- package/lib/vendor/blamejs/lib/safe-schema.js +4 -1
- package/lib/vendor/blamejs/lib/safe-sieve.js +33 -1
- package/lib/vendor/blamejs/lib/safe-sql.js +88 -0
- package/lib/vendor/blamejs/lib/safe-vcard.js +7 -3
- package/lib/vendor/blamejs/lib/scheduler.js +8 -1
- package/lib/vendor/blamejs/lib/security-assert.js +6 -6
- package/lib/vendor/blamejs/lib/self-update-standalone-verifier.js +74 -27
- package/lib/vendor/blamejs/lib/self-update.js +517 -87
- package/lib/vendor/blamejs/lib/server-timing.js +2 -4
- package/lib/vendor/blamejs/lib/session.js +34 -12
- package/lib/vendor/blamejs/lib/sql.js +15 -1
- package/lib/vendor/blamejs/lib/ssrf-guard.js +52 -0
- package/lib/vendor/blamejs/lib/static.js +55 -13
- package/lib/vendor/blamejs/lib/storage.js +8 -2
- package/lib/vendor/blamejs/lib/subject.js +6 -1
- package/lib/vendor/blamejs/lib/tenant-quota.js +52 -13
- package/lib/vendor/blamejs/lib/tls-exporter.js +3 -3
- package/lib/vendor/blamejs/lib/tsa.js +34 -13
- package/lib/vendor/blamejs/lib/uri-template.js +8 -1
- package/lib/vendor/blamejs/lib/vault/index.js +13 -0
- package/lib/vendor/blamejs/lib/vc.js +44 -5
- package/lib/vendor/blamejs/lib/vendor/MANIFEST.json +26 -26
- package/lib/vendor/blamejs/lib/vendor/noble-ciphers.cjs +792 -1
- package/lib/vendor/blamejs/lib/vendor/noble-curves.cjs +3443 -1
- package/lib/vendor/blamejs/lib/vendor/noble-post-quantum.cjs +2737 -1
- package/lib/vendor/blamejs/lib/vendor/pki.cjs +39550 -13
- package/lib/vendor/blamejs/lib/vendor/public-suffix-list.dat +55 -53
- package/lib/vendor/blamejs/lib/vendor/public-suffix-list.data.js +5551 -5554
- package/lib/vendor/blamejs/lib/vendor/simplewebauthn-server.cjs +22459 -32
- package/lib/vendor/blamejs/lib/watcher.js +89 -17
- package/lib/vendor/blamejs/lib/webhook-dispatcher.js +25 -3
- package/lib/vendor/blamejs/lib/webhook.js +12 -2
- package/lib/vendor/blamejs/lib/ws-client.js +32 -1
- package/lib/vendor/blamejs/lib/xml-c14n.js +29 -1
- package/lib/vendor/blamejs/oss-fuzz/projects/blamejs/Dockerfile +1 -1
- package/lib/vendor/blamejs/package-lock.json +2 -2
- package/lib/vendor/blamejs/package.json +1 -1
- package/lib/vendor/blamejs/release-notes/v0.16.x.json +1462 -0
- package/lib/vendor/blamejs/release-notes/v0.17.0.json +31 -0
- package/lib/vendor/blamejs/release-notes/v0.17.1.json +31 -0
- package/lib/vendor/blamejs/release-notes/v0.17.10.json +30 -0
- package/lib/vendor/blamejs/release-notes/v0.17.11.json +22 -0
- package/lib/vendor/blamejs/release-notes/v0.17.12.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.17.13.json +77 -0
- package/lib/vendor/blamejs/release-notes/v0.17.14.json +40 -0
- package/lib/vendor/blamejs/release-notes/v0.17.15.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.17.16.json +27 -0
- package/lib/vendor/blamejs/release-notes/v0.17.17.json +31 -0
- package/lib/vendor/blamejs/release-notes/v0.17.18.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.17.19.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.17.2.json +26 -0
- package/lib/vendor/blamejs/release-notes/v0.17.20.json +43 -0
- package/lib/vendor/blamejs/release-notes/v0.17.21.json +26 -0
- package/lib/vendor/blamejs/release-notes/v0.17.22.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.17.3.json +43 -0
- package/lib/vendor/blamejs/release-notes/v0.17.4.json +35 -0
- package/lib/vendor/blamejs/release-notes/v0.17.5.json +26 -0
- package/lib/vendor/blamejs/release-notes/v0.17.6.json +26 -0
- package/lib/vendor/blamejs/release-notes/v0.17.7.json +35 -0
- package/lib/vendor/blamejs/release-notes/v0.17.8.json +22 -0
- package/lib/vendor/blamejs/release-notes/v0.17.9.json +26 -0
- package/lib/vendor/blamejs/scripts/validate-source-comment-blocks.js +4 -0
- package/lib/vendor/blamejs/scripts/vendor-update.sh +293 -9
- package/lib/vendor/blamejs/socket.yml +94 -0
- package/lib/vendor/blamejs/test/00-primitives.js +605 -0
- package/lib/vendor/blamejs/test/helpers/drivers.js +12 -9
- package/lib/vendor/blamejs/test/integration/audit-stack-mysql.test.js +90 -1
- package/lib/vendor/blamejs/test/integration/audit-stack-postgres.test.js +122 -1
- package/lib/vendor/blamejs/test/integration/data-layer-mysql.test.js +30 -0
- package/lib/vendor/blamejs/test/integration/data-layer-pg.test.js +29 -0
- package/lib/vendor/blamejs/test/integration/dsr-cluster-mysql.test.js +479 -0
- package/lib/vendor/blamejs/test/integration/dsr-cluster-pg.test.js +487 -0
- package/lib/vendor/blamejs/test/integration/federation-auth.test.js +313 -5
- package/lib/vendor/blamejs/test/integration/mail-dkim.test.js +80 -0
- package/lib/vendor/blamejs/test/integration/openid-federation-chain.test.js +275 -0
- package/lib/vendor/blamejs/test/integration/webhook-dispatcher-pg.test.js +96 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/a2a-tasks.test.js +401 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/acme.test.js +1649 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/age-gate.test.js +33 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-idempotency.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-saga.test.js +60 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-snapshot.test.js +524 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/api-encrypt.test.js +891 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/app-shutdown.test.js +128 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/archive-adapters.test.js +271 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/archive-read.test.js +628 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/archive-tar-read.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/archive-wrap.test.js +227 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/arg-parser.test.js +15 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/asn1-der.test.js +73 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file.test.js +114 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-sign-anchor.test.js +141 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-tools.test.js +624 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit.test.js +769 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-jwt-defenses.test.js +436 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-oauth.test.js +1697 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-oid4vp.test.js +495 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-password.test.js +500 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-saml.test.js +1809 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-status-list.test.js +243 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/backup-index.test.js +2226 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/backup-manifest-signature.test.js +183 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/base32.test.js +105 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/break-glass.test.js +693 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cache.test.js +603 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/calendar.test.js +557 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/case-insensitive-security-token-guard.test.js +214 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cbor.test.js +25 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cert.test.js +843 -6
- package/lib/vendor/blamejs/test/layer-0-primitives/ciba-authreqid-binding.test.js +77 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/ciba.test.js +866 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/clear-site-data.test.js +18 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-erase.test.js +19 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli.test.js +2790 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cluster-lease-renewal-ttl.test.js +114 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cluster.test.js +1032 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cms-codec.test.js +575 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +739 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/codepoint-class.test.js +8 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance-ai-act.test.js +194 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance-lifecycle-proto-key.test.js +104 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance-sanctions.test.js +254 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/content-credentials.test.js +613 -3
- package/lib/vendor/blamejs/test/layer-0-primitives/cookies.test.js +140 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cose.test.js +407 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-adversarial.test.js +352 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-derived-hash.test.js +32 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field.test.js +575 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hash-files-parallel.test.js +10 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hash-stream.test.js +24 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-oprf.test.js +17 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-spki-pin.test.js +183 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-xwing.test.js +48 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto.test.js +345 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csp-report.test.js +58 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csv.test.js +48 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/daemon.test.js +459 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/dark-patterns.test.js +19 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-query.test.js +1019 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-raw-residency-gate.test.js +77 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db.test.js +1621 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ddl-change-control.test.js +462 -3
- package/lib/vendor/blamejs/test/layer-0-primitives/dnssec.test.js +323 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dpop-middleware-verify.test.js +562 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dsr.test.js +901 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/dual-control.test.js +249 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-migrate.test.js +561 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db.test.js +1214 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fal.test.js +21 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fedcm-dbsc.test.js +46 -20
- package/lib/vendor/blamejs/test/layer-0-primitives/federation-vc-suite.test.js +147 -3
- package/lib/vendor/blamejs/test/layer-0-primitives/fido-mds3.test.js +907 -38
- package/lib/vendor/blamejs/test/layer-0-primitives/file-upload-content-safety-skip-audit.test.js +29 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/file-upload.test.js +1135 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/flag.test.js +30 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/forms.test.js +747 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/gate-contract.test.js +1855 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/gdpr-ropa.test.js +30 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-archive.test.js +86 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-auth.test.js +117 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-cidr.test.js +151 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-domain.test.js +422 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-filename.test.js +424 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/{guard-gate-disposition-coverage.test.js → guard-gate-disposition.test.js} +1 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-graphql.test.js +340 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-html.test.js +29 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-idempotency-key.test.js +28 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-image.test.js +519 -45
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-imap-command.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-jmap.test.js +51 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-jsonpath.test.js +66 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-jwt.test.js +436 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-managesieve-command.test.js +25 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-markdown.test.js +16 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-mime.test.js +86 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-oauth.test.js +140 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-pdf.test.js +80 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-pop3-command.test.js +27 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-regex.test.js +99 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-shell.test.js +76 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/{guard-sql-coverage.test.js → guard-sql.test.js} +3 -3
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-svg.test.js +524 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-template.test.js +71 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-time.test.js +92 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-uuid.test.js +86 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/html-balance.test.js +38 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-client-cache-authorization.test.js +347 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-client-cache.test.js +908 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-client.test.js +2874 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-message-signature.test.js +643 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/i18n-messageformat.test.js +38 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/i18n.test.js +1153 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/idempotency-key.test.js +494 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ip-utils.test.js +30 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/jsdoc-example-execution.test.js +177 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/json-path.test.js +103 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/json-schema.test.js +214 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/jwk.test.js +41 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/jwt-external.test.js +276 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/keychain.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/log-stream.test.js +241 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/log.test.js +106 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-agent.test.js +441 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-auth.test.js +2136 -4
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-bimi.test.js +759 -12
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-bounce.test.js +811 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-pgp-experimental.test.js +401 -3
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-pgp.test.js +751 -8
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-smime.test.js +864 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto.test.js +110 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-dav.test.js +830 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-deploy.test.js +635 -86
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-dkim.test.js +818 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-helo.test.js +27 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-scan.test.js +199 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-send-deliver.test.js +686 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-imap.test.js +778 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-jmap.test.js +1438 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-managesieve.test.js +449 -6
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-mx.test.js +773 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-pop3.test.js +623 -3
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-rate-limit.test.js +55 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-registry.test.js +13 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-submission.test.js +877 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-sieve.test.js +326 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-store-fts.test.js +55 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-store.test.js +242 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail.test.js +1596 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/mcp.test.js +440 -4
- package/lib/vendor/blamejs/test/layer-0-primitives/metrics-snapshot.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/metrics.test.js +1230 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/middleware-assetlinks.test.js +113 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/middleware-body-parser.test.js +1169 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/middleware-bot-disclose.test.js +123 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/middleware-cookies.test.js +108 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/middleware-gpc.test.js +121 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/middleware-headers.test.js +163 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/middleware-host-allowlist.test.js +154 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/middleware-require-content-type.test.js +127 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/{middleware-tus-upload-coverage.test.js → middleware-tus-upload.test.js} +45 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/middleware-web-app-manifest.test.js +132 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mtls-ca-crl-fingerprint-only.test.js +92 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-dns-resolver.test.js +65 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-dns.test.js +1727 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/network-nts.test.js +699 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-smtp-policy.test.js +1181 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-tls.test.js +1876 -3
- package/lib/vendor/blamejs/test/layer-0-primitives/network.test.js +32 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/nonce-store-enforce-replay-nonboolean.test.js +81 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ntp-check.test.js +122 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/observability-tracing.test.js +378 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/observability.test.js +77 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/oid4vci.test.js +1016 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/openid-federation.test.js +946 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/outbox.test.js +818 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/own-property-membership-guards.test.js +109 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/parsers-safe-toml.test.js +543 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/{parsers-safe-yaml-coverage.test.js → parsers-safe-yaml.test.js} +2 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/parsers-standalone.test.js +73 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/passkey.test.js +649 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/permissions.test.js +524 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/pqc-agent-curve.test.js +22 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/pubsub.test.js +295 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/queue-flow-repeat.test.js +36 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/queue.test.js +83 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/rate-limit-xff-spoofing.test.js +34 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/redact.test.js +1031 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/request-helpers.test.js +94 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/require-bound-key.test.js +401 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/restore-empty-manifest-wipe.test.js +154 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/retention.test.js +600 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/router-host-path-injection.test.js +231 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/router-tls0rtt.test.js +21 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/router.test.js +1277 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-async.test.js +133 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-buffer-linear-scans.test.js +14 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-buffer.test.js +80 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-decompress.test.js +28 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-dns.test.js +245 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-ical.test.js +32 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-ini.test.js +376 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-json.test.js +175 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-mime.test.js +518 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-object.test.js +62 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-path.test.js +30 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-schema.test.js +104 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-sql.test.js +142 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-url.test.js +112 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-vcard.test.js +26 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/scheduler.test.js +157 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/scim-server.test.js +396 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc.test.js +930 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/security-assert.test.js +755 -82
- package/lib/vendor/blamejs/test/layer-0-primitives/self-update.test.js +1053 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/session-device-binding.test.js +200 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/session-strict-binding-missing-failclosed.test.js +110 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sigv4-bucket-ops.test.js +636 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sql-offset-without-limit.test.js +94 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sql.test.js +1190 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ssrf-guard.test.js +21 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/static.test.js +850 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/storage-presigned-url.test.js +146 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/storage.test.js +621 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/subject.test.js +47 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/template.test.js +582 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tenant-quota.test.js +587 -25
- package/lib/vendor/blamejs/test/layer-0-primitives/time.test.js +11 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tsa.test.js +524 -63
- package/lib/vendor/blamejs/test/layer-0-primitives/uri-template.test.js +27 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vault-rotate.test.js +866 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vault.test.js +74 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vc.test.js +215 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/watcher.test.js +550 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/webhook-dispatcher.test.js +388 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/webhook.test.js +262 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/websocket.test.js +1294 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ws-client.test.js +629 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/xml-c14n-attr-normalization.test.js +164 -0
- package/lib/vendor/blamejs/test/smoke.js +2 -0
- package/package.json +1 -1
- package/lib/vendor/blamejs/release-notes/v0.16.0.json +0 -44
- package/lib/vendor/blamejs/release-notes/v0.16.1.json +0 -36
- package/lib/vendor/blamejs/release-notes/v0.16.2.json +0 -71
- package/lib/vendor/blamejs/release-notes/v0.16.3.json +0 -71
- package/lib/vendor/blamejs/test/layer-0-primitives/acme-coverage.test.js +0 -441
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-oauth-coverage.test.js +0 -482
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-saml-coverage.test.js +0 -671
- package/lib/vendor/blamejs/test/layer-0-primitives/backup-index-coverage.test.js +0 -690
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-coverage.test.js +0 -238
- package/lib/vendor/blamejs/test/layer-0-primitives/content-credentials-coverage.test.js +0 -319
- package/lib/vendor/blamejs/test/layer-0-primitives/keychain-coverage.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-auth-coverage.test.js +0 -437
- package/lib/vendor/blamejs/test/layer-0-primitives/mcp-coverage.test.js +0 -443
- package/lib/vendor/blamejs/test/layer-0-primitives/metrics-coverage.test.js +0 -395
- package/lib/vendor/blamejs/test/layer-0-primitives/network-smtp-policy-coverage.test.js +0 -565
- package/lib/vendor/blamejs/test/layer-0-primitives/router-coverage.test.js +0 -592
- package/lib/vendor/blamejs/test/layer-0-primitives/sql-coverage.test.js +0 -422
- package/lib/vendor/blamejs/test/layer-0-primitives/test-coverage.test.js +0 -573
|
@@ -15,8 +15,12 @@ var helpers = require("../helpers");
|
|
|
15
15
|
var b = helpers.b;
|
|
16
16
|
var check = helpers.check;
|
|
17
17
|
|
|
18
|
-
var nodeNet
|
|
19
|
-
var nodeTls
|
|
18
|
+
var nodeNet = require("node:net");
|
|
19
|
+
var nodeTls = require("node:tls");
|
|
20
|
+
// The exact audit module auditEmit resolves via require("./audit") — patch
|
|
21
|
+
// its safeEmit to capture the drop-silent events the listener emits (there
|
|
22
|
+
// is no test helper for global-audit capture; restore in finally).
|
|
23
|
+
var auditMod = require("../../lib/audit");
|
|
20
24
|
|
|
21
25
|
function testSurface() {
|
|
22
26
|
check("mx.create is fn", typeof b.mail.server.mx.create === "function");
|
|
@@ -169,6 +173,41 @@ async function _readGreeting(socket) {
|
|
|
169
173
|
});
|
|
170
174
|
}
|
|
171
175
|
|
|
176
|
+
// Passive accumulator for unsolicited server replies (idle-timeout 421,
|
|
177
|
+
// shutdown 421) — the caller polls the buffer via helpers.waitUntil rather
|
|
178
|
+
// than issuing a command. Swallows post-close socket errors (ECONNRESET
|
|
179
|
+
// when the server destroys the connection) so they don't reject the run.
|
|
180
|
+
function _collect(socket) {
|
|
181
|
+
var buf = "";
|
|
182
|
+
socket.on("data", function (chunk) { buf += chunk.toString("utf8"); });
|
|
183
|
+
socket.on("error", function () { /* connection torn down by server; ignore */ });
|
|
184
|
+
return { text: function () { return buf; } };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Capture the drop-silent audit events the listener emits while `fn`'s
|
|
188
|
+
// window is open. Patches the exact module object auditEmit resolves and
|
|
189
|
+
// restores it unconditionally, so no global-audit state leaks to the
|
|
190
|
+
// smoke harness.
|
|
191
|
+
function _withAuditCapture(fn) {
|
|
192
|
+
var events = [];
|
|
193
|
+
var orig = auditMod.safeEmit;
|
|
194
|
+
auditMod.safeEmit = function (evt) {
|
|
195
|
+
events.push(evt);
|
|
196
|
+
return orig.call(auditMod, evt);
|
|
197
|
+
};
|
|
198
|
+
return Promise.resolve()
|
|
199
|
+
.then(function () { return fn(events); })
|
|
200
|
+
.finally(function () { auditMod.safeEmit = orig; });
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Connect + read the 220 greeting, returning the live socket.
|
|
204
|
+
async function _connectTo(info) {
|
|
205
|
+
var socket = nodeNet.connect(info.port, "127.0.0.1");
|
|
206
|
+
await new Promise(function (r) { socket.once("connect", r); });
|
|
207
|
+
await _readGreeting(socket);
|
|
208
|
+
return socket;
|
|
209
|
+
}
|
|
210
|
+
|
|
172
211
|
async function testEhloFlow() {
|
|
173
212
|
// Boot the server with a permissive profile so plaintext EHLO works
|
|
174
213
|
// (operator-acknowledged downgrade for staging). Skip if the test
|
|
@@ -632,10 +671,726 @@ async function testGuardEnvelopeGate() {
|
|
|
632
671
|
} finally { await acceptSrv.close({ timeoutMs: 1000 }); } // allow:raw-time-literal — test-only short drain
|
|
633
672
|
}
|
|
634
673
|
|
|
674
|
+
// ---- Boot-time validation of the guardEnvelope config object -----------
|
|
675
|
+
// The gate's tunables are validated at create() so an operator typo fails
|
|
676
|
+
// startup rather than turning every live DATA into an envelope_error.
|
|
677
|
+
function testGuardEnvelopeBootValidation() {
|
|
678
|
+
function expectThrow(label, opts) {
|
|
679
|
+
var threw = null;
|
|
680
|
+
try { b.mail.server.mx.create(opts); } catch (e) { threw = e; }
|
|
681
|
+
check(label, threw && (threw.code || "").indexOf("mail-server-mx/") === 0);
|
|
682
|
+
}
|
|
683
|
+
expectThrow("guardEnvelope.onTemperror invalid → throw",
|
|
684
|
+
{ tlsContext: {}, guardEnvelope: { onTemperror: "maybe" } });
|
|
685
|
+
expectThrow("guardEnvelope.authservId non-string → throw",
|
|
686
|
+
{ tlsContext: {}, guardEnvelope: { authservId: 123 } });
|
|
687
|
+
expectThrow("guardEnvelope.authservId empty string → throw",
|
|
688
|
+
{ tlsContext: {}, guardEnvelope: { authservId: "" } });
|
|
689
|
+
expectThrow("guardEnvelope.dnsLookup non-function → throw",
|
|
690
|
+
{ tlsContext: {}, guardEnvelope: { dnsLookup: "not-a-fn" } });
|
|
691
|
+
|
|
692
|
+
// A fully-specified gate config (explicit authservId + onTemperror
|
|
693
|
+
// accept + dnsLookup fn) constructs cleanly — exercises the accept
|
|
694
|
+
// branches the reject cases above skip.
|
|
695
|
+
var okServer = null;
|
|
696
|
+
try {
|
|
697
|
+
okServer = b.mail.server.mx.create({
|
|
698
|
+
tlsContext: {}, profile: "permissive",
|
|
699
|
+
guardEnvelope: {
|
|
700
|
+
mode: "monitor",
|
|
701
|
+
onTemperror: "accept",
|
|
702
|
+
authservId: "custom.mx.example",
|
|
703
|
+
dnsLookup: async function () { return []; },
|
|
704
|
+
},
|
|
705
|
+
});
|
|
706
|
+
} catch (_e) { okServer = null; }
|
|
707
|
+
check("guardEnvelope full valid config constructs", okServer !== null &&
|
|
708
|
+
typeof okServer.listen === "function");
|
|
709
|
+
|
|
710
|
+
// `guardEnvelope: true` shorthand — mode defaults to the profile-derived
|
|
711
|
+
// posture (permissive → monitor, otherwise enforce).
|
|
712
|
+
var trueMonitor = null, trueEnforce = null;
|
|
713
|
+
try {
|
|
714
|
+
trueMonitor = b.mail.server.mx.create({
|
|
715
|
+
tlsContext: {}, profile: "permissive", guardEnvelope: true });
|
|
716
|
+
} catch (_e) { trueMonitor = null; }
|
|
717
|
+
try {
|
|
718
|
+
trueEnforce = b.mail.server.mx.create({ tlsContext: {}, guardEnvelope: true });
|
|
719
|
+
} catch (_e) { trueEnforce = null; }
|
|
720
|
+
check("guardEnvelope:true under permissive constructs (monitor default)",
|
|
721
|
+
trueMonitor !== null);
|
|
722
|
+
check("guardEnvelope:true under default profile constructs (enforce default)",
|
|
723
|
+
trueEnforce !== null);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// ---- guardDomain opt: false (disable) and object (profile override) ----
|
|
727
|
+
function testGuardDomainBootOptions() {
|
|
728
|
+
var offServer = null;
|
|
729
|
+
try {
|
|
730
|
+
offServer = b.mail.server.mx.create({ tlsContext: {}, guardDomain: false });
|
|
731
|
+
} catch (_e) { offServer = null; }
|
|
732
|
+
check("guardDomain:false constructs (hardening disabled)", offServer !== null);
|
|
733
|
+
|
|
734
|
+
var objServer = null;
|
|
735
|
+
try {
|
|
736
|
+
objServer = b.mail.server.mx.create({
|
|
737
|
+
tlsContext: {}, guardDomain: { profile: "balanced" },
|
|
738
|
+
});
|
|
739
|
+
} catch (_e) { objServer = null; }
|
|
740
|
+
check("guardDomain object with profile override constructs", objServer !== null);
|
|
741
|
+
|
|
742
|
+
// guardDomain object WITHOUT its own profile falls back to the server
|
|
743
|
+
// profile.
|
|
744
|
+
var objDefault = null;
|
|
745
|
+
try {
|
|
746
|
+
objDefault = b.mail.server.mx.create({ tlsContext: {}, guardDomain: {} });
|
|
747
|
+
} catch (_e) { objDefault = null; }
|
|
748
|
+
check("guardDomain object without profile falls back to server profile",
|
|
749
|
+
objDefault !== null);
|
|
750
|
+
|
|
751
|
+
// An operator localDomains entry that guardDomain itself rejects (a
|
|
752
|
+
// special-use domain) must fail startup, not silently weaken the gate.
|
|
753
|
+
var badLocal = null;
|
|
754
|
+
try {
|
|
755
|
+
b.mail.server.mx.create({ tlsContext: {}, localDomains: ["foo.local"] });
|
|
756
|
+
} catch (e) { badLocal = e; }
|
|
757
|
+
check("localDomains rejected by guardDomain → bad-local-domain at boot",
|
|
758
|
+
badLocal !== null && /bad-local-domain/.test(badLocal.code || ""));
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// ---- Command dispatch: NOOP / RSET / VRFY / EXPN / unknown / HELO / EHLO-no-arg
|
|
762
|
+
async function testCommandDispatch() {
|
|
763
|
+
var ctx;
|
|
764
|
+
try { ctx = await _makeTestTlsContext(); }
|
|
765
|
+
catch (_e) { check("command dispatch (skipped — cert fixture unavailable)", true); return; }
|
|
766
|
+
var srv = b.mail.server.mx.create({
|
|
767
|
+
tlsContext: ctx, profile: "permissive", localDomains: ["example.com"],
|
|
768
|
+
});
|
|
769
|
+
var info = await srv.listen({ port: 0, address: "127.0.0.1" });
|
|
770
|
+
var sock;
|
|
771
|
+
try {
|
|
772
|
+
sock = await _connectTo(info);
|
|
773
|
+
check("NOOP → 250", /^250 /.test(await _sendCommand(sock, "NOOP")));
|
|
774
|
+
check("RSET → 250", /^250 /.test(await _sendCommand(sock, "RSET")));
|
|
775
|
+
check("VRFY → 502 not implemented",
|
|
776
|
+
/^502 5\.5\.1/.test(await _sendCommand(sock, "VRFY alice")));
|
|
777
|
+
check("EXPN → 502 not implemented",
|
|
778
|
+
/^502 5\.5\.1/.test(await _sendCommand(sock, "EXPN staff")));
|
|
779
|
+
check("unknown verb → 500",
|
|
780
|
+
/^500 5\.5\.2/.test(await _sendCommand(sock, "HELP")));
|
|
781
|
+
check("HELO (not EHLO) → single-line 250",
|
|
782
|
+
/^250 /.test(await _sendCommand(sock, "HELO relay.example.com")));
|
|
783
|
+
sock.destroy();
|
|
784
|
+
} finally { await srv.close({ timeoutMs: 1000 }); } // allow:raw-time-literal — test-only short drain
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// ---- Sequence + syntax errors: out-of-order commands and malformed args -
|
|
788
|
+
async function testSequenceAndSyntaxErrors() {
|
|
789
|
+
var ctx;
|
|
790
|
+
try { ctx = await _makeTestTlsContext(); }
|
|
791
|
+
catch (_e) { check("sequence/syntax errors (skipped)", true); return; }
|
|
792
|
+
var srv = b.mail.server.mx.create({
|
|
793
|
+
tlsContext: ctx, profile: "permissive", localDomains: ["example.com"],
|
|
794
|
+
});
|
|
795
|
+
var info = await srv.listen({ port: 0, address: "127.0.0.1" });
|
|
796
|
+
var s1, s2;
|
|
797
|
+
try {
|
|
798
|
+
// MAIL FROM before any EHLO → 503 bad sequence.
|
|
799
|
+
s1 = await _connectTo(info);
|
|
800
|
+
check("MAIL FROM before EHLO → 503",
|
|
801
|
+
/^503 5\.5\.1/.test(await _sendCommand(s1, "MAIL FROM:<a@external.com>")));
|
|
802
|
+
s1.destroy();
|
|
803
|
+
|
|
804
|
+
// Fresh connection: EHLO, then RCPT before MAIL, DATA before RCPT,
|
|
805
|
+
// and malformed MAIL / RCPT the shape-guard passes but the listener's
|
|
806
|
+
// stricter address regex rejects (501).
|
|
807
|
+
s2 = await _connectTo(info);
|
|
808
|
+
await _sendCommand(s2, "EHLO sender.example.com");
|
|
809
|
+
check("RCPT before MAIL → 503",
|
|
810
|
+
/^503 5\.5\.1/.test(await _sendCommand(s2, "RCPT TO:<a@example.com>")));
|
|
811
|
+
check("DATA before RCPT → 503",
|
|
812
|
+
/^503 5\.5\.1/.test(await _sendCommand(s2, "DATA")));
|
|
813
|
+
check("malformed MAIL FROM (trailing junk) → 501",
|
|
814
|
+
/^501 5\.5\.4/.test(await _sendCommand(s2, "MAIL FROM:<a@external.com>extra")));
|
|
815
|
+
// Land a good MAIL so the next RCPT reaches the address parse.
|
|
816
|
+
await _sendCommand(s2, "MAIL FROM:<a@external.com>");
|
|
817
|
+
check("malformed RCPT TO (trailing junk) → 501",
|
|
818
|
+
/^501 5\.5\.4/.test(await _sendCommand(s2, "RCPT TO:<a@example.com>extra")));
|
|
819
|
+
s2.destroy();
|
|
820
|
+
} finally { await srv.close({ timeoutMs: 1000 }); } // allow:raw-time-literal — test-only short drain
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// ---- Domain hardening refuses HELO / MAIL FROM / RCPT TO bad domains ----
|
|
824
|
+
// bare-IPv4-as-domain (CVE-2021-22931 class) + special-use domain (RFC 6761).
|
|
825
|
+
async function testDomainRefusals() {
|
|
826
|
+
var ctx;
|
|
827
|
+
try { ctx = await _makeTestTlsContext(); }
|
|
828
|
+
catch (_e) { check("domain refusals (skipped)", true); return; }
|
|
829
|
+
var srv = b.mail.server.mx.create({
|
|
830
|
+
tlsContext: ctx, profile: "permissive", localDomains: ["example.com"],
|
|
831
|
+
});
|
|
832
|
+
var info = await srv.listen({ port: 0, address: "127.0.0.1" });
|
|
833
|
+
var s1, s2;
|
|
834
|
+
try {
|
|
835
|
+
// HELO with a bare IPv4 (not an address literal) → guardDomain refuses.
|
|
836
|
+
s1 = await _connectTo(info);
|
|
837
|
+
check("HELO bare-IPv4 domain → 501",
|
|
838
|
+
/^501 5\.5\.4/.test(await _sendCommand(s1, "HELO 1.2.3.4")));
|
|
839
|
+
s1.destroy();
|
|
840
|
+
|
|
841
|
+
s2 = await _connectTo(info);
|
|
842
|
+
await _sendCommand(s2, "EHLO sender.example.com");
|
|
843
|
+
check("MAIL FROM bare-IPv4 domain → 501",
|
|
844
|
+
/^501 5\.5\.4/.test(await _sendCommand(s2, "MAIL FROM:<x@1.2.3.4>")));
|
|
845
|
+
await _sendCommand(s2, "MAIL FROM:<s@external.com>");
|
|
846
|
+
check("RCPT TO special-use domain → 501",
|
|
847
|
+
/^501 5\.5\.4/.test(await _sendCommand(s2, "RCPT TO:<x@foo.local>")));
|
|
848
|
+
s2.destroy();
|
|
849
|
+
} finally { await srv.close({ timeoutMs: 1000 }); } // allow:raw-time-literal — test-only short drain
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// ---- Resource caps: SIZE=, per-message size, recipient count, line length
|
|
853
|
+
async function testResourceLimits() {
|
|
854
|
+
var ctx;
|
|
855
|
+
try { ctx = await _makeTestTlsContext(); }
|
|
856
|
+
catch (_e) { check("resource limits (skipped)", true); return; }
|
|
857
|
+
|
|
858
|
+
// Small per-message cap: declared SIZE= over the cap refused at MAIL
|
|
859
|
+
// FROM (552), and a DATA body over the cap refused mid-stream (552).
|
|
860
|
+
var capSrv = b.mail.server.mx.create({
|
|
861
|
+
tlsContext: ctx, profile: "permissive", localDomains: ["example.com"],
|
|
862
|
+
maxMessageBytes: 64,
|
|
863
|
+
});
|
|
864
|
+
var capInfo = await capSrv.listen({ port: 0, address: "127.0.0.1" });
|
|
865
|
+
var capSock;
|
|
866
|
+
try {
|
|
867
|
+
capSock = await _connectTo(capInfo);
|
|
868
|
+
await _sendCommand(capSock, "EHLO sender.example.com");
|
|
869
|
+
check("MAIL FROM SIZE= over maxMessageBytes → 552",
|
|
870
|
+
/^552 5\.3\.4/.test(await _sendCommand(capSock, "MAIL FROM:<s@external.com> SIZE=100000")));
|
|
871
|
+
await _sendCommand(capSock, "MAIL FROM:<s@external.com>");
|
|
872
|
+
await _sendCommand(capSock, "RCPT TO:<alice@example.com>");
|
|
873
|
+
await _sendCommand(capSock, "DATA");
|
|
874
|
+
var big = "";
|
|
875
|
+
for (var i = 0; i < 200; i += 1) big += "A";
|
|
876
|
+
check("DATA body over maxMessageBytes → 552 mid-stream",
|
|
877
|
+
/^552 5\.3\.4/.test(await _sendCommand(capSock, big)));
|
|
878
|
+
capSock.destroy();
|
|
879
|
+
} finally { await capSrv.close({ timeoutMs: 1000 }); } // allow:raw-time-literal — test-only short drain
|
|
880
|
+
|
|
881
|
+
// Per-message recipient cap (default maxMessageBytes so SIZE overrun
|
|
882
|
+
// has room). maxRcptsPerMessage:1 → the second RCPT is refused 452.
|
|
883
|
+
var rcptSrv = b.mail.server.mx.create({
|
|
884
|
+
tlsContext: ctx, profile: "permissive", localDomains: ["example.com"],
|
|
885
|
+
maxRcptsPerMessage: 1,
|
|
886
|
+
});
|
|
887
|
+
var rcptInfo = await rcptSrv.listen({ port: 0, address: "127.0.0.1" });
|
|
888
|
+
var rcptSock;
|
|
889
|
+
try {
|
|
890
|
+
rcptSock = await _connectTo(rcptInfo);
|
|
891
|
+
await _sendCommand(rcptSock, "EHLO sender.example.com");
|
|
892
|
+
await _sendCommand(rcptSock, "MAIL FROM:<s@external.com>");
|
|
893
|
+
await _sendCommand(rcptSock, "RCPT TO:<alice@example.com>");
|
|
894
|
+
check("second RCPT past maxRcptsPerMessage → 452",
|
|
895
|
+
/^452 4\.5\.3/.test(await _sendCommand(rcptSock, "RCPT TO:<bob@example.com>")));
|
|
896
|
+
rcptSock.destroy();
|
|
897
|
+
} finally { await rcptSrv.close({ timeoutMs: 1000 }); } // allow:raw-time-literal — test-only short drain
|
|
898
|
+
|
|
899
|
+
// Declared SIZE= reconciled against the actual DATA byte count (RFC 1870
|
|
900
|
+
// §6.3): a body larger than the declared SIZE is refused after DATA.
|
|
901
|
+
var overrunSrv = b.mail.server.mx.create({
|
|
902
|
+
tlsContext: ctx, profile: "permissive", localDomains: ["example.com"],
|
|
903
|
+
});
|
|
904
|
+
var overrunInfo = await overrunSrv.listen({ port: 0, address: "127.0.0.1" });
|
|
905
|
+
var overrunSock;
|
|
906
|
+
try {
|
|
907
|
+
overrunSock = await _connectTo(overrunInfo);
|
|
908
|
+
await _sendCommand(overrunSock, "EHLO sender.example.com");
|
|
909
|
+
await _sendCommand(overrunSock, "MAIL FROM:<s@external.com> SIZE=10");
|
|
910
|
+
await _sendCommand(overrunSock, "RCPT TO:<alice@example.com>");
|
|
911
|
+
await _sendCommand(overrunSock, "DATA");
|
|
912
|
+
check("DATA body over declared SIZE= → 552 (RFC 1870 §6.3)",
|
|
913
|
+
/^552 5\.3\.4/.test(await _sendCommand(overrunSock,
|
|
914
|
+
"From: s@external.com\r\nSubject: overrun\r\n\r\nthis body is far larger than ten bytes\r\n.")));
|
|
915
|
+
overrunSock.destroy();
|
|
916
|
+
} finally { await overrunSrv.close({ timeoutMs: 1000 }); } // allow:raw-time-literal — test-only short drain
|
|
917
|
+
|
|
918
|
+
// Per-command line cap: a command line past the hard byte ceiling is
|
|
919
|
+
// refused 500 and the connection is dropped.
|
|
920
|
+
var lineSrv = b.mail.server.mx.create({
|
|
921
|
+
tlsContext: ctx, profile: "permissive", localDomains: ["example.com"],
|
|
922
|
+
maxLineBytes: 64,
|
|
923
|
+
});
|
|
924
|
+
var lineInfo = await lineSrv.listen({ port: 0, address: "127.0.0.1" });
|
|
925
|
+
var lineSock;
|
|
926
|
+
try {
|
|
927
|
+
lineSock = await _connectTo(lineInfo);
|
|
928
|
+
var overlong = "";
|
|
929
|
+
for (var j = 0; j < 400; j += 1) overlong += "A";
|
|
930
|
+
check("over-long command line → 500 5.5.6 + close",
|
|
931
|
+
/^500 5\.5\.6/.test(await _sendCommand(lineSock, overlong)));
|
|
932
|
+
lineSock.destroy();
|
|
933
|
+
} finally { await lineSrv.close({ timeoutMs: 1000 }); } // allow:raw-time-literal — test-only short drain
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
// ---- Per-IP RCPT-failure cap: repeated failed recipients trip a 421 + close
|
|
937
|
+
// (the mailbox-enumeration backstop — RFC 5321 §3.5). A low cap makes the
|
|
938
|
+
// backoff deterministic.
|
|
939
|
+
async function testRcptFailureRateLimit() {
|
|
940
|
+
var ctx;
|
|
941
|
+
try { ctx = await _makeTestTlsContext(); }
|
|
942
|
+
catch (_e) { check("RCPT-failure rate limit (skipped)", true); return; }
|
|
943
|
+
var srv = b.mail.server.mx.create({
|
|
944
|
+
tlsContext: ctx, profile: "permissive", localDomains: ["example.com"],
|
|
945
|
+
rateLimit: { rcptFailuresPerIpPerMinute: 2 },
|
|
946
|
+
});
|
|
947
|
+
var info = await srv.listen({ port: 0, address: "127.0.0.1" });
|
|
948
|
+
var sock;
|
|
949
|
+
try {
|
|
950
|
+
sock = await _connectTo(info);
|
|
951
|
+
await _sendCommand(sock, "EHLO sender.example.com");
|
|
952
|
+
await _sendCommand(sock, "MAIL FROM:<s@external.com>");
|
|
953
|
+
// Two relay-denied recipients spend the failure budget.
|
|
954
|
+
check("first relay-denied RCPT → 550",
|
|
955
|
+
/^550 5\.7\.1/.test(await _sendCommand(sock, "RCPT TO:<a@notlocal.example>")));
|
|
956
|
+
check("second relay-denied RCPT → 550",
|
|
957
|
+
/^550 5\.7\.1/.test(await _sendCommand(sock, "RCPT TO:<b@notlocal.example>")));
|
|
958
|
+
// The next RCPT trips the per-IP failure cap → 421 + connection close.
|
|
959
|
+
check("RCPT past the per-IP failure cap → 421 + close",
|
|
960
|
+
/^421 4\.7\.0/.test(await _sendCommand(sock, "RCPT TO:<c@notlocal.example>")));
|
|
961
|
+
sock.destroy();
|
|
962
|
+
} finally { await srv.close({ timeoutMs: 1000 }); } // allow:raw-time-literal — test-only short drain
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
// ---- SMTP-smuggling wire paths: bare-LF / bare-CR / NUL command lines and
|
|
966
|
+
// a bare-LF dot-terminator in the DATA body. Captures the smtp_smuggling
|
|
967
|
+
// _detected audit to prove the NUL-injection path is audited (regression:
|
|
968
|
+
// the code guard emits is `guard-smtp-command/nul`, not `nul-byte`).
|
|
969
|
+
async function testWireSmuggling() {
|
|
970
|
+
var ctx;
|
|
971
|
+
try { ctx = await _makeTestTlsContext(); }
|
|
972
|
+
catch (_e) { check("wire smuggling (skipped)", true); return; }
|
|
973
|
+
|
|
974
|
+
// strict profile refuses bare LF (permissive tolerates it), so the
|
|
975
|
+
// smuggling-detected audit fires for bare LF / bare CR / NUL here.
|
|
976
|
+
var strictSrv = b.mail.server.mx.create({
|
|
977
|
+
tlsContext: ctx, profile: "strict", localDomains: ["example.com"],
|
|
978
|
+
});
|
|
979
|
+
var strictInfo = await strictSrv.listen({ port: 0, address: "127.0.0.1" });
|
|
980
|
+
var cmdSock;
|
|
981
|
+
await _withAuditCapture(async function (events) {
|
|
982
|
+
try {
|
|
983
|
+
cmdSock = await _connectTo(strictInfo);
|
|
984
|
+
check("bare-LF in command line → 500",
|
|
985
|
+
/^500 5\.5\.2/.test(await _sendCommand(cmdSock, "EHLO x\ny")));
|
|
986
|
+
check("bare-CR in command line → 500",
|
|
987
|
+
/^500 5\.5\.2/.test(await _sendCommand(cmdSock, "EHLO x\rY")));
|
|
988
|
+
check("NUL byte in command line → 500",
|
|
989
|
+
/^500 5\.5\.2/.test(await _sendCommand(cmdSock, ("EHL" + String.fromCharCode(0) + "O example.com"))));
|
|
990
|
+
cmdSock.destroy();
|
|
991
|
+
} finally { if (cmdSock) cmdSock.destroy(); }
|
|
992
|
+
|
|
993
|
+
function smug(code) {
|
|
994
|
+
return events.filter(function (e) {
|
|
995
|
+
return e && e.action === "mail.server.mx.smtp_smuggling_detected" &&
|
|
996
|
+
e.metadata && e.metadata.code === code;
|
|
997
|
+
}).length;
|
|
998
|
+
}
|
|
999
|
+
check("bare-LF command emits smtp_smuggling_detected audit",
|
|
1000
|
+
smug("guard-smtp-command/bare-lf") >= 1);
|
|
1001
|
+
check("bare-CR command emits smtp_smuggling_detected audit",
|
|
1002
|
+
smug("guard-smtp-command/bare-cr") >= 1);
|
|
1003
|
+
check("NUL-byte command emits smtp_smuggling_detected audit (code /nul)",
|
|
1004
|
+
smug("guard-smtp-command/nul") >= 1);
|
|
1005
|
+
});
|
|
1006
|
+
await strictSrv.close({ timeoutMs: 1000 }); // allow:raw-time-literal — test-only short drain
|
|
1007
|
+
|
|
1008
|
+
// DATA-body bare-LF dot terminator (the CVE-2023-51764 smuggling shape)
|
|
1009
|
+
// is refused 554 mid-body under any profile.
|
|
1010
|
+
var bodySrv = b.mail.server.mx.create({
|
|
1011
|
+
tlsContext: ctx, profile: "permissive", localDomains: ["example.com"],
|
|
1012
|
+
});
|
|
1013
|
+
var bodyInfo = await bodySrv.listen({ port: 0, address: "127.0.0.1" });
|
|
1014
|
+
var bodySock;
|
|
1015
|
+
try {
|
|
1016
|
+
bodySock = await _connectTo(bodyInfo);
|
|
1017
|
+
await _sendCommand(bodySock, "EHLO sender.example.com");
|
|
1018
|
+
await _sendCommand(bodySock, "MAIL FROM:<s@external.com>");
|
|
1019
|
+
await _sendCommand(bodySock, "RCPT TO:<alice@example.com>");
|
|
1020
|
+
await _sendCommand(bodySock, "DATA");
|
|
1021
|
+
check("bare-LF dot-terminator in DATA body → 554 (SMTP smuggling)",
|
|
1022
|
+
/^554 5\.7\.0/.test(await _sendCommand(bodySock, "smuggled\n.\n")));
|
|
1023
|
+
bodySock.destroy();
|
|
1024
|
+
} finally { await bodySrv.close({ timeoutMs: 1000 }); } // allow:raw-time-literal — test-only short drain
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
// ---- Operator-explicit relay allowlist admits non-local recipients ------
|
|
1028
|
+
async function testRelayAllowed() {
|
|
1029
|
+
var ctx;
|
|
1030
|
+
try { ctx = await _makeTestTlsContext(); }
|
|
1031
|
+
catch (_e) { check("relay allowlist (skipped)", true); return; }
|
|
1032
|
+
var srv = b.mail.server.mx.create({
|
|
1033
|
+
tlsContext: ctx, profile: "permissive", localDomains: ["example.com"],
|
|
1034
|
+
relayAllowedFor: [{ cidr: "0.0.0.0/0", scope: "all" }],
|
|
1035
|
+
});
|
|
1036
|
+
var info = await srv.listen({ port: 0, address: "127.0.0.1" });
|
|
1037
|
+
var sock;
|
|
1038
|
+
try {
|
|
1039
|
+
sock = await _connectTo(info);
|
|
1040
|
+
await _sendCommand(sock, "EHLO sender.example.com");
|
|
1041
|
+
await _sendCommand(sock, "MAIL FROM:<s@external.com>");
|
|
1042
|
+
check("non-local RCPT admitted when relayAllowedFor is set → 250",
|
|
1043
|
+
/^250 /.test(await _sendCommand(sock, "RCPT TO:<bob@notlocal.example>")));
|
|
1044
|
+
sock.destroy();
|
|
1045
|
+
} finally { await srv.close({ timeoutMs: 1000 }); } // allow:raw-time-literal — test-only short drain
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
// ---- relayAllowedFor enforces the entry CIDR against the peer address ---
|
|
1049
|
+
// A peer OUTSIDE every allowlisted range must be relay-refused; only a peer
|
|
1050
|
+
// INSIDE a range is admitted. Regression guard for the open-relay class where
|
|
1051
|
+
// a non-empty relayAllowedFor admitted every peer regardless of source
|
|
1052
|
+
// address (the entry `cidr` was ignored).
|
|
1053
|
+
async function testRelayCidrEnforced() {
|
|
1054
|
+
var ctx;
|
|
1055
|
+
try { ctx = await _makeTestTlsContext(); }
|
|
1056
|
+
catch (_e) { check("relay CIDR enforcement (skipped)", true); return; }
|
|
1057
|
+
|
|
1058
|
+
// (a) Peer 127.0.0.1 is OUTSIDE 10.0.0.0/8 → relay refused with 550.
|
|
1059
|
+
var denySrv = b.mail.server.mx.create({
|
|
1060
|
+
tlsContext: ctx, profile: "permissive", localDomains: ["example.com"],
|
|
1061
|
+
relayAllowedFor: [{ cidr: "10.0.0.0/8", scope: "internal" }],
|
|
1062
|
+
});
|
|
1063
|
+
var denyInfo = await denySrv.listen({ port: 0, address: "127.0.0.1" });
|
|
1064
|
+
var denySock;
|
|
1065
|
+
try {
|
|
1066
|
+
denySock = await _connectTo(denyInfo);
|
|
1067
|
+
await _sendCommand(denySock, "EHLO sender.example.com");
|
|
1068
|
+
await _sendCommand(denySock, "MAIL FROM:<attacker@evil.com>");
|
|
1069
|
+
check("out-of-CIDR peer relay-refused → 550 (no open relay)",
|
|
1070
|
+
/^550 5\.7\.1/.test(await _sendCommand(denySock, "RCPT TO:<victim@notlocal.example>")));
|
|
1071
|
+
denySock.destroy();
|
|
1072
|
+
} finally { await denySrv.close({ timeoutMs: 1000 }); } // allow:raw-time-literal — test-only short drain
|
|
1073
|
+
|
|
1074
|
+
// (b) Peer 127.0.0.1 is INSIDE 127.0.0.0/8 → relay admitted with 250.
|
|
1075
|
+
var allowSrv = b.mail.server.mx.create({
|
|
1076
|
+
tlsContext: ctx, profile: "permissive", localDomains: ["example.com"],
|
|
1077
|
+
relayAllowedFor: [{ cidr: "127.0.0.0/8", scope: "loopback" }],
|
|
1078
|
+
});
|
|
1079
|
+
var allowInfo = await allowSrv.listen({ port: 0, address: "127.0.0.1" });
|
|
1080
|
+
var allowSock;
|
|
1081
|
+
try {
|
|
1082
|
+
allowSock = await _connectTo(allowInfo);
|
|
1083
|
+
await _sendCommand(allowSock, "EHLO sender.example.com");
|
|
1084
|
+
await _sendCommand(allowSock, "MAIL FROM:<s@external.com>");
|
|
1085
|
+
check("in-CIDR peer relay admitted → 250",
|
|
1086
|
+
/^250 /.test(await _sendCommand(allowSock, "RCPT TO:<bob@notlocal.example>")));
|
|
1087
|
+
allowSock.destroy();
|
|
1088
|
+
} finally { await allowSrv.close({ timeoutMs: 1000 }); } // allow:raw-time-literal — test-only short drain
|
|
1089
|
+
|
|
1090
|
+
// (b2) IPv4-mapped fold the relay gate relies on. An IPv4 client on the
|
|
1091
|
+
// common dual-stack `::` listener is reported by Node as ::ffff:a.b.c.d;
|
|
1092
|
+
// cidrContains refuses a cross-family compare, so the gate folds the mapped
|
|
1093
|
+
// peer via ssrfGuard.canonicalizeHost before matching (otherwise every IPv4
|
|
1094
|
+
// client on a `::` listener would be denied against a documented IPv4 CIDR).
|
|
1095
|
+
// Asserted at the composed-primitive level — deterministic and hang-free,
|
|
1096
|
+
// where an end-to-end `::` bind + IPv4 dialog is runtime/dual-stack dependent.
|
|
1097
|
+
check("relay fold: a mapped peer canonicalizes to its IPv4 dotted form",
|
|
1098
|
+
b.ssrfGuard.canonicalizeHost("::ffff:127.0.0.1") === "127.0.0.1");
|
|
1099
|
+
check("relay fold: the raw mixed-family compare does NOT match (fold is required)",
|
|
1100
|
+
b.ssrfGuard.cidrContains("127.0.0.0/8", "::ffff:127.0.0.1") === false);
|
|
1101
|
+
check("relay fold: the folded IPv4 peer matches the IPv4 relay CIDR",
|
|
1102
|
+
b.ssrfGuard.cidrContains("127.0.0.0/8", b.ssrfGuard.canonicalizeHost("::ffff:127.0.0.1")) === true);
|
|
1103
|
+
check("relay fold: an out-of-CIDR mapped peer stays refused after folding (no fail-open)",
|
|
1104
|
+
b.ssrfGuard.cidrContains("127.0.0.0/8", b.ssrfGuard.canonicalizeHost("::ffff:10.9.9.9")) === false);
|
|
1105
|
+
|
|
1106
|
+
// (c) Config-time: a malformed / mask-less relay CIDR is refused at boot.
|
|
1107
|
+
function bootRejects(label, entry) {
|
|
1108
|
+
var threw = null;
|
|
1109
|
+
try {
|
|
1110
|
+
b.mail.server.mx.create({
|
|
1111
|
+
tlsContext: {}, relayAllowedFor: [entry],
|
|
1112
|
+
});
|
|
1113
|
+
} catch (e) { threw = e; }
|
|
1114
|
+
check(label, threw && threw.code === "mail-server-mx/bad-relay-cidr");
|
|
1115
|
+
}
|
|
1116
|
+
bootRejects("malformed relay CIDR refused at boot", { cidr: "not-a-cidr", scope: "x" });
|
|
1117
|
+
bootRejects("mask-less relay CIDR refused at boot", { cidr: "203.0.113.5", scope: "x" });
|
|
1118
|
+
bootRejects("out-of-range prefix refused at boot", { cidr: "10.0.0.0/40", scope: "x" });
|
|
1119
|
+
bootRejects("non-object relay entry refused at boot", "10.0.0.0/8");
|
|
1120
|
+
|
|
1121
|
+
// (c2) A dotted IPv4-mapped IPv6 relay CIDR (::ffff:10.0.0.0/104) is a valid
|
|
1122
|
+
// spelling that cidrContains accepts; the config validation folds it to the
|
|
1123
|
+
// plain IPv4 CIDR (10.0.0.0/8) so it is accepted at boot (rather than refused
|
|
1124
|
+
// as bad-relay-cidr) and then matches BOTH a genuine IPv4 peer and a mapped
|
|
1125
|
+
// peer via the gate's peer fold — not just the mapped form.
|
|
1126
|
+
function bootAccepts(label, entry) {
|
|
1127
|
+
var ok = true;
|
|
1128
|
+
try { b.mail.server.mx.create({ tlsContext: {}, relayAllowedFor: [entry] }); }
|
|
1129
|
+
catch (_e) { ok = false; }
|
|
1130
|
+
check(label, ok);
|
|
1131
|
+
}
|
|
1132
|
+
bootAccepts("dotted IPv4-mapped relay CIDR accepted at boot (folded to IPv4)",
|
|
1133
|
+
{ cidr: "::ffff:10.0.0.0/104", scope: "internal" });
|
|
1134
|
+
bootAccepts("hex-group IPv4-mapped relay CIDR accepted at boot",
|
|
1135
|
+
{ cidr: "::ffff:0a00:0/104", scope: "internal" });
|
|
1136
|
+
check("the ::ffff:10.0.0.0/104 fold (10.0.0.0/8) matches a genuine IPv4 peer",
|
|
1137
|
+
b.ssrfGuard.cidrContains("10.0.0.0/8", "10.2.3.4") === true);
|
|
1138
|
+
check("the ::ffff:10.0.0.0/104 fold (10.0.0.0/8) matches an IPv4-mapped peer",
|
|
1139
|
+
b.ssrfGuard.cidrContains("10.0.0.0/8", b.ssrfGuard.canonicalizeHost("::ffff:10.2.3.4")) === true);
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
// ---- Agent handoff failure surfaces a 451 transient error ---------------
|
|
1143
|
+
async function testAgentHandoffFailure() {
|
|
1144
|
+
var ctx;
|
|
1145
|
+
try { ctx = await _makeTestTlsContext(); }
|
|
1146
|
+
catch (_e) { check("agent handoff failure (skipped)", true); return; }
|
|
1147
|
+
var srv = b.mail.server.mx.create({
|
|
1148
|
+
tlsContext: ctx, profile: "permissive", localDomains: ["example.com"],
|
|
1149
|
+
agent: { handoff: async function () { throw new Error("mail store unavailable"); } },
|
|
1150
|
+
});
|
|
1151
|
+
var info = await srv.listen({ port: 0, address: "127.0.0.1" });
|
|
1152
|
+
var sock;
|
|
1153
|
+
try {
|
|
1154
|
+
sock = await _connectTo(info);
|
|
1155
|
+
await _sendCommand(sock, "EHLO sender.example.com");
|
|
1156
|
+
await _sendCommand(sock, "MAIL FROM:<s@external.com>");
|
|
1157
|
+
await _sendCommand(sock, "RCPT TO:<alice@example.com>");
|
|
1158
|
+
await _sendCommand(sock, "DATA");
|
|
1159
|
+
check("agent handoff rejection → 451 local delivery error",
|
|
1160
|
+
/^451 4\.3\.0/.test(await _sendCommand(sock,
|
|
1161
|
+
"From: s@external.com\r\nSubject: hi\r\n\r\nhello\r\n.")));
|
|
1162
|
+
sock.destroy();
|
|
1163
|
+
} finally { await srv.close({ timeoutMs: 1000 }); } // allow:raw-time-literal — test-only short drain
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
// ---- A gate that throws is caught by the pump → 421 + connection close --
|
|
1167
|
+
async function testGateThrows() {
|
|
1168
|
+
var ctx;
|
|
1169
|
+
try { ctx = await _makeTestTlsContext(); }
|
|
1170
|
+
catch (_e) { check("gate throws (skipped)", true); return; }
|
|
1171
|
+
var srv = b.mail.server.mx.create({
|
|
1172
|
+
tlsContext: ctx, profile: "permissive", localDomains: ["example.com"],
|
|
1173
|
+
helo: { evaluate: async function () { throw new Error("gate backend down"); } },
|
|
1174
|
+
});
|
|
1175
|
+
var info = await srv.listen({ port: 0, address: "127.0.0.1" });
|
|
1176
|
+
var sock;
|
|
1177
|
+
try {
|
|
1178
|
+
sock = await _connectTo(info);
|
|
1179
|
+
check("throwing helo gate → 421 server error",
|
|
1180
|
+
/^421 4\.3\.0/.test(await _sendCommand(sock, "EHLO sender.example.com")));
|
|
1181
|
+
sock.destroy();
|
|
1182
|
+
} finally { await srv.close({ timeoutMs: 1000 }); } // allow:raw-time-literal — test-only short drain
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
// ---- Idle-timeout fires a 421 and closes the plaintext connection -------
|
|
1186
|
+
async function testIdleTimeout() {
|
|
1187
|
+
var ctx;
|
|
1188
|
+
try { ctx = await _makeTestTlsContext(); }
|
|
1189
|
+
catch (_e) { check("idle timeout (skipped)", true); return; }
|
|
1190
|
+
var srv = b.mail.server.mx.create({
|
|
1191
|
+
tlsContext: ctx, profile: "permissive", localDomains: ["example.com"],
|
|
1192
|
+
idleTimeoutMs: 300, // allow:raw-time-literal — test-only short idle window
|
|
1193
|
+
});
|
|
1194
|
+
var info = await srv.listen({ port: 0, address: "127.0.0.1" });
|
|
1195
|
+
var sock;
|
|
1196
|
+
try {
|
|
1197
|
+
sock = await _connectTo(info);
|
|
1198
|
+
var col = _collect(sock);
|
|
1199
|
+
// Send nothing — the idle timer fires the transient 421.
|
|
1200
|
+
await helpers.waitUntil(function () { return /^421 4\.4\.2/m.test(col.text()); },
|
|
1201
|
+
{ timeoutMs: 5000, label: "mx idle timeout: 421 4.4.2 delivered" });
|
|
1202
|
+
check("idle connection → 421 4.4.2 + close", /^421 4\.4\.2/m.test(col.text()));
|
|
1203
|
+
sock.destroy();
|
|
1204
|
+
} finally { await srv.close({ timeoutMs: 1000 }); } // allow:raw-time-literal — test-only short drain
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
// ---- close() drains, then force-destroys a lingering connection ---------
|
|
1208
|
+
async function testCloseDestroysLingering() {
|
|
1209
|
+
var ctx;
|
|
1210
|
+
try { ctx = await _makeTestTlsContext(); }
|
|
1211
|
+
catch (_e) { check("close-drain destroy (skipped)", true); return; }
|
|
1212
|
+
var srv = b.mail.server.mx.create({
|
|
1213
|
+
tlsContext: ctx, profile: "permissive", localDomains: ["example.com"],
|
|
1214
|
+
});
|
|
1215
|
+
var info = await srv.listen({ port: 0, address: "127.0.0.1" });
|
|
1216
|
+
var sock = await _connectTo(info);
|
|
1217
|
+
_collect(sock); // swallow the shutdown 421 + reset without rejecting
|
|
1218
|
+
await _sendCommand(sock, "EHLO sender.example.com");
|
|
1219
|
+
check("one live connection tracked before close", srv.connectionCount() === 1);
|
|
1220
|
+
// Hold the client socket open; close() writes the shutdown 421, waits
|
|
1221
|
+
// out the short drain, then force-destroys the lingering connection.
|
|
1222
|
+
await srv.close({ timeoutMs: 200 }); // allow:raw-time-literal — test-only short drain window
|
|
1223
|
+
check("close() force-destroys lingering connection → count 0",
|
|
1224
|
+
srv.connectionCount() === 0);
|
|
1225
|
+
sock.destroy();
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
// ---- TLS error/lifecycle paths: STARTTLS-when-already-active (503),
|
|
1229
|
+
// a non-TLS ClientHello after the STARTTLS 220 (handshake failure), and
|
|
1230
|
+
// the post-STARTTLS idle timeout. Mints one CA and reuses it. ------------
|
|
1231
|
+
async function testTlsErrorPaths() {
|
|
1232
|
+
var ca, leaf;
|
|
1233
|
+
try {
|
|
1234
|
+
ca = await b.mtlsEngine.generateCa({ name: "mx-tls-errpaths-ca" });
|
|
1235
|
+
leaf = await b.mtlsEngine.signClientCert({
|
|
1236
|
+
cn: "localhost", caCertPem: ca.caCertPem, caKeyPem: ca.caKeyPem,
|
|
1237
|
+
usage: "server", sans: ["DNS:localhost", "IP:127.0.0.1"], validityDays: 1,
|
|
1238
|
+
});
|
|
1239
|
+
} catch (_e) { check("TLS error paths (skipped — cert fixture unavailable)", true); return; }
|
|
1240
|
+
var ctx = nodeTls.createSecureContext({ key: leaf.key, cert: leaf.cert });
|
|
1241
|
+
|
|
1242
|
+
// ---- STARTTLS issued a second time over the negotiated TLS → 503 ----
|
|
1243
|
+
var dupSrv = b.mail.server.mx.create({
|
|
1244
|
+
tlsContext: ctx, profile: "permissive", localDomains: ["example.com"],
|
|
1245
|
+
});
|
|
1246
|
+
var dupInfo = await dupSrv.listen({ port: 0, address: "127.0.0.1" });
|
|
1247
|
+
var dupPlain, dupTls;
|
|
1248
|
+
try {
|
|
1249
|
+
dupPlain = await _connectTo(dupInfo);
|
|
1250
|
+
await _sendCommand(dupPlain, "EHLO sender.example.com");
|
|
1251
|
+
check("STARTTLS → 220 ready", /^220 /.test(await _sendCommand(dupPlain, "STARTTLS")));
|
|
1252
|
+
dupTls = nodeTls.connect({ socket: dupPlain, ca: [ca.caCertPem], servername: "localhost" });
|
|
1253
|
+
await new Promise(function (r, j) { dupTls.once("secureConnect", r); dupTls.once("error", j); });
|
|
1254
|
+
await _sendCommand(dupTls, "EHLO sender.example.com");
|
|
1255
|
+
check("STARTTLS when TLS already active → 503",
|
|
1256
|
+
/^503 5\.5\.1/.test(await _sendCommand(dupTls, "STARTTLS")));
|
|
1257
|
+
dupTls.destroy();
|
|
1258
|
+
} finally { await dupSrv.close({ timeoutMs: 1000 }); } // allow:raw-time-literal — test-only short drain
|
|
1259
|
+
|
|
1260
|
+
// ---- Non-TLS bytes after the STARTTLS 220 → handshake failure/close --
|
|
1261
|
+
var hsSrv = b.mail.server.mx.create({
|
|
1262
|
+
tlsContext: ctx, profile: "permissive", localDomains: ["example.com"],
|
|
1263
|
+
});
|
|
1264
|
+
var hsInfo = await hsSrv.listen({ port: 0, address: "127.0.0.1" });
|
|
1265
|
+
var hsSock;
|
|
1266
|
+
try {
|
|
1267
|
+
hsSock = await _connectTo(hsInfo);
|
|
1268
|
+
await _sendCommand(hsSock, "EHLO sender.example.com");
|
|
1269
|
+
await _sendCommand(hsSock, "STARTTLS");
|
|
1270
|
+
var closed = false;
|
|
1271
|
+
hsSock.on("close", function () { closed = true; });
|
|
1272
|
+
hsSock.on("error", function () { /* reset on failed handshake */ });
|
|
1273
|
+
// Garbage where the TLS ClientHello should be — the server's TLS
|
|
1274
|
+
// wrap errors and tears the connection down.
|
|
1275
|
+
hsSock.write("this is definitely not a tls client hello\r\n");
|
|
1276
|
+
await helpers.waitUntil(function () { return closed; },
|
|
1277
|
+
{ timeoutMs: 5000, label: "mx STARTTLS: non-TLS bytes close the connection" });
|
|
1278
|
+
check("non-TLS bytes after STARTTLS 220 → connection closed", closed === true);
|
|
1279
|
+
hsSock.destroy();
|
|
1280
|
+
} finally { await hsSrv.close({ timeoutMs: 1000 }); } // allow:raw-time-literal — test-only short drain
|
|
1281
|
+
|
|
1282
|
+
// ---- Post-STARTTLS idle timeout fires 421 over the TLS socket -------
|
|
1283
|
+
var idleSrv = b.mail.server.mx.create({
|
|
1284
|
+
tlsContext: ctx, profile: "permissive", localDomains: ["example.com"],
|
|
1285
|
+
idleTimeoutMs: 600, // allow:raw-time-literal — room for handshake, then idle
|
|
1286
|
+
});
|
|
1287
|
+
var idleInfo = await idleSrv.listen({ port: 0, address: "127.0.0.1" });
|
|
1288
|
+
var idlePlain, idleTls;
|
|
1289
|
+
try {
|
|
1290
|
+
idlePlain = await _connectTo(idleInfo);
|
|
1291
|
+
await _sendCommand(idlePlain, "EHLO sender.example.com");
|
|
1292
|
+
await _sendCommand(idlePlain, "STARTTLS");
|
|
1293
|
+
idleTls = nodeTls.connect({ socket: idlePlain, ca: [ca.caCertPem], servername: "localhost" });
|
|
1294
|
+
await new Promise(function (r, j) { idleTls.once("secureConnect", r); idleTls.once("error", j); });
|
|
1295
|
+
await _sendCommand(idleTls, "EHLO sender.example.com");
|
|
1296
|
+
var tlsCol = _collect(idleTls);
|
|
1297
|
+
await helpers.waitUntil(function () { return /^421 4\.4\.2/m.test(tlsCol.text()); },
|
|
1298
|
+
{ timeoutMs: 5000, label: "mx TLS idle timeout: 421 4.4.2 over TLS" });
|
|
1299
|
+
check("post-STARTTLS idle → 421 4.4.2 over TLS", /^421 4\.4\.2/m.test(tlsCol.text()));
|
|
1300
|
+
idleTls.destroy();
|
|
1301
|
+
} finally { await idleSrv.close({ timeoutMs: 1000 }); } // allow:raw-time-literal — test-only short drain
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
// ---- Address-literal HELO / null reverse-path skip domain hardening -----
|
|
1305
|
+
// RFC 5321 §4.1.3 address literals (`[1.2.3.4]`) and the §4.5.5 empty
|
|
1306
|
+
// reverse path (`<>`) are legitimate non-domain forms; the guardDomain
|
|
1307
|
+
// hardening is skipped for them rather than refusing.
|
|
1308
|
+
async function testAddressLiteralAndNullSender() {
|
|
1309
|
+
var ctx;
|
|
1310
|
+
try { ctx = await _makeTestTlsContext(); }
|
|
1311
|
+
catch (_e) { check("address-literal / null sender (skipped)", true); return; }
|
|
1312
|
+
var srv = b.mail.server.mx.create({
|
|
1313
|
+
tlsContext: ctx, profile: "permissive", localDomains: ["example.com"],
|
|
1314
|
+
});
|
|
1315
|
+
var info = await srv.listen({ port: 0, address: "127.0.0.1" });
|
|
1316
|
+
var sock;
|
|
1317
|
+
try {
|
|
1318
|
+
sock = await _connectTo(info);
|
|
1319
|
+
check("EHLO address literal [127.0.0.1] accepted (hardening skipped)",
|
|
1320
|
+
/^250[ -]/.test(await _sendCommand(sock, "EHLO [127.0.0.1]")));
|
|
1321
|
+
check("MAIL FROM:<> null reverse path accepted (bounce path)",
|
|
1322
|
+
/^250 /.test(await _sendCommand(sock, "MAIL FROM:<>")));
|
|
1323
|
+
check("RCPT after null sender still accepted → 250",
|
|
1324
|
+
/^250 /.test(await _sendCommand(sock, "RCPT TO:<alice@example.com>")));
|
|
1325
|
+
// RCPT TO address literal skips domain hardening (RFC 5321 §4.1.3);
|
|
1326
|
+
// the non-local literal is then relay-refused.
|
|
1327
|
+
check("RCPT TO address literal skips hardening, then relay-refused → 550",
|
|
1328
|
+
/^550 5\.7\.1/.test(await _sendCommand(sock, "RCPT TO:<x@[127.0.0.1]>")));
|
|
1329
|
+
sock.destroy();
|
|
1330
|
+
} finally { await srv.close({ timeoutMs: 1000 }); } // allow:raw-time-literal — test-only short drain
|
|
1331
|
+
|
|
1332
|
+
// guardDomain disabled → the HELO/EHLO hardening branch is skipped
|
|
1333
|
+
// entirely (operator closed-network opt-out).
|
|
1334
|
+
var offSrv = b.mail.server.mx.create({
|
|
1335
|
+
tlsContext: ctx, profile: "permissive", localDomains: ["example.com"],
|
|
1336
|
+
guardDomain: false,
|
|
1337
|
+
});
|
|
1338
|
+
var offInfo = await offSrv.listen({ port: 0, address: "127.0.0.1" });
|
|
1339
|
+
var offSock;
|
|
1340
|
+
try {
|
|
1341
|
+
offSock = await _connectTo(offInfo);
|
|
1342
|
+
check("EHLO with guardDomain disabled accepted (no hardening) → 250",
|
|
1343
|
+
/^250[ -]/.test(await _sendCommand(offSock, "EHLO sender.example.com")));
|
|
1344
|
+
offSock.destroy();
|
|
1345
|
+
} finally { await offSrv.close({ timeoutMs: 1000 }); } // allow:raw-time-literal — test-only short drain
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
// ---- Per-IP concurrent-connection cap refuses the excess connection ----
|
|
1349
|
+
async function testConnectionRateLimit() {
|
|
1350
|
+
var ctx;
|
|
1351
|
+
try { ctx = await _makeTestTlsContext(); }
|
|
1352
|
+
catch (_e) { check("connection rate limit (skipped)", true); return; }
|
|
1353
|
+
var srv = b.mail.server.mx.create({
|
|
1354
|
+
tlsContext: ctx, profile: "permissive", localDomains: ["example.com"],
|
|
1355
|
+
rateLimit: { maxConcurrentConnectionsPerIp: 1 },
|
|
1356
|
+
});
|
|
1357
|
+
var info = await srv.listen({ port: 0, address: "127.0.0.1" });
|
|
1358
|
+
var first, second;
|
|
1359
|
+
try {
|
|
1360
|
+
first = await _connectTo(info); // admitted; held open to occupy the single slot
|
|
1361
|
+
second = nodeNet.connect(info.port, "127.0.0.1");
|
|
1362
|
+
await new Promise(function (r) { second.once("connect", r); });
|
|
1363
|
+
var reply = await _readGreeting(second); // first line is the refusal, not a 220
|
|
1364
|
+
check("excess concurrent connection refused with 421 4.7.0",
|
|
1365
|
+
/^421 4\.7\.0/.test(reply));
|
|
1366
|
+
first.destroy();
|
|
1367
|
+
second.destroy();
|
|
1368
|
+
} finally { await srv.close({ timeoutMs: 1000 }); } // allow:raw-time-literal — test-only short drain
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
// ---- close() is idempotent: no-arg close drains, second close is a no-op
|
|
1372
|
+
async function testCloseIdempotent() {
|
|
1373
|
+
var ctx;
|
|
1374
|
+
try { ctx = await _makeTestTlsContext(); }
|
|
1375
|
+
catch (_e) { check("close idempotency (skipped)", true); return; }
|
|
1376
|
+
var srv = b.mail.server.mx.create({
|
|
1377
|
+
tlsContext: ctx, profile: "permissive", localDomains: ["example.com"],
|
|
1378
|
+
});
|
|
1379
|
+
await srv.listen({ port: 0, address: "127.0.0.1" });
|
|
1380
|
+
var firstErr = null;
|
|
1381
|
+
try { await srv.close(); } catch (e) { firstErr = e; } // no opts → default drain timeout
|
|
1382
|
+
check("close() with no options resolves", firstErr === null);
|
|
1383
|
+
var secondErr = null;
|
|
1384
|
+
try { await srv.close(); } catch (e) { secondErr = e; } // already closed → early return
|
|
1385
|
+
check("second close() is a no-op", secondErr === null);
|
|
1386
|
+
}
|
|
1387
|
+
|
|
635
1388
|
async function run() {
|
|
636
1389
|
testSurface();
|
|
637
1390
|
testCreateRequiresTlsContext();
|
|
638
1391
|
testCreateRejectsBadBounds();
|
|
1392
|
+
testGuardEnvelopeBootValidation();
|
|
1393
|
+
testGuardDomainBootOptions();
|
|
639
1394
|
testDetectSmugglingShape();
|
|
640
1395
|
testFindDotTerminator();
|
|
641
1396
|
testDotUnstuff();
|
|
@@ -645,6 +1400,22 @@ async function run() {
|
|
|
645
1400
|
await testConnectionGates();
|
|
646
1401
|
await testGateOverStartTls();
|
|
647
1402
|
await testGuardEnvelopeGate();
|
|
1403
|
+
await testCommandDispatch();
|
|
1404
|
+
await testSequenceAndSyntaxErrors();
|
|
1405
|
+
await testDomainRefusals();
|
|
1406
|
+
await testAddressLiteralAndNullSender();
|
|
1407
|
+
await testResourceLimits();
|
|
1408
|
+
await testRcptFailureRateLimit();
|
|
1409
|
+
await testConnectionRateLimit();
|
|
1410
|
+
await testWireSmuggling();
|
|
1411
|
+
await testRelayAllowed();
|
|
1412
|
+
await testRelayCidrEnforced();
|
|
1413
|
+
await testAgentHandoffFailure();
|
|
1414
|
+
await testGateThrows();
|
|
1415
|
+
await testIdleTimeout();
|
|
1416
|
+
await testCloseDestroysLingering();
|
|
1417
|
+
await testCloseIdempotent();
|
|
1418
|
+
await testTlsErrorPaths();
|
|
648
1419
|
}
|
|
649
1420
|
|
|
650
1421
|
module.exports = { run: run };
|