@blamejs/core 0.15.13 → 0.15.15

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 (208) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/index.js +2 -0
  3. package/lib/a2a-tasks.js +38 -6
  4. package/lib/agent-event-bus.js +13 -0
  5. package/lib/agent-idempotency.js +5 -1
  6. package/lib/agent-snapshot.js +32 -2
  7. package/lib/ai-aedt-bias-audit.js +2 -1
  8. package/lib/ai-content-detect.js +1 -3
  9. package/lib/ai-frontier-protocol.js +1 -1
  10. package/lib/ai-model-manifest.js +1 -1
  11. package/lib/ai-output.js +16 -7
  12. package/lib/api-snapshot.js +4 -1
  13. package/lib/app-shutdown.js +7 -1
  14. package/lib/archive-gz.js +9 -0
  15. package/lib/archive-read.js +9 -7
  16. package/lib/archive-tar-read.js +51 -8
  17. package/lib/archive.js +4 -2
  18. package/lib/asn1-der.js +70 -22
  19. package/lib/atomic-file.js +204 -2
  20. package/lib/audit-chain.js +54 -5
  21. package/lib/audit-daily-review.js +12 -2
  22. package/lib/audit-sign.js +2 -1
  23. package/lib/audit-tools.js +108 -23
  24. package/lib/audit.js +7 -2
  25. package/lib/auth/access-lock.js +2 -2
  26. package/lib/auth/bot-challenge.js +1 -1
  27. package/lib/auth/ciba.js +71 -8
  28. package/lib/auth/dpop.js +15 -1
  29. package/lib/auth/fido-mds3.js +30 -13
  30. package/lib/auth/jwt.js +21 -5
  31. package/lib/auth/lockout.js +18 -2
  32. package/lib/auth/oauth.js +8 -2
  33. package/lib/auth/passkey.js +1 -1
  34. package/lib/auth/password.js +1 -1
  35. package/lib/auth/saml.js +42 -12
  36. package/lib/auth/sd-jwt-vc.js +24 -4
  37. package/lib/auth/status-list.js +14 -2
  38. package/lib/auth/step-up.js +9 -1
  39. package/lib/auth-bot-challenge.js +21 -2
  40. package/lib/backup/bundle.js +7 -2
  41. package/lib/backup/crypto.js +23 -8
  42. package/lib/backup/index.js +41 -20
  43. package/lib/backup/manifest.js +7 -1
  44. package/lib/break-glass.js +41 -22
  45. package/lib/cbor.js +34 -11
  46. package/lib/cdn-cache-control.js +7 -3
  47. package/lib/cert.js +5 -3
  48. package/lib/cli.js +5 -1
  49. package/lib/cloud-events.js +3 -2
  50. package/lib/cluster-storage.js +7 -3
  51. package/lib/codepoint-class.js +17 -0
  52. package/lib/compliance-eaa.js +1 -1
  53. package/lib/compliance-sanctions.js +9 -7
  54. package/lib/compliance.js +1 -1
  55. package/lib/config-drift.js +22 -8
  56. package/lib/content-credentials.js +11 -8
  57. package/lib/content-digest.js +11 -4
  58. package/lib/cookies.js +10 -2
  59. package/lib/cose.js +20 -0
  60. package/lib/crdt.js +2 -1
  61. package/lib/crypto-field.js +48 -24
  62. package/lib/crypto.js +18 -4
  63. package/lib/csp.js +13 -0
  64. package/lib/daemon.js +4 -1
  65. package/lib/data-act.js +27 -4
  66. package/lib/db-file-lifecycle.js +14 -6
  67. package/lib/db-query.js +102 -11
  68. package/lib/db.js +32 -15
  69. package/lib/dora.js +43 -13
  70. package/lib/dr-runbook.js +1 -1
  71. package/lib/dsa.js +2 -1
  72. package/lib/dsr.js +22 -8
  73. package/lib/early-hints.js +19 -0
  74. package/lib/eat.js +5 -1
  75. package/lib/external-db.js +60 -4
  76. package/lib/fda-21cfr11.js +30 -5
  77. package/lib/flag-providers.js +6 -2
  78. package/lib/forms.js +1 -1
  79. package/lib/gate-contract.js +46 -5
  80. package/lib/gdpr-ropa.js +18 -9
  81. package/lib/graphql-federation.js +17 -4
  82. package/lib/guard-all.js +2 -2
  83. package/lib/guard-dsn.js +1 -1
  84. package/lib/guard-envelope.js +1 -1
  85. package/lib/guard-html.js +9 -11
  86. package/lib/guard-imap-command.js +1 -1
  87. package/lib/guard-jmap.js +1 -1
  88. package/lib/guard-json.js +14 -6
  89. package/lib/guard-mail-move.js +1 -1
  90. package/lib/guard-managesieve-command.js +1 -1
  91. package/lib/guard-pop3-command.js +1 -1
  92. package/lib/guard-smtp-command.js +1 -1
  93. package/lib/guard-svg.js +8 -9
  94. package/lib/html-balance.js +7 -3
  95. package/lib/http-client-cookie-jar.js +33 -12
  96. package/lib/http-client.js +225 -53
  97. package/lib/iab-tcf.js +3 -2
  98. package/lib/importmap-integrity.js +41 -1
  99. package/lib/incident-report.js +9 -6
  100. package/lib/json-patch.js +1 -1
  101. package/lib/json-path.js +24 -3
  102. package/lib/jtd.js +2 -2
  103. package/lib/legal-hold.js +24 -8
  104. package/lib/log.js +2 -2
  105. package/lib/mail-agent.js +2 -2
  106. package/lib/mail-arf.js +1 -1
  107. package/lib/mail-auth.js +27 -4
  108. package/lib/mail-bimi.js +16 -16
  109. package/lib/mail-bounce.js +3 -3
  110. package/lib/mail-crypto-smime.js +71 -6
  111. package/lib/mail-deploy.js +9 -5
  112. package/lib/mail-dkim.js +20 -7
  113. package/lib/mail-greylist.js +2 -4
  114. package/lib/mail-helo.js +2 -4
  115. package/lib/mail-journal.js +11 -8
  116. package/lib/mail-mdn.js +8 -4
  117. package/lib/mail-rbl.js +2 -4
  118. package/lib/mail-scan.js +3 -5
  119. package/lib/mail-server-jmap.js +4 -1
  120. package/lib/mail-server-registry.js +1 -1
  121. package/lib/mail-server-tls.js +9 -2
  122. package/lib/mail-spam-score.js +2 -4
  123. package/lib/mail-store-fts.js +1 -1
  124. package/lib/mail.js +22 -2
  125. package/lib/markup-tokenizer.js +24 -0
  126. package/lib/mcp.js +6 -4
  127. package/lib/mdoc.js +26 -3
  128. package/lib/metrics.js +14 -3
  129. package/lib/middleware/api-encrypt.js +2 -2
  130. package/lib/middleware/body-parser.js +10 -4
  131. package/lib/middleware/bot-guard.js +26 -18
  132. package/lib/middleware/clear-site-data.js +5 -1
  133. package/lib/middleware/compose-pipeline.js +39 -5
  134. package/lib/middleware/compression.js +9 -0
  135. package/lib/middleware/cors.js +32 -23
  136. package/lib/middleware/csrf-protect.js +60 -21
  137. package/lib/middleware/daily-byte-quota.js +6 -4
  138. package/lib/middleware/fetch-metadata.js +28 -4
  139. package/lib/middleware/network-allowlist.js +61 -30
  140. package/lib/middleware/rate-limit.js +25 -16
  141. package/lib/middleware/scim-server.js +2 -1
  142. package/lib/middleware/security-headers.js +24 -6
  143. package/lib/middleware/speculation-rules.js +6 -3
  144. package/lib/middleware/tus-upload.js +2 -2
  145. package/lib/money.js +1 -1
  146. package/lib/mtls-ca.js +10 -6
  147. package/lib/network-dns-resolver.js +1 -1
  148. package/lib/network-dns.js +9 -2
  149. package/lib/network-dnssec.js +2 -1
  150. package/lib/network-smtp-policy.js +23 -5
  151. package/lib/network-tls.js +27 -5
  152. package/lib/network-tsig.js +2 -2
  153. package/lib/nis2-report.js +1 -1
  154. package/lib/nist-crosswalk.js +1 -1
  155. package/lib/ntp-check.js +28 -0
  156. package/lib/numeric-bounds.js +9 -0
  157. package/lib/object-store/azure-blob.js +1 -2
  158. package/lib/object-store/gcs-bucket-ops.js +4 -2
  159. package/lib/object-store/gcs.js +6 -4
  160. package/lib/object-store/http-put.js +1 -2
  161. package/lib/object-store/http-request.js +30 -1
  162. package/lib/object-store/local.js +37 -17
  163. package/lib/object-store/sigv4.js +1 -2
  164. package/lib/observability-otlp-exporter.js +20 -4
  165. package/lib/outbox.js +11 -4
  166. package/lib/parsers/safe-xml.js +1 -1
  167. package/lib/parsers/safe-yaml.js +21 -3
  168. package/lib/pipl-cn.js +11 -8
  169. package/lib/queue-local.js +10 -3
  170. package/lib/redact.js +7 -3
  171. package/lib/request-helpers.js +347 -36
  172. package/lib/resource-access-lock.js +3 -3
  173. package/lib/restore-bundle.js +46 -18
  174. package/lib/restore-rollback.js +10 -4
  175. package/lib/restore.js +19 -0
  176. package/lib/retention.js +20 -4
  177. package/lib/router.js +17 -4
  178. package/lib/safe-ical.js +2 -2
  179. package/lib/safe-icap.js +1 -1
  180. package/lib/safe-json.js +70 -0
  181. package/lib/safe-sieve.js +1 -1
  182. package/lib/safe-vcard.js +1 -1
  183. package/lib/sandbox-worker.js +6 -0
  184. package/lib/sandbox.js +1 -1
  185. package/lib/scheduler.js +17 -1
  186. package/lib/self-update-standalone-verifier.js +16 -0
  187. package/lib/session.js +62 -120
  188. package/lib/sql.js +25 -3
  189. package/lib/static.js +65 -13
  190. package/lib/template.js +7 -5
  191. package/lib/tenant-quota.js +52 -19
  192. package/lib/tsa.js +5 -2
  193. package/lib/vault/index.js +5 -0
  194. package/lib/vault/passphrase-ops.js +22 -26
  195. package/lib/vault/passphrase-source.js +8 -3
  196. package/lib/vault/rotate.js +13 -18
  197. package/lib/vault/seal-pem-file.js +4 -1
  198. package/lib/vc.js +1 -1
  199. package/lib/vendor/MANIFEST.json +10 -10
  200. package/lib/vendor/public-suffix-list.dat +23 -10
  201. package/lib/vendor/public-suffix-list.data.js +5498 -5494
  202. package/lib/webhook.js +16 -1
  203. package/lib/websocket.js +1 -1
  204. package/lib/worm.js +1 -1
  205. package/lib/ws-client.js +83 -46
  206. package/lib/x509-chain.js +91 -0
  207. package/package.json +1 -1
  208. package/sbom.cdx.json +6 -6
@@ -77,6 +77,9 @@ var audit = lazyRequire(function () { return require("../audit"); });
77
77
  var DEFAULT_FIELD_NAME = "_csrf";
78
78
  var DEFAULT_HEADER_NAME = "X-CSRF-Token";
79
79
  var DEFAULT_METHODS = Object.freeze(["POST", "PUT", "DELETE", "PATCH"]);
80
+ // Per-process counter giving each csrfProtect mount a unique idempotency id so
81
+ // a stricter sub-route instance is not silently disabled by an earlier one.
82
+ var _csrfGateSeq = 0;
80
83
 
81
84
  // Default cookie name uses the RFC 6265bis __Host- prefix when the request
82
85
  // is over HTTPS. The prefix forces browsers to refuse the cookie unless
@@ -124,17 +127,6 @@ function _parseCookieHeader(header) {
124
127
  return Object.assign(Object.create(null), Object.fromEntries(pairs));
125
128
  }
126
129
 
127
- // `_isHttps` defers to `requestHelpers.requestProtocol` so the
128
- // per-middleware `trustProxy` opt gates whether X-Forwarded-Proto is
129
- // consulted. Without trustProxy, an attacker could otherwise forge
130
- // the header to force the Secure cookie attribute (and inversely,
131
- // suppress it) on direct-to-server connections.
132
- function _isHttpsFor(trustProxy) {
133
- return function (req) {
134
- return requestHelpers.requestProtocol(req, { trustProxy: trustProxy }) === "https";
135
- };
136
- }
137
-
138
130
  function _formatSetCookie(name, value, opts) {
139
131
  var parts = [name + "=" + value];
140
132
  parts.push("Path=" + (opts.path || "/"));
@@ -275,7 +267,9 @@ function _writeReject(req, res, message, reason, onDeny, problemMode) {
275
267
  * allowedOrigins: string[],
276
268
  * requireOrigin: boolean,
277
269
  * requireJsonContentType: boolean,
278
- * trustProxy: boolean|number,
270
+ * trustedProxies: string|string[], // CIDRs of your reverse proxies — peer-gates X-Forwarded-Proto for the Secure-cookie decision
271
+ * protocolResolver: function(req): "http"|"https", // own the HTTPS decision
272
+ * trustProxy: boolean|number, // legacy; refused unless paired with trustedProxies/protocolResolver (spoofable)
279
273
  * audit: boolean,
280
274
  * skipStateless: boolean, // default false — skip validation for Authorization-header / cookieless (not-CSRF-able) requests
281
275
  * onDeny: function(req, res, info): void, // own the 403; info = { status, reason }
@@ -296,14 +290,31 @@ function create(opts) {
296
290
 
297
291
  validateOpts(opts, [
298
292
  "cookie", "tokenLookup", "fieldName", "headerName", "methods", "audit",
299
- "trustProxy", "checkOrigin", "allowedOrigins", "requireJsonContentType",
293
+ "trustProxy", "trustedProxies", "protocolResolver",
294
+ "checkOrigin", "allowedOrigins", "requireJsonContentType",
300
295
  "requireOrigin", "skipStateless", "skipPaths", "skip", "onDeny", "problemDetails",
301
296
  ], "middleware.csrfProtect");
302
297
  var onDeny = typeof opts.onDeny === "function" ? opts.onDeny : null;
303
298
  var problemMode = opts.problemDetails === true;
304
- var trustProxy = opts.trustProxy === true || typeof opts.trustProxy === "number"
305
- ? opts.trustProxy : false;
306
- var _isHttps = _isHttpsFor(trustProxy);
299
+ // The Secure-cookie decision turns on whether the request is HTTPS, which
300
+ // behind a proxy comes from X-Forwarded-Proto. A bare trustProxy trusts that
301
+ // forgeable header from any caller — a direct request could suppress the
302
+ // Secure flag (cookie downgrade) or force it. Peer-gate it: declare your
303
+ // reverse proxies via trustedProxies, or own the decision via
304
+ // protocolResolver. A bare trustProxy is refused at construction.
305
+ var _proto;
306
+ try {
307
+ _proto = requestHelpers.trustedProtocol({
308
+ trustedProxies: opts.trustedProxies,
309
+ protocolResolver: opts.protocolResolver,
310
+ });
311
+ } catch (e) { throw new Error("middleware.csrfProtect: " + e.message); }
312
+ if ((opts.trustProxy === true || typeof opts.trustProxy === "number") && !_proto.peerGated) {
313
+ throw new Error("middleware.csrfProtect: trustProxy is spoofable for the Secure-cookie " +
314
+ "decision — a direct caller could forge X-Forwarded-Proto. Declare your reverse proxies " +
315
+ "via trustedProxies: [\"10.0.0.0/8\", …] or supply protocolResolver(req).");
316
+ }
317
+ function _isHttps(req) { return _proto.resolve(req) === "https"; }
307
318
 
308
319
  // Throw at create() — exactly one issuance source allowed.
309
320
  var hasCookie = opts.cookie != null && opts.cookie !== false;
@@ -318,8 +329,22 @@ function create(opts) {
318
329
 
319
330
  var fieldName = opts.fieldName || DEFAULT_FIELD_NAME;
320
331
  var headerName = (opts.headerName || DEFAULT_HEADER_NAME).toLowerCase();
332
+ // An empty methods array is truthy → `opts.methods || DEFAULT_METHODS` keeps
333
+ // `[]` and the method-gate matches nothing, silently disabling the primary
334
+ // CSRF check for all state-changing requests. Reject at config time.
335
+ if (opts.methods !== undefined) {
336
+ if (!Array.isArray(opts.methods) || opts.methods.length === 0 ||
337
+ !opts.methods.every(function (m) { return typeof m === "string" && m.length > 0; })) {
338
+ throw new Error("middleware.csrfProtect: opts.methods must be a non-empty array of HTTP method tokens (omit it for the POST/PUT/DELETE/PATCH default)");
339
+ }
340
+ }
321
341
  var methods = (opts.methods || DEFAULT_METHODS).map(function (m) { return m.toUpperCase(); });
322
342
  var auditOn = opts.audit !== false;
343
+ // Per-instance idempotency id — a stricter sub-route csrf mount must not be
344
+ // silently disabled by an earlier lenient one sharing a global flag (token
345
+ // issue is idempotent and double-validate is safe, so per-instance re-run is
346
+ // sound).
347
+ var GATE_ID = "csrf:" + (_csrfGateSeq++);
323
348
 
324
349
  // Origin / Referer cross-check — second-line defense alongside the
325
350
  // double-submit token. If the request's Origin (or Referer when
@@ -452,6 +477,17 @@ function create(opts) {
452
477
  req.csrfToken = existing;
453
478
  return existing;
454
479
  }
480
+ // Cookie issuance is idempotent at the RESPONSE level, keyed by cookie
481
+ // name: a redundant mount (createApp wired csrf AND an operator re-mounted
482
+ // it with the same cookie name) must emit a single Set-Cookie, not one per
483
+ // instance. Enforcement stays per instance (each gate still validates the
484
+ // token below) — only the response-cookie resource is deduped, so a mount
485
+ // with a DIFFERENT cookie name still issues its own.
486
+ if (!req._csrfIssuedCookies) req._csrfIssuedCookies = Object.create(null);
487
+ if (Object.prototype.hasOwnProperty.call(req._csrfIssuedCookies, cookieName)) {
488
+ req.csrfToken = req._csrfIssuedCookies[cookieName];
489
+ return req.csrfToken;
490
+ }
455
491
  if (existing && !/^[a-f0-9]{64}$/.test(existing)) {
456
492
  // Audit-emit so operators see when a planted/short cookie is
457
493
  // refused — surfaces the attack class in compliance logs.
@@ -472,16 +508,19 @@ function create(opts) {
472
508
  maxAge: cookieCfg.maxAge,
473
509
  });
474
510
  _appendSetCookie(res, setCookie);
511
+ req._csrfIssuedCookies[cookieName] = fresh;
475
512
  req.csrfToken = fresh;
476
513
  return fresh;
477
514
  }
478
515
 
479
516
  return function csrfProtect(req, res, next) {
480
- // Idempotent: a second csrf mount this request (e.g. createApp wired
481
- // it AND an operator mounted it again) is a no-op the first instance
482
- // already issued + validated.
483
- if (req._csrfApplied) return next();
484
- req._csrfApplied = true;
517
+ // Idempotent PER INSTANCE: the SAME mount running twice (createApp wired it
518
+ // AND an operator mounted it again) is a no-op, but a distinct stricter
519
+ // sub-route instance still runs — a shared global flag let the first
520
+ // (lenient) mount disable the second.
521
+ if (!req._csrfGates) req._csrfGates = Object.create(null);
522
+ if (req._csrfGates[GATE_ID]) return next();
523
+ req._csrfGates[GATE_ID] = true;
485
524
 
486
525
  // Issue/refresh the token on EVERY request (safe + state-changing)
487
526
  // when running in cookie mode — templates rendered after a POST
@@ -187,13 +187,15 @@ function create(opts) {
187
187
  // is observed via writableLength as res.write / res.end fire.
188
188
  var inboundBytes = 0;
189
189
  if (req.headers && typeof req.headers === "object") {
190
- // Approximate: each header line is "Name: Value\r\n". Sum the
191
- // string lengths; the actual byte count differs only on multi-
192
- // byte UTF-8, which is uncommon in standard headers.
190
+ // Each header line is "Name: Value\r\n". Count UTF-8 BYTES (not .length
191
+ // / UTF-16 code units) so a multibyte header value isn't under-accounted
192
+ // against the byte quota matching the outbound counting below, which
193
+ // already uses Buffer.byteLength.
193
194
  var keys = Object.keys(req.headers);
194
195
  for (var hi = 0; hi < keys.length; hi++) {
195
196
  var v = req.headers[keys[hi]];
196
- inboundBytes += keys[hi].length + 2 + (typeof v === "string" ? v.length : 0) + 2; // ": " + "\r\n" overhead
197
+ inboundBytes += Buffer.byteLength(keys[hi], "utf8") + 2 +
198
+ (typeof v === "string" ? Buffer.byteLength(v, "utf8") : 0) + 2; // ": " + "\r\n" overhead
197
199
  }
198
200
  }
199
201
  if (req.headers && req.headers["content-length"]) {
@@ -42,6 +42,10 @@ var audit = lazyRequire(function () { return require("../audit"); });
42
42
  var observability = lazyRequire(function () { return require("../observability"); });
43
43
 
44
44
  var DEFAULT_METHODS = Object.freeze(["POST", "PUT", "DELETE", "PATCH"]);
45
+ // Monotonic per-process counter giving each fetchMetadata mount a unique
46
+ // idempotency id (see GATE_ID in create) so a stricter sub-route instance is
47
+ // not silently disabled by an earlier lenient one sharing a global flag.
48
+ var _fmGateSeq = 0;
45
49
 
46
50
  // Sec-Fetch-Dest request-destination vocabulary (Fetch Standard §3.2.6
47
51
  // "destination", https://fetch.spec.whatwg.org/#concept-request-destination;
@@ -81,7 +85,7 @@ function _validateDestList(list, label) {
81
85
  // the typo surfaces before it silently fails to match at request time.
82
86
  if (!Array.isArray(list)) return;
83
87
  for (var i = 0; i < list.length; i += 1) {
84
- if (!KNOWN_DEST_SET[list[i]]) {
88
+ if (!Object.prototype.hasOwnProperty.call(KNOWN_DEST_SET, list[i])) {
85
89
  throw new Error("middleware.fetchMetadata: " + label + "[" + i +
86
90
  "] is not a known Sec-Fetch-Dest value (got '" + String(list[i]) +
87
91
  "'). Known destinations: " + KNOWN_DESTINATIONS.join(", ") + ".");
@@ -206,8 +210,26 @@ function create(opts) {
206
210
  }
207
211
  }
208
212
  var allowedNavigate = opts.allowedNavigate !== false;
213
+ // An empty methods array is truthy, so `opts.methods || DEFAULT_METHODS`
214
+ // would keep `[]` and `methods.indexOf(req.method) === -1` would be true for
215
+ // EVERY request — silently turning the gate into a pass-through. Reject a
216
+ // present-but-empty/garbage list at config time (fail-fast, don't degrade
217
+ // the gate to a no-op).
218
+ if (opts.methods !== undefined) {
219
+ if (!Array.isArray(opts.methods) || opts.methods.length === 0 ||
220
+ !opts.methods.every(function (m) { return typeof m === "string" && m.length > 0; })) {
221
+ throw new Error("middleware.fetchMetadata: opts.methods must be a non-empty array of HTTP method tokens (omit it for the POST/PUT/DELETE/PATCH default)");
222
+ }
223
+ }
209
224
  var methods = (opts.methods || DEFAULT_METHODS).map(function (m) { return m.toUpperCase(); });
210
225
  var auditOn = opts.audit !== false;
226
+ // Per-instance idempotency id: a request carries a Set of gates that have
227
+ // already run. The OLD shared `req._fetchMetadataChecked` boolean let the
228
+ // FIRST fetch-metadata mount (e.g. the lenient app-level default) permanently
229
+ // disable a STRICTER instance layered on a sub-route. Keying by a unique id
230
+ // lets each distinct mount run once while the SAME instance mounted twice
231
+ // still no-ops.
232
+ var GATE_ID = "fm:" + (_fmGateSeq++);
211
233
 
212
234
  function _emitDenied(req, reason) {
213
235
  if (!auditOn) return;
@@ -223,9 +245,11 @@ function create(opts) {
223
245
  }
224
246
 
225
247
  return function fetchMetadata(req, res, next) {
226
- // Idempotent: a second fetch-metadata mount this request is a no-op.
227
- if (req._fetchMetadataChecked) return next();
228
- req._fetchMetadataChecked = true;
248
+ // Idempotent PER INSTANCE: the same mount running twice on a request is a
249
+ // no-op, but a distinct (e.g. stricter sub-route) instance still evaluates.
250
+ if (!req._fetchMetadataGates) req._fetchMetadataGates = Object.create(null);
251
+ if (req._fetchMetadataGates[GATE_ID]) return next();
252
+ req._fetchMetadataGates[GATE_ID] = true;
229
253
  if (_shouldSkip(req)) return next();
230
254
  if (methods.indexOf(req.method) === -1) return next();
231
255
 
@@ -16,12 +16,12 @@
16
16
  * the wiki example default).
17
17
  *
18
18
  * var fence = b.middleware.networkAllowlist({
19
- * paths: ["/admin", "/admin/", "/healthz/internal"],
20
- * allowedCidrs: ["10.0.0.0/8", "192.168.0.0/16", "::1/128"],
21
- * trustProxy: true, // honour x-forwarded-for; default false
22
- * denyStatus: 404, // default — reveal nothing about the gate
23
- * denyBody: "Not Found", // default
24
- * audit: b.audit, // default: null — emits network.gate.denied
19
+ * paths: ["/admin", "/admin/", "/healthz/internal"],
20
+ * allowedCidrs: ["10.0.0.0/8", "192.168.0.0/16", "::1/128"],
21
+ * trustedProxies: ["10.0.0.0/8"], // peer-gate XFF to your proxy range
22
+ * denyStatus: 404, // default — reveal nothing about the gate
23
+ * denyBody: "Not Found", // default
24
+ * audit: b.audit, // default: null — emits network.gate.denied
25
25
  * });
26
26
  *
27
27
  * router.use(fence);
@@ -29,9 +29,11 @@
29
29
  * Behaviour:
30
30
  * - The middleware is path-scoped: requests whose pathname doesn't
31
31
  * start with any of `paths` pass through unchanged. Hot-path-cheap.
32
- * - Requests on a gated path get their client IP resolved through
33
- * b.requestHelpers.clientIp(req, { trustProxy })same trust
34
- * model as every other middleware that reads client IP.
32
+ * - Requests on a gated path get their client IP resolved peer-gated:
33
+ * the socket address by default, or when `trustedProxies` /
34
+ * `clientIpResolver` is set X-Forwarded-For honored only from a
35
+ * trusted proxy peer. A bare `trustProxy` is refused at construction
36
+ * because it would let a direct caller forge an allowed address.
35
37
  * - The IP is checked against the CIDR allowlist using
36
38
  * b.ssrfGuard.cidrContains. A miss returns denyStatus + denyBody
37
39
  * and audits the rejection. Default 404 hides the gate's
@@ -84,38 +86,47 @@ function _validateCidr(cidr) {
84
86
  * authorization prevents unauthorized USERS from reaching sensitive
85
87
  * routes; this middleware adds a NETWORK-layer fence so a credential
86
88
  * leak doesn't compromise the gate. Path-scoped — requests outside
87
- * the configured prefixes pass through hot-path-cheap. Resolves
88
- * client IP via `b.requestHelpers.clientIp` (with `trustProxy`),
89
- * checks against the CIDR allowlist via `b.ssrfGuard.cidrContains`,
90
- * and refuses misses with HTTP 404 by default (hides the gate from
91
- * probes). Throws at create-time on malformed opts.
89
+ * the configured prefixes pass through hot-path-cheap. Checks the
90
+ * resolved client IP against the CIDR allowlist via
91
+ * `b.ssrfGuard.cidrContains` and refuses misses with HTTP 404 by
92
+ * default (hides the gate from probes). Throws at create-time on
93
+ * malformed opts.
94
+ *
95
+ * Client-IP resolution is peer-gated. By default only the socket
96
+ * address is used — X-Forwarded-For is attacker-forgeable, so trusting
97
+ * it bare would let a direct caller spoof an allowed IP through the
98
+ * gate. Behind a reverse proxy, declare it with `trustedProxies`
99
+ * (CIDRs — XFF is then honored only when the immediate peer is one of
100
+ * them) or own resolution entirely with `clientIpResolver(req)`. A bare
101
+ * `trustProxy` is refused at construction.
92
102
  *
93
103
  * @opts
94
104
  * {
95
- * paths: string[], // pathname prefixes, required
96
- * allowedCidrs: string[], // required
97
- * deniedCidrs: string[],
98
- * trustProxy: boolean, // default false
99
- * denyStatus: number, // default 404
100
- * denyBody: string, // default "Not Found"
101
- * audit: object,
102
- * onDeny: function(req, res, info): void, // own the refusal; info = { status, reason, clientIp, route }
103
- * problemDetails: boolean, // default false emit RFC 9457 application/problem+json instead of text/plain
105
+ * paths: string[], // pathname prefixes, required
106
+ * allowedCidrs: string[], // required
107
+ * deniedCidrs: string[],
108
+ * trustedProxies: string[], // CIDRs of your reverse proxies — peer-gates X-Forwarded-For
109
+ * clientIpResolver: function(req): string|null, // own client-IP resolution
110
+ * denyStatus: number, // default 404
111
+ * denyBody: string, // default "Not Found"
112
+ * audit: object,
113
+ * onDeny: function(req, res, info): void, // own the refusal; info = { status, reason, clientIp, route }
114
+ * problemDetails: boolean, // default false — emit RFC 9457 application/problem+json instead of text/plain
104
115
  * }
105
116
  *
106
117
  * @example
107
118
  * var b = require("@blamejs/core");
108
119
  * var app = b.router.create();
109
120
  * app.use(b.middleware.networkAllowlist({
110
- * paths: ["/admin"],
111
- * allowedCidrs: ["10.0.0.0/8", "::1/128"],
112
- * trustProxy: true,
121
+ * paths: ["/admin"],
122
+ * allowedCidrs: ["10.0.0.0/8", "::1/128"],
123
+ * trustedProxies: ["10.0.0.0/8"], // your reverse proxy's range
113
124
  * }));
114
125
  */
115
126
  function create(opts) {
116
127
  opts = opts || {};
117
128
  validateOpts(opts, [
118
- "paths", "allowedCidrs", "deniedCidrs", "trustProxy",
129
+ "paths", "allowedCidrs", "deniedCidrs", "trustProxy", "trustedProxies", "clientIpResolver",
119
130
  "denyStatus", "denyBody", "audit", "onDeny", "problemDetails",
120
131
  ], "middleware.networkAllowlist");
121
132
 
@@ -148,10 +159,30 @@ function create(opts) {
148
159
  }
149
160
  }
150
161
 
162
+ // Client-IP resolution for an access-control gate must be peer-gated:
163
+ // a bare `trustProxy` honors X-Forwarded-For from any caller, so a client
164
+ // connecting directly can forge an allowed address and walk through the
165
+ // gate. Operators behind a reverse proxy declare it via `trustedProxies`
166
+ // (CIDRs of their proxies — XFF is then peer-gated) or own resolution
167
+ // entirely via `clientIpResolver`. We refuse, at construction, the
168
+ // spoofable combination of `trustProxy` without either.
169
+ var _ipResolver;
170
+ try {
171
+ _ipResolver = requestHelpers.trustedClientIp({
172
+ trustedProxies: opts.trustedProxies,
173
+ clientIpResolver: opts.clientIpResolver,
174
+ });
175
+ } catch (e) { throw _err("BAD_OPT", e.message); }
176
+ var trustProxyOpt = opts.trustProxy === true || typeof opts.trustProxy === "number";
177
+ if (trustProxyOpt && !_ipResolver.peerGated) {
178
+ throw _err("BAD_OPT",
179
+ "trustProxy is spoofable for an access-control gate — X-Forwarded-For from a " +
180
+ "direct caller would be trusted. Declare your reverse proxies via " +
181
+ "trustedProxies: [\"10.0.0.0/8\", …] (peer-gated XFF) or supply clientIpResolver(req).");
182
+ }
183
+
151
184
  var paths = opts.paths.slice();
152
185
  var allowedCidrs = opts.allowedCidrs.slice();
153
- var trustProxy = opts.trustProxy === true || typeof opts.trustProxy === "number"
154
- ? opts.trustProxy : false;
155
186
  var denyStatus = typeof opts.denyStatus === "number" ? opts.denyStatus : 404;
156
187
  if (denyStatus < 400 || denyStatus >= 600 || Math.floor(denyStatus) !== denyStatus) {
157
188
  throw _err("BAD_OPT", "denyStatus must be a 4xx or 5xx integer, got " + denyStatus);
@@ -212,7 +243,7 @@ function create(opts) {
212
243
  var pathname = req.pathname || (req.url || "").split("?")[0];
213
244
  if (!_matchesPath(pathname)) return next();
214
245
 
215
- var ip = requestHelpers.clientIp(req, { trustProxy: trustProxy });
246
+ var ip = _ipResolver.resolve(req);
216
247
  if (!ip) {
217
248
  // Fail closed: a request we can't even derive an IP for shouldn't
218
249
  // bypass the gate.
@@ -118,17 +118,6 @@ function _conflictRefs(dialect, table) {
118
118
  var audit = lazyRequire(function () { return require("../audit"); });
119
119
  var logger = lazyRequire(function () { return require("../log").boot("rate-limit"); });
120
120
 
121
- // `_clientIp` defers to `requestHelpers.clientIp`, threading the
122
- // per-middleware `trustProxy` opt. Default refuses forwarded headers
123
- // (returning the socket address only) — operators behind a sanitizing
124
- // reverse proxy opt in via `trustProxy: true` (or a hop count).
125
- function _clientIpFor(trustProxy) {
126
- return function (req) {
127
- var ip = requestHelpers.clientIp(req, { trustProxy: trustProxy });
128
- return ip || "unknown";
129
- };
130
- }
131
-
132
121
  // Reject NaN / Infinity / negative / non-positive / non-number at create
133
122
  // time so a misconfigured rate-limit can't silently degrade to "no
134
123
  // limit" or produce divide-by-zero verdicts at request time.
@@ -457,7 +446,9 @@ function _resolveBackend(opts) {
457
446
  * limit: number,
458
447
  * windowMs: number,
459
448
  * pruneIntervalMs: number,
460
- * trustProxy: boolean|number,
449
+ * trustedProxies: string|string[], // CIDRs of your reverse proxies — peer-gates X-Forwarded-For for the IP key
450
+ * clientIpResolver: function(req): string|null, // own the rate-limit key's client IP
451
+ * trustProxy: boolean|number, // legacy; refused with the default IP key (spoofable) — use trustedProxies
461
452
  * }
462
453
  *
463
454
  * @example
@@ -475,15 +466,33 @@ function create(opts) {
475
466
  validateOpts(opts, [
476
467
  "keyFn", "statusOnLimit", "bodyOnLimit", "onDeny", "problemDetails",
477
468
  "header", "headerPrefix", "skipPaths", "scope",
478
- "backend", "trustProxy", "algorithm",
469
+ "backend", "trustProxy", "trustedProxies", "clientIpResolver", "algorithm",
479
470
  // memory backend (token-bucket)
480
471
  "burst", "refillPerSecond",
481
472
  // memory backend (fixed-window) + cluster backend
482
473
  "max", "limit", "windowMs", "pruneIntervalMs",
483
474
  ], "middleware.rateLimit");
484
- var trustProxy = opts.trustProxy === true || typeof opts.trustProxy === "number"
485
- ? opts.trustProxy : false;
486
- var _clientIp = _clientIpFor(trustProxy);
475
+ // Peer-gated client-IP resolution. The default key (and the audit-actor
476
+ // IP) is the client address; a bare trustProxy would let a caller forge
477
+ // X-Forwarded-For to evade their own limit or poison a victim's bucket.
478
+ // Operators behind a proxy declare trustedProxies (CIDRs) or own
479
+ // resolution via clientIpResolver. A bare trustProxy is refused when the
480
+ // default IP key is in use (no keyFn) — it would be spoofable.
481
+ var _ipResolver;
482
+ try {
483
+ _ipResolver = requestHelpers.trustedClientIp({
484
+ trustedProxies: opts.trustedProxies,
485
+ clientIpResolver: opts.clientIpResolver,
486
+ });
487
+ } catch (e) { throw new Error("middleware.rateLimit: " + e.message); }
488
+ var trustProxyBare = opts.trustProxy === true || typeof opts.trustProxy === "number";
489
+ if (trustProxyBare && !_ipResolver.peerGated && !opts.keyFn) {
490
+ throw new Error("middleware.rateLimit: trustProxy is spoofable — a caller can forge " +
491
+ "X-Forwarded-For to evade the limit or poison another IP's bucket. Declare your " +
492
+ "reverse proxies via trustedProxies: [\"10.0.0.0/8\", …], supply clientIpResolver(req), " +
493
+ "or set your own keyFn.");
494
+ }
495
+ var _clientIp = function (req) { return _ipResolver.resolve(req) || "unknown"; };
487
496
  var keyFn = opts.keyFn || _clientIp;
488
497
  var statusOnLimit = opts.statusOnLimit || 429;
489
498
  var bodyOnLimit = opts.bodyOnLimit !== undefined ? opts.bodyOnLimit : "Too Many Requests";
@@ -4,6 +4,7 @@
4
4
  // + /Schemas surfaces backed by operator-supplied CRUD callbacks.
5
5
 
6
6
  var framework_error = require("../framework-error");
7
+ var numericBounds = require("../numeric-bounds");
7
8
  var pick = require("../pick");
8
9
  var validateOpts = require("../validate-opts");
9
10
  var safeJson = require("../safe-json");
@@ -609,7 +610,7 @@ function _anyDependencyFailed(refs, plan, executed, bulkIdMap) {
609
610
  // RFC 7644 §3.7 — failOnErrors is an OPTIONAL integer >= 1. Absent /
610
611
  // non-conforming values mean "process every operation" (null).
611
612
  function _parseFailOnErrors(value) {
612
- if (typeof value !== "number" || !isFinite(value) || Math.floor(value) !== value || value < 1) {
613
+ if (!numericBounds.isPositiveFiniteInt(value)) {
613
614
  return null;
614
615
  }
615
616
  return value;
@@ -210,7 +210,9 @@ function _validatePermissionsPolicy(value) {
210
210
  * acceptCh: string|false,
211
211
  * criticalCh: string|false,
212
212
  * reportingEndpoints: object,
213
- * trustProxy: boolean|number,
213
+ * trustedProxies: string|string[], // CIDRs of your reverse proxies — peer-gates X-Forwarded-Proto for HSTS
214
+ * protocolResolver: function(req): "http"|"https", // own the HTTPS decision
215
+ * trustProxy: boolean|number, // legacy; refused unless paired with trustedProxies/protocolResolver (spoofable)
214
216
  * coopReportOnly: string, // default: off — monitor-mode COOP
215
217
  * coepReportOnly: string, // default: off — monitor-mode COEP
216
218
  * documentPolicyReportOnly: string, // default: off — monitor-mode Document-Policy
@@ -231,6 +233,7 @@ function create(opts) {
231
233
  "hsts", "contentTypeOptions", "frameOptions", "referrerPolicy",
232
234
  "permissionsPolicy", "coop", "coep", "corp",
233
235
  "originAgentCluster", "dnsPrefetchControl", "csp", "trustProxy",
236
+ "trustedProxies", "protocolResolver",
234
237
  "reportingEndpoints", "documentPolicy", "criticalCh", "acceptCh",
235
238
  "coopReportOnly", "coepReportOnly", "documentPolicyReportOnly",
236
239
  "requireDocumentPolicy", "serviceWorkerAllowed",
@@ -238,8 +241,23 @@ function create(opts) {
238
241
  if (opts.permissionsPolicy && typeof opts.permissionsPolicy === "string") {
239
242
  _validatePermissionsPolicy(opts.permissionsPolicy);
240
243
  }
241
- var trustProxy = opts.trustProxy === true || typeof opts.trustProxy === "number"
242
- ? opts.trustProxy : false;
244
+ // HSTS is emitted only on HTTPS responses; behind a proxy that comes from
245
+ // X-Forwarded-Proto. A bare trustProxy trusts the forgeable header from any
246
+ // caller, so a direct request forging "http" could suppress HSTS on a real
247
+ // HTTPS response (SSL-strip window). Peer-gate it via trustedProxies, or own
248
+ // the decision via protocolResolver; a bare trustProxy is refused.
249
+ var _proto;
250
+ try {
251
+ _proto = requestHelpers.trustedProtocol({
252
+ trustedProxies: opts.trustedProxies,
253
+ protocolResolver: opts.protocolResolver,
254
+ });
255
+ } catch (e) { throw new TypeError("middleware.securityHeaders: " + e.message); }
256
+ if ((opts.trustProxy === true || typeof opts.trustProxy === "number") && !_proto.peerGated) {
257
+ throw new TypeError("middleware.securityHeaders: trustProxy is spoofable for the HSTS " +
258
+ "decision — a direct caller could forge X-Forwarded-Proto to suppress HSTS. Declare your " +
259
+ "reverse proxies via trustedProxies: [\"10.0.0.0/8\", …] or supply protocolResolver(req).");
260
+ }
243
261
  var hsts = opts.hsts === undefined ? "max-age=63072000; includeSubDomains; preload" : opts.hsts;
244
262
  var ctOpts = opts.contentTypeOptions === undefined ? "nosniff" : opts.contentTypeOptions;
245
263
  var frameOpts = opts.frameOptions === undefined ? "DENY" : opts.frameOptions;
@@ -317,9 +335,9 @@ function create(opts) {
317
335
  // RFC 6797 §7.2: HSTS over plain HTTP is meaningless (UAs ignore
318
336
  // it). Skip the header on non-TLS requests so dev-over-HTTP doesn't
319
337
  // surface confusing "Strict-Transport-Security on http://" lines.
320
- // requestProtocol respects trustProxyoperators behind a TLS
321
- // terminator opt in to read X-Forwarded-Proto.
322
- if (hsts && requestHelpers.requestProtocol(req, { trustProxy: trustProxy }) === "https") {
338
+ // Peer-gated protocol resolutionX-Forwarded-Proto honored only from a
339
+ // trusted proxy (trustedProxies / protocolResolver), else the TLS socket.
340
+ if (hsts && _proto.resolve(req) === "https") {
323
341
  res.setHeader("Strict-Transport-Security", hsts);
324
342
  }
325
343
  if (ctOpts) res.setHeader("X-Content-Type-Options", ctOpts);
@@ -66,6 +66,7 @@
66
66
 
67
67
  var validateOpts = require("../validate-opts");
68
68
  var safeBuffer = require("../safe-buffer");
69
+ var safeJson = require("../safe-json");
69
70
 
70
71
  // Per W3C draft + Chromium implementation. `immediate` triggers the
71
72
  // speculation as soon as the rules are seen; `conservative` waits
@@ -129,7 +130,7 @@ function _validateRules(rules, label) {
129
130
  throw new TypeError(label + "." + actionKey + "[" + ei +
130
131
  "].where must be a `where` clause object (W3C draft, e.g. { href_matches: \"/path/*\" })");
131
132
  }
132
- if (typeof rule.eagerness !== "string" || !EAGERNESS_LEVELS[rule.eagerness]) {
133
+ if (typeof rule.eagerness !== "string" || !Object.prototype.hasOwnProperty.call(EAGERNESS_LEVELS, rule.eagerness)) {
133
134
  throw new TypeError(label + "." + actionKey + "[" + ei +
134
135
  "].eagerness must be one of: " + Object.keys(EAGERNESS_LEVELS).join(", "));
135
136
  }
@@ -245,8 +246,10 @@ function create(opts) {
245
246
  }
246
247
 
247
248
  // Pre-built inline JSON. The body is small (rules objects are
248
- // typically ~200 bytes); JSON.stringify once and cache.
249
- var inlineJson = inline ? JSON.stringify(rulesObj) : null;
249
+ // typically ~200 bytes); stringify once and cache. Uses the
250
+ // <script>-safe serializer so a rules value containing "</script>"
251
+ // (or U+2028/U+2029) cannot break out of the injected element.
252
+ var inlineJson = inline ? safeJson.stringifyForScript(rulesObj) : null;
250
253
 
251
254
  return function speculationRules(req, res, next) {
252
255
  if (!inline) {
@@ -399,7 +399,7 @@ function create(opts) {
399
399
 
400
400
  var extensions = Array.isArray(opts.extensions) ? opts.extensions.slice() : DEFAULT_EXTENSIONS.slice();
401
401
  for (var i = 0; i < extensions.length; i++) {
402
- if (!KNOWN_EXTENSIONS[extensions[i]]) {
402
+ if (!Object.prototype.hasOwnProperty.call(KNOWN_EXTENSIONS, extensions[i])) {
403
403
  throw new TusError("tus/bad-opts",
404
404
  "middleware.tusUpload: unknown extension '" + extensions[i] + "'");
405
405
  }
@@ -416,7 +416,7 @@ function create(opts) {
416
416
  var checksumAlgorithmSet = {};
417
417
  for (var j = 0; j < checksumAlgorithms.length; j++) {
418
418
  var algo = checksumAlgorithms[j];
419
- if (!KNOWN_CHECKSUM_ALGORITHMS[algo]) {
419
+ if (!Object.prototype.hasOwnProperty.call(KNOWN_CHECKSUM_ALGORITHMS, algo)) {
420
420
  throw new TusError("tus/bad-opts",
421
421
  "middleware.tusUpload: unknown checksum algorithm '" + algo + "'");
422
422
  }
package/lib/money.js CHANGED
@@ -412,7 +412,7 @@ function roundMinor(minor, step, mode) {
412
412
  throw new MoneyError("money/bad-minor-units",
413
413
  "minor must be an integer (BigInt or safe integer Number); got " + (typeof minor));
414
414
  }
415
- if (!INCREMENT_MODES[mode]) {
415
+ if (!Object.prototype.hasOwnProperty.call(INCREMENT_MODES, mode)) {
416
416
  throw new MoneyError("money/bad-rounding-mode",
417
417
  "mode must be one of half-even | half-up | half-down | ceiling | floor; got " + String(mode));
418
418
  }
package/lib/mtls-ca.js CHANGED
@@ -211,7 +211,7 @@ function create(opts) {
211
211
  var paths = _resolvePaths(opts.dataDir, opts.paths);
212
212
  var vault = opts.vault || null;
213
213
  var caKeySealedMode = (opts.caKeySealedMode || "required").toLowerCase();
214
- if (!VALID_SEAL_MODES[caKeySealedMode]) {
214
+ if (!Object.prototype.hasOwnProperty.call(VALID_SEAL_MODES, caKeySealedMode)) {
215
215
  throw new MtlsCaError("mtls-ca/bad-mode",
216
216
  "caKeySealedMode must be 'required' or 'disabled' " +
217
217
  "(legacy 'auto' was removed — it defaulted to plaintext-on-disk)");
@@ -246,7 +246,7 @@ function create(opts) {
246
246
  current: generation,
247
247
  };
248
248
  }
249
- var pem = nodeFs.readFileSync(paths.caCert);
249
+ var pem = atomicFile.fdSafeReadSync(paths.caCert, { maxBytes: C.BYTES.mib(1) });
250
250
  var gen = parseGeneration(pem);
251
251
  return {
252
252
  exists: true,
@@ -272,7 +272,9 @@ function create(opts) {
272
272
  "CA_KEY_SEALED='required' but " + paths.caKeySealed + " does not exist");
273
273
  }
274
274
  _requireVault("sealed CA key load");
275
- var sealedBytes = nodeFs.readFileSync(paths.caKeySealed, "utf8").trim();
275
+ // Cap + fd-bound CA-private-key read. NO refuseSymlink: caKeySealed may be
276
+ // an operator-absolute path on a k8s/KMS secret volume that symlinks it.
277
+ var sealedBytes = atomicFile.fdSafeReadSync(paths.caKeySealed, { maxBytes: C.BYTES.kib(64), encoding: "utf8" }).trim();
276
278
  var pem = vault.unseal(sealedBytes);
277
279
  if (!pem) {
278
280
  throw new MtlsCaError("mtls-ca/unseal-failed",
@@ -285,7 +287,9 @@ function create(opts) {
285
287
  throw new MtlsCaError("mtls-ca/plain-required",
286
288
  "caKeySealedMode='disabled' but " + paths.caKey + " does not exist");
287
289
  }
288
- return nodeFs.readFileSync(paths.caKey);
290
+ // Cap + fd-bound plaintext CA-private-key read (disabled mode = dev opt-out).
291
+ // NO refuseSymlink (operator-absolute path may symlink).
292
+ return atomicFile.fdSafeReadSync(paths.caKey, { maxBytes: C.BYTES.kib(64) });
289
293
  }
290
294
 
291
295
  function loadCert() {
@@ -293,7 +297,7 @@ function create(opts) {
293
297
  throw new MtlsCaError("mtls-ca/missing-cert",
294
298
  "no CA cert on disk at " + paths.caCert);
295
299
  }
296
- return nodeFs.readFileSync(paths.caCert);
300
+ return atomicFile.fdSafeReadSync(paths.caCert, { maxBytes: C.BYTES.mib(1) });
297
301
  }
298
302
 
299
303
  // Atomic commit: write .tmp + atomic rename for both key and cert.
@@ -446,7 +450,7 @@ function create(opts) {
446
450
  // safeJson.parse caps depth + size + protects against
447
451
  // proto-pollution; a tampered or truncated file shouldn't be able to
448
452
  // corrupt the rotator process.
449
- var json = safeJson.parse(nodeFs.readFileSync(paths.revocations, "utf8"),
453
+ var json = safeJson.parse(atomicFile.fdSafeReadSync(paths.revocations, { maxBytes: C.BYTES.mib(16), encoding: "utf8" }),
450
454
  { maxBytes: C.BYTES.mib(16) });
451
455
  return (json && Array.isArray(json.revocations)) ? json.revocations : [];
452
456
  } catch (e) {
@@ -179,7 +179,7 @@ var QTYPE_BY_NAME = Object.freeze({
179
179
  function create(opts) {
180
180
  opts = opts || {};
181
181
  var profile = opts.profile || (opts.posture && safeDns.compliancePosture(opts.posture)) || DEFAULT_PROFILE;
182
- if (!safeDns.PROFILES[profile]) {
182
+ if (!Object.prototype.hasOwnProperty.call(safeDns.PROFILES, profile)) {
183
183
  throw new ResolverError("resolver/bad-profile",
184
184
  "create: unknown profile '" + profile + "'");
185
185
  }