@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
@@ -103,7 +103,6 @@
103
103
 
104
104
  var net = require("node:net");
105
105
  var nodeTls = require("node:tls");
106
- var lazyRequire = require("./lazy-require");
107
106
  var C = require("./constants");
108
107
  var bCrypto = require("./crypto");
109
108
  var numericBounds = require("./numeric-bounds");
@@ -115,9 +114,10 @@ var guardSmtpCommand = require("./guard-smtp-command");
115
114
  var guardDomain = require("./guard-domain");
116
115
  var mailServerRateLimit = require("./mail-server-rate-limit");
117
116
  var mailServerTls = require("./mail-server-tls");
117
+ var mailServerNet = require("./mail-server-net");
118
118
  var { defineClass } = require("./framework-error");
119
119
 
120
- var audit = lazyRequire(function () { return require("./audit"); });
120
+ var auditEmit = require("./audit-emit");
121
121
 
122
122
  var MailServerSubmissionError = defineClass("MailServerSubmissionError", { alwaysPermanent: true });
123
123
 
@@ -341,14 +341,7 @@ function create(opts) {
341
341
  // Default-on per-IP rate limit (see lib/mail-server-rate-limit.js).
342
342
  // Operators pass `rateLimit: false` to disable, a rate-limit handle
343
343
  // to share across listeners, or an opts object to override defaults.
344
- var rateLimit;
345
- if (opts.rateLimit === false) {
346
- rateLimit = mailServerRateLimit.create({ disabled: true });
347
- } else if (opts.rateLimit && typeof opts.rateLimit.admitConnection === "function") {
348
- rateLimit = opts.rateLimit;
349
- } else {
350
- rateLimit = mailServerRateLimit.create(opts.rateLimit || {});
351
- }
344
+ var rateLimit = mailServerRateLimit.resolve(opts.rateLimit);
352
345
 
353
346
  // Default-on guardDomain hardening for HELO / MAIL FROM / RCPT TO.
354
347
  // Same posture as mail-server-mx — IDN homograph / Punycode-spoof
@@ -367,45 +360,25 @@ function create(opts) {
367
360
  });
368
361
  }
369
362
  function _validateDomainHardened(d, label) {
370
- if (!guardDomainProfile) return { ok: true };
371
- var verdict = guardDomain.validate(d, guardDomainProfile);
372
- if (!verdict.ok) {
373
- _emit("mail.server.submission.domain_refused", {
374
- reason: verdict.issues && verdict.issues[0] && verdict.issues[0].kind,
375
- domain: d,
376
- label: label,
377
- }, "denied");
378
- }
379
- return verdict;
363
+ return mailServerNet.validateDomainHardened(d, label, {
364
+ guardDomainProfile: guardDomainProfile,
365
+ guardDomain: guardDomain,
366
+ emit: _emit,
367
+ refusedEvent: "mail.server.submission.domain_refused",
368
+ });
380
369
  }
381
370
 
382
- var tcpServer = null;
383
- var listening = false;
384
371
  var connections = new Set();
385
372
 
386
- function _emit(action, metadata, outcome) {
387
- try {
388
- audit().safeEmit({
389
- action: action,
390
- outcome: outcome || "success",
391
- metadata: metadata || {},
392
- });
393
- } catch (_e) { /* drop-silent */ }
394
- }
373
+ var _emit = auditEmit.emit;
395
374
 
396
375
  function _handleConnection(rawSocket) {
397
- var remoteAddress = rawSocket.remoteAddress || "0.0.0.0";
398
- var admit = rateLimit.admitConnection(remoteAddress);
399
- if (!admit.ok) {
400
- // 421 4.7.0 transient; sender retries elsewhere.
401
- _emit("mail.server.submission.rate_limit_refused",
402
- { remoteAddress: remoteAddress, reason: admit.reason }, "denied");
403
- try {
404
- rawSocket.write("421 4.7.0 Too many connections from your IP\r\n");
405
- } catch (_e) { /* socket may already be torn down */ }
406
- try { rawSocket.destroy(); } catch (_e2) { /* idempotent */ }
407
- return;
408
- }
376
+ // 421 4.7.0 transient; sender retries elsewhere.
377
+ var remoteAddress = mailServerNet.admitConnection(rawSocket, rateLimit, _emit, {
378
+ refusedEvent: "mail.server.submission.rate_limit_refused",
379
+ refusalLine: "421 4.7.0 Too many connections from your IP\r\n",
380
+ });
381
+ if (remoteAddress === null) return;
409
382
  rawSocket.once("close", function () { rateLimit.releaseConnection(remoteAddress); });
410
383
 
411
384
  var connectionId = "submitconn-" + bCrypto.generateToken(8); // connection-id length
@@ -570,7 +543,7 @@ function create(opts) {
570
543
  }
571
544
 
572
545
  lineBuffer = lineBuffer.length === 0 ? chunk : Buffer.concat([lineBuffer, chunk]);
573
- if (lineBuffer.length > maxLineBytes * 4) {
546
+ if (safeBuffer.byteLengthOf(lineBuffer) > maxLineBytes * 4) {
574
547
  _writeReply(socket, REPLY_500_SYNTAX,
575
548
  "5.5.6 Line too long (>" + maxLineBytes + " bytes)");
576
549
  _closeConnection(socket);
@@ -1310,37 +1283,25 @@ function create(opts) {
1310
1283
  }
1311
1284
  }
1312
1285
 
1313
- async function listen(listenOpts) {
1314
- listenOpts = listenOpts || {};
1315
- if (listening) {
1316
- throw new MailServerSubmissionError("mail-server-submission/already-listening",
1317
- "listen: already listening");
1318
- }
1319
- // Port 0 (ephemeral, test mode) must NOT fall back to the protocol
1320
- // default the `|| <default>` short-circuit was a footgun on the
1321
- // test path.
1322
- var defaultPort = implicitTls ? 465 : 587; // RFC 8314 implicit-TLS / RFC 6409 submission ports
1323
- var port = listenOpts.port === undefined ? defaultPort : listenOpts.port;
1324
- var address = listenOpts.address || "0.0.0.0";
1325
- tcpServer = net.createServer(function (socket) { _handleConnection(socket); });
1326
- return new Promise(function (resolve, reject) {
1327
- tcpServer.once("error", reject);
1328
- tcpServer.listen(port, address, function () {
1329
- listening = true;
1330
- tcpServer.removeListener("error", reject);
1331
- _emit("mail.server.submission.listening",
1332
- { port: port, address: address, implicitTls: implicitTls });
1333
- resolve({ port: tcpServer.address().port, address: address });
1334
- });
1335
- });
1336
- }
1286
+ // Port 0 (ephemeral, test mode) must NOT fall back to the protocol default —
1287
+ // the `|| <default>` short-circuit was a footgun on the test path;
1288
+ // createTcpListener honors an explicit 0 (only an OMITTED port defaults). The
1289
+ // listening event reports implicitTls so an operator can confirm the wire mode.
1290
+ var _tcpListener = mailServerNet.createTcpListener(net, {
1291
+ defaultPort: implicitTls ? 465 : 587, // RFC 8314 implicit-TLS / RFC 6409 submission ports
1292
+ handleConnection: _handleConnection,
1293
+ errorFactory: function (code, message) { return new MailServerSubmissionError("mail-server-submission/" + code, message); },
1294
+ emit: _emit,
1295
+ listeningEvent: "mail.server.submission.listening",
1296
+ listeningExtra: function () { return { implicitTls: implicitTls }; },
1297
+ });
1337
1298
 
1338
1299
  async function close(closeOpts) {
1339
1300
  closeOpts = closeOpts || {};
1340
- if (!listening) return;
1301
+ if (!_tcpListener.isListening()) return;
1341
1302
  var timeoutMs = closeOpts.timeoutMs || C.TIME.seconds(30);
1342
- listening = false;
1343
- tcpServer.close();
1303
+ _tcpListener.markClosed();
1304
+ _tcpListener.getServer().close();
1344
1305
  connections.forEach(function (sock) {
1345
1306
  try { _writeReply(sock, REPLY_421_SERVICE_NOT_AVAIL, "4.3.0 Server shutting down"); }
1346
1307
  catch (_e) { /* socket already gone */ }
@@ -1359,10 +1320,10 @@ function create(opts) {
1359
1320
  function connectionCount() { return connections.size; }
1360
1321
 
1361
1322
  return {
1362
- listen: listen,
1323
+ listen: _tcpListener.listen,
1363
1324
  close: close,
1364
1325
  connectionCount: connectionCount,
1365
- _portForTest: function () { return tcpServer ? tcpServer.address().port : null; },
1326
+ _portForTest: function () { var s = _tcpListener.getServer(); return s ? s.address().port : null; },
1366
1327
  };
1367
1328
  }
1368
1329
 
@@ -93,6 +93,7 @@ var nodeFs = require("node:fs");
93
93
  var nodeTls = require("node:tls");
94
94
  var lazyRequire = require("./lazy-require");
95
95
  var C = require("./constants");
96
+ var atomicFile = require("./atomic-file");
96
97
  var validateOpts = require("./validate-opts");
97
98
  var { defineClass } = require("./framework-error");
98
99
 
@@ -177,7 +178,11 @@ function context(opts) {
177
178
  var stopped = false;
178
179
 
179
180
  function _readKey() {
180
- var raw = nodeFs.readFileSync(keyFile, "utf8");
181
+ // Cap + fd-bound TLS-private-key read. NO refuseSymlink: keyFile is commonly
182
+ // certbot's /etc/letsencrypt/live/<host>/privkey.pem, a symlink chain into
183
+ // ../../archive — refusing symlinks would break the documented Let's Encrypt
184
+ // layout. The cap is the OOM-before-use defense.
185
+ var raw = atomicFile.fdSafeReadSync(keyFile, { maxBytes: C.BYTES.kib(64), encoding: "utf8" });
181
186
  // b.vault.sealPemFile produces blobs that decrypt via vault.unseal.
182
187
  // Detect by the sealed-cell prefix the framework's vault layer
183
188
  // already documents (everything else passes through as plain PEM).
@@ -196,7 +201,9 @@ function context(opts) {
196
201
  function _build() {
197
202
  var certPem;
198
203
  try {
199
- certPem = nodeFs.readFileSync(certFile, "utf8");
204
+ // Cap + fd-bound cert-chain read. NO refuseSymlink (certbot fullchain.pem
205
+ // is a symlink); the cap is the safe hardening.
206
+ certPem = atomicFile.fdSafeReadSync(certFile, { maxBytes: C.BYTES.mib(1), encoding: "utf8" });
200
207
  } catch (e) {
201
208
  throw new MailServerTlsError("mail-server-tls/cert-unreadable",
202
209
  "b.mail.server.tls.context: cannot read certFile " + certFile + ": " +
@@ -438,8 +445,97 @@ function upgradeSocket(opts) {
438
445
  return tlsSocket;
439
446
  }
440
447
 
448
+ /**
449
+ * @primitive b.mail.server.tls.upgradeLineProtocol
450
+ * @signature b.mail.server.tls.upgradeLineProtocol(opts)
451
+ * @since 0.15.13
452
+ * @status stable
453
+ * @related b.mail.server.tls.upgradeSocket
454
+ *
455
+ * STARTTLS / STLS completion for the line-buffered store listeners
456
+ * (IMAP / POP3 / ManageSieve), layered over `upgradeSocket`. The
457
+ * caller has already validated protocol state and written its
458
+ * "begin TLS" response; this owns the steps that recur identically
459
+ * across the three:
460
+ *
461
+ * 1. Drop the pre-handshake `state.lineBuffer` (always) plus any
462
+ * protocol-specific half-parsed command / literal / auth fields
463
+ * (`clearFields`) so bytes the peer pipelined before the upgrade
464
+ * cannot survive into the post-TLS session (CVE-2021-33515 /
465
+ * CVE-2021-38371 STARTTLS-injection class). Centralizing the
466
+ * `lineBuffer` reset makes it impossible for a listener to forget.
467
+ * 2. Mark `state.tls = true` on the secure event, then run the
468
+ * caller's optional `onSecure` for protocol-specific work (e.g.
469
+ * ManageSieve re-emitting its capability banner per RFC 5804).
470
+ * 3. Feed every post-handshake chunk through the caller's `drain`
471
+ * via the standard `state.lineBuffer` append.
472
+ *
473
+ * The transfer listeners (MX / submission) ingest via a serialized
474
+ * feed pump, not the line buffer, so they call `upgradeSocket`
475
+ * directly.
476
+ *
477
+ * @opts
478
+ * state: object, // connection state ({ lineBuffer, tls, … })
479
+ * socket: net.Socket, // pre-upgrade plain socket
480
+ * secureContext: tls.SecureContext, // from b.mail.server.tls.context
481
+ * idleTimeoutMs: number, // re-armed post-handshake
482
+ * clearFields: Array<string>, // extra state fields to null pre-upgrade
483
+ * drain: function(state, tlsSocket), // the protocol line drainer
484
+ * onSecure: function(tlsSocket), // optional post-secure work
485
+ * onError: function(err), // handshake / runtime error
486
+ * onTimeout: function(tlsSocket), // optional idle-timeout handler
487
+ *
488
+ * @example
489
+ * b.mail.server.tls.upgradeLineProtocol({
490
+ * state: state, socket: socket, secureContext: opts.tlsContext,
491
+ * idleTimeoutMs: idleTimeoutMs, clearFields: ["pendingLiteral"],
492
+ * drain: _drainBuffer,
493
+ * onError: function (err) { _emit("imap.tls_failed", { err: err.message }); _close(socket, state); },
494
+ * });
495
+ */
496
+ function upgradeLineProtocol(opts) {
497
+ if (!opts || typeof opts !== "object") {
498
+ throw new MailServerTlsError("mail-server-tls/bad-upgrade-line-opts",
499
+ "upgradeLineProtocol: opts required");
500
+ }
501
+ var state = opts.state;
502
+ if (!state || typeof state !== "object") {
503
+ throw new MailServerTlsError("mail-server-tls/bad-upgrade-line-state",
504
+ "upgradeLineProtocol: opts.state object required");
505
+ }
506
+ if (typeof opts.drain !== "function") {
507
+ throw new MailServerTlsError("mail-server-tls/bad-upgrade-line-drain",
508
+ "upgradeLineProtocol: opts.drain(state, tlsSocket) required");
509
+ }
510
+ // CVE-2021-33515 / CVE-2021-38371 injection defense: discard the
511
+ // pre-handshake line buffer plus every protocol-specific half-parsed
512
+ // field so nothing the peer pipelined pre-upgrade survives the TLS
513
+ // boundary.
514
+ state.lineBuffer = Buffer.alloc(0);
515
+ var clearFields = opts.clearFields;
516
+ if (clearFields) {
517
+ for (var i = 0; i < clearFields.length; i += 1) state[clearFields[i]] = null;
518
+ }
519
+ return upgradeSocket({
520
+ plainSocket: opts.socket,
521
+ secureContext: opts.secureContext,
522
+ idleTimeoutMs: opts.idleTimeoutMs,
523
+ onSecure: function (tlsSocket) {
524
+ state.tls = true;
525
+ if (typeof opts.onSecure === "function") opts.onSecure(tlsSocket);
526
+ },
527
+ onData: function (tlsSocket, chunk) {
528
+ state.lineBuffer = Buffer.concat([state.lineBuffer, chunk]);
529
+ opts.drain(state, tlsSocket);
530
+ },
531
+ onError: opts.onError,
532
+ onTimeout: opts.onTimeout,
533
+ });
534
+ }
535
+
441
536
  module.exports = {
442
537
  context: context,
443
538
  upgradeSocket: upgradeSocket,
539
+ upgradeLineProtocol: upgradeLineProtocol,
444
540
  MailServerTlsError: MailServerTlsError,
445
541
  };
@@ -71,6 +71,7 @@ var { defineClass } = require("./framework-error");
71
71
  var lazyRequire = require("./lazy-require");
72
72
  var validateOpts = require("./validate-opts");
73
73
  var gateContract = require("./gate-contract");
74
+ var codepointClass = require("./codepoint-class");
74
75
 
75
76
  var audit = lazyRequire(function () { return require("./audit"); });
76
77
 
@@ -131,8 +132,8 @@ function create(opts) {
131
132
  throw new MailSpamScoreError("mail-spam-score/bad-scorer",
132
133
  "mail.spamScore.create.scorer must be a function; got " + (typeof opts.scorer));
133
134
  }
134
- var profile = opts.profile || (opts.posture && COMPLIANCE_POSTURES[opts.posture]) || DEFAULT_PROFILE;
135
- if (!PROFILES[profile]) {
135
+ var profile = gateContract.resolveProfileName(opts, COMPLIANCE_POSTURES, DEFAULT_PROFILE);
136
+ if (!Object.prototype.hasOwnProperty.call(PROFILES, profile)) {
136
137
  throw new MailSpamScoreError("mail-spam-score/bad-profile",
137
138
  "mail.spamScore.create.profile: unknown '" + profile +
138
139
  "' (valid: strict / balanced / permissive)");
@@ -221,9 +222,7 @@ function create(opts) {
221
222
  * @example
222
223
  * b.mail.spamScore.compliancePosture("hipaa"); // → "strict"
223
224
  */
224
- function compliancePosture(posture) {
225
- return COMPLIANCE_POSTURES[posture] || null;
226
- }
225
+ var compliancePosture = gateContract.makePostureAccessor(COMPLIANCE_POSTURES);
227
226
 
228
227
  function _sanitizeReasons(reasons, caps) {
229
228
  if (reasons === undefined || reasons === null) return [];
@@ -250,13 +249,11 @@ function _sanitizeReasons(reasons, caps) {
250
249
  // Refuse control bytes (CR / LF / NUL / etc.) — a compromised
251
250
  // scorer could try to smuggle CRLF into an outbound X-Spam-Status
252
251
  // header.
253
- for (var c = 0; c < r.length; c += 1) {
254
- var cc = r.charCodeAt(c);
255
- if (cc < 0x20 || cc === 0x7f) { // RFC 5234 CTL refusal range
256
- throw new MailSpamScoreError("mail-spam-score/control-byte",
257
- "mail.spamScore.score: reasons[" + i + "] contains control byte 0x" +
258
- cc.toString(16)); // hex radix
259
- }
252
+ var _ctlOff = codepointClass.firstControlCharOffset(r, { forbidTab: true });
253
+ if (_ctlOff !== -1) {
254
+ throw new MailSpamScoreError("mail-spam-score/control-byte",
255
+ "mail.spamScore.score: reasons[" + i + "] contains control byte 0x" +
256
+ r.charCodeAt(_ctlOff).toString(16)); // hex radix
260
257
  }
261
258
  out.push(r);
262
259
  }
@@ -321,7 +321,7 @@ var QUERY_KEY_MAP = {
321
321
  * // → { column: "addr_toks", field: "addr" }
322
322
  */
323
323
  function columnAndFieldFor(key) {
324
- return QUERY_KEY_MAP[key] || null;
324
+ return Object.prototype.hasOwnProperty.call(QUERY_KEY_MAP, key) ? QUERY_KEY_MAP[key] : null;
325
325
  }
326
326
 
327
327
  // Rewrite an operator query term into a FTS5 MATCH expression. The
package/lib/mail-store.js CHANGED
@@ -61,6 +61,7 @@ var C = require("./constants");
61
61
  var bCrypto = require("./crypto");
62
62
  var cryptoField = require("./crypto-field");
63
63
  var vault = require("./vault");
64
+ var safeBuffer = require("./safe-buffer");
64
65
  var safeMime = require("./safe-mime");
65
66
  var safeSql = require("./safe-sql");
66
67
  var sql = require("./sql");
@@ -798,7 +799,7 @@ function _appendMessage(args) {
798
799
  "appendMessage: rawBytes must be Buffer or string");
799
800
  }
800
801
  var buf = Buffer.isBuffer(rawBytes) ? rawBytes : Buffer.from(rawBytes, "utf8");
801
- if (buf.length > args.maxMessageBytes) {
802
+ if (safeBuffer.byteLengthOf(buf) > args.maxMessageBytes) {
802
803
  throw new MailStoreError("mail-store/oversize-message",
803
804
  "appendMessage: " + buf.length + " bytes exceeds maxMessageBytes=" + args.maxMessageBytes);
804
805
  }
@@ -854,7 +855,7 @@ function _appendMessage(args) {
854
855
  var bodyText = textPart ? textPart.body : "";
855
856
  var bodyHtml = htmlPart && htmlPart.contentType === "text/html" ? htmlPart.body : "";
856
857
 
857
- if (bodyText.length > args.maxBodyBytes || bodyHtml.length > args.maxBodyBytes) {
858
+ if (safeBuffer.byteLengthOf(bodyText) > args.maxBodyBytes || safeBuffer.byteLengthOf(bodyHtml) > args.maxBodyBytes) {
858
859
  throw new MailStoreError("mail-store/oversize-body",
859
860
  "appendMessage: body exceeds maxBodyBytes=" + args.maxBodyBytes);
860
861
  }
package/lib/mail.js CHANGED
@@ -57,6 +57,8 @@
57
57
  */
58
58
  var C = require("./constants");
59
59
  var bCrypto = require("./crypto");
60
+ var codepointClass = require("./codepoint-class");
61
+ var markupEscape = require("./markup-escape").markupEscape;
60
62
  var lazyRequire = require("./lazy-require");
61
63
  var safeBuffer = require("./safe-buffer");
62
64
  var guardDomain = require("./guard-domain");
@@ -388,11 +390,7 @@ function _renderPostalAddressHtml(addr) {
388
390
  }
389
391
 
390
392
  function _htmlEscape(s) {
391
- return String(s)
392
- .replace(/&/g, "&amp;")
393
- .replace(/</g, "&lt;")
394
- .replace(/>/g, "&gt;")
395
- .replace(/"/g, "&quot;");
393
+ return markupEscape(s);
396
394
  }
397
395
 
398
396
  function _hasUnsubscribe(message) {
@@ -432,6 +430,26 @@ function _validateMessage(message) {
432
430
  throw new MailError("mail/invalid-subject",
433
431
  "message.subject contains forbidden CRLF", true);
434
432
  }
433
+ // Reply-To and custom header KEYS go straight onto the wire in _buildRfc822;
434
+ // a CRLF in either smuggles arbitrary headers (Bcc / Reply-To override /
435
+ // Content-Type) — RFC 5322 / CWE-93 header injection. Fail closed.
436
+ if (message.replyTo && safeBuffer.hasCrlf(String(message.replyTo))) {
437
+ throw new MailError("mail/invalid-reply-to",
438
+ "message.replyTo contains forbidden CRLF (header injection)", true);
439
+ }
440
+ if (message.headers && typeof message.headers === "object") {
441
+ for (var _hk in message.headers) {
442
+ if (!Object.prototype.hasOwnProperty.call(message.headers, _hk)) continue;
443
+ if (safeBuffer.hasCrlf(_hk) || _hk.indexOf("\0") !== -1) {
444
+ throw new MailError("mail/invalid-header",
445
+ "message.headers key contains forbidden CRLF/NUL (header injection)", true);
446
+ }
447
+ if (safeBuffer.hasCrlf(String(message.headers[_hk]))) {
448
+ throw new MailError("mail/invalid-header",
449
+ "message.headers value for '" + _hk + "' contains forbidden CRLF (header injection)", true);
450
+ }
451
+ }
452
+ }
435
453
 
436
454
  if (!message.text && !message.html && !message.calendar) {
437
455
  throw new MailError("mail/missing-body",
@@ -713,7 +731,7 @@ function _buildRfc822(message) {
713
731
  headers.push("From: " + message.from);
714
732
  headers.push("To: " + (Array.isArray(message.to) ? message.to.join(", ") : message.to));
715
733
  if (message.cc) headers.push("Cc: " + (Array.isArray(message.cc) ? message.cc.join(", ") : message.cc));
716
- if (message.replyTo) headers.push("Reply-To: " + message.replyTo);
734
+ if (message.replyTo) headers.push("Reply-To: " + safeBuffer.stripCrlf(String(message.replyTo)));
717
735
  if (message.subject) headers.push("Subject: " + message.subject);
718
736
  headers.push("MIME-Version: 1.0");
719
737
  headers.push("Date: " + new Date().toUTCString());
@@ -723,7 +741,7 @@ function _buildRfc822(message) {
723
741
  // Strip CRLF defensively even though we already validated the
724
742
  // message; custom headers go straight onto the wire.
725
743
  var v = safeBuffer.stripCrlf(String(message.headers[k]));
726
- headers.push(k + ": " + v);
744
+ headers.push(safeBuffer.stripCrlf(String(k)) + ": " + v);
727
745
  }
728
746
  }
729
747
  }
@@ -1920,12 +1938,9 @@ function feedbackId(opts) {
1920
1938
  // Refuse CR/LF (header-injection) + control chars. Walk codepoints
1921
1939
  // manually because eslint's no-control-regex refuses control-char
1922
1940
  // ranges in regex literals regardless of escape form.
1923
- for (var ci = 0; ci < f.value.length; ci += 1) {
1924
- var code = f.value.charCodeAt(ci);
1925
- if (code < 32 || code === 127) { // C0 + DEL codepoint range
1926
- throw new MailError("mail/bad-feedback-id-field",
1927
- "feedbackId: " + f.key + " contains control characters");
1928
- }
1941
+ if (codepointClass.firstControlCharOffset(f.value, { forbidTab: true }) !== -1) { // C0 + DEL codepoint range
1942
+ throw new MailError("mail/bad-feedback-id-field",
1943
+ "feedbackId: " + f.key + " contains control characters");
1929
1944
  }
1930
1945
  parts.push(f.value);
1931
1946
  }
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+
3
+ // markup-escape — the shared XML/HTML text + attribute escaper used wherever
4
+ // the framework SERIALIZES a value into markup (mail report / DAV / autoconfig
5
+ // XML, the Azure blob XML API, and — once routed — the b.guardHtml / b.guardSvg
6
+ // sanitizer output). Distinct from markup-tokenizer (which LEXES markup for the
7
+ // sanitizers) and xml-c14n (which canonicalizes for XMLDSig with a different
8
+ // escape set, e.g. \r\n\t and NO ">").
9
+ //
10
+ // markupEscape(str, opts) escapes the four always-dangerous markup
11
+ // metacharacters — & < > " — to their entity forms, with "&" first so the
12
+ // entities it emits are not double-escaped. The one axis that genuinely varies
13
+ // across callers is the apostrophe, so it is a parameter:
14
+ // opts.apos omitted / falsy → "'" is left as-is (element content and
15
+ // double-quoted attributes don't require it escaped)
16
+ // opts.apos === "&#39;" → HTML numeric form
17
+ // opts.apos === "&apos;" → XML named form
18
+ // Input COERCION stays with the caller (some map null/undefined or any
19
+ // non-string to "" before serializing); markupEscape String()s defensively so
20
+ // a stray number/boolean still escapes rather than throwing.
21
+ function markupEscape(str, opts) {
22
+ var s = String(str)
23
+ .replace(/&/g, "&amp;")
24
+ .replace(/</g, "&lt;")
25
+ .replace(/>/g, "&gt;")
26
+ .replace(/"/g, "&quot;");
27
+ var apos = opts && opts.apos;
28
+ return apos ? s.replace(/'/g, apos) : s;
29
+ }
30
+
31
+ module.exports = { markupEscape: markupEscape };
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+
3
+ // markup-tokenizer — neutral lexing helpers shared by the markup sanitizers
4
+ // (b.guardHtml, b.guardSvg) and the BIMI SVG Tiny PS validator (b.mail.bimi).
5
+ // Each of those keeps its OWN tokenizer loop because their security postures
6
+ // genuinely diverge — guard-html/guard-svg recover leniently from a truncated
7
+ // tag (emit what was scanned, keep going), while the BIMI validator fails
8
+ // closed (throws on any malformation), and each recognizes a different set of
9
+ // declaration forms (`<?`, `<!ENTITY>`, balanced `<!DOCTYPE [...]>`). What they
10
+ // share verbatim is the one quote-aware step below; centralizing it keeps the
11
+ // attribute-quote handling — the part a bypass hides in — identical everywhere.
12
+
13
+ // scanToTagEnd(s, from, len) — advance from `from` (the index just past the
14
+ // opening "<") to the tag's closing ">", treating a ">" that appears inside a
15
+ // single- or double-quoted attribute value as a literal, not a terminator
16
+ // (e.g. `<a title="a>b">` ends at the SECOND ">"). Returns the index of the
17
+ // terminating ">", or `len` if the tag is unterminated (the caller decides
18
+ // whether that is lenient end-of-input or a hard error).
19
+ function scanToTagEnd(s, from, len) {
20
+ var p = from;
21
+ var inQuote = "";
22
+ while (p < len) {
23
+ var ch = s.charAt(p);
24
+ if (inQuote) {
25
+ if (ch === inQuote) inQuote = "";
26
+ } else {
27
+ if (ch === '"' || ch === "'") inQuote = ch;
28
+ else if (ch === ">") break;
29
+ }
30
+ p += 1;
31
+ }
32
+ return p;
33
+ }
34
+
35
+ // splitTagNameAttrs(inner, tagNameRe) — given a start tag's inner text (the
36
+ // bytes between "<" and ">", with any trailing self-closing "/" already
37
+ // stripped by the caller), split it into the lower-cased `tagName` and the raw
38
+ // `attrSrc` remainder. `tagNameRe` is the caller's tag-name grammar (it must
39
+ // capture the name in group 1) — HTML allows `[A-Za-z][A-Za-z0-9:-]*`, while
40
+ // the XML-family grammars (SVG, BIMI SVG Tiny PS) also allow `_`. A tag whose
41
+ // start does not match the grammar yields an empty name + empty attrSrc (the
42
+ // caller treats it as a bogus tag).
43
+ function splitTagNameAttrs(inner, tagNameRe) {
44
+ var nameMatch = inner.match(tagNameRe);
45
+ return {
46
+ tagName: nameMatch ? nameMatch[1].toLowerCase() : "",
47
+ attrSrc: nameMatch ? inner.slice(nameMatch[0].length) : "",
48
+ };
49
+ }
50
+
51
+ // htmlCommentEnd(s, lt) — given that an HTML comment opens at index `lt`
52
+ // (s.startsWith("<!--", lt)), return the index ONE PAST the comment's
53
+ // terminator per the WHATWG HTML tokenizer, not just the legacy "-->" form.
54
+ // A browser also closes a comment at "--!>" (comment-end-bang state) and
55
+ // ABRUPTLY closes one that begins "<!-->" or "<!--->". A scanner that honours
56
+ // only "-->" therefore disagrees with the browser about where the comment
57
+ // ends, so markup AFTER an early "--!>" / abrupt close is swallowed as inert
58
+ // comment by the sanitizer but parsed as a LIVE element by the browser (mXSS,
59
+ // the comment-parser differential). Returns -1 if the comment is unterminated
60
+ // so each caller keeps its own policy (lenient end-of-input vs. fail-closed
61
+ // throw). NOTE: HTML/SVG-in-HTML only — XML comments do NOT have these forms.
62
+ function htmlCommentEnd(s, lt) {
63
+ var i = lt + 4; // first char after "<!--"
64
+ if (s.charAt(i) === ">") return i + 1; // <!--> abrupt close
65
+ if (s.charAt(i) === "-" && s.charAt(i + 1) === ">") return i + 2; // <!---> abrupt close
66
+ var a = s.indexOf("-->", i);
67
+ var b = s.indexOf("--!>", i);
68
+ if (a === -1 && b === -1) return -1; // unterminated
69
+ if (a === -1) return b + 4;
70
+ if (b === -1) return a + 3;
71
+ return a <= b ? a + 3 : b + 4; // earliest terminator wins
72
+ }
73
+
74
+ module.exports = {
75
+ scanToTagEnd: scanToTagEnd,
76
+ splitTagNameAttrs: splitTagNameAttrs,
77
+ htmlCommentEnd: htmlCommentEnd,
78
+ };
@@ -65,8 +65,8 @@ var validateOpts = require("./validate-opts");
65
65
  var { McpError } = require("./framework-error");
66
66
 
67
67
  var bCrypto = lazyRequire(function () { return require("./crypto"); });
68
- var audit = lazyRequire(function () { return require("./audit"); });
69
- var C = require("./constants");
68
+ var auditEmit = require("./audit-emit");
69
+ var C = require("./constants");
70
70
 
71
71
  // Shared name-shape regex across mcp.js / mcp-tool-registry.js / a2a-tasks.js
72
72
  // — every agent-protocol identifier follows the same RFC-3986-unreserved
@@ -80,15 +80,7 @@ var ALLOWED_ALGS = Object.freeze([
80
80
  "slh-dsa-shake-256f",
81
81
  ]);
82
82
 
83
- function _emitAudit(action, metadata, outcome) {
84
- try {
85
- audit().safeEmit({
86
- action: action,
87
- outcome: outcome || "success",
88
- metadata: metadata,
89
- });
90
- } catch (_e) { /* best-effort */ }
91
- }
83
+ var _emitAudit = auditEmit.emit;
92
84
 
93
85
  function _validateAlg(alg, label) {
94
86
  if (alg === undefined || alg === null) return "ml-dsa-87";
@@ -175,20 +167,31 @@ function create(opts) {
175
167
  throw new McpError("mcp/bad-registry-opts",
176
168
  "toolRegistry.create: opts required (tools + signingKey)", true);
177
169
  }
178
- if (!Array.isArray(opts.tools)) {
179
- throw new McpError("mcp/bad-registry-opts",
180
- "toolRegistry.create: opts.tools must be an array", true);
181
- }
182
- validateOpts.requireNonEmptyString(
183
- opts.signingKey, "toolRegistry.create.signingKey", McpError, "mcp/bad-signing-key");
184
- validateOpts.optionalNonEmptyString(
185
- opts.verifyingKey, "toolRegistry.create.verifyingKey", McpError, "mcp/bad-verifying-key");
170
+ validateOpts.shape(opts, {
171
+ tools: function (value) {
172
+ if (!Array.isArray(value)) {
173
+ throw new McpError("mcp/bad-registry-opts",
174
+ "toolRegistry.create: opts.tools must be an array", true);
175
+ }
176
+ },
177
+ signingKey: { rule: "required-string", code: "mcp/bad-signing-key", label: "toolRegistry.create.signingKey" },
178
+ verifyingKey: { rule: "optional-string", code: "mcp/bad-verifying-key", label: "toolRegistry.create.verifyingKey" },
179
+ alg: function (value) {
180
+ // undefined → default applied below; any present value is registry-checked.
181
+ _validateAlg(value, "toolRegistry.create.alg");
182
+ },
183
+ ttlMs: function (value) {
184
+ // undefined → registry default applied below; any present value must be a
185
+ // finite millisecond count at or above the 1-second floor.
186
+ if (value === undefined) return;
187
+ if (typeof value !== "number" || !isFinite(value) || value < 1000) { // minimum-ttl threshold (1 second), not bytes
188
+ throw new McpError("mcp/bad-ttl",
189
+ "toolRegistry.create.ttlMs must be >= 1000 ms", true);
190
+ }
191
+ },
192
+ }, "toolRegistry.create", McpError, "mcp/bad-registry-opts");
186
193
  var alg = _validateAlg(opts.alg, "toolRegistry.create.alg");
187
194
  var defaultTtlMs = opts.ttlMs !== undefined ? opts.ttlMs : C.TIME.minutes(5);
188
- if (typeof defaultTtlMs !== "number" || !isFinite(defaultTtlMs) || defaultTtlMs < 1000) { // minimum-ttl threshold (1 second), not bytes
189
- throw new McpError("mcp/bad-ttl",
190
- "toolRegistry.create.ttlMs must be >= 1000 ms", true);
191
- }
192
195
 
193
196
  var signingKey = opts.signingKey;
194
197
  var verifyingKey = opts.verifyingKey || null;