@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
@@ -150,9 +150,10 @@ var guardSmtpCommand = require("./guard-smtp-command");
150
150
  var guardDomain = require("./guard-domain");
151
151
  var mailServerRateLimit = require("./mail-server-rate-limit");
152
152
  var mailServerTls = require("./mail-server-tls");
153
+ var mailServerNet = require("./mail-server-net");
153
154
  var { defineClass } = require("./framework-error");
154
155
 
155
- var audit = lazyRequire(function () { return require("./audit"); });
156
+ var auditEmit = require("./audit-emit");
156
157
  // Lazy like the sibling host primitives' guard loads — the inbound
157
158
  // authentication pipeline (and the DKIM verifier whose range
158
159
  // constants the boot validation mirrors) only loads when an operator
@@ -338,14 +339,7 @@ function create(opts) {
338
339
  // handle from b.mail.server.rateLimit.create({...}) to share one
339
340
  // budget across multiple listeners, or pass an opts object to
340
341
  // override defaults.
341
- var rateLimit;
342
- if (opts.rateLimit === false) {
343
- rateLimit = mailServerRateLimit.create({ disabled: true });
344
- } else if (opts.rateLimit && typeof opts.rateLimit.admitConnection === "function") {
345
- rateLimit = opts.rateLimit;
346
- } else {
347
- rateLimit = mailServerRateLimit.create(opts.rateLimit || {});
348
- }
342
+ var rateLimit = mailServerRateLimit.resolve(opts.rateLimit);
349
343
 
350
344
  // DATA-phase message-authentication gate. `guardEnvelope: true`
351
345
  // gates with defaults; an object tunes it. Like the sibling gates
@@ -451,16 +445,12 @@ function create(opts) {
451
445
  });
452
446
  }
453
447
  function _validateDomainHardened(d, label) {
454
- if (!guardDomainProfile) return { ok: true };
455
- var verdict = guardDomain.validate(d, guardDomainProfile);
456
- if (!verdict.ok) {
457
- _emit("mail.server.mx.domain_refused", {
458
- reason: verdict.issues && verdict.issues[0] && verdict.issues[0].kind,
459
- domain: d,
460
- label: label,
461
- }, "denied");
462
- }
463
- return verdict;
448
+ return mailServerNet.validateDomainHardened(d, label, {
449
+ guardDomainProfile: guardDomainProfile,
450
+ guardDomain: guardDomain,
451
+ emit: _emit,
452
+ refusedEvent: "mail.server.mx.domain_refused",
453
+ });
464
454
  }
465
455
 
466
456
  // Pre-validate operator-supplied localDomains at boot — the same
@@ -479,35 +469,19 @@ function create(opts) {
479
469
  }
480
470
  }
481
471
 
482
- var tcpServer = null;
483
- var listening = false;
484
472
  var connections = new Set();
485
473
 
486
- function _emit(action, metadata, outcome) {
487
- try {
488
- audit().safeEmit({
489
- action: action,
490
- outcome: outcome || "success",
491
- metadata: metadata || {},
492
- });
493
- } catch (_e) { /* drop-silent — audit best-effort */ }
494
- }
474
+ var _emit = auditEmit.emit;
495
475
 
496
476
  // ---- Per-connection state machine ---------------------------------------
497
477
  function _handleConnection(socket) {
498
- var remoteAddress = socket.remoteAddress || "0.0.0.0";
499
- var admit = rateLimit.admitConnection(remoteAddress);
500
- if (!admit.ok) {
501
- // 421 4.7.0 — transient refusal; sender retries elsewhere or later.
502
- // RFC 5321 §3.8 + §4.5.4.2 (transient negative completion).
503
- _emit("mail.server.mx.rate_limit_refused",
504
- { remoteAddress: remoteAddress, reason: admit.reason }, "denied");
505
- try {
506
- socket.write("421 4.7.0 Too many connections from your IP\r\n");
507
- } catch (_e) { /* socket may already be torn down */ }
508
- try { socket.destroy(); } catch (_e2) { /* idempotent */ }
509
- return;
510
- }
478
+ // 421 4.7.0 transient refusal; sender retries elsewhere or later.
479
+ // RFC 5321 §3.8 + §4.5.4.2 (transient negative completion).
480
+ var remoteAddress = mailServerNet.admitConnection(socket, rateLimit, _emit, {
481
+ refusedEvent: "mail.server.mx.rate_limit_refused",
482
+ refusalLine: "421 4.7.0 Too many connections from your IP\r\n",
483
+ });
484
+ if (remoteAddress === null) return;
511
485
  socket.once("close", function () { rateLimit.releaseConnection(remoteAddress); });
512
486
 
513
487
  var connectionId = "mxconn-" + bCrypto.generateToken(8); // connection-id length
@@ -653,7 +627,7 @@ function create(opts) {
653
627
 
654
628
  // Command phase — byte-buffered (8BITMIME-safe).
655
629
  lineBuffer = lineBuffer.length === 0 ? chunk : Buffer.concat([lineBuffer, chunk]);
656
- if (lineBuffer.length > maxLineBytes * 4) {
630
+ if (safeBuffer.byteLengthOf(lineBuffer) > maxLineBytes * 4) {
657
631
  _writeReply(socket, REPLY_500_SYNTAX,
658
632
  "5.5.6 Line too long (>" + maxLineBytes + " bytes)");
659
633
  _closeConnection(socket);
@@ -1229,38 +1203,23 @@ function create(opts) {
1229
1203
  }
1230
1204
 
1231
1205
  // ---- Lifecycle ----------------------------------------------------------
1232
- async function listen(listenOpts) {
1233
- listenOpts = listenOpts || {};
1234
- if (listening) {
1235
- throw new MailServerMxError("mail-server-mx/already-listening",
1236
- "listen: already listening");
1237
- }
1238
- // Port 0 (ephemeral, test mode) must NOT fall back to 25 the
1239
- // `|| 25` short-circuit was a footgun on the test path.
1240
- var port = listenOpts.port === undefined ? 25 : listenOpts.port; // SMTP MX port (IANA)
1241
- var address = listenOpts.address || "0.0.0.0";
1242
- tcpServer = net.createServer(function (socket) {
1243
- _handleConnection(socket);
1244
- });
1245
- return new Promise(function (resolve, reject) {
1246
- tcpServer.once("error", reject);
1247
- tcpServer.listen(port, address, function () {
1248
- listening = true;
1249
- tcpServer.removeListener("error", reject);
1250
- _emit("mail.server.mx.listening", {
1251
- port: port, address: address,
1252
- });
1253
- resolve({ port: tcpServer.address().port, address: address });
1254
- });
1255
- });
1256
- }
1206
+ // Port 0 (ephemeral, test mode) must NOT fall back to 25 — the `|| 25`
1207
+ // short-circuit was a footgun on the test path; createTcpListener honors an
1208
+ // explicit 0 (only an OMITTED port falls back to the default).
1209
+ var _tcpListener = mailServerNet.createTcpListener(net, {
1210
+ defaultPort: 25, // SMTP MX port (IANA)
1211
+ handleConnection: _handleConnection,
1212
+ errorFactory: function (code, message) { return new MailServerMxError("mail-server-mx/" + code, message); },
1213
+ emit: _emit,
1214
+ listeningEvent: "mail.server.mx.listening",
1215
+ });
1257
1216
 
1258
1217
  async function close(closeOpts) {
1259
1218
  closeOpts = closeOpts || {};
1260
- if (!listening) return;
1219
+ if (!_tcpListener.isListening()) return;
1261
1220
  var timeoutMs = closeOpts.timeoutMs || C.TIME.seconds(30);
1262
- listening = false;
1263
- tcpServer.close();
1221
+ _tcpListener.markClosed();
1222
+ _tcpListener.getServer().close();
1264
1223
  connections.forEach(function (sock) {
1265
1224
  try { _writeReply(sock, REPLY_421_SERVICE_NOT_AVAIL, "4.3.0 Server shutting down"); }
1266
1225
  catch (_e) { /* socket already gone */ }
@@ -1279,10 +1238,10 @@ function create(opts) {
1279
1238
  function connectionCount() { return connections.size; }
1280
1239
 
1281
1240
  return {
1282
- listen: listen,
1241
+ listen: _tcpListener.listen,
1283
1242
  close: close,
1284
1243
  connectionCount: connectionCount,
1285
- _portForTest: function () { return tcpServer ? tcpServer.address().port : null; },
1244
+ _portForTest: function () { var s = _tcpListener.getServer(); return s ? s.address().port : null; },
1286
1245
  };
1287
1246
  }
1288
1247
 
@@ -0,0 +1,177 @@
1
+ "use strict";
2
+
3
+ // mail-server-net — the TCP-listener lifecycle shared by the mailbox / transfer
4
+ // servers (b.mail.server.imap / pop3 / mx / managesieve / submission). Each of
5
+ // those keeps its OWN connection set and close() drain because those diverge:
6
+ // the store servers (IMAP/POP3/ManageSieve) await a Promise-wrapped
7
+ // tcpServer.close(), while the transfer servers (MX/Submission) send an SMTP
8
+ // 421 to every live socket and drain with a timeout. What every server shares
9
+ // verbatim is the bind: refuse a double-listen, resolve the default port (never
10
+ // falling back off an explicit port 0 — the ephemeral test-bind path), create
11
+ // the listener, and arm a one-shot "error"→reject so a bind failure (EADDRINUSE,
12
+ // EACCES) rejects the listen promise instead of crashing the process. That, plus
13
+ // the listening/server state, is what createTcpListener owns.
14
+
15
+ // createTcpListener(net, cfg) — build a listener lifecycle.
16
+ // cfg.defaultPort port used when listenOpts.port is omitted (an explicit
17
+ // 0 is honored, for an ephemeral test bind).
18
+ // cfg.handleConnection (socket) => void — the server's per-connection handler.
19
+ // cfg.errorFactory (code, message) => Error — builds the typed
20
+ // "<prefix>/already-listening" double-listen error.
21
+ // cfg.emit (action, metadata) => void — the server's audit emitter.
22
+ // cfg.listeningEvent the "...listening" audit action.
23
+ // cfg.listeningExtra optional () => object merged onto the listening event
24
+ // payload (Submission reports implicitTls).
25
+ // Returns { listen, getServer, isListening, markClosed } — the server wires its
26
+ // own close() through getServer()/isListening()/markClosed().
27
+ function createTcpListener(net, cfg) {
28
+ var server = null;
29
+ var listening = false;
30
+
31
+ function listen(listenOpts) {
32
+ listenOpts = listenOpts || {};
33
+ if (listening) {
34
+ throw cfg.errorFactory("already-listening", "listen: already listening");
35
+ }
36
+ var port = listenOpts.port === undefined ? cfg.defaultPort : listenOpts.port;
37
+ var address = listenOpts.address || "0.0.0.0";
38
+ server = net.createServer(function (socket) { cfg.handleConnection(socket); });
39
+ return new Promise(function (resolve, reject) {
40
+ server.once("error", reject);
41
+ server.listen(port, address, function () {
42
+ listening = true;
43
+ server.removeListener("error", reject);
44
+ var payload = { port: port, address: address };
45
+ if (cfg.listeningExtra) {
46
+ var extra = cfg.listeningExtra();
47
+ for (var k in extra) {
48
+ if (Object.prototype.hasOwnProperty.call(extra, k)) payload[k] = extra[k];
49
+ }
50
+ }
51
+ cfg.emit(cfg.listeningEvent, payload);
52
+ resolve({ port: server.address().port, address: address });
53
+ });
54
+ });
55
+ }
56
+
57
+ // closeSimple(closeCfg) — the store-server shutdown (IMAP / POP3 / ManageSieve):
58
+ // mark closed, destroy every live socket immediately, then await the listener's
59
+ // own close before emitting the "...closed" audit. The transfer servers
60
+ // (MX / Submission) do NOT use this — they run a graceful SMTP-421 drain with a
61
+ // timeout, so they own their close() and drive it through markClosed()/getServer().
62
+ // closeCfg.connections the server's live-socket Set (destroyed + cleared).
63
+ // closeCfg.emit the server's audit emitter.
64
+ // closeCfg.closedEvent the "...closed" audit action.
65
+ function closeSimple(closeCfg) {
66
+ if (!listening) return Promise.resolve();
67
+ listening = false;
68
+ for (var s of closeCfg.connections) { try { s.destroy(); } catch (_e) { /* idempotent */ } }
69
+ closeCfg.connections.clear();
70
+ return new Promise(function (resolve) {
71
+ server.close(function () {
72
+ closeCfg.emit(closeCfg.closedEvent, {});
73
+ resolve();
74
+ });
75
+ });
76
+ }
77
+
78
+ return {
79
+ listen: listen,
80
+ closeSimple: closeSimple,
81
+ getServer: function () { return server; },
82
+ isListening: function () { return listening; },
83
+ markClosed: function () { listening = false; },
84
+ };
85
+ }
86
+
87
+ // createStoreServer(net, cfg) — the COMPLETE lifecycle of a mailbox store server
88
+ // (b.mail.server.imap / pop3 / managesieve): compose createTcpListener with the
89
+ // destroy-then-await closeSimple shutdown and return the { listen, close } a
90
+ // store server exposes. The three store servers are byte-identical here, varying
91
+ // only in port, error class, error-code prefix, and audit-event base — so the
92
+ // wiring lives here once. The transfer servers (MX / Submission) do NOT use this:
93
+ // they run a graceful SMTP-421 drain close + a richer return ({ connectionCount,
94
+ // _portForTest, ... }), so they call createTcpListener directly.
95
+ // cfg.defaultPort port used when listen() omits one (explicit 0 honored).
96
+ // cfg.handleConnection (socket) => void — the per-connection handler.
97
+ // cfg.errorClass the server's typed error constructor (code, message).
98
+ // cfg.errorCodePrefix prepended to the double-listen error code
99
+ // (e.g. "mail-server-imap/").
100
+ // cfg.emit (action, metadata) => void — the server's audit emitter.
101
+ // cfg.connections the live-socket Set (destroyed + cleared on close).
102
+ // cfg.eventBase the audit-action base; listeningEvent = eventBase +
103
+ // ".listening", closedEvent = eventBase + ".closed".
104
+ // Returns { listen, close }.
105
+ function createStoreServer(net, cfg) {
106
+ var ErrorClass = cfg.errorClass;
107
+ var listener = createTcpListener(net, {
108
+ defaultPort: cfg.defaultPort,
109
+ handleConnection: cfg.handleConnection,
110
+ errorFactory: function (code, message) { return new ErrorClass(cfg.errorCodePrefix + code, message); },
111
+ emit: cfg.emit,
112
+ listeningEvent: cfg.eventBase + ".listening",
113
+ });
114
+ function close() {
115
+ return listener.closeSimple({
116
+ connections: cfg.connections,
117
+ emit: cfg.emit,
118
+ closedEvent: cfg.eventBase + ".closed",
119
+ });
120
+ }
121
+ return { listen: listener.listen, close: close };
122
+ }
123
+
124
+ // admitConnection(socket, rateLimit, emit, cfg) — the per-connection rate-limit
125
+ // gate every mail listener's _handleConnection opens with: resolve the remote
126
+ // IP, admit it via the shared b.mail.server.rateLimit, or refuse it with a
127
+ // protocol-specific line + a "<...>.rate_limit_refused" audit (outcome "denied")
128
+ // and tear the socket down. Returns the remote address on admit, or null when
129
+ // refused — the caller does `if (addr === null) return;` then runs its own
130
+ // (protocol-specific) close handler, connection-id, tracking-set insert, and
131
+ // state machine, none of which this touches.
132
+ // cfg.refusedEvent the "<...>.rate_limit_refused" audit action.
133
+ // cfg.refusalLine the wire bytes written before destroy (IMAP "* BAD …",
134
+ // POP3 "-ERR …", SMTP "421 4.7.0 …", ManageSieve 'NO "…"').
135
+ function admitConnection(socket, rateLimit, emit, cfg) {
136
+ var remoteAddress = socket.remoteAddress || "0.0.0.0";
137
+ var admit = rateLimit.admitConnection(remoteAddress);
138
+ if (!admit.ok) {
139
+ emit(cfg.refusedEvent, { remoteAddress: remoteAddress, reason: admit.reason }, "denied");
140
+ try { socket.write(cfg.refusalLine); } catch (_e) { /* socket may be down */ }
141
+ try { socket.destroy(); } catch (_e2) { /* idempotent */ }
142
+ return null;
143
+ }
144
+ return remoteAddress;
145
+ }
146
+
147
+ // validateDomainHardened(d, label, cfg) — the hardened-domain check the MX and
148
+ // Submission transfer servers run on every HELO / MAIL FROM / RCPT TO domain.
149
+ // When a guardDomain profile is configured it validates the domain and, on
150
+ // refusal, emits a "<refusedEvent>" audit (the only per-server difference — MX
151
+ // vs Submission) before returning the verdict; with no profile it passes
152
+ // through { ok: true }. Sharing it keeps the two servers' domain-validation
153
+ // posture identical (a divergence would be a silent spoofing / IDN-homograph
154
+ // gap on one server).
155
+ // cfg.guardDomainProfile the b.guardDomain profile (falsy = validation off).
156
+ // cfg.guardDomain the b.guardDomain module (its validate(d, profile)).
157
+ // cfg.emit (action, metadata, outcome) => void audit emitter.
158
+ // cfg.refusedEvent the "<...>.domain_refused" audit action.
159
+ function validateDomainHardened(d, label, cfg) {
160
+ if (!cfg.guardDomainProfile) return { ok: true };
161
+ var verdict = cfg.guardDomain.validate(d, cfg.guardDomainProfile);
162
+ if (!verdict.ok) {
163
+ cfg.emit(cfg.refusedEvent, {
164
+ reason: verdict.issues && verdict.issues[0] && verdict.issues[0].kind,
165
+ domain: d,
166
+ label: label,
167
+ }, "denied");
168
+ }
169
+ return verdict;
170
+ }
171
+
172
+ module.exports = {
173
+ createTcpListener: createTcpListener,
174
+ createStoreServer: createStoreServer,
175
+ admitConnection: admitConnection,
176
+ validateDomainHardened: validateDomainHardened,
177
+ };
@@ -103,7 +103,7 @@
103
103
  */
104
104
 
105
105
  var net = require("node:net");
106
- var lazyRequire = require("./lazy-require");
106
+ var safeBuffer = require("./safe-buffer");
107
107
  var C = require("./constants");
108
108
  var bCrypto = require("./crypto");
109
109
  var numericBounds = require("./numeric-bounds");
@@ -111,11 +111,12 @@ var validateOpts = require("./validate-opts");
111
111
  var guardPop3Command = require("./guard-pop3-command");
112
112
  var mailServerRateLimit = require("./mail-server-rate-limit");
113
113
  var mailServerTls = require("./mail-server-tls");
114
+ var mailServerNet = require("./mail-server-net");
114
115
  var safeSmtp = require("./safe-smtp");
115
116
  var safeAsync = require("./safe-async");
116
117
  var { defineClass } = require("./framework-error");
117
118
 
118
- var audit = lazyRequire(function () { return require("./audit"); });
119
+ var auditEmit = require("./audit-emit");
119
120
 
120
121
  var MailServerPop3Error = defineClass("MailServerPop3Error", { alwaysPermanent: true });
121
122
 
@@ -229,40 +230,18 @@ function create(opts) {
229
230
  }
230
231
  }
231
232
 
232
- var rateLimit;
233
- if (opts.rateLimit === false) {
234
- rateLimit = mailServerRateLimit.create({ disabled: true });
235
- } else if (opts.rateLimit && typeof opts.rateLimit.admitConnection === "function") {
236
- rateLimit = opts.rateLimit;
237
- } else {
238
- rateLimit = mailServerRateLimit.create(opts.rateLimit || {});
239
- }
233
+ var rateLimit = mailServerRateLimit.resolve(opts.rateLimit);
240
234
 
241
- var tcpServer = null;
242
- var listening = false;
243
235
  var connections = new Set();
244
236
 
245
- function _emit(action, metadata, outcome) {
246
- try {
247
- audit().safeEmit({
248
- action: action,
249
- outcome: outcome || "success",
250
- metadata: metadata || {},
251
- });
252
- } catch (_e) { /* drop-silent */ }
253
- }
237
+ var _emit = auditEmit.emit;
254
238
 
255
239
  function _handleConnection(rawSocket) {
256
- var remoteAddress = rawSocket.remoteAddress || "0.0.0.0";
257
- var admit = rateLimit.admitConnection(remoteAddress);
258
- if (!admit.ok) {
259
- _emit("mail.server.pop3.rate_limit_refused",
260
- { remoteAddress: remoteAddress, reason: admit.reason }, "denied");
261
- try { rawSocket.write("-ERR Too many connections from your IP\r\n"); }
262
- catch (_e) { /* socket may be down */ }
263
- try { rawSocket.destroy(); } catch (_e2) { /* idempotent */ }
264
- return;
265
- }
240
+ var remoteAddress = mailServerNet.admitConnection(rawSocket, rateLimit, _emit, {
241
+ refusedEvent: "mail.server.pop3.rate_limit_refused",
242
+ refusalLine: "-ERR Too many connections from your IP\r\n",
243
+ });
244
+ if (remoteAddress === null) return;
266
245
  var connectionId = "pop3conn-" + bCrypto.generateToken(8); // connection-id length
267
246
  var socket = rawSocket;
268
247
  connections.add(socket);
@@ -312,7 +291,7 @@ function create(opts) {
312
291
  while (true) {
313
292
  var crlf = state.lineBuffer.indexOf("\r\n");
314
293
  if (crlf === -1) {
315
- if (state.lineBuffer.length > maxLineBytes) {
294
+ if (safeBuffer.byteLengthOf(state.lineBuffer) > maxLineBytes) {
316
295
  _writeErr(socket, "Line too long (cap " + maxLineBytes + ")");
317
296
  _close(socket);
318
297
  }
@@ -398,22 +377,18 @@ function create(opts) {
398
377
  // Drain pre-handshake buffer (RFC 2595 §4 + CVE-2021-33515 class
399
378
  // STLS-injection defense — any pipelined commands the client
400
379
  // queued before the upgrade are discarded; post-TLS reads fresh).
401
- // Listener-removal + idle-timeout re-arm live in the shared
402
- // upgradeSocket helper (b.mail.server.tls.upgradeSocket).
403
- state.lineBuffer = Buffer.alloc(0);
404
- // POP3 doesn't have an authPending shape (the SASL state is local
405
- // to _handleAuth), but reset tentativeUser so a USER pipelined
406
- // pre-handshake cannot bind a post-handshake PASS.
407
- state.tentativeUser = null;
408
- mailServerTls.upgradeSocket({
409
- plainSocket: socket,
380
+ // POP3 has no authPending shape (SASL state is local to
381
+ // _handleAuth), but tentativeUser is reset so a USER pipelined
382
+ // pre-handshake cannot bind a post-handshake PASS. The shared
383
+ // upgradeLineProtocol helper owns the lineBuffer drain + listener-
384
+ // strip + idle re-arm + read-pump.
385
+ mailServerTls.upgradeLineProtocol({
386
+ state: state,
387
+ socket: socket,
410
388
  secureContext: opts.tlsContext,
411
389
  idleTimeoutMs: idleTimeoutMs,
412
- onSecure: function (_tlsSocket) { state.tls = true; },
413
- onData: function (tlsSocket, chunk) {
414
- state.lineBuffer = Buffer.concat([state.lineBuffer, chunk]);
415
- _drainBuffer(state, tlsSocket);
416
- },
390
+ clearFields: ["tentativeUser"],
391
+ drain: _drainBuffer,
417
392
  onError: function (err) {
418
393
  _emit("mail.server.pop3.tls_handshake_failed",
419
394
  { connectionId: state.id, error: (err && err.message) || String(err) }, "failure");
@@ -870,43 +845,15 @@ function create(opts) {
870
845
  }
871
846
 
872
847
  // ---- Lifecycle ----------------------------------------------------------
873
- async function listen(listenOpts) {
874
- listenOpts = listenOpts || {};
875
- if (listening) {
876
- throw new MailServerPop3Error("mail-server-pop3/already-listening",
877
- "listen: already listening");
878
- }
879
- var port = listenOpts.port === undefined ? 110 : listenOpts.port; // RFC 1939 POP3 port (IANA)
880
- var address = listenOpts.address || "0.0.0.0";
881
- tcpServer = net.createServer(function (socket) { _handleConnection(socket); });
882
- return new Promise(function (resolve, reject) {
883
- tcpServer.once("error", reject);
884
- tcpServer.listen(port, address, function () {
885
- listening = true;
886
- tcpServer.removeListener("error", reject);
887
- _emit("mail.server.pop3.listening", { port: port, address: address });
888
- resolve({ port: tcpServer.address().port, address: address });
889
- });
890
- });
891
- }
892
-
893
- async function close() {
894
- if (!listening) return;
895
- listening = false;
896
- for (var s of connections) { try { s.destroy(); } catch (_e) { /* idempotent */ } }
897
- connections.clear();
898
- return new Promise(function (resolve) {
899
- tcpServer.close(function () {
900
- _emit("mail.server.pop3.closed", {});
901
- resolve();
902
- });
903
- });
904
- }
905
-
906
- return {
907
- listen: listen,
908
- close: close,
909
- };
848
+ return mailServerNet.createStoreServer(net, {
849
+ defaultPort: 110, // RFC 1939 POP3 port (IANA)
850
+ handleConnection: _handleConnection,
851
+ errorClass: MailServerPop3Error,
852
+ errorCodePrefix: "mail-server-pop3/",
853
+ emit: _emit,
854
+ connections: connections,
855
+ eventBase: "mail.server.pop3",
856
+ });
910
857
  }
911
858
 
912
859
  module.exports = {
@@ -88,6 +88,7 @@ var C = require("./constants");
88
88
  var lazyRequire = require("./lazy-require");
89
89
  var numericBounds = require("./numeric-bounds");
90
90
  var validateOpts = require("./validate-opts");
91
+ var boundedMap = require("./bounded-map");
91
92
  var { defineClass } = require("./framework-error");
92
93
 
93
94
  var audit = lazyRequire(function () { return require("./audit"); });
@@ -206,8 +207,7 @@ function create(opts) {
206
207
  { reason: "concurrent-per-ip", ip: ip, cap: cfg.maxConcurrentConnectionsPerIp });
207
208
  return { ok: false, reason: "concurrent-per-ip" };
208
209
  }
209
- var times = connectionTimes.get(ip);
210
- if (!times) { times = []; connectionTimes.set(ip, times); }
210
+ var times = boundedMap.getOrInsert(connectionTimes, ip, function () { return []; });
211
211
  _pruneWindow(times, CONNECTION_RATE_WINDOW_MS);
212
212
  if (times.length >= cfg.connectionsPerIpPerMinute) {
213
213
  _audit("mail.server.rate_limit.refused", "denied",
@@ -258,8 +258,7 @@ function create(opts) {
258
258
 
259
259
  function noteAuthFailure(ip) {
260
260
  if (cfg.disabled) return;
261
- var times = authFailureTimes.get(ip);
262
- if (!times) { times = []; authFailureTimes.set(ip, times); }
261
+ var times = boundedMap.getOrInsert(authFailureTimes, ip, function () { return []; });
263
262
  times.push(Date.now());
264
263
  }
265
264
 
@@ -288,8 +287,7 @@ function create(opts) {
288
287
 
289
288
  function noteRcptFailure(ip) {
290
289
  if (cfg.disabled) return;
291
- var times = rcptFailureTimes.get(ip);
292
- if (!times) { times = []; rcptFailureTimes.set(ip, times); }
290
+ var times = boundedMap.getOrInsert(rcptFailureTimes, ip, function () { return []; });
293
291
  times.push(Date.now());
294
292
  }
295
293
 
@@ -308,8 +306,34 @@ function create(opts) {
308
306
  };
309
307
  }
310
308
 
309
+ /**
310
+ * @primitive b.mail.server.rateLimit.resolve
311
+ * @signature b.mail.server.rateLimit.resolve(spec)
312
+ * @since 0.15.13
313
+ * @status stable
314
+ * @related b.mail.server.rateLimit.create
315
+ *
316
+ * Resolve a rate-limit `spec` into a limiter. `false` disables limiting
317
+ * (a disabled limiter that always admits), an already-built limiter — one
318
+ * exposing `admitConnection` — passes through unchanged, and anything else
319
+ * is treated as `create()` options. Every mail server (IMAP / POP3 / SMTP
320
+ * MX / Submission / ManageSieve) composes this at the top of its `create()`
321
+ * so the spec contract is identical across protocols.
322
+ *
323
+ * @example
324
+ * var b = require("blamejs");
325
+ * var rl = b.mail.server.rateLimit.resolve(false); // disabled
326
+ * // → a limiter whose admitConnection always admits
327
+ */
328
+ function resolve(spec) {
329
+ if (spec === false) return create({ disabled: true });
330
+ if (spec && typeof spec.admitConnection === "function") return spec;
331
+ return create(spec || {});
332
+ }
333
+
311
334
  module.exports = {
312
335
  create: create,
336
+ resolve: resolve,
313
337
  MailServerRateLimitError: MailServerRateLimitError,
314
338
  DEFAULTS: DEFAULTS,
315
339
  };
@@ -142,7 +142,7 @@ function create(opts) {
142
142
  validateOpts.requireNonEmptyString(opts.protocol,
143
143
  "b.mail.serverRegistry.create: protocol", MailServerRegistryError,
144
144
  "mail-server-registry/bad-protocol");
145
- if (!CATALOGUE[opts.protocol]) {
145
+ if (!Object.prototype.hasOwnProperty.call(CATALOGUE, opts.protocol)) {
146
146
  throw new MailServerRegistryError("mail-server-registry/unknown-protocol",
147
147
  "create: protocol must be 'imap', 'jmap', or 'managesieve' (got '" + opts.protocol + "')");
148
148
  }