@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
@@ -37,6 +37,7 @@
37
37
  */
38
38
 
39
39
  var numericBounds = require("./numeric-bounds");
40
+ var validateOpts = require("./validate-opts");
40
41
  var { defineClass } = require("./framework-error");
41
42
 
42
43
  var BoundedMapError = defineClass("BoundedMapError");
@@ -99,4 +100,114 @@ function boundedMap(opts) {
99
100
  };
100
101
  }
101
102
 
102
- module.exports = { boundedMap: boundedMap, BoundedMapError: BoundedMapError };
103
+ // getOrInsert(map, key, factory, opts?) Map.prototype.getOrInsertComputed(key,
104
+ // factory) polyfill. The native method lands in Node 26 but the framework floor
105
+ // is 24.16, so the framework's request-keyed Maps hand-roll `var v = m.get(k);
106
+ // if (!v) { v = ...; m.set(k, v); }` everywhere. This is the ONE place that
107
+ // shape lives, so the floor-bump sweep swaps the body for the native method in
108
+ // a single edit instead of N call sites. Returns the existing value; otherwise
109
+ // computes factory(key), stores it, and returns it.
110
+ //
111
+ // Optional cardinality ceiling so a CAPPED caller (e.g. b.metrics' label
112
+ // cardinality cap) composes this rather than re-rolling the get-then-set with
113
+ // its own size guard: when `opts.maxSize` is a number and the key is absent at
114
+ // or above that size, the value is NOT stored and `opts.onFull(key)` (or
115
+ // undefined) is returned. Works on a plain Map or the boundedMap facade above.
116
+ function _assertMapLike(map, fnName) {
117
+ validateOpts.requireMethods(map, ["has", "get", "set"],
118
+ fnName + ": map (Map-like)", BoundedMapError, "bounded-map/bad-map");
119
+ }
120
+
121
+ function getOrInsert(map, key, factory, opts) {
122
+ _assertMapLike(map, "getOrInsert");
123
+ if (typeof factory !== "function") {
124
+ throw new BoundedMapError("bounded-map/bad-factory",
125
+ "getOrInsert: factory must be a function, got " + (typeof factory));
126
+ }
127
+ if (map.has(key)) return map.get(key);
128
+ if (opts && opts.maxSize !== undefined) {
129
+ // A bad maxSize silently breaks the ceiling (NaN/Infinity never cap →
130
+ // unbounded; negative always caps → never stores) — validate via the
131
+ // shared numeric-bounds / validate-opts assertions, never a hand-rolled
132
+ // typeof check.
133
+ numericBounds.requirePositiveFiniteIntIfPresent(opts.maxSize,
134
+ "getOrInsert: opts.maxSize", BoundedMapError, "bounded-map/bad-max-size");
135
+ validateOpts.optionalFunction(opts.onFull,
136
+ "getOrInsert: opts.onFull", BoundedMapError, "bounded-map/bad-on-full");
137
+ if (map.size >= opts.maxSize) {
138
+ return opts.onFull ? opts.onFull(key) : undefined;
139
+ }
140
+ }
141
+ var value = factory(key);
142
+ map.set(key, value);
143
+ return value;
144
+ }
145
+
146
+ // requireAbsent / requirePresent complete the keyed-store guard family that
147
+ // getOrInsert opened. A framework registry (topics, jobs, metrics, RoPA
148
+ // activities, DSR tickets, …) keys a Map and guards every mutation by
149
+ // presence: a unique INSERT throws on a duplicate key, an UPDATE/lookup throws
150
+ // when the key is missing. That guard — `if (map.has(k)) throw <duplicate>` /
151
+ // `if (!map.has(k)) throw <not-found>` — recurred across ~18 registries, each
152
+ // re-rolling the check beside its own .set. Centralizing it makes the
153
+ // uniqueness/existence check impossible to forget (the primitive IS the check)
154
+ // while leaving the caller its own typed error and value shape.
155
+
156
+ // requireAbsent(map, key, onConflict) — uniqueness guard before an insert.
157
+ // When `key` is already present, invokes onConflict(key, existingValue)
158
+ // (callers throw their own typed duplicate error from it) and returns its
159
+ // result; otherwise returns undefined and the caller performs its insert.
160
+ function requireAbsent(map, key, onConflict) {
161
+ _assertMapLike(map, "requireAbsent");
162
+ if (typeof onConflict !== "function") {
163
+ throw new BoundedMapError("bounded-map/bad-on-conflict",
164
+ "requireAbsent: onConflict must be a function, got " + (typeof onConflict));
165
+ }
166
+ if (map.has(key)) return onConflict(key, map.get(key));
167
+ return undefined;
168
+ }
169
+
170
+ // requirePresent(map, key, onMissing) — existence guard before an update or
171
+ // lookup. When `key` is absent, invokes onMissing(key) (callers throw their
172
+ // own typed not-found error) and returns its result; otherwise returns the
173
+ // existing value, so a must-exist lookup is a single call.
174
+ function requirePresent(map, key, onMissing) {
175
+ _assertMapLike(map, "requirePresent");
176
+ if (typeof onMissing !== "function") {
177
+ throw new BoundedMapError("bounded-map/bad-on-missing",
178
+ "requirePresent: onMissing must be a function, got " + (typeof onMissing));
179
+ }
180
+ if (!map.has(key)) return onMissing(key);
181
+ return map.get(key);
182
+ }
183
+
184
+ // requireAbsentMember(set, key, onConflict) — the Set sibling of requireAbsent.
185
+ // A value-LESS membership store (a Set tracking seen keys while parsing, or
186
+ // visited nodes during a recursive walk) rejects a re-occurrence:
187
+ // `if (set.has(key)) throw <duplicate|cycle>` before `set.add(key)`. Unlike
188
+ // requireAbsent (a keyed Map with values), the container has nothing to return,
189
+ // so only `.has` is required — a Set, or any { has } membership view. When the
190
+ // member is already present, invokes onConflict(key) (the caller throws its own
191
+ // typed duplicate / cycle error) and returns its result; otherwise returns
192
+ // undefined and the caller performs its `.add`.
193
+ function requireAbsentMember(set, key, onConflict) {
194
+ if (!set || typeof set.has !== "function") {
195
+ throw new BoundedMapError("bounded-map/bad-set",
196
+ "requireAbsentMember: set must be a Set-like { has }");
197
+ }
198
+ if (typeof onConflict !== "function") {
199
+ throw new BoundedMapError("bounded-map/bad-on-conflict",
200
+ "requireAbsentMember: onConflict must be a function, got " + (typeof onConflict));
201
+ }
202
+ if (set.has(key)) return onConflict(key);
203
+ return undefined;
204
+ }
205
+
206
+ module.exports = {
207
+ boundedMap: boundedMap,
208
+ BoundedMapError: BoundedMapError,
209
+ getOrInsert: getOrInsert,
210
+ requireAbsent: requireAbsent,
211
+ requirePresent: requirePresent,
212
+ requireAbsentMember: requireAbsentMember,
213
+ };
@@ -149,21 +149,11 @@ function forStates(states, detectedAtMs) {
149
149
  // deadline registry and tracks per-state filing status.
150
150
  function createReporter(opts) {
151
151
  opts = opts || {};
152
- var auditOn = opts.audit !== false;
153
152
  var now = typeof opts.now === "function" ? opts.now : function () { return Date.now(); };
154
153
  var seq = 0;
155
154
  var breaches = new Map();
156
155
 
157
- function _emitAudit(action, outcome, metadata) {
158
- if (!auditOn) return;
159
- try {
160
- audit().safeEmit({
161
- action: "breach.report." + action,
162
- outcome: outcome,
163
- metadata: metadata || {},
164
- });
165
- } catch (_e) { /* drop-silent */ }
166
- }
156
+ var _emitAudit = audit().namespaced("breach.report", opts.audit);
167
157
 
168
158
  function open(spec) {
169
159
  if (!spec || typeof spec !== "object") {
@@ -120,10 +120,13 @@ function _appSqlOpts() { return { dialect: clusterStorage.dialect(), quoteName:
120
120
  // Populated on first access per-table; invalidated on policy.set/delete.
121
121
  var policyCache = new Map(); // table -> policy
122
122
  var initialized = false;
123
- // Framework-wide trustProxy setting (set at init). When true, the
124
- // break-glass primitive consults X-Forwarded-For to populate the
125
- // grant row's `ip` field same trust boundary as middleware.
126
- var _trustProxy = false;
123
+ // Framework-wide client-IP resolver (built at init). The grant row's `ip`
124
+ // field is a security control — a grant is pinned to the IP that minted it
125
+ // and re-checked on redeem. X-Forwarded-For is forgeable, so the binding is
126
+ // meaningful only when resolution is peer-gated: operators declare their
127
+ // reverse proxies via init({ trustedProxies }) or own resolution via
128
+ // init({ clientIpResolver }). Default resolves the socket address only.
129
+ var _ipResolver = requestHelpers.trustedClientIp();
127
130
 
128
131
  // Factor lockout — wrap auth.lockout so a hostile actor brute-forcing
129
132
  // TOTP codes against break-glass gets shut out after a few failures.
@@ -457,28 +460,45 @@ async function migrate(table, opts) {
457
460
  * @related b.breakGlass.policy.set, b.breakGlass.grant
458
461
  *
459
462
  * One-shot boot wiring. Clears the in-memory policy cache, resets the
460
- * factor-lockout counter, and records the framework-wide trustProxy
461
- * boundary so subsequent `grant()` calls populate the grant row's `ip`
462
- * field from `X-Forwarded-For` only when proxies are trusted. Operators
463
- * call this once at boot, before any policy / grant / unseal call —
464
- * every other primitive throws `breakglass/not-initialized` until init
465
- * has run.
463
+ * factor-lockout counter, and records how the grant row's `ip` field is
464
+ * resolved. That IP is a security binding — the grant pins to it at mint
465
+ * and re-checks it on redeem so resolution is peer-gated: declare your
466
+ * reverse proxies via `trustedProxies` (CIDRs; X-Forwarded-For honored
467
+ * only from a trusted peer) or own resolution via `clientIpResolver`. A
468
+ * bare `trustProxy` is refused — a forgeable pin is no pin. Operators call
469
+ * this once at boot, before any policy / grant / unseal call — every other
470
+ * primitive throws `breakglass/not-initialized` until init has run.
466
471
  *
467
472
  * @opts
468
- * trustProxy: boolean, // honor X-Forwarded-For when populating grant.ip (default false)
473
+ * trustedProxies: string|string[], // CIDRs of your reverse proxies — peer-gates X-Forwarded-For
474
+ * clientIpResolver: function(req): string|null, // own grant-IP resolution
469
475
  *
470
476
  * @example
471
- * b.breakGlass.init({ trustProxy: true });
477
+ * b.breakGlass.init({ trustedProxies: ["10.0.0.0/8"] });
472
478
  * // → undefined (init returns nothing; throws on bad opts)
473
479
  */
474
480
  function init(opts) {
475
481
  opts = opts || {};
476
- validateOpts(opts, ["trustProxy"], "breakGlass.init");
482
+ validateOpts(opts, ["trustProxy", "trustedProxies", "clientIpResolver"], "breakGlass.init");
483
+ var resolver;
484
+ try {
485
+ resolver = requestHelpers.trustedClientIp({
486
+ trustedProxies: opts.trustedProxies,
487
+ clientIpResolver: opts.clientIpResolver,
488
+ });
489
+ } catch (e) {
490
+ throw new BreakGlassError("breakglass/bad-opt", e.message);
491
+ }
492
+ if ((opts.trustProxy === true || typeof opts.trustProxy === "number") && !resolver.peerGated) {
493
+ throw new BreakGlassError("breakglass/bad-opt",
494
+ "trustProxy is spoofable — a grant pinned to a forgeable X-Forwarded-For is no " +
495
+ "pin at all. Declare your reverse proxies via trustedProxies: [\"10.0.0.0/8\", …] " +
496
+ "or supply clientIpResolver(req).");
497
+ }
477
498
  initialized = true;
478
499
  policyCache.clear();
479
500
  _factorLockout = null;
480
- _trustProxy = opts.trustProxy === true || typeof opts.trustProxy === "number"
481
- ? opts.trustProxy : false;
501
+ _ipResolver = resolver;
482
502
  }
483
503
 
484
504
  function _resetForTest() {
@@ -490,7 +510,7 @@ function _resetForTest() {
490
510
  }
491
511
  _factorLockout = null;
492
512
  _factorLockoutCache = null;
493
- _trustProxy = false;
513
+ _ipResolver = requestHelpers.trustedClientIp();
494
514
  }
495
515
 
496
516
  function _requireInit() {
@@ -1119,11 +1139,10 @@ async function grant(opts) {
1119
1139
  var nowMs = Date.now();
1120
1140
  var grantId = "bg-" + generateToken(GRANT_ID_BYTES);
1121
1141
  var sessionId = (opts.req && opts.req.session && opts.req.session.id) || null;
1122
- // Honor the framework-wide trustProxy setting from init() same
1123
- // boundary as middleware. Without trustProxy, X-Forwarded-For is
1124
- // ignored as attacker-forgeable, and the grant pins to the socket
1125
- // remoteAddress only.
1126
- var ipFromReq = requestHelpers.clientIp(opts.req, { trustProxy: _trustProxy });
1142
+ // Peer-gated client-IP resolution from init() (trustedProxies /
1143
+ // clientIpResolver). Without it, X-Forwarded-For is ignored as
1144
+ // attacker-forgeable and the grant pins to the socket remoteAddress only.
1145
+ var ipFromReq = _ipResolver.resolve(opts.req);
1127
1146
 
1128
1147
  var grantRow = {
1129
1148
  _id: grantId,
@@ -1218,7 +1237,7 @@ function _enforceGrantPins(policy, grantRow, redeemReq, actorFor) {
1218
1237
  "captured at mint (fail-closed) — re-mint from a request whose client " +
1219
1238
  "IP the framework can resolve", true);
1220
1239
  }
1221
- var redeemIp = requestHelpers.clientIp(redeemReq, { trustProxy: _trustProxy });
1240
+ var redeemIp = _ipResolver.resolve(redeemReq);
1222
1241
  if (redeemIp !== grantRow.ip) {
1223
1242
  audit.safeEmit({
1224
1243
  action: "breakglass.unsealrow",
package/lib/cache.js CHANGED
@@ -70,6 +70,7 @@
70
70
  * LRU + TTL cache with operator-supplied namespacing, drop-silent key validation on hot-path observability, and pluggable backends that share semantics across single-process and clustered nodes.
71
71
  */
72
72
 
73
+ var boundedMap = require("./bounded-map");
73
74
  var cacheRedis = require("./cache-redis");
74
75
  var redisClient = require("./redis-client");
75
76
  var clusterStorage = require("./cluster-storage");
@@ -179,16 +180,8 @@ function _defaultSizeOf(value) {
179
180
  }
180
181
 
181
182
  function _validateBackendObject(backend) {
182
- var required = ["get", "set", "del", "clear", "size", "close"];
183
- if (typeof backend !== "object" || backend === null) {
184
- throw _err("BAD_OPT", "cache.create: custom backend must be an object");
185
- }
186
- for (var i = 0; i < required.length; i++) {
187
- if (typeof backend[required[i]] !== "function") {
188
- throw _err("BAD_OPT", "cache.create: custom backend missing method '" + required[i] +
189
- "' (required: " + required.join(", ") + ")");
190
- }
191
- }
183
+ validateOpts.requireMethods(backend, ["get", "set", "del", "clear", "size", "close"],
184
+ "cache.create: custom backend", CacheError, "BAD_OPT");
192
185
  }
193
186
 
194
187
  function _validateCreateOpts(opts) {
@@ -338,8 +331,7 @@ function _memoryBackend(cfg) {
338
331
  totalBytes += bytes;
339
332
  if (tags && tags.length > 0) {
340
333
  for (var i = 0; i < tags.length; i++) {
341
- var s = tagIndex.get(tags[i]);
342
- if (!s) { s = new Set(); tagIndex.set(tags[i], s); }
334
+ var s = boundedMap.getOrInsert(tagIndex, tags[i], function () { return new Set(); });
343
335
  s.add(key);
344
336
  }
345
337
  }
@@ -1028,12 +1020,9 @@ function create(opts) {
1028
1020
  var operatorObs = opts.observability || null;
1029
1021
  var clock = opts.clock || function () { return Date.now(); };
1030
1022
  var invalidationPubsub = opts.invalidationPubsub || null;
1031
- if (invalidationPubsub && (
1032
- typeof invalidationPubsub.publish !== "function" ||
1033
- typeof invalidationPubsub.subscribe !== "function" ||
1034
- typeof invalidationPubsub.unsubscribe !== "function")) {
1035
- throw _err("BAD_OPT",
1036
- "cache.create: invalidationPubsub must implement { publish, subscribe, unsubscribe } (b.pubsub.create instance)");
1023
+ if (invalidationPubsub) {
1024
+ validateOpts.requireMethods(invalidationPubsub, ["publish", "subscribe", "unsubscribe"],
1025
+ "cache.create: invalidationPubsub (b.pubsub.create instance)", CacheError, "BAD_OPT");
1037
1026
  }
1038
1027
  var invalidationChannel = "cache:" + namespace + ":invalidate";
1039
1028
  var invalidationToken = null;
package/lib/cbor.js CHANGED
@@ -54,10 +54,18 @@
54
54
  */
55
55
 
56
56
  var C = require("./constants");
57
+ var safeBuffer = require("./safe-buffer");
58
+ var boundedMap = require("./bounded-map");
57
59
  var { defineClass } = require("./framework-error");
58
60
 
59
61
  var CborError = defineClass("CborError", { alwaysPermanent: true });
60
62
 
63
+ // Hoisted so the map-decode loop's per-key uniqueness guard doesn't allocate a
64
+ // closure per key (the decode hot path).
65
+ function _throwDuplicateKey() {
66
+ throw new CborError("cbor/duplicate-key", "cbor.decode: duplicate map key (RFC 8949 §5.6)");
67
+ }
68
+
61
69
  var DEFAULT_MAX_DEPTH = 64; // nesting depth, not a size
62
70
  var ABSOLUTE_MAX_DEPTH = 256; // nesting depth ceiling, not a size
63
71
  var DEFAULT_MAX_BYTES = C.BYTES.mib(16);
@@ -126,6 +134,7 @@ function _encodeFloat(value) {
126
134
  // exponent must fit the half range and the low 13 mantissa bits must
127
135
  // be zero (half has a 10-bit mantissa vs float32's 23).
128
136
  function _doubleToHalfBits(value) {
137
+ if (value === 0) return Object.is(value, -0) ? 0x8000 : 0x0000; // ±zero → half zero (sign-preserving); -0 must not fall to the subnormal path
129
138
  var fbuf = Buffer.alloc(4);
130
139
  fbuf.writeFloatBE(value, 0);
131
140
  if (fbuf.readFloatBE(0) !== value) return -1; // not exact in float32 → not in float16
@@ -176,8 +185,10 @@ function _encodeValue(value, opts) {
176
185
  // Exact integers within the safe range encode as CBOR integers;
177
186
  // an integer-VALUED number beyond 2^53 (e.g. 1e300) has lost
178
187
  // integer precision and is a float — encode it as a float (use a
179
- // bigint for exact 64-bit CBOR integers).
180
- if (Number.isInteger(value) && Math.abs(value) <= Number.MAX_SAFE_INTEGER) {
188
+ // bigint for exact 64-bit CBOR integers). NEGATIVE ZERO is excluded:
189
+ // CBOR integers have no -0, so encoding -0 as the uint 0 would silently
190
+ // drop the sign; it falls through to the float branch (float16 -0.0).
191
+ if (Number.isInteger(value) && !Object.is(value, -0) && Math.abs(value) <= Number.MAX_SAFE_INTEGER) {
181
192
  return value >= 0 ? _head(0, value) : _head(1, -1 - value);
182
193
  }
183
194
  if (!isFinite(value) && !opts.allowNonFinite) {
@@ -300,7 +311,7 @@ function decode(buffer, opts) {
300
311
  }
301
312
  var buf = Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer);
302
313
  var maxBytes = _capInt(opts.maxBytes, DEFAULT_MAX_BYTES, ABSOLUTE_MAX_BYTES);
303
- if (buf.length > maxBytes) {
314
+ if (safeBuffer.byteLengthOf(buf) > maxBytes) {
304
315
  throw new CborError("cbor/too-large",
305
316
  "cbor.decode: input " + buf.length + " bytes exceeds maxBytes " + maxBytes);
306
317
  }
@@ -336,7 +347,11 @@ function _need(state, n) {
336
347
 
337
348
  function _readArgument(state, ai) {
338
349
  // ai is the low-5-bits additional info. Returns the argument as a
339
- // Number (or BigInt for 8-byte values beyond Number range).
350
+ // Number (or BigInt for 8-byte values beyond Number range). Non-minimal
351
+ // (non-canonical) heads are tolerated here by design — strict canonical
352
+ // enforcement is the opt-in `requireDeterministic` mode (round-trip compare);
353
+ // duplicate keys encoded non-minimally are caught value-based in the map
354
+ // decoder regardless of mode (RFC 8949 §5.6).
340
355
  if (ai < CBOR_AI_1BYTE) return ai;
341
356
  if (ai === CBOR_AI_1BYTE) { _need(state, 1); var v1 = state.buf[state.pos]; state.pos += 1; return v1; }
342
357
  if (ai === 25) { _need(state, 2); var v2 = state.buf.readUInt16BE(state.pos); state.pos += 2; return v2; }
@@ -409,17 +424,26 @@ function _decodeItem(state, depth) {
409
424
  case 5: { // map
410
425
  var mlen = _lenOf(_readArgument(state, ai));
411
426
  var m = new Map();
412
- var seen = [];
427
+ // O(1) duplicate-key detection (RFC 8949 §5.6) keyed on the CANONICAL
428
+ // re-encoding of each decoded key — not its raw input bytes. A per-key
429
+ // Buffer.compare scan over a `seen` array is O(n²), and cbor.decode runs on
430
+ // raw attacker bytes BEFORE any signature check on every COSE / CWT / EAT /
431
+ // mdoc verify path, so a large distinct-key map is an unauthenticated
432
+ // algorithmic-complexity DoS (CWE-407). Keying on the canonical encoding
433
+ // (rather than the input bytes) also closes the bypass where the SAME key
434
+ // value is sent twice with different (e.g. non-minimal) encodings to dodge
435
+ // a byte-wise dup check and silently last-value-win — both copies
436
+ // re-encode identically here and the duplicate is caught regardless of the
437
+ // requireDeterministic mode.
438
+ var seen = new Set();
413
439
  for (var j = 0; j < mlen; j++) {
414
440
  var keyStart = state.pos;
415
441
  var key = _decodeItem(state, depth + 1);
416
- var keyBytes = state.buf.slice(keyStart, state.pos);
417
- for (var s = 0; s < seen.length; s++) {
418
- if (Buffer.compare(seen[s], keyBytes) === 0) {
419
- throw new CborError("cbor/duplicate-key", "cbor.decode: duplicate map key (RFC 8949 §5.6)");
420
- }
421
- }
422
- seen.push(keyBytes);
442
+ var keyId;
443
+ try { keyId = encode(key).toString("latin1"); }
444
+ catch (_e) { keyId = "raw:" + state.buf.toString("latin1", keyStart, state.pos); }
445
+ boundedMap.requireAbsentMember(seen, keyId, _throwDuplicateKey);
446
+ seen.add(keyId);
423
447
  var val = _decodeItem(state, depth + 1);
424
448
  m.set(key, val);
425
449
  }
@@ -336,20 +336,19 @@ function parse(headerValue) {
336
336
  if (trimmed.length === 0) return null;
337
337
 
338
338
  var out = { directives: {} };
339
- var pieces = structuredFields.splitTopLevel(trimmed, ",");
340
- for (var p = 0; p < pieces.length; p += 1) {
341
- var raw = pieces[p].trim();
342
- if (raw.length === 0) continue;
343
- var eq = raw.indexOf("=");
339
+ var kvps = structuredFields.parseKeyValuePieces(
340
+ structuredFields.splitTopLevel(trimmed, ","));
341
+ for (var p = 0; p < kvps.length; p += 1) {
342
+ var kvp = kvps[p];
344
343
  var key, val, bare;
345
- if (eq === -1) {
346
- key = raw.toLowerCase();
344
+ if (kvp.value === null) {
345
+ key = kvp.key;
347
346
  val = "";
348
347
  bare = true;
349
348
  }
350
349
  else {
351
- key = raw.slice(0, eq).trim().toLowerCase();
352
- val = raw.slice(eq + 1).trim();
350
+ key = kvp.key;
351
+ val = kvp.value.trim();
353
352
  bare = false;
354
353
  // Unquote sf-string per RFC 8941 §3.3.3 (defensive — operators
355
354
  // routinely emit `s-maxage="3600"` even though the directive is
@@ -377,9 +376,13 @@ function parse(headerValue) {
377
376
  }
378
377
  continue;
379
378
  }
380
- var n = Number(val);
381
- if (isFinite(n) && n >= 0) {
382
- out[_camel(key)] = Math.floor(n);
379
+ // RFC 9111 delta-seconds is 1*DIGIT — Number() would also accept hex
380
+ // ("0x10"), exponential ("1e3"), and surrounding whitespace, which are
381
+ // not valid cache-directive values. Round-trip parseInt to require pure
382
+ // decimal digits.
383
+ var n = parseInt(val, 10);
384
+ if (Number.isFinite(n) && n >= 0 && String(n) === val) {
385
+ out[_camel(key)] = n;
383
386
  }
384
387
  continue;
385
388
  }
package/lib/cert.js CHANGED
@@ -100,10 +100,8 @@ function _createSealedDiskStorage(opts) {
100
100
  CertError, "cert/bad-storage-root");
101
101
  var rootDir = nodePath.resolve(opts.rootDir);
102
102
  var vaultStore = opts.vault || vault().getDefaultStore();
103
- if (!vaultStore || typeof vaultStore.seal !== "function" || typeof vaultStore.unseal !== "function") {
104
- throw new CertError("cert/bad-storage-vault",
105
- "cert.storage: vault must expose seal(buf) + unseal(buf) — typically b.vault.getDefaultStore()");
106
- }
103
+ validateOpts.requireMethods(vaultStore, ["seal", "unseal"],
104
+ "cert.storage: vault (typically b.vault.getDefaultStore())", CertError, "cert/bad-storage-vault");
107
105
 
108
106
  function _ensureDir(dir) { atomicFile.ensureDir(dir); }
109
107
  function _certDir(name) { return nodePath.join(rootDir, name); }
@@ -127,7 +125,9 @@ function _createSealedDiskStorage(opts) {
127
125
  async readSealed(relPath) {
128
126
  var p = nodePath.join(rootDir, relPath + ".sealed");
129
127
  if (!nodeFs.existsSync(p)) return null;
130
- var sealed = nodeFs.readFileSync(p);
128
+ // Cap + fd-bound (sealed cert/key envelope is well under 256 KiB). NO
129
+ // refuseSymlink: the sealed store may be operator-mounted.
130
+ var sealed = atomicFile.fdSafeReadSync(p, { maxBytes: C.BYTES.kib(256) });
131
131
  var plain = vaultStore.unseal(sealed);
132
132
  return Buffer.isBuffer(plain) ? plain : Buffer.from(plain);
133
133
  },
@@ -141,7 +141,7 @@ function _createSealedDiskStorage(opts) {
141
141
  async readMeta(certName) {
142
142
  var p = nodePath.join(_certDir(certName), "meta.json");
143
143
  if (!nodeFs.existsSync(p)) return null;
144
- try { return safeJson.parse(nodeFs.readFileSync(p, "utf8"), { maxBytes: C.BYTES.kib(16) }); }
144
+ try { return safeJson.parse(atomicFile.fdSafeReadSync(p, { maxBytes: C.BYTES.kib(16), encoding: "utf8" }), { maxBytes: C.BYTES.kib(16) }); }
145
145
  catch (e) {
146
146
  // meta.json is a derived index (expiry + fingerprint), not a
147
147
  // source of truth — the sealed cert is. A corrupt meta must not
@@ -411,12 +411,7 @@ function create(opts) {
411
411
  var ocspTimer = null;
412
412
  var stopped = false;
413
413
 
414
- function _emitAudit(action, outcome, metadata) {
415
- if (!auditEnabled) return;
416
- try {
417
- audit().safeEmit({ action: action, outcome: outcome, metadata: metadata || {} });
418
- } catch (_e) { /* drop-silent — audit emission is hot-path */ }
419
- }
414
+ var _emitAudit = audit().namespaced(null, { audit: auditEnabled });
420
415
 
421
416
  function _bootAcme() {
422
417
  if (acmeClient) return acmeClient;
@@ -436,7 +431,7 @@ function create(opts) {
436
431
  function _loadOrGenerateAccountKey() {
437
432
  // Read sealed account JWK; generate + persist if absent.
438
433
  var sealedBuf = nodeFs.existsSync(nodePath.join(storage.rootDir, "account/jwk.json.sealed"))
439
- ? nodeFs.readFileSync(nodePath.join(storage.rootDir, "account/jwk.json.sealed"))
434
+ ? atomicFile.fdSafeReadSync(nodePath.join(storage.rootDir, "account/jwk.json.sealed"), { maxBytes: C.BYTES.kib(64) })
440
435
  : null;
441
436
  if (sealedBuf) {
442
437
  var jwk;