@blamejs/core 0.15.12 → 0.15.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/index.js +2 -0
- package/lib/a2a-tasks.js +45 -29
- package/lib/acme.js +6 -5
- package/lib/agent-event-bus.js +18 -4
- package/lib/agent-idempotency.js +7 -6
- package/lib/agent-orchestrator.js +2 -5
- package/lib/agent-saga.js +3 -5
- package/lib/agent-snapshot.js +32 -2
- package/lib/agent-tenant.js +2 -5
- package/lib/ai-adverse-decision.js +2 -15
- package/lib/ai-aedt-bias-audit.js +2 -1
- package/lib/ai-capability.js +1 -6
- package/lib/ai-content-detect.js +1 -3
- package/lib/ai-dp.js +1 -5
- package/lib/ai-frontier-protocol.js +1 -1
- package/lib/ai-input.js +2 -2
- package/lib/ai-model-manifest.js +1 -1
- package/lib/ai-output.js +16 -7
- package/lib/ai-pref.js +3 -8
- package/lib/ai-quota.js +3 -14
- package/lib/api-key.js +37 -28
- package/lib/api-snapshot.js +4 -1
- package/lib/app-shutdown.js +7 -1
- package/lib/archive-adapters.js +2 -4
- package/lib/archive-entry-policy.js +32 -0
- package/lib/archive-gz.js +9 -0
- package/lib/archive-read.js +14 -24
- package/lib/archive-tar-read.js +56 -24
- package/lib/archive.js +6 -12
- package/lib/arg-parser.js +7 -6
- package/lib/asn1-der.js +70 -22
- package/lib/asyncapi-traits.js +2 -6
- package/lib/atomic-file.js +312 -33
- package/lib/audit-chain.js +183 -54
- package/lib/audit-daily-review.js +36 -16
- package/lib/audit-emit.js +82 -0
- package/lib/audit-sign.js +258 -0
- package/lib/audit-tools.js +108 -23
- package/lib/audit.js +91 -2
- package/lib/auth/access-lock.js +7 -29
- package/lib/auth/bot-challenge.js +1 -1
- package/lib/auth/ciba.js +43 -4
- package/lib/auth/dpop.js +29 -36
- package/lib/auth/fido-mds3.js +14 -16
- package/lib/auth/jwt-external.js +26 -8
- package/lib/auth/jwt.js +15 -17
- package/lib/auth/lockout.js +24 -27
- package/lib/auth/oauth.js +67 -45
- package/lib/auth/oid4vci.js +55 -32
- package/lib/auth/oid4vp.js +3 -2
- package/lib/auth/openid-federation.js +6 -6
- package/lib/auth/passkey.js +4 -4
- package/lib/auth/password.js +8 -2
- package/lib/auth/saml.js +70 -40
- package/lib/auth/sd-jwt-vc-holder.js +2 -14
- package/lib/auth/sd-jwt-vc-issuer.js +2 -14
- package/lib/auth/sd-jwt-vc.js +25 -5
- package/lib/auth/status-list.js +21 -9
- package/lib/auth/step-up.js +15 -13
- package/lib/auth-bot-challenge.js +27 -39
- package/lib/backup/bundle.js +18 -20
- package/lib/backup/crypto.js +23 -8
- package/lib/backup/index.js +55 -67
- package/lib/backup/manifest.js +7 -1
- package/lib/bounded-map.js +112 -1
- package/lib/breach-deadline.js +1 -11
- package/lib/break-glass.js +41 -22
- package/lib/cache.js +7 -18
- package/lib/cbor.js +36 -12
- package/lib/cdn-cache-control.js +15 -12
- package/lib/cert.js +8 -13
- package/lib/chain-writer.js +162 -47
- package/lib/cli.js +10 -2
- package/lib/client-hints.js +7 -9
- package/lib/cloud-events.js +43 -33
- package/lib/cluster-storage.js +9 -41
- package/lib/cms-codec.js +2 -1
- package/lib/codepoint-class.js +84 -1
- package/lib/compliance-ai-act-logging.js +1 -6
- package/lib/compliance-ai-act-transparency.js +2 -4
- package/lib/compliance-eaa.js +2 -12
- package/lib/compliance-sanctions-fetcher.js +21 -28
- package/lib/compliance-sanctions.js +11 -21
- package/lib/compliance.js +3 -10
- package/lib/config-drift.js +24 -20
- package/lib/content-credentials.js +11 -8
- package/lib/content-digest.js +21 -12
- package/lib/cookies.js +15 -13
- package/lib/cose.js +39 -11
- package/lib/cra-report.js +1 -11
- package/lib/crdt.js +2 -1
- package/lib/crypto-field.js +34 -33
- package/lib/crypto.js +138 -7
- package/lib/csp.js +239 -3
- package/lib/daemon.js +46 -42
- package/lib/data-act.js +46 -13
- package/lib/db-file-lifecycle.js +14 -6
- package/lib/db-query.js +75 -49
- package/lib/db.js +66 -27
- package/lib/dbsc.js +5 -6
- package/lib/ddl-change-control.js +18 -14
- package/lib/deprecate.js +4 -5
- package/lib/did.js +6 -2
- package/lib/dora.js +43 -13
- package/lib/dr-runbook.js +1 -1
- package/lib/dsa.js +2 -1
- package/lib/dsr.js +30 -20
- package/lib/early-hints.js +19 -0
- package/lib/eat.js +5 -1
- package/lib/external-db-migrate.js +21 -22
- package/lib/external-db.js +74 -30
- package/lib/fda-21cfr11.js +42 -13
- package/lib/fdx.js +22 -18
- package/lib/file-upload.js +80 -66
- package/lib/flag-evaluation-context.js +5 -8
- package/lib/flag-providers.js +6 -2
- package/lib/forms.js +1 -1
- package/lib/framework-error.js +12 -0
- package/lib/framework-schema.js +19 -0
- package/lib/fsm.js +7 -12
- package/lib/gate-contract.js +911 -39
- package/lib/gdpr-ropa.js +22 -22
- package/lib/graphql-federation.js +18 -5
- package/lib/guard-agent-registry.js +2 -1
- package/lib/guard-all.js +3 -2
- package/lib/guard-archive.js +9 -30
- package/lib/guard-auth.js +23 -80
- package/lib/guard-cidr.js +20 -96
- package/lib/guard-csv.js +135 -196
- package/lib/guard-domain.js +23 -106
- package/lib/guard-dsn.js +17 -14
- package/lib/guard-email.js +46 -53
- package/lib/guard-envelope.js +2 -2
- package/lib/guard-event-bus-topic.js +2 -1
- package/lib/guard-filename.js +12 -60
- package/lib/guard-graphql.js +28 -75
- package/lib/guard-html-wcag.js +15 -2
- package/lib/guard-html.js +74 -128
- package/lib/guard-idempotency-key.js +2 -1
- package/lib/guard-image.js +280 -77
- package/lib/guard-imap-command.js +9 -10
- package/lib/guard-jmap.js +1 -1
- package/lib/guard-json.js +101 -109
- package/lib/guard-jsonpath.js +20 -88
- package/lib/guard-jwt.js +32 -114
- package/lib/guard-list-id.js +2 -7
- package/lib/guard-list-unsubscribe.js +2 -7
- package/lib/guard-mail-compose.js +5 -6
- package/lib/guard-mail-move.js +3 -2
- package/lib/guard-mail-query.js +5 -3
- package/lib/guard-mail-sieve.js +6 -7
- package/lib/guard-managesieve-command.js +6 -5
- package/lib/guard-markdown.js +76 -110
- package/lib/guard-message-id.js +5 -6
- package/lib/guard-mime.js +20 -99
- package/lib/guard-oauth.js +19 -73
- package/lib/guard-pdf.js +65 -72
- package/lib/guard-pop3-command.js +13 -14
- package/lib/guard-posture-chain.js +2 -1
- package/lib/guard-regex.js +24 -99
- package/lib/guard-saga-config.js +2 -1
- package/lib/guard-shell.js +22 -87
- package/lib/guard-smtp-command.js +9 -12
- package/lib/guard-sql.js +15 -13
- package/lib/guard-stream-args.js +2 -1
- package/lib/guard-svg.js +103 -149
- package/lib/guard-template.js +23 -80
- package/lib/guard-tenant-id.js +2 -1
- package/lib/guard-text.js +592 -0
- package/lib/guard-time.js +27 -95
- package/lib/guard-uuid.js +21 -93
- package/lib/guard-xml.js +76 -106
- package/lib/guard-yaml.js +24 -60
- package/lib/html-balance.js +7 -3
- package/lib/http-client-cache.js +5 -12
- package/lib/http-client-cookie-jar.js +35 -16
- package/lib/http-client.js +233 -74
- package/lib/http-message-signature.js +3 -2
- package/lib/i18n-messageformat.js +5 -7
- package/lib/i18n.js +83 -26
- package/lib/iab-tcf.js +3 -2
- package/lib/importmap-integrity.js +41 -1
- package/lib/inbox.js +8 -8
- package/lib/incident-report.js +12 -27
- package/lib/ip-utils.js +49 -6
- package/lib/jobs.js +3 -2
- package/lib/json-patch.js +1 -1
- package/lib/json-path.js +24 -3
- package/lib/jtd.js +2 -2
- package/lib/keychain.js +6 -18
- package/lib/legal-hold.js +30 -23
- package/lib/log-stream-cloudwatch.js +44 -112
- package/lib/log-stream-otlp-grpc.js +17 -14
- package/lib/log-stream-otlp.js +16 -80
- package/lib/log-stream-webhook.js +20 -92
- package/lib/log.js +2 -2
- package/lib/mail-agent.js +2 -2
- package/lib/mail-arc-sign.js +8 -3
- package/lib/mail-arf.js +4 -3
- package/lib/mail-auth.js +43 -69
- package/lib/mail-bimi.js +35 -55
- package/lib/mail-bounce.js +3 -3
- package/lib/mail-crypto-pgp.js +3 -2
- package/lib/mail-crypto-smime.js +71 -6
- package/lib/mail-dav.js +8 -35
- package/lib/mail-deploy.js +15 -14
- package/lib/mail-dkim.js +19 -38
- package/lib/mail-greylist.js +17 -30
- package/lib/mail-helo.js +4 -7
- package/lib/mail-journal.js +13 -9
- package/lib/mail-mdn.js +12 -7
- package/lib/mail-rbl.js +7 -12
- package/lib/mail-scan.js +5 -7
- package/lib/mail-send-deliver.js +33 -20
- package/lib/mail-server-imap.js +32 -87
- package/lib/mail-server-jmap.js +39 -52
- package/lib/mail-server-managesieve.js +29 -83
- package/lib/mail-server-mx.js +33 -74
- package/lib/mail-server-net.js +177 -0
- package/lib/mail-server-pop3.js +30 -83
- package/lib/mail-server-rate-limit.js +30 -6
- package/lib/mail-server-registry.js +1 -1
- package/lib/mail-server-submission.js +34 -73
- package/lib/mail-server-tls.js +98 -2
- package/lib/mail-spam-score.js +9 -12
- package/lib/mail-store-fts.js +1 -1
- package/lib/mail-store.js +3 -2
- package/lib/mail.js +28 -13
- package/lib/markup-escape.js +31 -0
- package/lib/markup-tokenizer.js +78 -0
- package/lib/mcp-tool-registry.js +26 -23
- package/lib/mcp.js +7 -5
- package/lib/mdoc.js +36 -14
- package/lib/metrics.js +46 -33
- package/lib/middleware/age-gate.js +3 -23
- package/lib/middleware/api-encrypt.js +13 -24
- package/lib/middleware/assetlinks.js +2 -7
- package/lib/middleware/bearer-auth.js +2 -1
- package/lib/middleware/body-parser.js +41 -52
- package/lib/middleware/bot-disclose.js +7 -10
- package/lib/middleware/bot-guard.js +28 -28
- package/lib/middleware/clear-site-data.js +5 -1
- package/lib/middleware/compression.js +9 -0
- package/lib/middleware/cors.js +32 -23
- package/lib/middleware/csrf-protect.js +73 -23
- package/lib/middleware/daily-byte-quota.js +12 -30
- package/lib/middleware/deny-response.js +19 -1
- package/lib/middleware/fetch-metadata.js +35 -4
- package/lib/middleware/idempotency-key.js +4 -20
- package/lib/middleware/network-allowlist.js +61 -30
- package/lib/middleware/rate-limit.js +45 -44
- package/lib/middleware/scim-server.js +5 -3
- package/lib/middleware/security-headers.js +33 -7
- package/lib/middleware/security-txt.js +2 -6
- package/lib/middleware/speculation-rules.js +6 -3
- package/lib/middleware/tus-upload.js +14 -29
- package/lib/middleware/web-app-manifest.js +2 -7
- package/lib/migrations.js +4 -13
- package/lib/mime-parse.js +34 -17
- package/lib/module-loader.js +44 -0
- package/lib/money.js +105 -0
- package/lib/mtls-ca.js +125 -41
- package/lib/network-byte-quota.js +4 -18
- package/lib/network-dane.js +8 -6
- package/lib/network-dns-resolver.js +98 -7
- package/lib/network-dns.js +9 -2
- package/lib/network-dnssec.js +18 -17
- package/lib/network-heartbeat.js +3 -2
- package/lib/network-smtp-policy.js +52 -46
- package/lib/network-tls.js +67 -47
- package/lib/network-tsig.js +2 -2
- package/lib/nis2-report.js +2 -12
- package/lib/nist-crosswalk.js +1 -1
- package/lib/nonce-store.js +81 -3
- package/lib/ntp-check.js +28 -0
- package/lib/numeric-bounds.js +31 -8
- package/lib/object-store/azure-blob-bucket-ops.js +2 -6
- package/lib/object-store/azure-blob.js +6 -47
- package/lib/object-store/gcs-bucket-ops.js +4 -2
- package/lib/object-store/gcs.js +14 -75
- package/lib/object-store/http-put.js +2 -7
- package/lib/object-store/http-request.js +157 -0
- package/lib/object-store/local.js +37 -17
- package/lib/object-store/sigv4-bucket-ops.js +2 -1
- package/lib/object-store/sigv4.js +10 -79
- package/lib/observability-otlp-exporter.js +29 -29
- package/lib/observability.js +95 -0
- package/lib/openapi-paths-builder.js +7 -3
- package/lib/otel-export.js +7 -10
- package/lib/outbox.js +54 -41
- package/lib/pagination.js +11 -15
- package/lib/parsers/safe-env.js +5 -4
- package/lib/parsers/safe-ini.js +10 -4
- package/lib/parsers/safe-toml.js +11 -9
- package/lib/parsers/safe-xml.js +3 -3
- package/lib/parsers/safe-yaml.js +28 -9
- package/lib/pick.js +63 -5
- package/lib/pipl-cn.js +69 -59
- package/lib/privacy-pass.js +8 -6
- package/lib/problem-details.js +2 -5
- package/lib/pubsub.js +4 -8
- package/lib/queue-local.js +10 -3
- package/lib/redact.js +9 -4
- package/lib/render.js +4 -2
- package/lib/request-helpers.js +334 -29
- package/lib/resource-access-lock.js +3 -3
- package/lib/restore-bundle.js +46 -18
- package/lib/restore-rollback.js +10 -4
- package/lib/restore.js +24 -22
- package/lib/retention.js +23 -9
- package/lib/router.js +17 -4
- package/lib/safe-async.js +457 -0
- package/lib/safe-buffer.js +137 -6
- package/lib/safe-decompress.js +2 -1
- package/lib/safe-dns.js +2 -1
- package/lib/safe-ical.js +14 -28
- package/lib/safe-icap.js +1 -1
- package/lib/safe-json.js +51 -8
- package/lib/safe-jsonpath.js +6 -5
- package/lib/safe-mime.js +25 -29
- package/lib/safe-redirect.js +2 -5
- package/lib/safe-schema.js +7 -6
- package/lib/safe-sieve.js +1 -1
- package/lib/safe-sql.js +126 -0
- package/lib/safe-vcard.js +13 -27
- package/lib/sandbox-worker.js +15 -2
- package/lib/sandbox.js +4 -3
- package/lib/scheduler.js +22 -13
- package/lib/sec-cyber.js +82 -37
- package/lib/seeders.js +4 -11
- package/lib/self-update-standalone-verifier.js +16 -0
- package/lib/self-update.js +92 -69
- package/lib/session-device-binding.js +112 -66
- package/lib/session.js +219 -7
- package/lib/sql.js +228 -81
- package/lib/sse.js +6 -10
- package/lib/static.js +201 -111
- package/lib/storage.js +4 -2
- package/lib/structured-fields.js +275 -16
- package/lib/template.js +7 -5
- package/lib/tenant-quota.js +53 -38
- package/lib/tsa.js +16 -17
- package/lib/validate-opts.js +154 -8
- package/lib/vault/index.js +5 -0
- package/lib/vault/passphrase-ops.js +22 -26
- package/lib/vault/passphrase-source.js +8 -3
- package/lib/vault/rotate.js +13 -18
- package/lib/vault/seal-pem-file.js +34 -49
- package/lib/vault-aad.js +3 -2
- package/lib/vc.js +3 -8
- package/lib/vendor/MANIFEST.json +10 -10
- package/lib/vendor/public-suffix-list.dat +23 -10
- package/lib/vendor/public-suffix-list.data.js +5498 -5494
- package/lib/vex.js +35 -13
- package/lib/web-push-vapid.js +23 -20
- package/lib/webhook-dispatcher.js +706 -0
- package/lib/webhook.js +59 -19
- package/lib/websocket-channels.js +9 -7
- package/lib/websocket.js +8 -4
- package/lib/worm.js +3 -4
- package/lib/ws-client.js +65 -56
- package/lib/x509-chain.js +44 -0
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/mail-dav.js
CHANGED
|
@@ -129,8 +129,8 @@
|
|
|
129
129
|
* backend. Defends the RRULE-recursion expansion-DoS class at the PUT boundary.
|
|
130
130
|
*/
|
|
131
131
|
|
|
132
|
-
var lazyRequire = require("./lazy-require");
|
|
133
132
|
var C = require("./constants");
|
|
133
|
+
var markupEscape = require("./markup-escape").markupEscape;
|
|
134
134
|
var safeIcal = require("./safe-ical");
|
|
135
135
|
var safeVcard = require("./safe-vcard");
|
|
136
136
|
var safeBuffer = require("./safe-buffer");
|
|
@@ -138,7 +138,7 @@ var validateOpts = require("./validate-opts");
|
|
|
138
138
|
var xmlC14n = require("./xml-c14n");
|
|
139
139
|
var { defineClass } = require("./framework-error");
|
|
140
140
|
|
|
141
|
-
var
|
|
141
|
+
var auditEmit = require("./audit-emit");
|
|
142
142
|
|
|
143
143
|
var MailDavError = defineClass("MailDavError", { alwaysPermanent: true });
|
|
144
144
|
|
|
@@ -221,15 +221,7 @@ function create(opts) {
|
|
|
221
221
|
var calStorage = opts.storage.calendar || null;
|
|
222
222
|
var cardStorage = opts.storage.addressbook || null;
|
|
223
223
|
|
|
224
|
-
|
|
225
|
-
try {
|
|
226
|
-
audit().safeEmit({
|
|
227
|
-
action: action,
|
|
228
|
-
outcome: outcome || "success",
|
|
229
|
-
metadata: metadata || {},
|
|
230
|
-
});
|
|
231
|
-
} catch (_e) { /* drop-silent — audit best-effort */ }
|
|
232
|
-
}
|
|
224
|
+
var _emit = auditEmit.emit;
|
|
233
225
|
|
|
234
226
|
// ---- URL parsing (per-tenant principal isolation) ----------------------
|
|
235
227
|
//
|
|
@@ -299,28 +291,14 @@ function create(opts) {
|
|
|
299
291
|
resolve(Buffer.from(JSON.stringify(req.body), "utf8"));
|
|
300
292
|
return;
|
|
301
293
|
}
|
|
302
|
-
//
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
// single allocation in result() is bounded by maxBytes.
|
|
306
|
-
var collector = safeBuffer.boundedChunkCollector({
|
|
294
|
+
// collectStream's boundedChunkCollector enforces maxBytes inside push()
|
|
295
|
+
// (overflow throws → reject) and destroys the stream on cap/error.
|
|
296
|
+
safeBuffer.collectStream(req, {
|
|
307
297
|
maxBytes: maxBody,
|
|
308
298
|
errorClass: MailDavError,
|
|
309
299
|
sizeCode: "mail-dav/oversize-body",
|
|
310
300
|
sizeMessage: "request body exceeds " + maxBody + " bytes",
|
|
311
|
-
});
|
|
312
|
-
req.on("data", function (chunk) {
|
|
313
|
-
try { collector.push(chunk); }
|
|
314
|
-
catch (e) {
|
|
315
|
-
req.destroy();
|
|
316
|
-
reject(e);
|
|
317
|
-
}
|
|
318
|
-
});
|
|
319
|
-
req.on("end", function () {
|
|
320
|
-
try { resolve(collector.result()); }
|
|
321
|
-
catch (e) { reject(e); }
|
|
322
|
-
});
|
|
323
|
-
req.on("error", function (e) { reject(e); });
|
|
301
|
+
}).then(resolve, reject);
|
|
324
302
|
});
|
|
325
303
|
}
|
|
326
304
|
|
|
@@ -370,12 +348,7 @@ function create(opts) {
|
|
|
370
348
|
|
|
371
349
|
function _xmlEscape(s) {
|
|
372
350
|
if (s === null || s === undefined) return "";
|
|
373
|
-
return
|
|
374
|
-
.replace(/&/g, "&")
|
|
375
|
-
.replace(/</g, "<")
|
|
376
|
-
.replace(/>/g, ">")
|
|
377
|
-
.replace(/"/g, """)
|
|
378
|
-
.replace(/'/g, "'");
|
|
351
|
+
return markupEscape(s, { apos: "'" });
|
|
379
352
|
}
|
|
380
353
|
|
|
381
354
|
// Render a small subset of well-known DAV / CalDAV / CardDAV
|
package/lib/mail-deploy.js
CHANGED
|
@@ -51,8 +51,10 @@ var lazyRequire = require("./lazy-require");
|
|
|
51
51
|
var validateOpts = require("./validate-opts");
|
|
52
52
|
var numericBounds = require("./numeric-bounds");
|
|
53
53
|
var C = require("./constants");
|
|
54
|
+
var markupEscape = require("./markup-escape").markupEscape;
|
|
54
55
|
var safeJson = require("./safe-json");
|
|
55
56
|
var safeBuffer = require("./safe-buffer");
|
|
57
|
+
var codepointClass = require("./codepoint-class");
|
|
56
58
|
var guardJson = lazyRequire(function () { return require("./guard-json"); });
|
|
57
59
|
var audit = lazyRequire(function () { return require("./audit"); });
|
|
58
60
|
var { defineClass } = require("./framework-error");
|
|
@@ -73,18 +75,13 @@ function _domainOk(d) {
|
|
|
73
75
|
// header-injection class + XML-attribute-injection class.
|
|
74
76
|
for (var i = 0; i < d.length; i++) {
|
|
75
77
|
var c = d.charCodeAt(i);
|
|
76
|
-
if (c
|
|
78
|
+
if (codepointClass.isForbiddenControlChar(c, { forbidTab: true }) || c === 0x22) return false; // refuse C0 / DEL / "
|
|
77
79
|
}
|
|
78
80
|
return true;
|
|
79
81
|
}
|
|
80
82
|
|
|
81
83
|
function _xmlEscape(s) {
|
|
82
|
-
return
|
|
83
|
-
.replace(/&/g, "&")
|
|
84
|
-
.replace(/</g, "<")
|
|
85
|
-
.replace(/>/g, ">")
|
|
86
|
-
.replace(/"/g, """)
|
|
87
|
-
.replace(/'/g, "'");
|
|
84
|
+
return markupEscape(s, { apos: "'" });
|
|
88
85
|
}
|
|
89
86
|
|
|
90
87
|
/**
|
|
@@ -126,7 +123,7 @@ function mtaStsPublish(opts) {
|
|
|
126
123
|
throw new MailDeployError("mail-deploy/bad-domain",
|
|
127
124
|
"mtaStsPublish: opts.domain must be a valid hostname");
|
|
128
125
|
}
|
|
129
|
-
if (!STS_MODES
|
|
126
|
+
if (!Object.prototype.hasOwnProperty.call(STS_MODES, opts.mode)) {
|
|
130
127
|
throw new MailDeployError("mail-deploy/bad-mode",
|
|
131
128
|
"mtaStsPublish: opts.mode must be 'enforce' | 'testing' | 'none'");
|
|
132
129
|
}
|
|
@@ -375,7 +372,7 @@ function autoConfigXml(opts) {
|
|
|
375
372
|
// Refuse control bytes / quote in the URL.
|
|
376
373
|
for (var k = 0; k < cfg.url.length; k++) {
|
|
377
374
|
var c = cfg.url.charCodeAt(k);
|
|
378
|
-
if (c
|
|
375
|
+
if (codepointClass.isForbiddenControlChar(c, { forbidTab: true }) || c === 0x22) { // C0 / DEL / "
|
|
379
376
|
throw new MailDeployError("mail-deploy/bad-jmap-url",
|
|
380
377
|
"autoConfigXml: opts.jmap.url contains control byte");
|
|
381
378
|
}
|
|
@@ -444,7 +441,7 @@ function autoDiscoverXml(opts) {
|
|
|
444
441
|
// Refuse CR / LF / NUL / control bytes in email (XML injection class).
|
|
445
442
|
for (var i = 0; i < opts.email.length; i++) {
|
|
446
443
|
var c = opts.email.charCodeAt(i);
|
|
447
|
-
if (c
|
|
444
|
+
if (codepointClass.isForbiddenControlChar(c, { forbidTab: true })) { // C0 / DEL
|
|
448
445
|
throw new MailDeployError("mail-deploy/bad-email",
|
|
449
446
|
"autoDiscoverXml: opts.email contains control byte");
|
|
450
447
|
}
|
|
@@ -733,14 +730,14 @@ function _validateTlsRptReport(raw, ctx) {
|
|
|
733
730
|
var sRaw = summary["total-successful-session-count"];
|
|
734
731
|
var fRaw = summary["total-failure-session-count"];
|
|
735
732
|
if (sRaw !== undefined) {
|
|
736
|
-
if (
|
|
733
|
+
if (!numericBounds.isNonNegativeFiniteInt(sRaw)) {
|
|
737
734
|
throw new TlsRptParseError("mail-tlsrpt/bad-summary",
|
|
738
735
|
"parseTlsRptReport: policies[" + i + "].summary.total-successful-session-count must be a finite non-negative integer");
|
|
739
736
|
}
|
|
740
737
|
totalSuccess += sRaw;
|
|
741
738
|
}
|
|
742
739
|
if (fRaw !== undefined) {
|
|
743
|
-
if (
|
|
740
|
+
if (!numericBounds.isNonNegativeFiniteInt(fRaw)) {
|
|
744
741
|
throw new TlsRptParseError("mail-tlsrpt/bad-summary",
|
|
745
742
|
"parseTlsRptReport: policies[" + i + "].summary.total-failure-session-count must be a finite non-negative integer");
|
|
746
743
|
}
|
|
@@ -779,7 +776,11 @@ function _validatePolicy(p, idx) {
|
|
|
779
776
|
"parseTlsRptReport: policies[" + idx + "].policy missing");
|
|
780
777
|
}
|
|
781
778
|
var pType = policy["policy-type"];
|
|
782
|
-
|
|
779
|
+
// hasOwnProperty, not `TLSRPT_POLICY_TYPES[pType]`: a remote MTA's report JSON
|
|
780
|
+
// controls pType, and a bracket lookup on the plain-object allowlist resolves
|
|
781
|
+
// inherited members ("constructor"/"__proto__"/"toString") to truthy, passing
|
|
782
|
+
// an out-of-vocabulary policy-type (prototype shadowing).
|
|
783
|
+
if (typeof pType !== "string" || !Object.prototype.hasOwnProperty.call(TLSRPT_POLICY_TYPES, pType)) {
|
|
783
784
|
throw new TlsRptParseError("mail-tlsrpt/bad-policy",
|
|
784
785
|
"parseTlsRptReport: policies[" + idx + "].policy.policy-type '" + pType +
|
|
785
786
|
"' not in {sts, tlsa, no-policy-found}");
|
|
@@ -808,7 +809,7 @@ function _validatePolicy(p, idx) {
|
|
|
808
809
|
throw new TlsRptParseError("mail-tlsrpt/bad-failure-detail",
|
|
809
810
|
"parseTlsRptReport: policies[" + idx + "].failure-details[" + k + "] must be an object");
|
|
810
811
|
}
|
|
811
|
-
if (typeof fd["result-type"] === "string" && !TLSRPT_RESULT_TYPES
|
|
812
|
+
if (typeof fd["result-type"] === "string" && !Object.prototype.hasOwnProperty.call(TLSRPT_RESULT_TYPES, fd["result-type"])) {
|
|
812
813
|
// Unknown result-type — surface as audit metadata but don't
|
|
813
814
|
// refuse; RFC 8460 §4.4 result-type registry can grow over
|
|
814
815
|
// time and we shouldn't break on new IANA entries.
|
package/lib/mail-dkim.js
CHANGED
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
*/
|
|
41
41
|
var lazyRequire = require("./lazy-require");
|
|
42
42
|
var audit = lazyRequire(function () { return require("./audit"); });
|
|
43
|
+
var structuredFields = require("./structured-fields");
|
|
43
44
|
var nodeCrypto = require("node:crypto");
|
|
44
45
|
var safeBuffer = require("./safe-buffer");
|
|
45
46
|
var validateOpts = require("./validate-opts");
|
|
@@ -85,7 +86,7 @@ var RSA_LEGACY_MIN_BITS = 1024;
|
|
|
85
86
|
function _canonHeaderRelaxed(name, value) {
|
|
86
87
|
// Lowercase name, unfold continuations, collapse internal WSP runs to
|
|
87
88
|
// single SP, strip leading/trailing WSP from value.
|
|
88
|
-
var unfolded =
|
|
89
|
+
var unfolded = structuredFields.unfoldHeaderContinuations(value);
|
|
89
90
|
var trimmed = unfolded.replace(/[ \t]+/g, " ").replace(/^[ \t]+|[ \t]+$/g, "");
|
|
90
91
|
return name.toLowerCase() + ":" + trimmed + "\r\n";
|
|
91
92
|
}
|
|
@@ -460,26 +461,13 @@ function create(opts) {
|
|
|
460
461
|
// hard dependency on it. When omitted, falls back to node:dns.
|
|
461
462
|
|
|
462
463
|
function _parseDkimTagList(value) {
|
|
463
|
-
// RFC 6376 §3.2 — tags are `key=value` separated by
|
|
464
|
-
// around `=` and `;` is
|
|
465
|
-
// DKIM-Signature
|
|
466
|
-
//
|
|
467
|
-
var
|
|
464
|
+
// RFC 6376 §3.2 — tags are `key=value` separated by `;`, no DQUOTE in
|
|
465
|
+
// the grammar. Whitespace around `=` and `;` is stripped; the signer
|
|
466
|
+
// folds the DKIM-Signature across CRLF + WSP, so unfold first, and FWS
|
|
467
|
+
// inside a tag value is ignored, so strip all value whitespace.
|
|
468
|
+
var pairs = structuredFields.parseTagList(value, { unfold: true, stripValueWs: true });
|
|
468
469
|
var tags = {};
|
|
469
|
-
var
|
|
470
|
-
for (var i = 0; i < parts.length; i += 1) {
|
|
471
|
-
var p = parts[i].trim();
|
|
472
|
-
if (p.length === 0) continue;
|
|
473
|
-
var eq = p.indexOf("=");
|
|
474
|
-
if (eq === -1) continue;
|
|
475
|
-
var key = p.slice(0, eq).trim().toLowerCase();
|
|
476
|
-
var val = p.slice(eq + 1).trim();
|
|
477
|
-
// Whitespace inside values is unfolded — RFC 6376 §3.2 says FWS
|
|
478
|
-
// (folding whitespace) is ignored within a tag value. Strip
|
|
479
|
-
// newlines + tabs while preserving the meaningful tokens.
|
|
480
|
-
val = val.replace(/\s+/g, "");
|
|
481
|
-
tags[key] = val;
|
|
482
|
-
}
|
|
470
|
+
for (var i = 0; i < pairs.length; i += 1) tags[pairs[i][0]] = pairs[i][1];
|
|
483
471
|
return tags;
|
|
484
472
|
}
|
|
485
473
|
|
|
@@ -570,25 +558,10 @@ function _getDefaultResolver() {
|
|
|
570
558
|
return _defaultResolver;
|
|
571
559
|
}
|
|
572
560
|
|
|
561
|
+
// Reshape TXT records off this module's own resolver via the shared
|
|
562
|
+
// networkDnsResolver.resolveTxt (the legacy `[[chunk1, chunk2], ...]` shape).
|
|
573
563
|
async function _safeResolveTxt(qname, operatorLookup) {
|
|
574
|
-
|
|
575
|
-
var r = await _getDefaultResolver().queryTxt(qname);
|
|
576
|
-
// Resolver returns parsed RRs; reshape to the legacy
|
|
577
|
-
// `[[chunk1, chunk2], ...]` shape so callers downstream don't care
|
|
578
|
-
// which path produced the bytes.
|
|
579
|
-
var out = [];
|
|
580
|
-
for (var i = 0; i < r.rrs.length; i += 1) {
|
|
581
|
-
var rr = r.rrs[i];
|
|
582
|
-
if (rr && rr.type === 16) { // IANA DNS qtype TXT
|
|
583
|
-
out.push(Array.isArray(rr.decoded) ? rr.decoded : [String(rr.decoded)]);
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
if (out.length === 0) {
|
|
587
|
-
var err = new Error("no TXT records for " + qname);
|
|
588
|
-
err.code = "ENODATA";
|
|
589
|
-
throw err;
|
|
590
|
-
}
|
|
591
|
-
return out;
|
|
564
|
+
return networkDnsResolver().resolveTxt(qname, operatorLookup, _getDefaultResolver());
|
|
592
565
|
}
|
|
593
566
|
|
|
594
567
|
function _resetDkimKeyCacheForTest() { DKIM_KEY_CACHE.clear(); }
|
|
@@ -1244,8 +1217,16 @@ module.exports = {
|
|
|
1244
1217
|
DKIM_MAX_SIGNATURES_PER_MESSAGE: DKIM_MAX_SIGNATURES_PER_MESSAGE,
|
|
1245
1218
|
DKIM_MAX_SIGNATURES_PER_MESSAGE_CEILING: DKIM_MAX_SIGNATURES_PER_MESSAGE_CEILING,
|
|
1246
1219
|
DKIM_CLOCK_SKEW_MS_MAX: DKIM_CLOCK_SKEW_MS_MAX,
|
|
1220
|
+
canonHeaderRelaxed: _canonHeaderRelaxed, // RFC 6376 §3.4.2 — shared by the ARC signer + DMARC/ARC verifier
|
|
1247
1221
|
_canonHeaderRelaxedForTest: _canonHeaderRelaxed,
|
|
1248
1222
|
_canonBodyRelaxedForTest: _canonBodyRelaxed,
|
|
1249
1223
|
_canonBodySimpleForTest: _canonBodySimple,
|
|
1250
1224
|
_stripBTagValueForTest: _stripBTagValue,
|
|
1225
|
+
// The header-block parser that produces the { name, value } pairs fed to the
|
|
1226
|
+
// canonicalizers. Exposed so a golden-vector test can pin its byte-exact
|
|
1227
|
+
// extraction (folding, exact name, leading-SP-preserved value) — the
|
|
1228
|
+
// sign->verify round-trip cannot, since both sides share this parser, so a
|
|
1229
|
+
// parser byte-error is self-consistent and would only surface as a signature
|
|
1230
|
+
// rejection at a real external verifier.
|
|
1231
|
+
_parseHeadersForTest: _parseHeaders,
|
|
1251
1232
|
};
|
package/lib/mail-greylist.js
CHANGED
|
@@ -103,6 +103,7 @@ var bCrypto = require("./crypto");
|
|
|
103
103
|
var lazyRequire = require("./lazy-require");
|
|
104
104
|
var ipUtils = require("./ip-utils");
|
|
105
105
|
var gateContract = require("./gate-contract");
|
|
106
|
+
var { boundedMap } = require("./bounded-map");
|
|
106
107
|
|
|
107
108
|
var audit = lazyRequire(function () { return require("./audit"); });
|
|
108
109
|
|
|
@@ -129,9 +130,6 @@ var PROFILES = Object.freeze({
|
|
|
129
130
|
|
|
130
131
|
var COMPLIANCE_POSTURES = gateContract.ALL_STRICT_POSTURES;
|
|
131
132
|
|
|
132
|
-
var IPV4_RE = /^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}$/; // allow:regex-no-length-cap — anchored + per-octet repeat-cap
|
|
133
|
-
var IPV6_RE = /^[0-9a-fA-F:]+$/; // allow:regex-no-length-cap — length-checked separately
|
|
134
|
-
|
|
135
133
|
/**
|
|
136
134
|
* @primitive b.mail.greylist.create
|
|
137
135
|
* @signature b.mail.greylist.create(opts?)
|
|
@@ -163,8 +161,8 @@ var IPV6_RE = /^[0-9a-fA-F:]+$/;
|
|
|
163
161
|
*/
|
|
164
162
|
function create(opts) {
|
|
165
163
|
opts = opts || {};
|
|
166
|
-
var profile =
|
|
167
|
-
if (!PROFILES
|
|
164
|
+
var profile = gateContract.resolveProfileName(opts, COMPLIANCE_POSTURES, DEFAULT_PROFILE);
|
|
165
|
+
if (!Object.prototype.hasOwnProperty.call(PROFILES, profile)) {
|
|
168
166
|
throw new MailGreylistError("mail-greylist/bad-profile",
|
|
169
167
|
"create: unknown profile '" + profile + "'");
|
|
170
168
|
}
|
|
@@ -325,9 +323,7 @@ function create(opts) {
|
|
|
325
323
|
* @example
|
|
326
324
|
* b.mail.greylist.compliancePosture("hipaa"); // → "strict"
|
|
327
325
|
*/
|
|
328
|
-
|
|
329
|
-
return COMPLIANCE_POSTURES[posture] || null;
|
|
330
|
-
}
|
|
326
|
+
var compliancePosture = gateContract.makePostureAccessor(COMPLIANCE_POSTURES);
|
|
331
327
|
|
|
332
328
|
function _hashFingerprint(cidr, mailFrom, rcptTo) {
|
|
333
329
|
// Namespace-hash so the on-disk key is unlinkable to the PII
|
|
@@ -341,7 +337,7 @@ function _hashFingerprint(cidr, mailFrom, rcptTo) {
|
|
|
341
337
|
}
|
|
342
338
|
|
|
343
339
|
function _cidrKey(ip, ipv4Prefix, ipv6Prefix) {
|
|
344
|
-
if (
|
|
340
|
+
if (ipUtils.isIPv4(ip)) {
|
|
345
341
|
var octets = ip.split(".").map(function (s) { return parseInt(s, 10); });
|
|
346
342
|
var prefix = Math.min(32, Math.max(0, ipv4Prefix)); // IPv4 address bit width
|
|
347
343
|
// Apply prefix: zero out the host bits.
|
|
@@ -355,7 +351,7 @@ function _cidrKey(ip, ipv4Prefix, ipv6Prefix) {
|
|
|
355
351
|
masked & 0xff,
|
|
356
352
|
].join(".") + "/" + prefix;
|
|
357
353
|
}
|
|
358
|
-
if (
|
|
354
|
+
if (ipUtils.looksLikeIPv6Hex(ip)) {
|
|
359
355
|
// Expand IPv6, then mask. Reuse the expansion approach from
|
|
360
356
|
// mail-rbl by inlining since this is a different prefix shape.
|
|
361
357
|
var expanded = ipUtils.expandIpv6Hex(ip);
|
|
@@ -390,41 +386,32 @@ function _emitAudit(auditImpl, action, metadata) {
|
|
|
390
386
|
}
|
|
391
387
|
|
|
392
388
|
function _memoryStore(maxEntries) {
|
|
393
|
-
|
|
394
|
-
|
|
389
|
+
// The in-memory backend is exactly the bounded-map ceiling primitive: cap
|
|
390
|
+
// the entry count and drop the oldest on insert at capacity. boundedMap owns
|
|
391
|
+
// the insertion-order tracking and eviction; this store layers greylist TTL
|
|
392
|
+
// semantics (putAt/ttlMs) on top.
|
|
393
|
+
var data = boundedMap({ maxEntries: maxEntries, policy: "evict-oldest" });
|
|
395
394
|
return {
|
|
396
395
|
get: async function (key) {
|
|
397
396
|
var entry = data.get(key);
|
|
398
397
|
return entry ? entry.value : null;
|
|
399
398
|
},
|
|
400
399
|
put: async function (key, value, ttlMs) {
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
// Evict oldest.
|
|
404
|
-
var oldest = insertionOrder.shift();
|
|
405
|
-
if (oldest) data.delete(oldest);
|
|
406
|
-
}
|
|
407
|
-
insertionOrder.push(key);
|
|
408
|
-
}
|
|
400
|
+
// boundedMap evicts the oldest entry when a NEW key arrives at capacity;
|
|
401
|
+
// updating an existing key neither grows the map nor evicts.
|
|
409
402
|
data.set(key, { value: value, putAt: Date.now(), ttlMs: ttlMs });
|
|
410
403
|
},
|
|
411
404
|
delete: async function (key) {
|
|
412
405
|
data.delete(key);
|
|
413
|
-
var i = insertionOrder.indexOf(key);
|
|
414
|
-
if (i !== -1) insertionOrder.splice(i, 1);
|
|
415
406
|
},
|
|
416
407
|
gc: async function (olderThanMs) {
|
|
417
408
|
var now = Date.now();
|
|
418
|
-
var
|
|
409
|
+
var expired = [];
|
|
419
410
|
data.forEach(function (entry, key) {
|
|
420
|
-
if (now - entry.putAt > olderThanMs)
|
|
421
|
-
data.delete(key);
|
|
422
|
-
var i = insertionOrder.indexOf(key);
|
|
423
|
-
if (i !== -1) insertionOrder.splice(i, 1);
|
|
424
|
-
removed += 1;
|
|
425
|
-
}
|
|
411
|
+
if (now - entry.putAt > olderThanMs) expired.push(key);
|
|
426
412
|
});
|
|
427
|
-
|
|
413
|
+
expired.forEach(function (key) { data.delete(key); });
|
|
414
|
+
return expired.length;
|
|
428
415
|
},
|
|
429
416
|
};
|
|
430
417
|
}
|
package/lib/mail-helo.js
CHANGED
|
@@ -114,7 +114,6 @@ var MailHeloError = defineClass("MailHeloError", { alwaysPermanent: true });
|
|
|
114
114
|
// trailing); §2.3.5 domain: dot-joined LDH labels; total ≤ 255 octets
|
|
115
115
|
// per RFC 1035 §2.3.4.
|
|
116
116
|
var LDH_LABEL_RE = /^[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?$/; // allow:regex-no-length-cap — anchored + per-label repeat cap matches RFC 5321 §2.3.5
|
|
117
|
-
var ADDR_LIT_V4_RE = /^\[((?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})\]$/; // allow:regex-no-length-cap — anchored + per-octet repeat cap
|
|
118
117
|
var ADDR_LIT_V6_RE = /^\[IPv6:([0-9a-fA-F:.]+)\]$/; // allow:regex-no-length-cap — IPv6 textual bounded by overall maxBytes
|
|
119
118
|
|
|
120
119
|
var DEFAULT_MAX_BYTES = 255; // RFC 1035 §2.3.4 cap
|
|
@@ -201,8 +200,8 @@ var LOCALHOST_REFUSED = Object.freeze({
|
|
|
201
200
|
*/
|
|
202
201
|
async function evaluate(ctx, opts) {
|
|
203
202
|
opts = opts || {};
|
|
204
|
-
var profile =
|
|
205
|
-
if (!PROFILES
|
|
203
|
+
var profile = gateContract.resolveProfileName(opts, COMPLIANCE_POSTURES, DEFAULT_PROFILE);
|
|
204
|
+
if (!Object.prototype.hasOwnProperty.call(PROFILES, profile)) {
|
|
206
205
|
throw new MailHeloError("mail-helo/bad-profile",
|
|
207
206
|
"evaluate: unknown profile '" + profile + "'");
|
|
208
207
|
}
|
|
@@ -233,7 +232,7 @@ async function evaluate(ctx, opts) {
|
|
|
233
232
|
}
|
|
234
233
|
|
|
235
234
|
// Classify shape: address-literal vs domain vs invalid.
|
|
236
|
-
var v4Lit = claimed.match(
|
|
235
|
+
var v4Lit = claimed.match(ipUtils.IPV4_ADDR_LITERAL_RE);
|
|
237
236
|
var v6Lit = claimed.match(ADDR_LIT_V6_RE);
|
|
238
237
|
if (v4Lit) {
|
|
239
238
|
if (v4Lit[1] !== ctx.ip) {
|
|
@@ -454,9 +453,7 @@ function _emitAudit(auditImpl, action, metadata) {
|
|
|
454
453
|
* @example
|
|
455
454
|
* b.mail.helo.compliancePosture("hipaa"); // → "strict"
|
|
456
455
|
*/
|
|
457
|
-
|
|
458
|
-
return COMPLIANCE_POSTURES[posture] || null;
|
|
459
|
-
}
|
|
456
|
+
var compliancePosture = gateContract.makePostureAccessor(COMPLIANCE_POSTURES);
|
|
460
457
|
|
|
461
458
|
module.exports = {
|
|
462
459
|
evaluate: evaluate,
|
package/lib/mail-journal.js
CHANGED
|
@@ -69,6 +69,7 @@
|
|
|
69
69
|
*/
|
|
70
70
|
|
|
71
71
|
var C = require("./constants");
|
|
72
|
+
var safeBuffer = require("./safe-buffer");
|
|
72
73
|
var validateOpts = require("./validate-opts");
|
|
73
74
|
var numericBounds = require("./numeric-bounds");
|
|
74
75
|
var safeJson = require("./safe-json");
|
|
@@ -179,9 +180,9 @@ function create(opts) {
|
|
|
179
180
|
throw new MailJournalError("mail-journal/bad-vault",
|
|
180
181
|
"b.mail.journal.create: opts.vault must be a b.vault handle");
|
|
181
182
|
}
|
|
182
|
-
if (!opts.legalHold || typeof opts.legalHold.
|
|
183
|
+
if (!opts.legalHold || typeof opts.legalHold.isHeld !== "function") {
|
|
183
184
|
throw new MailJournalError("mail-journal/bad-legal-hold",
|
|
184
|
-
"b.mail.journal.create: opts.legalHold must be a b.legalHold handle (must expose
|
|
185
|
+
"b.mail.journal.create: opts.legalHold must be a b.legalHold handle (must expose isHeld)");
|
|
185
186
|
}
|
|
186
187
|
if (!opts.db || typeof opts.db.runSql !== "function") {
|
|
187
188
|
throw new MailJournalError("mail-journal/bad-db",
|
|
@@ -251,7 +252,7 @@ function create(opts) {
|
|
|
251
252
|
async function record(req) {
|
|
252
253
|
validateOpts.requireObject(req, "mail.journal.record",
|
|
253
254
|
MailJournalError, "mail-journal/bad-record");
|
|
254
|
-
if (!ALLOWED_DIRECTIONS
|
|
255
|
+
if (!Object.prototype.hasOwnProperty.call(ALLOWED_DIRECTIONS, req.direction)) {
|
|
255
256
|
throw new MailJournalError("mail-journal/bad-direction",
|
|
256
257
|
"mail.journal.record: opts.direction must be 'inbound' | 'outbound' | 'internal'");
|
|
257
258
|
}
|
|
@@ -265,7 +266,7 @@ function create(opts) {
|
|
|
265
266
|
throw new MailJournalError("mail-journal/bad-body",
|
|
266
267
|
"mail.journal.record: opts.bodyBytes must be a Buffer");
|
|
267
268
|
}
|
|
268
|
-
if (req.bodyBytes
|
|
269
|
+
if (safeBuffer.byteLengthOf(req.bodyBytes) > C.BYTES.mib(256)) { // per-message cap
|
|
269
270
|
throw new MailJournalError("mail-journal/too-large",
|
|
270
271
|
"mail.journal.record: message " + req.bodyBytes.length + " bytes exceeds 256 MiB cap");
|
|
271
272
|
}
|
|
@@ -299,11 +300,13 @@ function create(opts) {
|
|
|
299
300
|
|
|
300
301
|
var regimesJson = JSON.stringify(opts.regimes);
|
|
301
302
|
var floorUntil = archivedAt + floorMs;
|
|
302
|
-
//
|
|
303
|
-
//
|
|
304
|
-
//
|
|
305
|
-
//
|
|
306
|
-
//
|
|
303
|
+
// Auto-derive the legal-hold flag from the registry: an entry whose actor is
|
|
304
|
+
// under an active legal hold is born held, exempt from retention expiry even
|
|
305
|
+
// after the floor passes (the doc's "every entry carries a legalHold flag").
|
|
306
|
+
// setLegalHold remains the manual override. isHeld already treats a lapsed
|
|
307
|
+
// (retainUntil-expired) hold as not-held, so a stale hold won't over-flag.
|
|
308
|
+
var actorHeld = !!opts.legalHold.isHeld(req.actorId);
|
|
309
|
+
// b.sql quotes every column + binds every value as a placeholder.
|
|
307
310
|
var insBuilt = _t("insert").values({
|
|
308
311
|
journal_id: journalId,
|
|
309
312
|
direction: req.direction,
|
|
@@ -313,6 +316,7 @@ function create(opts) {
|
|
|
313
316
|
size_bytes: sizeBytes,
|
|
314
317
|
regimes: regimesJson,
|
|
315
318
|
floor_until: floorUntil,
|
|
319
|
+
legal_hold: actorHeld ? 1 : 0,
|
|
316
320
|
storage_key: storageKey,
|
|
317
321
|
sealed_payload: sealedBlob,
|
|
318
322
|
}).toSql();
|
package/lib/mail-mdn.js
CHANGED
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
var bCrypto = require("./crypto");
|
|
32
32
|
var lazyRequire = require("./lazy-require");
|
|
33
33
|
var mimeParse = require("./mime-parse");
|
|
34
|
+
var structuredFields = require("./structured-fields");
|
|
34
35
|
var audit = lazyRequire(function () { return require("./audit"); });
|
|
35
36
|
var C = require("./constants");
|
|
36
37
|
var validateOpts = require("./validate-opts");
|
|
@@ -84,9 +85,9 @@ function _parseDisposition(value) {
|
|
|
84
85
|
}
|
|
85
86
|
var modePart = value.slice(0, semi).trim();
|
|
86
87
|
var typePart = value.slice(semi + 1).trim();
|
|
87
|
-
var
|
|
88
|
-
var actionMode =
|
|
89
|
-
var sendingMode =
|
|
88
|
+
var mkv = structuredFields.parseKeyValuePiece(modePart, "/");
|
|
89
|
+
var actionMode = mkv.key;
|
|
90
|
+
var sendingMode = mkv.value === null ? null : mkv.value.trim().toLowerCase();
|
|
90
91
|
var type = typePart.toLowerCase();
|
|
91
92
|
// Strip /modifier off the type token.
|
|
92
93
|
var typeSlash = type.indexOf("/");
|
|
@@ -161,14 +162,14 @@ function build(opts) {
|
|
|
161
162
|
validateOpts.requireNonEmptyString(opts.finalRecipient,
|
|
162
163
|
"mailMdn.build: opts.finalRecipient", MailMdnError, "mdn/missing-required-field");
|
|
163
164
|
var disposition = String(opts.disposition || "").toLowerCase();
|
|
164
|
-
if (!DISPOSITION_TYPES
|
|
165
|
+
if (!Object.prototype.hasOwnProperty.call(DISPOSITION_TYPES, disposition)) {
|
|
165
166
|
throw _err("mdn/missing-required-field",
|
|
166
167
|
"mailMdn.build: opts.disposition must be one of " +
|
|
167
168
|
Object.keys(DISPOSITION_TYPES).join(" / ") +
|
|
168
169
|
"; got '" + String(opts.disposition) + "'");
|
|
169
170
|
}
|
|
170
171
|
var actionMode = String(opts.actionMode || "manual-action").toLowerCase();
|
|
171
|
-
if (!ACTION_MODES
|
|
172
|
+
if (!Object.prototype.hasOwnProperty.call(ACTION_MODES, actionMode)) {
|
|
172
173
|
throw _err("mdn/missing-required-field",
|
|
173
174
|
"mailMdn.build: opts.actionMode must be one of " +
|
|
174
175
|
Object.keys(ACTION_MODES).join(" / ") +
|
|
@@ -178,7 +179,7 @@ function build(opts) {
|
|
|
178
179
|
// Accept the canonical mixed-case form too — RFC 3798 §3.2.6.2 uses
|
|
179
180
|
// `MDN-sent-manually` / `MDN-sent-automatically`. Compare lower-case
|
|
180
181
|
// for robustness; emit canonical mixed-case in the output.
|
|
181
|
-
if (!SENDING_MODES
|
|
182
|
+
if (!Object.prototype.hasOwnProperty.call(SENDING_MODES, sendingMode)) {
|
|
182
183
|
throw _err("mdn/missing-required-field",
|
|
183
184
|
"mailMdn.build: opts.sendingMode must be one of " +
|
|
184
185
|
"MDN-sent-manually / MDN-sent-automatically; got '" +
|
|
@@ -394,7 +395,11 @@ function parse(rawMessage) {
|
|
|
394
395
|
"mailMdn.parse: message/disposition-notification missing Disposition");
|
|
395
396
|
}
|
|
396
397
|
var disposition = _parseDisposition(dispositionField);
|
|
397
|
-
|
|
398
|
+
// hasOwnProperty: the Disposition token comes from an untrusted inbound MDN, so
|
|
399
|
+
// a bracket lookup lets "constructor"/"__proto__" pass as a valid type (proto
|
|
400
|
+
// shadowing).
|
|
401
|
+
if (!disposition || typeof disposition.type !== "string" ||
|
|
402
|
+
!Object.prototype.hasOwnProperty.call(DISPOSITION_TYPES, disposition.type)) {
|
|
398
403
|
throw _err("mdn/parse-failed",
|
|
399
404
|
"mailMdn.parse: Disposition type token not in RFC 3798 §3.2.6 vocabulary; got '" +
|
|
400
405
|
(disposition && disposition.type) + "'");
|
package/lib/mail-rbl.js
CHANGED
|
@@ -87,15 +87,12 @@ var { defineClass } = require("./framework-error");
|
|
|
87
87
|
var lazyRequire = require("./lazy-require");
|
|
88
88
|
var ipUtils = require("./ip-utils");
|
|
89
89
|
var gateContract = require("./gate-contract");
|
|
90
|
+
var codepointClass = require("./codepoint-class");
|
|
90
91
|
|
|
91
92
|
var audit = lazyRequire(function () { return require("./audit"); });
|
|
92
93
|
|
|
93
94
|
var MailRblError = defineClass("MailRblError", { alwaysPermanent: true });
|
|
94
95
|
|
|
95
|
-
var IPV4_RE = /^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}$/; // allow:regex-no-length-cap — anchored + per-octet repeat-cap
|
|
96
|
-
var IPV6_HEX_RE = /^[0-9a-fA-F:]+$/; // allow:regex-no-length-cap — checked by length cap below
|
|
97
|
-
var IPV6_MAX_LEN = 39; // max IPv6 textual length (8 groups × 4 hex + 7 colons)
|
|
98
|
-
|
|
99
96
|
var DEFAULT_TIMEOUT_MS = C.TIME.seconds(5);
|
|
100
97
|
var DEFAULT_CONCURRENCY = 8; // concurrent-query cap, not bytes
|
|
101
98
|
var DEFAULT_PROFILE = "strict";
|
|
@@ -142,8 +139,8 @@ function create(opts) {
|
|
|
142
139
|
throw new MailRblError("mail-rbl/bad-resolver",
|
|
143
140
|
"create: opts.resolver must be a b.network.dns.resolver.create() instance");
|
|
144
141
|
}
|
|
145
|
-
var profile =
|
|
146
|
-
if (!PROFILES
|
|
142
|
+
var profile = gateContract.resolveProfileName(opts, COMPLIANCE_POSTURES, DEFAULT_PROFILE);
|
|
143
|
+
if (!Object.prototype.hasOwnProperty.call(PROFILES, profile)) {
|
|
147
144
|
throw new MailRblError("mail-rbl/bad-profile",
|
|
148
145
|
"create: unknown profile '" + profile + "'");
|
|
149
146
|
}
|
|
@@ -286,11 +283,11 @@ function reverseIp(ip) {
|
|
|
286
283
|
"reverseIp: ip must be a non-empty string");
|
|
287
284
|
}
|
|
288
285
|
// IPv4 first.
|
|
289
|
-
if (
|
|
286
|
+
if (ipUtils.isIPv4(ip)) {
|
|
290
287
|
return ip.split(".").reverse().join(".");
|
|
291
288
|
}
|
|
292
289
|
// IPv6 — accept canonical / compressed forms. Expand and nibble-reverse.
|
|
293
|
-
if (
|
|
290
|
+
if (!ipUtils.looksLikeIPv6Hex(ip)) {
|
|
294
291
|
throw new MailRblError("mail-rbl/bad-input",
|
|
295
292
|
"reverseIp: '" + ip + "' is not a valid IPv4 or IPv6 address");
|
|
296
293
|
}
|
|
@@ -317,9 +314,7 @@ function reverseIp(ip) {
|
|
|
317
314
|
* @example
|
|
318
315
|
* b.mail.rbl.compliancePosture("hipaa"); // → "strict"
|
|
319
316
|
*/
|
|
320
|
-
|
|
321
|
-
return COMPLIANCE_POSTURES[posture] || null;
|
|
322
|
-
}
|
|
317
|
+
var compliancePosture = gateContract.makePostureAccessor(COMPLIANCE_POSTURES);
|
|
323
318
|
|
|
324
319
|
function _validateZoneNames(zones) {
|
|
325
320
|
for (var i = 0; i < zones.length; i += 1) {
|
|
@@ -336,7 +331,7 @@ function _validateZoneNames(zones) {
|
|
|
336
331
|
// if non-ASCII upstream).
|
|
337
332
|
for (var c = 0; c < z.length; c += 1) {
|
|
338
333
|
var cc = z.charCodeAt(c);
|
|
339
|
-
if (cc
|
|
334
|
+
if (codepointClass.isForbiddenControlChar(cc, { forbidTab: true }) || cc > 0x7e) { // RFC 1035 ASCII zone-name shape (printable ASCII only)
|
|
340
335
|
throw new MailRblError("mail-rbl/bad-zone",
|
|
341
336
|
"list zone '" + z + "' contains non-ASCII or control chars");
|
|
342
337
|
}
|
package/lib/mail-scan.js
CHANGED
|
@@ -164,7 +164,7 @@ function create(opts) {
|
|
|
164
164
|
numericBounds.requirePositiveFiniteInt(opts.port, "mail.scan.create.port",
|
|
165
165
|
MailScanError, "mail-scan/bad-port", { max: 65535 }); // TCP port-number range cap
|
|
166
166
|
var protocol = opts.protocol || DEFAULT_PROTOCOL;
|
|
167
|
-
if (!ALLOWED_PROTOCOLS
|
|
167
|
+
if (!Object.prototype.hasOwnProperty.call(ALLOWED_PROTOCOLS, protocol)) {
|
|
168
168
|
throw new MailScanError("mail-scan/bad-protocol",
|
|
169
169
|
"mail.scan.create.protocol must be 'icap' or 'clamav-instream'; got '" + protocol + "'");
|
|
170
170
|
}
|
|
@@ -173,8 +173,8 @@ function create(opts) {
|
|
|
173
173
|
validateOpts.requireNonEmptyString(service, "mail.scan.create.service",
|
|
174
174
|
MailScanError, "mail-scan/bad-service");
|
|
175
175
|
}
|
|
176
|
-
var profile =
|
|
177
|
-
if (!PROFILES
|
|
176
|
+
var profile = gateContract.resolveProfileName(opts, COMPLIANCE_POSTURES, DEFAULT_PROFILE);
|
|
177
|
+
if (!Object.prototype.hasOwnProperty.call(PROFILES, profile)) {
|
|
178
178
|
throw new MailScanError("mail-scan/bad-profile",
|
|
179
179
|
"mail.scan.create.profile: unknown '" + profile + "' (valid: strict / balanced / permissive)");
|
|
180
180
|
}
|
|
@@ -194,7 +194,7 @@ function create(opts) {
|
|
|
194
194
|
throw new MailScanError("mail-scan/bad-input",
|
|
195
195
|
"mail.scan.scan: messageBytes must be non-empty");
|
|
196
196
|
}
|
|
197
|
-
if (messageBytes
|
|
197
|
+
if (safeBuffer.byteLengthOf(messageBytes) > caps.maxMessageBytes) {
|
|
198
198
|
throw new MailScanError("mail-scan/oversize-message",
|
|
199
199
|
"mail.scan.scan: messageBytes=" + messageBytes.length + " exceeds maxMessageBytes=" +
|
|
200
200
|
caps.maxMessageBytes);
|
|
@@ -448,9 +448,7 @@ function create(opts) {
|
|
|
448
448
|
* @example
|
|
449
449
|
* b.mail.scan.compliancePosture("hipaa"); // → "strict"
|
|
450
450
|
*/
|
|
451
|
-
|
|
452
|
-
return COMPLIANCE_POSTURES[posture] || null;
|
|
453
|
-
}
|
|
451
|
+
var compliancePosture = gateContract.makePostureAccessor(COMPLIANCE_POSTURES);
|
|
454
452
|
|
|
455
453
|
function _emitScanResult(auditImpl, rv) {
|
|
456
454
|
if (rv.verdict === "clean") {
|