@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
@@ -22,6 +22,7 @@
22
22
 
23
23
  var validateOpts = require("./validate-opts");
24
24
  var bCrypto = require("./crypto");
25
+ var safeJson = require("./safe-json");
25
26
  var { defineClass } = require("./framework-error");
26
27
 
27
28
  var ImportmapError = defineClass("ImportmapError", { alwaysPermanent: true });
@@ -46,7 +47,9 @@ var ImportmapError = defineClass("ImportmapError", { alwaysPermanent: true });
46
47
  * "@org/lib": { url: "/static/lib.js", body: fileBytes },
47
48
  * },
48
49
  * });
49
- * res.end("<script type=\"importmap\">" + JSON.stringify(im) + "</script>");
50
+ * // Embed with the <script>-safe helper NEVER raw JSON.stringify, which
51
+ * // does not escape "</script>" in a module url and breaks out of the tag.
52
+ * res.end(b.importmapIntegrity.scriptTag(im));
50
53
  */
51
54
  function build(opts) {
52
55
  opts = validateOpts.requireObject(opts, "importmapIntegrity.build",
@@ -84,7 +87,44 @@ function build(opts) {
84
87
  return { imports: imports, integrity: integrity };
85
88
  }
86
89
 
90
+ /**
91
+ * @primitive b.importmapIntegrity.scriptTag
92
+ * @signature b.importmapIntegrity.scriptTag(importmap, opts?)
93
+ * @since 0.15.14
94
+ * @status stable
95
+ * @related b.importmapIntegrity.build, b.safeJson.stringifyForScript
96
+ *
97
+ * Render an import-map object (from `build`) as a ready-to-embed
98
+ * `<script type="importmap">…</script>` tag using the `<script>`-safe
99
+ * JSON serializer. Raw `JSON.stringify` does not escape `</script>` in a
100
+ * module url, so concatenating it into the page lets a `</script>` in a
101
+ * url close the element and inject markup — this escapes `< > &` (and the
102
+ * U+2028 / U+2029 separators) so no url can break out.
103
+ *
104
+ * @opts
105
+ * nonce: string, // CSP nonce added as nonce="…" on the <script>
106
+ *
107
+ * @example
108
+ * res.end(b.importmapIntegrity.scriptTag(im, { nonce: req.cspNonce }));
109
+ */
110
+ function scriptTag(importmap, opts) {
111
+ opts = opts || {};
112
+ var nonceAttr = "";
113
+ if (typeof opts.nonce === "string" && opts.nonce.length > 0) {
114
+ // The nonce is operator-supplied (a CSP per-request token); reject any
115
+ // value that could itself break the attribute rather than escape it.
116
+ if (/[^A-Za-z0-9+/=_-]/.test(opts.nonce)) {
117
+ throw new ImportmapError("importmap/bad-nonce",
118
+ "scriptTag: opts.nonce must be a base64/token string");
119
+ }
120
+ nonceAttr = ' nonce="' + opts.nonce + '"';
121
+ }
122
+ return '<script type="importmap"' + nonceAttr + '>' +
123
+ safeJson.stringifyForScript(importmap) + "</script>";
124
+ }
125
+
87
126
  module.exports = {
88
127
  build: build,
128
+ scriptTag: scriptTag,
89
129
  ImportmapError: ImportmapError,
90
130
  };
package/lib/inbox.js CHANGED
@@ -43,9 +43,11 @@
43
43
  */
44
44
 
45
45
  var C = require("./constants");
46
+ var codepointClass = require("./codepoint-class");
46
47
  var lazyRequire = require("./lazy-require");
47
48
  var safeJson = require("./safe-json");
48
49
  var safeSql = require("./safe-sql");
50
+ var safeBuffer = require("./safe-buffer");
49
51
  var sql = require("./sql");
50
52
  var validateOpts = require("./validate-opts");
51
53
  var { defineClass } = require("./framework-error");
@@ -204,13 +206,11 @@ function create(opts) {
204
206
  }
205
207
 
206
208
  function _rejectControlChars(value, label, field) {
207
- for (var i = 0; i < value.length; i += 1) {
208
- var code = value.charCodeAt(i);
209
- if (code === 0 || (code < 32 && code !== 9) || code === 127) { // ASCII control codepoints (NUL + C0 + DEL); allow tab
210
- throw new InboxError("inbox/bad-receive",
211
- label + ": " + field + " contains control character at index " + i +
212
- " (codepoint " + code + ")");
213
- }
209
+ var _off = codepointClass.firstControlCharOffset(value); // allow tab (RFC 5322 folding WS)
210
+ if (_off !== -1) {
211
+ throw new InboxError("inbox/bad-receive",
212
+ label + ": " + field + " contains control character at index " + _off +
213
+ " (codepoint " + value.charCodeAt(_off) + ")");
214
214
  }
215
215
  }
216
216
 
@@ -223,7 +223,7 @@ function create(opts) {
223
223
  var metaJson = null;
224
224
  if (receiveOpts.metadata != null) {
225
225
  var serialized = safeJson.stringify(receiveOpts.metadata);
226
- if (serialized.length > maxPayloadBytes) {
226
+ if (safeBuffer.byteLengthOf(serialized) > maxPayloadBytes) {
227
227
  throw new InboxError("inbox/bad-receive",
228
228
  "recordReceive: metadata serialized exceeds maxPayloadBytes (" +
229
229
  maxPayloadBytes + " bytes)");
@@ -93,12 +93,15 @@ var REGIME_DEADLINES = Object.freeze({
93
93
  intermediate: C.TIME.hours(72),
94
94
  final: C.TIME.days(30),
95
95
  }),
96
- // DORA (EU Digital Operational Resilience Act) Article 19: initial
97
- // within 4h of classification, intermediate within 24h, final
98
- // within 1 month.
96
+ // DORA (EU Digital Operational Resilience Act) Art. 19 + RTS (EU)
97
+ // 2024/1772 Art. 5: initial within 4h of classifying the incident as
98
+ // major (outer bound 24h from awareness), intermediate within 72h of the
99
+ // initial notification, final within 1 month of the intermediate report.
100
+ // (intermediate was 24h here, contradicting lib/dora.js's 72h — the RTS
101
+ // value is 72h from the initial notification; aligned.)
99
102
  dora: Object.freeze({
100
103
  initial: C.TIME.hours(4),
101
- intermediate: C.TIME.hours(24),
104
+ intermediate: C.TIME.hours(72),
102
105
  final: C.TIME.days(30),
103
106
  }),
104
107
  // CRA (EU Cyber Resilience Act) Article 14: early warning within
@@ -138,7 +141,6 @@ function create(opts) {
138
141
  "audit", "persist", "onStage", "deadlines", "now",
139
142
  ], "incident.report");
140
143
 
141
- var auditOn = opts.audit !== false;
142
144
  var persist = typeof opts.persist === "function" ? opts.persist : null;
143
145
  var onStage = typeof opts.onStage === "function" ? opts.onStage : null;
144
146
  var deadlinesOverride = opts.deadlines || null;
@@ -150,20 +152,8 @@ function create(opts) {
150
152
  var incidents = new Map();
151
153
  var seq = 0;
152
154
 
153
- function _emitAudit(action, outcome, metadata) {
154
- if (!auditOn) return;
155
- try {
156
- audit().safeEmit({
157
- action: "incident.report." + action,
158
- outcome: outcome,
159
- metadata: metadata || {},
160
- });
161
- } catch (_e) { /* drop-silent */ }
162
- }
163
- function _emitMetric(verb, n, labels) {
164
- try { observability().safeEvent("incident.report." + verb, n || 1, labels || {}); }
165
- catch (_e) { /* drop-silent */ }
166
- }
155
+ var _emitAudit = audit().namespaced("incident.report", opts.audit);
156
+ var _emitMetric = observability().namespaced("incident.report");
167
157
 
168
158
  function _genIncidentId(regime, detectedAt) {
169
159
  seq += 1;
@@ -219,7 +209,7 @@ function create(opts) {
219
209
  }
220
210
 
221
211
  async function _recordStage(incidentId, stage, payload) {
222
- if (!VALID_STAGES[stage]) {
212
+ if (!Object.prototype.hasOwnProperty.call(VALID_STAGES, stage)) {
223
213
  throw new IncidentReportError("incident-report/bad-stage",
224
214
  "incident.report._recordStage: stage must be one of " + Object.keys(VALID_STAGES).join(", "));
225
215
  }
@@ -339,12 +329,7 @@ function createDeadlineClock(opts) {
339
329
  var tracked = new Map(); // incidentId -> { detectedAt, dueBy, regime, acked, fired }
340
330
  var timer = null;
341
331
 
342
- function _emit(action, outcome, metadata) {
343
- if (!auditOn) return;
344
- try {
345
- audit().safeEmit({ action: "incident.report.clock." + action, outcome: outcome, metadata: metadata || {} });
346
- } catch (_e) { /* drop-silent — by design */ }
347
- }
332
+ var _emit = audit().namespaced("incident.report.clock", auditOn);
348
333
  function _notify(payload) {
349
334
  if (!notify) return;
350
335
  try {
@@ -376,7 +361,7 @@ function createDeadlineClock(opts) {
376
361
  function untrack(id) { return tracked.delete(id); }
377
362
 
378
363
  function acknowledgeSubmission(id, stage, info) {
379
- if (!VALID_STAGES[stage]) {
364
+ if (!Object.prototype.hasOwnProperty.call(VALID_STAGES, stage)) {
380
365
  throw new IncidentReportError("incident-report/bad-stage",
381
366
  "createDeadlineClock.acknowledgeSubmission: stage must be one of " + Object.keys(VALID_STAGES).join(", "));
382
367
  }
package/lib/ip-utils.js CHANGED
@@ -82,9 +82,9 @@ function expandIpv6Groups(ip) {
82
82
  // Loose IPv4 textual-form check — for primitives that need to
83
83
  // classify a string as "looks like dotted-quad IPv4" but don't need
84
84
  // the strict per-octet bound check (callers that DO need octet
85
- // bounds use the strict form in `mail-rbl.js` etc.). Shared so
86
- // `lib/mail.js`, `lib/mail-helo.js`, and `lib/redis-client.js`
87
- // don't drift on the same shape.
85
+ // bounds use `isIPv4` below). Shared so `lib/mail.js`,
86
+ // `lib/mail-helo.js`, and `lib/redis-client.js` don't drift on the
87
+ // same shape.
88
88
  //
89
89
  // isIPv4Shape("1.2.3.4") → true
90
90
  // isIPv4Shape("999.0.0.0") → true (shape only; octets unbounded)
@@ -95,8 +95,51 @@ function isIPv4Shape(s) {
95
95
  return typeof s === "string" && IPV4_SHAPE_RE.test(s);
96
96
  }
97
97
 
98
+ // Strict RFC 791 dotted-quad IPv4: four 0-255 octets separated by `.`.
99
+ // The single source for textual IPv4 validation across the framework
100
+ // — `b.mail.greylist` / `b.mail.rbl` CIDR fingerprinting, `b.mail.helo`
101
+ // RFC 5321 §4.1.3 address-literals, `b.guardDomain` IPv4-as-domain
102
+ // detection, and `b.safeSchema` `.ip()`. Hand-rolling the per-octet
103
+ // alternation had drifted into several spellings; this is canonical.
104
+ //
105
+ // isIPv4("203.0.113.42") → true
106
+ // isIPv4("256.0.0.1") → false (octet > 255)
107
+ // isIPv4("1.2.3") → false (three octets)
108
+ 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
109
+ function isIPv4(s) {
110
+ return typeof s === "string" && IPV4_RE.test(s);
111
+ }
112
+
113
+ // RFC 5321 §4.1.3 IPv4 address-literal `[1.2.3.4]`. Capture group 1 is
114
+ // the inner dotted-quad; `b.mail.helo` matches a claimed literal
115
+ // against the connection IP. Same per-octet bound as `IPV4_RE`.
116
+ var IPV4_ADDR_LITERAL_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
117
+
118
+ // Loose IPv6 textual pre-filter: hex digits + colons only, bounded to
119
+ // the RFC 4291 §2.2 max no-IPv4-tail textual length (8 groups × 4 hex
120
+ // + 7 colons = 39). A cheap fail-fast before the full `expandIpv6Hex`
121
+ // parse — NOT a complete validator; callers follow with
122
+ // `expandIpv6Hex`. A hex-colon string longer than 39 chars is never a
123
+ // valid IPv6 textual form, so the bound only rejects garbage.
124
+ //
125
+ // looksLikeIPv6Hex("2001:db8::1") → true
126
+ // looksLikeIPv6Hex("::1") → true
127
+ // looksLikeIPv6Hex("::ffff:1.2.3.4")→ false (dotted tail — use expandIpv6Hex)
128
+ // looksLikeIPv6Hex("nothex") → false
129
+ var IPV6_TEXT_MAX_LEN = 39; // RFC 4291 §2.2 max no-IPv4-tail textual length
130
+ var IPV6_HEX_RE = /^[0-9a-fA-F:]+$/; // allow:regex-no-length-cap — length-bounded by IPV6_TEXT_MAX_LEN in looksLikeIPv6Hex
131
+ function looksLikeIPv6Hex(s) {
132
+ return typeof s === "string" && s.length <= IPV6_TEXT_MAX_LEN && IPV6_HEX_RE.test(s);
133
+ }
134
+
98
135
  module.exports = {
99
- expandIpv6Hex: expandIpv6Hex,
100
- expandIpv6Groups: expandIpv6Groups,
101
- isIPv4Shape: isIPv4Shape,
136
+ expandIpv6Hex: expandIpv6Hex,
137
+ expandIpv6Groups: expandIpv6Groups,
138
+ isIPv4Shape: isIPv4Shape,
139
+ isIPv4: isIPv4,
140
+ IPV4_RE: IPV4_RE,
141
+ IPV4_ADDR_LITERAL_RE: IPV4_ADDR_LITERAL_RE,
142
+ looksLikeIPv6Hex: looksLikeIPv6Hex,
143
+ IPV6_HEX_RE: IPV6_HEX_RE,
144
+ IPV6_TEXT_MAX_LEN: IPV6_TEXT_MAX_LEN,
102
145
  };
package/lib/jobs.js CHANGED
@@ -77,6 +77,7 @@ var { boot } = require("./log");
77
77
  var queue = require("./queue");
78
78
  var validateOpts = require("./validate-opts");
79
79
  var { JobsError } = require("./framework-error");
80
+ var boundedMap = require("./bounded-map");
80
81
 
81
82
  var log = boot("jobs");
82
83
 
@@ -102,10 +103,10 @@ function create(opts) {
102
103
  if (typeof handler !== "function") {
103
104
  throw _err("INVALID_HANDLER", "jobs.define: handler must be a function", true);
104
105
  }
105
- if (registry.has(name)) {
106
+ boundedMap.requireAbsent(registry, name, function () {
106
107
  throw _err("DUPLICATE_NAME",
107
108
  "jobs.define: '" + name + "' is already defined", true);
108
- }
109
+ });
109
110
  if (started) {
110
111
  // Defining after start would mean the new handler doesn't run
111
112
  // until the next start cycle. Reject loudly so operators don't
package/lib/json-patch.js CHANGED
@@ -157,7 +157,7 @@ function apply(doc, operations) {
157
157
  var work = structuredClone(doc);
158
158
  for (var i = 0; i < operations.length; i += 1) {
159
159
  var op = operations[i];
160
- if (!op || typeof op !== "object" || typeof op.op !== "string" || !OPS[op.op]) {
160
+ if (!op || typeof op !== "object" || typeof op.op !== "string" || !Object.prototype.hasOwnProperty.call(OPS, op.op)) {
161
161
  throw new JsonPatchError("json-patch/bad-op", "jsonPatch.apply: operations[" + i + "] has an invalid 'op'");
162
162
  }
163
163
  if (typeof op.path !== "string") throw new JsonPatchError("json-patch/bad-op", "jsonPatch.apply: operations[" + i + "] is missing 'path'");
package/lib/json-path.js CHANGED
@@ -35,6 +35,7 @@ var { defineClass } = require("./framework-error");
35
35
  var JsonPathError = defineClass("JsonPathError", { alwaysPermanent: true });
36
36
 
37
37
  var MAX_DESCEND_NODES = 1000000; // DoS ceiling on nodes visited by a descendant walk
38
+ var MAX_TOTAL_NODES = 1000000; // DoS ceiling on the running nodelist across ALL segments (chained wildcard/slice/filter cross-product)
38
39
 
39
40
  // ---------------------------------------------------------------------------
40
41
  // Parser — recursive descent over the RFC 9535 ABNF.
@@ -46,7 +47,20 @@ function _isDigit(c) { return c >= "0" && c <= "9"; }
46
47
  function _isNameFirst(c) { var cc = c.charCodeAt(0); return (c >= "A" && c <= "Z") || (c >= "a" && c <= "z") || c === "_" || cc >= 0x80; }
47
48
  function _isNameChar(c) { return _isNameFirst(c) || _isDigit(c); }
48
49
 
49
- function _Parser(s) { this.s = s; this.i = 0; }
50
+ function _Parser(s) { this.s = s; this.i = 0; this._depth = 0; }
51
+
52
+ // Nesting-depth cap for the filter-expression recursion (parens + `!`). Without
53
+ // it, a deeply-nested filter like `?(((((...)))))` recurses until V8 throws a
54
+ // raw RangeError ("Maximum call stack size exceeded") that escapes JsonPathError
55
+ // handling — a DoS / unhandled-crash lever on attacker-supplied JSONPath.
56
+ var MAX_FILTER_DEPTH = 200;
57
+ _Parser.prototype._descend = function () {
58
+ if (++this._depth > MAX_FILTER_DEPTH) {
59
+ throw new JsonPathError("json-path/filter-too-deep",
60
+ "jsonPath: filter expression nesting exceeds " + MAX_FILTER_DEPTH);
61
+ }
62
+ };
63
+ _Parser.prototype._ascend = function () { this._depth -= 1; };
50
64
  _Parser.prototype.err = function (msg) { throw new JsonPathError("json-path/invalid", "jsonPath: " + msg + " at index " + this.i); };
51
65
  _Parser.prototype.peek = function () { return this.i < this.s.length ? this.s.charAt(this.i) : ""; };
52
66
  _Parser.prototype.eat = function (c) { if (this.peek() !== c) this.err("expected '" + c + "'"); this.i += 1; };
@@ -226,9 +240,9 @@ _Parser.prototype.parseLogicalAnd = function () {
226
240
  };
227
241
  _Parser.prototype.parseBasic = function () {
228
242
  this.skipBlank();
229
- if (this.peek() === "!") { this.i += 1; this.skipBlank(); var inner = this.parseBasic(); this._requireTestable(inner); return { type: "not", e: inner, vtype: "logical" }; }
243
+ if (this.peek() === "!") { this.i += 1; this.skipBlank(); this._descend(); var inner = this.parseBasic(); this._ascend(); this._requireTestable(inner); return { type: "not", e: inner, vtype: "logical" }; }
230
244
  if (this.peek() === "(") {
231
- this.i += 1; this.skipBlank(); var e = this.parseLogicalOr(); this.skipBlank(); this.eat(")");
245
+ this.i += 1; this.skipBlank(); this._descend(); var e = this.parseLogicalOr(); this._ascend(); this.skipBlank(); this.eat(")");
232
246
  return e;
233
247
  }
234
248
  // comparison or test
@@ -436,6 +450,13 @@ function _evalSegments(segments, root) {
436
450
  base = acc;
437
451
  }
438
452
  base.forEach(function (nd) { seg.selectors.forEach(function (sel) { _applySelector(sel, nd, root, next); }); });
453
+ // Cap the running nodelist across the WHOLE query, not just per descendant
454
+ // walk: chained wildcard/slice/filter selectors multiply the nodelist each
455
+ // segment ([*][*][*]…), an OOM lever the per-`_descend` budget never sees.
456
+ if (next.length > MAX_TOTAL_NODES) {
457
+ throw new JsonPathError("json-path/too-large",
458
+ "jsonPath: nodelist exceeded " + MAX_TOTAL_NODES + " nodes (chained selector cross-product)");
459
+ }
439
460
  nodes = next;
440
461
  }
441
462
  return nodes;
package/lib/jtd.js CHANGED
@@ -58,7 +58,7 @@ function _checkSchema(schema, root, isRoot) {
58
58
  if (!_isPlainObject(schema)) throw new JtdError("jtd/bad-schema", "jtd: schema must be an object");
59
59
  if (!isRoot && Object.prototype.hasOwnProperty.call(schema, "definitions")) throw new JtdError("jtd/bad-schema", "jtd: 'definitions' is allowed only at the root");
60
60
  Object.keys(schema).forEach(function (k) {
61
- if (FORM_KEYWORDS.indexOf(k) === -1 && !SHARED_KEYWORDS[k] && k !== "additionalProperties" && k !== "mapping") throw new JtdError("jtd/bad-schema", "jtd: unknown keyword '" + k + "'");
61
+ if (FORM_KEYWORDS.indexOf(k) === -1 && !Object.prototype.hasOwnProperty.call(SHARED_KEYWORDS, k) && k !== "additionalProperties" && k !== "mapping") throw new JtdError("jtd/bad-schema", "jtd: unknown keyword '" + k + "'");
62
62
  });
63
63
  if (Object.prototype.hasOwnProperty.call(schema, "nullable") && typeof schema.nullable !== "boolean") throw new JtdError("jtd/bad-schema", "jtd: 'nullable' must be a boolean");
64
64
  if (Object.prototype.hasOwnProperty.call(schema, "metadata") && !_isPlainObject(schema.metadata)) throw new JtdError("jtd/bad-schema", "jtd: 'metadata' must be an object");
@@ -75,7 +75,7 @@ function _checkSchema(schema, root, isRoot) {
75
75
  if ("ref" in schema) {
76
76
  if (typeof schema.ref !== "string" || !root.definitions || !Object.prototype.hasOwnProperty.call(root.definitions, schema.ref)) throw new JtdError("jtd/bad-schema", "jtd: 'ref' must name a key in the root definitions");
77
77
  }
78
- if ("type" in schema && !TYPES[schema.type]) throw new JtdError("jtd/bad-schema", "jtd: unknown type '" + schema.type + "'");
78
+ if ("type" in schema && !Object.prototype.hasOwnProperty.call(TYPES, schema.type)) throw new JtdError("jtd/bad-schema", "jtd: unknown type '" + schema.type + "'");
79
79
  if ("enum" in schema) {
80
80
  if (!Array.isArray(schema.enum) || schema.enum.length === 0) throw new JtdError("jtd/bad-schema", "jtd: 'enum' must be a non-empty array");
81
81
  var seen = Object.create(null);
package/lib/keychain.js CHANGED
@@ -242,24 +242,12 @@ async function _writeFile(fallbackFile, doc, passphrase) {
242
242
  // Drain a stream into a Buffer, honoring an upper byte cap so a
243
243
  // runaway tool can't OOM the framework.
244
244
  function _drain(stream, capBytes) {
245
- return new Promise(function (resolve, reject) {
246
- if (!stream) { resolve(Buffer.alloc(0)); return; }
247
- var collector = safeBuffer.boundedChunkCollector({
248
- maxBytes: capBytes,
249
- errorClass: KeychainError,
250
- sizeCode: "keychain/native-output-too-large",
251
- sizeMessage: "native tool output exceeded " + capBytes + " bytes",
252
- });
253
- stream.on("data", function (chunk) {
254
- try {
255
- collector.push(chunk);
256
- } catch (e) {
257
- try { stream.destroy(); } catch (_e) { /* destroy best-effort */ }
258
- reject(e);
259
- }
260
- });
261
- stream.on("end", function () { resolve(collector.result()); });
262
- stream.on("error", function (e) { reject(e); });
245
+ if (!stream) return Promise.resolve(Buffer.alloc(0));
246
+ return safeBuffer.collectStream(stream, {
247
+ maxBytes: capBytes,
248
+ errorClass: KeychainError,
249
+ sizeCode: "keychain/native-output-too-large",
250
+ sizeMessage: "native tool output exceeded " + capBytes + " bytes",
263
251
  });
264
252
  }
265
253
 
package/lib/legal-hold.js CHANGED
@@ -60,7 +60,6 @@
60
60
  * on policy denials (already-held / not-held / invalid-citation).
61
61
  */
62
62
  var bCrypto = require("./crypto");
63
- var lazyRequire = require("./lazy-require");
64
63
  var safeJson = require("./safe-json");
65
64
  var validateOpts = require("./validate-opts");
66
65
  var sql = require("./sql");
@@ -77,7 +76,7 @@ var { defineClass } = require("./framework-error");
77
76
  var HOLD_TABLE = "_blamejs_legal_hold"; // allow:hand-rolled-sql — canonical local table-name; passed to b.sql with quoteName
78
77
  var AUDIT_TABLE = "audit_log";
79
78
 
80
- var audit = lazyRequire(function () { return require("./audit"); });
79
+ var auditEmit = require("./audit-emit");
81
80
 
82
81
  var LegalHoldError = defineClass("LegalHoldError", { alwaysPermanent: true });
83
82
  var _err = LegalHoldError.factory;
@@ -151,19 +150,11 @@ function create(opts) {
151
150
  validateOpts.optionalObjectWithMethod(opts.signWith, "sign",
152
151
  "create: opts.signWith", LegalHoldError, "BAD_OPT", "b.auditSign-shaped object");
153
152
 
154
- function _emit(action, info, outcome) {
155
- if (!auditOn) return;
156
- var sink = auditInstance || audit();
157
- try {
158
- sink.safeEmit({
159
- action: action,
160
- outcome: outcome,
161
- resource: { kind: "legal-hold", id: info && info.subjectId },
162
- reason: (info && info.reason) || null,
163
- metadata: info || {},
164
- });
165
- } catch (_e) { /* best-effort */ }
166
- }
153
+ var _emit = auditEmit.gatedReasonEmitter({
154
+ audit: auditOn,
155
+ sink: auditInstance,
156
+ extra: function (info) { return { resource: { kind: "legal-hold", id: info && info.subjectId } }; },
157
+ });
167
158
 
168
159
  function _ensureSchema() {
169
160
  // Idempotent migration. The framework SQLite path installs the
@@ -219,19 +210,34 @@ function create(opts) {
219
210
  _ensureSchema();
220
211
  var hash = _hashSubject(sid);
221
212
  var placeSelBuilt = sql.select(HOLD_TABLE, SQL_OPTS)
222
- .columns(["placedAt"])
213
+ .columns(["placedAt", "retainUntil"])
223
214
  .where("subjectIdHash", hash)
224
215
  .toSql();
225
216
  var placeSelStmt = db.prepare(placeSelBuilt.sql);
226
217
  var existing = placeSelStmt.get.apply(placeSelStmt, placeSelBuilt.params);
218
+ var nowMs = Date.now();
219
+ var renewedFromLapsed = false;
227
220
  if (existing) {
228
- _emit("legalhold.place_rejected",
229
- { subjectId: sid, reason: "already-held",
230
- existingSince: existing.placedAt },
231
- "denied");
232
- return { error: "already-held", placedAt: existing.placedAt };
221
+ // A LAPSED hold (retainUntil sunset already passed → isHeld() is already
222
+ // false) must not block a fresh placement: otherwise a subject whose hold
223
+ // expired cannot be re-held when new litigation arises, leaving the data
224
+ // both unprotected (isHeld false) and erasable by retention. Replace the
225
+ // lapsed row with the new hold. An ACTIVE hold still rejects as before.
226
+ var existingLapsed = existing.retainUntil && existing.retainUntil < nowMs;
227
+ if (!existingLapsed) {
228
+ _emit("legalhold.place_rejected",
229
+ { subjectId: sid, reason: "already-held",
230
+ existingSince: existing.placedAt },
231
+ "denied");
232
+ return { error: "already-held", placedAt: existing.placedAt };
233
+ }
234
+ renewedFromLapsed = true;
235
+ var lapseDelBuilt = sql.delete(HOLD_TABLE, SQL_OPTS)
236
+ .where("subjectIdHash", hash)
237
+ .toSql();
238
+ var lapseDelStmt = db.prepare(lapseDelBuilt.sql);
239
+ lapseDelStmt.run.apply(lapseDelStmt, lapseDelBuilt.params);
233
240
  }
234
- var nowMs = Date.now();
235
241
  var placeInsBuilt = sql.insert(HOLD_TABLE, SQL_OPTS)
236
242
  .values(cryptoField.sealRow(HOLD_TABLE, {
237
243
  subjectIdHash: hash,
@@ -251,9 +257,10 @@ function create(opts) {
251
257
  citation: args.citation || null,
252
258
  retainUntil: args.retainUntil || null,
253
259
  placedBy: args.placedBy || null,
260
+ renewedFromLapsed: renewedFromLapsed,
254
261
  knownCitation: args.citation && KNOWN_CITATIONS.indexOf(args.citation) !== -1 },
255
262
  "success");
256
- return { placed: true, placedAt: nowMs };
263
+ return { placed: true, placedAt: nowMs, renewedFromLapsed: renewedFromLapsed };
257
264
  }
258
265
 
259
266
  function release(subjectId, args) {