@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.
Files changed (365) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/index.js +2 -0
  3. package/lib/a2a-tasks.js +45 -29
  4. package/lib/acme.js +6 -5
  5. package/lib/agent-event-bus.js +18 -4
  6. package/lib/agent-idempotency.js +7 -6
  7. package/lib/agent-orchestrator.js +2 -5
  8. package/lib/agent-saga.js +3 -5
  9. package/lib/agent-snapshot.js +32 -2
  10. package/lib/agent-tenant.js +2 -5
  11. package/lib/ai-adverse-decision.js +2 -15
  12. package/lib/ai-aedt-bias-audit.js +2 -1
  13. package/lib/ai-capability.js +1 -6
  14. package/lib/ai-content-detect.js +1 -3
  15. package/lib/ai-dp.js +1 -5
  16. package/lib/ai-frontier-protocol.js +1 -1
  17. package/lib/ai-input.js +2 -2
  18. package/lib/ai-model-manifest.js +1 -1
  19. package/lib/ai-output.js +16 -7
  20. package/lib/ai-pref.js +3 -8
  21. package/lib/ai-quota.js +3 -14
  22. package/lib/api-key.js +37 -28
  23. package/lib/api-snapshot.js +4 -1
  24. package/lib/app-shutdown.js +7 -1
  25. package/lib/archive-adapters.js +2 -4
  26. package/lib/archive-entry-policy.js +32 -0
  27. package/lib/archive-gz.js +9 -0
  28. package/lib/archive-read.js +14 -24
  29. package/lib/archive-tar-read.js +56 -24
  30. package/lib/archive.js +6 -12
  31. package/lib/arg-parser.js +7 -6
  32. package/lib/asn1-der.js +70 -22
  33. package/lib/asyncapi-traits.js +2 -6
  34. package/lib/atomic-file.js +312 -33
  35. package/lib/audit-chain.js +183 -54
  36. package/lib/audit-daily-review.js +36 -16
  37. package/lib/audit-emit.js +82 -0
  38. package/lib/audit-sign.js +258 -0
  39. package/lib/audit-tools.js +108 -23
  40. package/lib/audit.js +91 -2
  41. package/lib/auth/access-lock.js +7 -29
  42. package/lib/auth/bot-challenge.js +1 -1
  43. package/lib/auth/ciba.js +43 -4
  44. package/lib/auth/dpop.js +29 -36
  45. package/lib/auth/fido-mds3.js +14 -16
  46. package/lib/auth/jwt-external.js +26 -8
  47. package/lib/auth/jwt.js +15 -17
  48. package/lib/auth/lockout.js +24 -27
  49. package/lib/auth/oauth.js +67 -45
  50. package/lib/auth/oid4vci.js +55 -32
  51. package/lib/auth/oid4vp.js +3 -2
  52. package/lib/auth/openid-federation.js +6 -6
  53. package/lib/auth/passkey.js +4 -4
  54. package/lib/auth/password.js +8 -2
  55. package/lib/auth/saml.js +70 -40
  56. package/lib/auth/sd-jwt-vc-holder.js +2 -14
  57. package/lib/auth/sd-jwt-vc-issuer.js +2 -14
  58. package/lib/auth/sd-jwt-vc.js +25 -5
  59. package/lib/auth/status-list.js +21 -9
  60. package/lib/auth/step-up.js +15 -13
  61. package/lib/auth-bot-challenge.js +27 -39
  62. package/lib/backup/bundle.js +18 -20
  63. package/lib/backup/crypto.js +23 -8
  64. package/lib/backup/index.js +55 -67
  65. package/lib/backup/manifest.js +7 -1
  66. package/lib/bounded-map.js +112 -1
  67. package/lib/breach-deadline.js +1 -11
  68. package/lib/break-glass.js +41 -22
  69. package/lib/cache.js +7 -18
  70. package/lib/cbor.js +36 -12
  71. package/lib/cdn-cache-control.js +15 -12
  72. package/lib/cert.js +8 -13
  73. package/lib/chain-writer.js +162 -47
  74. package/lib/cli.js +10 -2
  75. package/lib/client-hints.js +7 -9
  76. package/lib/cloud-events.js +43 -33
  77. package/lib/cluster-storage.js +9 -41
  78. package/lib/cms-codec.js +2 -1
  79. package/lib/codepoint-class.js +84 -1
  80. package/lib/compliance-ai-act-logging.js +1 -6
  81. package/lib/compliance-ai-act-transparency.js +2 -4
  82. package/lib/compliance-eaa.js +2 -12
  83. package/lib/compliance-sanctions-fetcher.js +21 -28
  84. package/lib/compliance-sanctions.js +11 -21
  85. package/lib/compliance.js +3 -10
  86. package/lib/config-drift.js +24 -20
  87. package/lib/content-credentials.js +11 -8
  88. package/lib/content-digest.js +21 -12
  89. package/lib/cookies.js +15 -13
  90. package/lib/cose.js +39 -11
  91. package/lib/cra-report.js +1 -11
  92. package/lib/crdt.js +2 -1
  93. package/lib/crypto-field.js +34 -33
  94. package/lib/crypto.js +138 -7
  95. package/lib/csp.js +239 -3
  96. package/lib/daemon.js +46 -42
  97. package/lib/data-act.js +46 -13
  98. package/lib/db-file-lifecycle.js +14 -6
  99. package/lib/db-query.js +75 -49
  100. package/lib/db.js +66 -27
  101. package/lib/dbsc.js +5 -6
  102. package/lib/ddl-change-control.js +18 -14
  103. package/lib/deprecate.js +4 -5
  104. package/lib/did.js +6 -2
  105. package/lib/dora.js +43 -13
  106. package/lib/dr-runbook.js +1 -1
  107. package/lib/dsa.js +2 -1
  108. package/lib/dsr.js +30 -20
  109. package/lib/early-hints.js +19 -0
  110. package/lib/eat.js +5 -1
  111. package/lib/external-db-migrate.js +21 -22
  112. package/lib/external-db.js +74 -30
  113. package/lib/fda-21cfr11.js +42 -13
  114. package/lib/fdx.js +22 -18
  115. package/lib/file-upload.js +80 -66
  116. package/lib/flag-evaluation-context.js +5 -8
  117. package/lib/flag-providers.js +6 -2
  118. package/lib/forms.js +1 -1
  119. package/lib/framework-error.js +12 -0
  120. package/lib/framework-schema.js +19 -0
  121. package/lib/fsm.js +7 -12
  122. package/lib/gate-contract.js +911 -39
  123. package/lib/gdpr-ropa.js +22 -22
  124. package/lib/graphql-federation.js +18 -5
  125. package/lib/guard-agent-registry.js +2 -1
  126. package/lib/guard-all.js +3 -2
  127. package/lib/guard-archive.js +9 -30
  128. package/lib/guard-auth.js +23 -80
  129. package/lib/guard-cidr.js +20 -96
  130. package/lib/guard-csv.js +135 -196
  131. package/lib/guard-domain.js +23 -106
  132. package/lib/guard-dsn.js +17 -14
  133. package/lib/guard-email.js +46 -53
  134. package/lib/guard-envelope.js +2 -2
  135. package/lib/guard-event-bus-topic.js +2 -1
  136. package/lib/guard-filename.js +12 -60
  137. package/lib/guard-graphql.js +28 -75
  138. package/lib/guard-html-wcag.js +15 -2
  139. package/lib/guard-html.js +74 -128
  140. package/lib/guard-idempotency-key.js +2 -1
  141. package/lib/guard-image.js +280 -77
  142. package/lib/guard-imap-command.js +9 -10
  143. package/lib/guard-jmap.js +1 -1
  144. package/lib/guard-json.js +101 -109
  145. package/lib/guard-jsonpath.js +20 -88
  146. package/lib/guard-jwt.js +32 -114
  147. package/lib/guard-list-id.js +2 -7
  148. package/lib/guard-list-unsubscribe.js +2 -7
  149. package/lib/guard-mail-compose.js +5 -6
  150. package/lib/guard-mail-move.js +3 -2
  151. package/lib/guard-mail-query.js +5 -3
  152. package/lib/guard-mail-sieve.js +6 -7
  153. package/lib/guard-managesieve-command.js +6 -5
  154. package/lib/guard-markdown.js +76 -110
  155. package/lib/guard-message-id.js +5 -6
  156. package/lib/guard-mime.js +20 -99
  157. package/lib/guard-oauth.js +19 -73
  158. package/lib/guard-pdf.js +65 -72
  159. package/lib/guard-pop3-command.js +13 -14
  160. package/lib/guard-posture-chain.js +2 -1
  161. package/lib/guard-regex.js +24 -99
  162. package/lib/guard-saga-config.js +2 -1
  163. package/lib/guard-shell.js +22 -87
  164. package/lib/guard-smtp-command.js +9 -12
  165. package/lib/guard-sql.js +15 -13
  166. package/lib/guard-stream-args.js +2 -1
  167. package/lib/guard-svg.js +103 -149
  168. package/lib/guard-template.js +23 -80
  169. package/lib/guard-tenant-id.js +2 -1
  170. package/lib/guard-text.js +592 -0
  171. package/lib/guard-time.js +27 -95
  172. package/lib/guard-uuid.js +21 -93
  173. package/lib/guard-xml.js +76 -106
  174. package/lib/guard-yaml.js +24 -60
  175. package/lib/html-balance.js +7 -3
  176. package/lib/http-client-cache.js +5 -12
  177. package/lib/http-client-cookie-jar.js +35 -16
  178. package/lib/http-client.js +233 -74
  179. package/lib/http-message-signature.js +3 -2
  180. package/lib/i18n-messageformat.js +5 -7
  181. package/lib/i18n.js +83 -26
  182. package/lib/iab-tcf.js +3 -2
  183. package/lib/importmap-integrity.js +41 -1
  184. package/lib/inbox.js +8 -8
  185. package/lib/incident-report.js +12 -27
  186. package/lib/ip-utils.js +49 -6
  187. package/lib/jobs.js +3 -2
  188. package/lib/json-patch.js +1 -1
  189. package/lib/json-path.js +24 -3
  190. package/lib/jtd.js +2 -2
  191. package/lib/keychain.js +6 -18
  192. package/lib/legal-hold.js +30 -23
  193. package/lib/log-stream-cloudwatch.js +44 -112
  194. package/lib/log-stream-otlp-grpc.js +17 -14
  195. package/lib/log-stream-otlp.js +16 -80
  196. package/lib/log-stream-webhook.js +20 -92
  197. package/lib/log.js +2 -2
  198. package/lib/mail-agent.js +2 -2
  199. package/lib/mail-arc-sign.js +8 -3
  200. package/lib/mail-arf.js +4 -3
  201. package/lib/mail-auth.js +43 -69
  202. package/lib/mail-bimi.js +35 -55
  203. package/lib/mail-bounce.js +3 -3
  204. package/lib/mail-crypto-pgp.js +3 -2
  205. package/lib/mail-crypto-smime.js +71 -6
  206. package/lib/mail-dav.js +8 -35
  207. package/lib/mail-deploy.js +15 -14
  208. package/lib/mail-dkim.js +19 -38
  209. package/lib/mail-greylist.js +17 -30
  210. package/lib/mail-helo.js +4 -7
  211. package/lib/mail-journal.js +13 -9
  212. package/lib/mail-mdn.js +12 -7
  213. package/lib/mail-rbl.js +7 -12
  214. package/lib/mail-scan.js +5 -7
  215. package/lib/mail-send-deliver.js +33 -20
  216. package/lib/mail-server-imap.js +32 -87
  217. package/lib/mail-server-jmap.js +39 -52
  218. package/lib/mail-server-managesieve.js +29 -83
  219. package/lib/mail-server-mx.js +33 -74
  220. package/lib/mail-server-net.js +177 -0
  221. package/lib/mail-server-pop3.js +30 -83
  222. package/lib/mail-server-rate-limit.js +30 -6
  223. package/lib/mail-server-registry.js +1 -1
  224. package/lib/mail-server-submission.js +34 -73
  225. package/lib/mail-server-tls.js +98 -2
  226. package/lib/mail-spam-score.js +9 -12
  227. package/lib/mail-store-fts.js +1 -1
  228. package/lib/mail-store.js +3 -2
  229. package/lib/mail.js +28 -13
  230. package/lib/markup-escape.js +31 -0
  231. package/lib/markup-tokenizer.js +78 -0
  232. package/lib/mcp-tool-registry.js +26 -23
  233. package/lib/mcp.js +7 -5
  234. package/lib/mdoc.js +36 -14
  235. package/lib/metrics.js +46 -33
  236. package/lib/middleware/age-gate.js +3 -23
  237. package/lib/middleware/api-encrypt.js +13 -24
  238. package/lib/middleware/assetlinks.js +2 -7
  239. package/lib/middleware/bearer-auth.js +2 -1
  240. package/lib/middleware/body-parser.js +41 -52
  241. package/lib/middleware/bot-disclose.js +7 -10
  242. package/lib/middleware/bot-guard.js +28 -28
  243. package/lib/middleware/clear-site-data.js +5 -1
  244. package/lib/middleware/compression.js +9 -0
  245. package/lib/middleware/cors.js +32 -23
  246. package/lib/middleware/csrf-protect.js +73 -23
  247. package/lib/middleware/daily-byte-quota.js +12 -30
  248. package/lib/middleware/deny-response.js +19 -1
  249. package/lib/middleware/fetch-metadata.js +35 -4
  250. package/lib/middleware/idempotency-key.js +4 -20
  251. package/lib/middleware/network-allowlist.js +61 -30
  252. package/lib/middleware/rate-limit.js +45 -44
  253. package/lib/middleware/scim-server.js +5 -3
  254. package/lib/middleware/security-headers.js +33 -7
  255. package/lib/middleware/security-txt.js +2 -6
  256. package/lib/middleware/speculation-rules.js +6 -3
  257. package/lib/middleware/tus-upload.js +14 -29
  258. package/lib/middleware/web-app-manifest.js +2 -7
  259. package/lib/migrations.js +4 -13
  260. package/lib/mime-parse.js +34 -17
  261. package/lib/module-loader.js +44 -0
  262. package/lib/money.js +105 -0
  263. package/lib/mtls-ca.js +125 -41
  264. package/lib/network-byte-quota.js +4 -18
  265. package/lib/network-dane.js +8 -6
  266. package/lib/network-dns-resolver.js +98 -7
  267. package/lib/network-dns.js +9 -2
  268. package/lib/network-dnssec.js +18 -17
  269. package/lib/network-heartbeat.js +3 -2
  270. package/lib/network-smtp-policy.js +52 -46
  271. package/lib/network-tls.js +67 -47
  272. package/lib/network-tsig.js +2 -2
  273. package/lib/nis2-report.js +2 -12
  274. package/lib/nist-crosswalk.js +1 -1
  275. package/lib/nonce-store.js +81 -3
  276. package/lib/ntp-check.js +28 -0
  277. package/lib/numeric-bounds.js +31 -8
  278. package/lib/object-store/azure-blob-bucket-ops.js +2 -6
  279. package/lib/object-store/azure-blob.js +6 -47
  280. package/lib/object-store/gcs-bucket-ops.js +4 -2
  281. package/lib/object-store/gcs.js +14 -75
  282. package/lib/object-store/http-put.js +2 -7
  283. package/lib/object-store/http-request.js +157 -0
  284. package/lib/object-store/local.js +37 -17
  285. package/lib/object-store/sigv4-bucket-ops.js +2 -1
  286. package/lib/object-store/sigv4.js +10 -79
  287. package/lib/observability-otlp-exporter.js +29 -29
  288. package/lib/observability.js +95 -0
  289. package/lib/openapi-paths-builder.js +7 -3
  290. package/lib/otel-export.js +7 -10
  291. package/lib/outbox.js +54 -41
  292. package/lib/pagination.js +11 -15
  293. package/lib/parsers/safe-env.js +5 -4
  294. package/lib/parsers/safe-ini.js +10 -4
  295. package/lib/parsers/safe-toml.js +11 -9
  296. package/lib/parsers/safe-xml.js +3 -3
  297. package/lib/parsers/safe-yaml.js +28 -9
  298. package/lib/pick.js +63 -5
  299. package/lib/pipl-cn.js +69 -59
  300. package/lib/privacy-pass.js +8 -6
  301. package/lib/problem-details.js +2 -5
  302. package/lib/pubsub.js +4 -8
  303. package/lib/queue-local.js +10 -3
  304. package/lib/redact.js +9 -4
  305. package/lib/render.js +4 -2
  306. package/lib/request-helpers.js +334 -29
  307. package/lib/resource-access-lock.js +3 -3
  308. package/lib/restore-bundle.js +46 -18
  309. package/lib/restore-rollback.js +10 -4
  310. package/lib/restore.js +24 -22
  311. package/lib/retention.js +23 -9
  312. package/lib/router.js +17 -4
  313. package/lib/safe-async.js +457 -0
  314. package/lib/safe-buffer.js +137 -6
  315. package/lib/safe-decompress.js +2 -1
  316. package/lib/safe-dns.js +2 -1
  317. package/lib/safe-ical.js +14 -28
  318. package/lib/safe-icap.js +1 -1
  319. package/lib/safe-json.js +51 -8
  320. package/lib/safe-jsonpath.js +6 -5
  321. package/lib/safe-mime.js +25 -29
  322. package/lib/safe-redirect.js +2 -5
  323. package/lib/safe-schema.js +7 -6
  324. package/lib/safe-sieve.js +1 -1
  325. package/lib/safe-sql.js +126 -0
  326. package/lib/safe-vcard.js +13 -27
  327. package/lib/sandbox-worker.js +15 -2
  328. package/lib/sandbox.js +4 -3
  329. package/lib/scheduler.js +22 -13
  330. package/lib/sec-cyber.js +82 -37
  331. package/lib/seeders.js +4 -11
  332. package/lib/self-update-standalone-verifier.js +16 -0
  333. package/lib/self-update.js +92 -69
  334. package/lib/session-device-binding.js +112 -66
  335. package/lib/session.js +219 -7
  336. package/lib/sql.js +228 -81
  337. package/lib/sse.js +6 -10
  338. package/lib/static.js +201 -111
  339. package/lib/storage.js +4 -2
  340. package/lib/structured-fields.js +275 -16
  341. package/lib/template.js +7 -5
  342. package/lib/tenant-quota.js +53 -38
  343. package/lib/tsa.js +16 -17
  344. package/lib/validate-opts.js +154 -8
  345. package/lib/vault/index.js +5 -0
  346. package/lib/vault/passphrase-ops.js +22 -26
  347. package/lib/vault/passphrase-source.js +8 -3
  348. package/lib/vault/rotate.js +13 -18
  349. package/lib/vault/seal-pem-file.js +34 -49
  350. package/lib/vault-aad.js +3 -2
  351. package/lib/vc.js +3 -8
  352. package/lib/vendor/MANIFEST.json +10 -10
  353. package/lib/vendor/public-suffix-list.dat +23 -10
  354. package/lib/vendor/public-suffix-list.data.js +5498 -5494
  355. package/lib/vex.js +35 -13
  356. package/lib/web-push-vapid.js +23 -20
  357. package/lib/webhook-dispatcher.js +706 -0
  358. package/lib/webhook.js +59 -19
  359. package/lib/websocket-channels.js +9 -7
  360. package/lib/websocket.js +8 -4
  361. package/lib/worm.js +3 -4
  362. package/lib/ws-client.js +65 -56
  363. package/lib/x509-chain.js +44 -0
  364. package/package.json +1 -1
  365. 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 audit = lazyRequire(function () { return require("./audit"); });
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
- function _emit(action, metadata, outcome) {
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
- // safeBuffer.boundedChunkCollector enforces maxBytes inside
303
- // push(); any chunk that would overflow throws + we reject the
304
- // promise. The body is bounded BEFORE the cap is reached so the
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 String(s)
374
- .replace(/&/g, "&")
375
- .replace(/</g, "&lt;")
376
- .replace(/>/g, "&gt;")
377
- .replace(/"/g, "&quot;")
378
- .replace(/'/g, "&apos;");
351
+ return markupEscape(s, { apos: "&apos;" });
379
352
  }
380
353
 
381
354
  // Render a small subset of well-known DAV / CalDAV / CardDAV
@@ -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 < 0x20 || c === 0x7F || c === 0x22) return false; // refuse C0 / DEL / "
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 String(s)
83
- .replace(/&/g, "&amp;")
84
- .replace(/</g, "&lt;")
85
- .replace(/>/g, "&gt;")
86
- .replace(/"/g, "&quot;")
87
- .replace(/'/g, "&apos;");
84
+ return markupEscape(s, { apos: "&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[opts.mode]) {
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 < 0x20 || c === 0x7F || c === 0x22) { // C0 / DEL / "
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 < 0x20 || c === 0x7F) { // C0 / DEL
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 (typeof sRaw !== "number" || !isFinite(sRaw) || sRaw < 0 || Math.floor(sRaw) !== sRaw) {
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 (typeof fRaw !== "number" || !isFinite(fRaw) || fRaw < 0 || Math.floor(fRaw) !== fRaw) {
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
- if (!TLSRPT_POLICY_TYPES[pType]) {
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[fd["result-type"]]) {
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 = String(value).replace(/\r?\n[ \t]+/g, " ");
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 `;`. Whitespace
464
- // around `=` and `;` is allowed and stripped. The signer folds the
465
- // DKIM-Signature header across CRLF + WSP; unfold first so tag
466
- // boundaries land in the right place.
467
- var unfolded = String(value).replace(/\r?\n[ \t]+/g, " ");
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 parts = unfolded.split(";");
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
- if (operatorLookup) return operatorLookup(qname, "TXT");
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
  };
@@ -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 = opts.profile || (opts.posture && COMPLIANCE_POSTURES[opts.posture]) || DEFAULT_PROFILE;
167
- if (!PROFILES[profile]) {
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
- function compliancePosture(posture) {
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 (IPV4_RE.test(ip)) {
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 (IPV6_RE.test(ip)) {
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
- var data = new Map();
394
- var insertionOrder = [];
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
- if (!data.has(key)) {
402
- if (data.size >= maxEntries) {
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 removed = 0;
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
- return removed;
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 = opts.profile || (opts.posture && COMPLIANCE_POSTURES[opts.posture]) || DEFAULT_PROFILE;
205
- if (!PROFILES[profile]) {
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(ADDR_LIT_V4_RE);
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
- function compliancePosture(posture) {
458
- return COMPLIANCE_POSTURES[posture] || null;
459
- }
456
+ var compliancePosture = gateContract.makePostureAccessor(COMPLIANCE_POSTURES);
460
457
 
461
458
  module.exports = {
462
459
  evaluate: evaluate,
@@ -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.isOnHold !== "function") {
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 isOnHold)");
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[req.direction]) {
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.length > C.BYTES.mib(256)) { // per-message cap
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
- // legal_hold is omitted from the INSERT so the column's
303
- // `NOT NULL DEFAULT 0` applies (the prior inline `0` SQL literal) b.sql
304
- // binds every value it is given, so leaving the column out keeps the row
305
- // unsealed-at-rest default without binding a redundant constant. b.sql
306
- // quotes every column + binds every remaining value as a placeholder.
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 slash = modePart.indexOf("/");
88
- var actionMode = slash === -1 ? modePart.toLowerCase() : modePart.slice(0, slash).trim().toLowerCase();
89
- var sendingMode = slash === -1 ? null : modePart.slice(slash + 1).trim().toLowerCase();
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[disposition]) {
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[actionMode]) {
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[sendingMode]) {
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
- if (!disposition || !DISPOSITION_TYPES[disposition.type]) {
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 = opts.profile || (opts.posture && COMPLIANCE_POSTURES[opts.posture]) || DEFAULT_PROFILE;
146
- if (!PROFILES[profile]) {
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 (IPV4_RE.test(ip)) {
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 (ip.length > IPV6_MAX_LEN || !IPV6_HEX_RE.test(ip)) {
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
- function compliancePosture(posture) {
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 < 0x20 || cc === 0x7f || cc > 0x7e) { // RFC 1035 ASCII zone-name shape
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[protocol]) {
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 = opts.profile || (opts.posture && COMPLIANCE_POSTURES[opts.posture]) || DEFAULT_PROFILE;
177
- if (!PROFILES[profile]) {
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.length > caps.maxMessageBytes) {
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
- function compliancePosture(posture) {
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") {