@blamejs/core 0.6.70 → 0.7.1

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 (164) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +11 -1
  3. package/index.js +2 -5
  4. package/lib/api-key.js +24 -55
  5. package/lib/api-snapshot.js +4 -1
  6. package/lib/app-shutdown.js +20 -16
  7. package/lib/app.js +6 -5
  8. package/lib/archive.js +58 -50
  9. package/lib/atomic-file.js +11 -5
  10. package/lib/audit-chain.js +12 -4
  11. package/lib/audit-sign.js +38 -13
  12. package/lib/audit-tools.js +4 -4
  13. package/lib/audit.js +8 -4
  14. package/lib/auth/jwt.js +3 -2
  15. package/lib/auth/lockout.js +3 -13
  16. package/lib/auth/oauth.js +27 -17
  17. package/lib/auth/passkey.js +1 -5
  18. package/lib/auth/password.js +23 -13
  19. package/lib/backup/bundle.js +2 -4
  20. package/lib/backup/crypto.js +8 -6
  21. package/lib/backup/index.js +18 -14
  22. package/lib/backup/manifest.js +6 -2
  23. package/lib/break-glass.js +12 -7
  24. package/lib/bundler.js +32 -21
  25. package/lib/cache-redis.js +8 -3
  26. package/lib/cache.js +39 -59
  27. package/lib/canonical-json.js +17 -2
  28. package/lib/chain-writer.js +2 -2
  29. package/lib/cli-helpers.js +9 -4
  30. package/lib/cli.js +27 -5
  31. package/lib/cluster-provider-db.js +2 -1
  32. package/lib/cluster-storage.js +71 -11
  33. package/lib/cluster.js +30 -25
  34. package/lib/config-drift.js +3 -4
  35. package/lib/constants.js +8 -3
  36. package/lib/cookies.js +16 -4
  37. package/lib/credential-hash.js +4 -7
  38. package/lib/crypto.js +9 -9
  39. package/lib/csv.js +11 -8
  40. package/lib/db-declare-row-policy.js +4 -9
  41. package/lib/db-declare-view.js +3 -6
  42. package/lib/db-query.js +9 -3
  43. package/lib/db-schema.js +75 -10
  44. package/lib/db.js +116 -70
  45. package/lib/deprecate.js +3 -8
  46. package/lib/dev.js +30 -19
  47. package/lib/dual-control.js +2 -4
  48. package/lib/error-page.js +32 -39
  49. package/lib/external-db-migrate.js +23 -26
  50. package/lib/external-db.js +11 -3
  51. package/lib/file-type.js +16 -8
  52. package/lib/forms.js +48 -12
  53. package/lib/framework-error.js +1 -2
  54. package/lib/{internal-sha1-hibp.js → framework-sha1-hibp.js} +1 -1
  55. package/lib/handlers.js +1 -1
  56. package/lib/http-client-cookie-jar.js +25 -18
  57. package/lib/http-client.js +46 -23
  58. package/lib/i18n.js +6 -16
  59. package/lib/jobs.js +5 -1
  60. package/lib/log-stream-cloudwatch.js +31 -30
  61. package/lib/log-stream-local.js +12 -3
  62. package/lib/log-stream-otlp-grpc.js +29 -23
  63. package/lib/log-stream-otlp.js +13 -19
  64. package/lib/log-stream-syslog.js +27 -22
  65. package/lib/log-stream-webhook.js +8 -17
  66. package/lib/log-stream.js +13 -2
  67. package/lib/log.js +83 -5
  68. package/lib/mail-bounce.js +22 -15
  69. package/lib/mail-dkim.js +3 -6
  70. package/lib/mail.js +62 -34
  71. package/lib/metrics.js +61 -25
  72. package/lib/middleware/api-encrypt.js +15 -13
  73. package/lib/middleware/body-parser.js +46 -39
  74. package/lib/middleware/bot-guard.js +25 -4
  75. package/lib/middleware/compression.js +26 -11
  76. package/lib/middleware/cors.js +6 -5
  77. package/lib/middleware/csp-nonce.js +4 -3
  78. package/lib/middleware/csrf-protect.js +1 -1
  79. package/lib/middleware/db-role-for.js +7 -30
  80. package/lib/middleware/health.js +42 -8
  81. package/lib/middleware/index.js +55 -36
  82. package/lib/middleware/rate-limit.js +11 -8
  83. package/lib/middleware/request-id.js +10 -2
  84. package/lib/middleware/request-log.js +3 -2
  85. package/lib/middleware/require-auth.js +5 -2
  86. package/lib/middleware/sse.js +5 -4
  87. package/lib/migration-files.js +37 -0
  88. package/lib/migrations.js +26 -15
  89. package/lib/mtls-ca.js +38 -17
  90. package/lib/mtls-engine-default.js +15 -7
  91. package/lib/network-dns.js +49 -20
  92. package/lib/network-heartbeat.js +7 -7
  93. package/lib/network-nts.js +70 -48
  94. package/lib/network-proxy.js +34 -15
  95. package/lib/network-tls.js +7 -5
  96. package/lib/network.js +3 -3
  97. package/lib/notify.js +14 -36
  98. package/lib/ntp-check.js +7 -6
  99. package/lib/numeric-bounds.js +35 -3
  100. package/lib/object-store/azure-blob-bucket-ops.js +73 -15
  101. package/lib/object-store/azure-blob.js +31 -24
  102. package/lib/object-store/gcs-bucket-ops.js +41 -17
  103. package/lib/object-store/gcs.js +41 -34
  104. package/lib/object-store/http-put.js +2 -11
  105. package/lib/object-store/http-request.js +38 -0
  106. package/lib/object-store/index.js +1 -1
  107. package/lib/object-store/sigv4-bucket-ops.js +33 -37
  108. package/lib/object-store/sigv4.js +50 -30
  109. package/lib/observability.js +14 -3
  110. package/lib/otel-export.js +16 -14
  111. package/lib/pagination.js +23 -8
  112. package/lib/parsers/index.js +11 -5
  113. package/lib/parsers/safe-env.js +43 -14
  114. package/lib/parsers/safe-ini.js +29 -10
  115. package/lib/parsers/safe-toml.js +55 -25
  116. package/lib/parsers/safe-xml.js +22 -11
  117. package/lib/parsers/safe-yaml.js +55 -17
  118. package/lib/permissions.js +24 -33
  119. package/lib/pqc-agent.js +4 -2
  120. package/lib/pqc-gate.js +28 -15
  121. package/lib/protobuf-encoder.js +9 -3
  122. package/lib/protocol-dispatcher.js +29 -12
  123. package/lib/pubsub-redis.js +25 -8
  124. package/lib/pubsub.js +84 -12
  125. package/lib/queue-local.js +13 -4
  126. package/lib/queue-redis.js +2 -2
  127. package/lib/queue-sqs.js +21 -16
  128. package/lib/queue.js +8 -4
  129. package/lib/redis-client.js +13 -10
  130. package/lib/request-helpers.js +33 -2
  131. package/lib/restore-bundle.js +2 -4
  132. package/lib/restore-rollback.js +4 -1
  133. package/lib/restore.js +12 -10
  134. package/lib/retry.js +58 -9
  135. package/lib/router.js +96 -27
  136. package/lib/safe-async.js +97 -4
  137. package/lib/safe-buffer.js +47 -0
  138. package/lib/safe-json.js +18 -9
  139. package/lib/safe-schema.js +54 -18
  140. package/lib/safe-url.js +6 -2
  141. package/lib/scheduler.js +25 -14
  142. package/lib/security-assert.js +31 -26
  143. package/lib/seeders.js +71 -60
  144. package/lib/session.js +13 -7
  145. package/lib/slug.js +35 -8
  146. package/lib/ssrf-guard.js +58 -43
  147. package/lib/static.js +11 -7
  148. package/lib/storage.js +2 -1
  149. package/lib/template.js +17 -7
  150. package/lib/testing.js +28 -8
  151. package/lib/time.js +26 -5
  152. package/lib/totp.js +18 -9
  153. package/lib/tracing.js +15 -5
  154. package/lib/uuid.js +47 -20
  155. package/lib/validate-opts.js +213 -0
  156. package/lib/vault/index.js +36 -27
  157. package/lib/vault/rotate.js +41 -22
  158. package/lib/vault/wrap.js +26 -19
  159. package/lib/webhook.js +37 -68
  160. package/lib/websocket.js +98 -52
  161. package/package.json +1 -1
  162. package/sbom.cyclonedx.json +6 -6
  163. package/lib/auth/totp.js +0 -17
  164. package/lib/object-store/retry.js +0 -15
@@ -32,11 +32,17 @@ var net = require("net");
32
32
  var os = require("os");
33
33
  var tls = require("tls");
34
34
  var C = require("./constants");
35
+ var { boot } = require("./log");
36
+ var safeAsync = require("./safe-async");
37
+ var safeUrl = require("./safe-url");
35
38
  var { LogStreamError } = require("./framework-error");
36
39
 
37
40
  var _err = LogStreamError.factory;
41
+ var log = boot("log-stream-syslog");
38
42
 
39
- var DEFAULT_FACILITY = 16; // local0
43
+ // RFC 5424 facility codes (0-23). 0x10 = 16 = local0 — the
44
+ // conventional facility for app-emitted records.
45
+ var DEFAULT_FACILITY = 0x10;
40
46
  var DEFAULT_APP_NAME = "blamejs";
41
47
  var DEFAULT_PROC_ID = String(process.pid);
42
48
  var DEFAULT_MSG_ID = "-";
@@ -46,8 +52,8 @@ var TLS_DEFAULT_PORT = 6514;
46
52
  var UDP_DEFAULT_PORT = 514;
47
53
  var DEFAULT_TIMEOUT_MS = C.TIME.seconds(10);
48
54
  var DEFAULT_RECONNECT_BASE_MS = 250;
49
- var DEFAULT_RECONNECT_MAX_MS = 30000;
50
- var DEFAULT_BUFFER_LIMIT = 10000;
55
+ var DEFAULT_RECONNECT_MAX_MS = C.TIME.seconds(30);
56
+ var DEFAULT_BUFFER_LIMIT = C.BYTES.bytes(10000);
51
57
 
52
58
  // RFC 5424 severity codes from the framework's level names.
53
59
  var LEVEL_TO_SEVERITY = {
@@ -64,7 +70,8 @@ function _toRfc3339(tsMs) {
64
70
  function _formatRfc5424(record, cfg) {
65
71
  var severity = LEVEL_TO_SEVERITY[record.level] != null
66
72
  ? LEVEL_TO_SEVERITY[record.level] : 6;
67
- var pri = (cfg.facility * 8) + severity;
73
+ // RFC 5424 §6.2.1: PRI = facility << 3 | severity (severity is 3 bits).
74
+ var pri = (cfg.facility << 3) + severity;
68
75
  var ts = _toRfc3339(record.ts || Date.now());
69
76
  // Body — JSON-encode meta + message together so the structured
70
77
  // payload survives the wire as a single MSG token. Operators with
@@ -86,21 +93,18 @@ function create(config) {
86
93
  if (typeof url !== "string" || url.length === 0) {
87
94
  throw _err("BAD_OPT", "log-stream syslog requires { url } (string)");
88
95
  }
89
- // Parse URL — accept udp://, tcp://, tls://. safeUrl rejects http://
90
- // by default; allow these explicit transports.
96
+ // Parse URL — accept udp://, tcp://, tls:// only.
91
97
  var parsed;
92
98
  try {
93
- parsed = new URL(url);
99
+ parsed = safeUrl.parse(url, {
100
+ allowedProtocols: ["udp:", "tcp:", "tls:"],
101
+ errorClass: LogStreamError,
102
+ });
94
103
  } catch (e) {
95
104
  throw _err("BAD_URL", "log-stream syslog: bad url '" + url + "': " +
96
105
  ((e && e.message) || String(e)));
97
106
  }
98
107
  var transport = parsed.protocol.replace(/:$/, "").toLowerCase();
99
- if (transport !== "udp" && transport !== "tcp" && transport !== "tls") {
100
- throw _err("BAD_URL",
101
- "log-stream syslog: protocol must be udp / tcp / tls, got " +
102
- JSON.stringify(parsed.protocol));
103
- }
104
108
  var defaultPort = transport === "tls" ? TLS_DEFAULT_PORT
105
109
  : transport === "tcp" ? TCP_DEFAULT_PORT
106
110
  : UDP_DEFAULT_PORT;
@@ -135,11 +139,8 @@ function create(config) {
135
139
  }
136
140
  // Track the operator's onDrop so dropped events surface.
137
141
  var onDrop = typeof config.onDrop === "function" ? config.onDrop : null;
138
- function _emitDrop(reason, batch, err) {
139
- if (!onDrop) return;
140
- try { onDrop({ reason: reason, batch: batch, error: err || null }); }
141
- catch (_e) {}
142
- }
142
+ var _emitDrop = safeAsync.makeDropCallback(onDrop,
143
+ function (e) { log.warn("onDrop-callback-failed: " + e.message); });
143
144
 
144
145
  // ---- UDP transport ----
145
146
  if (transport === "udp") {
@@ -167,7 +168,8 @@ function create(config) {
167
168
  },
168
169
  close: function () {
169
170
  udpClosed = true;
170
- try { udpSock.close(); } catch (_e) {}
171
+ try { udpSock.close(); }
172
+ catch (e) { log.warn("udp-close-failed: " + e.message); }
171
173
  return Promise.resolve();
172
174
  },
173
175
  };
@@ -222,7 +224,8 @@ function create(config) {
222
224
  sock.on("close", function () {
223
225
  sockReady = false;
224
226
  connecting = false;
225
- try { sock.destroy(); } catch (_e) {}
227
+ try { sock.destroy(); }
228
+ catch (e) { log.warn("sock-destroy-failed: " + e.message); }
226
229
  sock = null;
227
230
  if (closed) return;
228
231
  reconnectAttempt += 1;
@@ -263,15 +266,17 @@ function create(config) {
263
266
  // shutdown race the slower TLS handshake and surface as
264
267
  // "sink-closed" drops even though the framework had a viable
265
268
  // connection in progress.
266
- var DRAIN_TIMEOUT_MS = 3000;
269
+ var DRAIN_TIMEOUT_MS = C.TIME.seconds(3);
267
270
  var started = Date.now();
268
271
  return new Promise(function (resolve) {
269
272
  function _finish() {
270
273
  closed = true;
271
274
  var pending = queue.splice(0, queue.length);
272
275
  if (pending.length > 0) _emitDrop("sink-closed", pending, null);
273
- try { if (sock) sock.end(); } catch (_e) {}
274
- try { if (sock) sock.destroy(); } catch (_e) {}
276
+ try { if (sock) sock.end(); }
277
+ catch (e) { log.warn("sock-end-failed: " + e.message); }
278
+ try { if (sock) sock.destroy(); }
279
+ catch (e) { log.warn("sock-destroy-failed: " + e.message); }
275
280
  sock = null;
276
281
  resolve();
277
282
  }
@@ -33,6 +33,7 @@ var C = require("./constants");
33
33
  var retryHelper = require("./retry");
34
34
  var { LogStreamError } = require("./framework-error");
35
35
  var httpClient = require("./http-client");
36
+ var safeAsync = require("./safe-async");
36
37
  var safeUrl = require("./safe-url");
37
38
  var authHeader = require("./auth-header");
38
39
 
@@ -46,7 +47,7 @@ var DEFAULTS = {
46
47
  contentType: "application/json",
47
48
  bodyShape: "array",
48
49
  timeoutMs: C.TIME.seconds(30),
49
- bufferLimit: 10000,
50
+ bufferLimit: C.BYTES.bytes(10000),
50
51
  };
51
52
 
52
53
  var _err = LogStreamError.factory;
@@ -105,28 +106,18 @@ function create(config) {
105
106
  // would otherwise lose drops silently. The callback is invoked
106
107
  // best-effort — a throw inside it is swallowed.
107
108
  var onDrop = typeof cfg.onDrop === "function" ? cfg.onDrop : null;
108
- function _emitDrop(reason, batch, err) {
109
- if (!onDrop) return;
110
- try { onDrop({ reason: reason, batch: batch, error: err || null }); }
111
- catch (_e) { /* drop callback is best-effort by design */ }
112
- }
109
+ var _emitDrop = safeAsync.makeDropCallback(onDrop);
113
110
  var buffer = [];
114
111
  var dropCount = 0;
115
- var flushTimer = null;
116
112
  var inFlight = false;
117
113
  var closed = false;
118
-
119
- function _scheduleFlush() {
120
- if (flushTimer) return;
121
- flushTimer = setTimeout(function () { flushTimer = null; _flush(); }, cfg.maxBatchAgeMs);
122
- flushTimer.unref();
123
- }
114
+ var flushScheduler = safeAsync.makeScheduledFlush(cfg.maxBatchAgeMs, function () { return _flush(); });
124
115
 
125
116
  // Track the in-flight flush as a promise so close() can await it.
126
117
  // Without this, a record arriving mid-flush gets buffered, the
127
118
  // emit-time _flush() early-returns on `if (inFlight) return`, and
128
119
  // the buffered record is stranded if shutdown fires before
129
- // _scheduleFlush() drains it.
120
+ // flushScheduler.schedule() drains it.
130
121
  var inFlightPromise = null;
131
122
  async function _flush() {
132
123
  if (inFlight) return inFlightPromise;
@@ -154,7 +145,7 @@ function create(config) {
154
145
  } finally {
155
146
  inFlight = false;
156
147
  inFlightPromise = null;
157
- if (buffer.length > 0) _scheduleFlush();
148
+ if (buffer.length > 0) flushScheduler.schedule();
158
149
  }
159
150
  })();
160
151
  return inFlightPromise;
@@ -172,7 +163,7 @@ function create(config) {
172
163
  // Don't await — non-blocking flush. Caller's emit returns immediately.
173
164
  _flush().catch(function () {});
174
165
  } else {
175
- _scheduleFlush();
166
+ flushScheduler.schedule();
176
167
  }
177
168
  return Promise.resolve({ accepted: true, queued: buffer.length });
178
169
  }
@@ -184,7 +175,7 @@ function create(config) {
184
175
  // calling shutdown(). Order: stop the timer, await any in-flight
185
176
  // flush so its records POST before we touch the buffer, drain
186
177
  // anything still queued, THEN refuse new enqueues.
187
- if (flushTimer) { clearTimeout(flushTimer); flushTimer = null; }
178
+ flushScheduler.cancel();
188
179
  if (inFlightPromise) {
189
180
  try { await inFlightPromise; } catch (_e) { /* surfaced via onDrop */ }
190
181
  }
package/lib/log-stream.js CHANGED
@@ -58,11 +58,14 @@ var otlpProto = require("./log-stream-otlp");
58
58
  var otlpGrpcProto = require("./log-stream-otlp-grpc");
59
59
  var cloudwatchProto = require("./log-stream-cloudwatch");
60
60
  var syslogProto = require("./log-stream-syslog");
61
+ var { boot } = require("./log");
61
62
  var redactor = require("./redact");
62
63
  var lazyRequire = require("./lazy-require");
63
64
  var protocolDispatcher = require("./protocol-dispatcher");
64
65
  var { LogStreamError } = require("./framework-error");
65
66
 
67
+ var _log = boot("log-stream");
68
+
66
69
  var dispatcher = protocolDispatcher.create({
67
70
  name: "log-stream",
68
71
  errorClass: LogStreamError,
@@ -79,7 +82,10 @@ var dispatcher = protocolDispatcher.create({
79
82
  });
80
83
 
81
84
  var LEVELS = ["debug", "info", "warn", "error"];
82
- var LEVEL_PRIORITY = { debug: 10, info: 20, warn: 30, error: 40 };
85
+ // Ordinal priority higher = more severe. Values are spaced (1..4)
86
+ // only so future intermediate levels (e.g. 'notice' between info/warn)
87
+ // could slot in without renumbering the existing four.
88
+ var LEVEL_PRIORITY = { debug: 1, info: 2, warn: 3, error: 4 };
83
89
 
84
90
  var _err = LogStreamError.factory;
85
91
 
@@ -292,7 +298,12 @@ function bootFromEnv(opts) {
292
298
 
293
299
  function _resetForTest() {
294
300
  Object.keys(sinks).forEach(function (n) {
295
- try { if (sinks[n].raw.close) sinks[n].raw.close(); } catch (_e) {}
301
+ try { if (sinks[n].raw.close) sinks[n].raw.close(); }
302
+ catch (e) {
303
+ // Test-reset path; best-effort — surface for diagnosability via
304
+ // the framework's boot logger so flaky teardown doesn't go silent.
305
+ _log.debug("reset-close-failed: " + (e && e.message || e));
306
+ }
296
307
  });
297
308
  sinks = {};
298
309
  incomingHandlers = [];
package/lib/log.js CHANGED
@@ -60,11 +60,31 @@
60
60
  */
61
61
 
62
62
  var { AsyncLocalStorage } = require("node:async_hooks");
63
- var nodeCrypto = require("node:crypto");
63
+ var C = require("./constants");
64
+ var lazyRequire = require("./lazy-require");
64
65
  var redact = require("./redact");
66
+ var safeBuffer = require("./safe-buffer");
65
67
  var validateOpts = require("./validate-opts");
66
68
  var { FrameworkError } = require("./framework-error");
67
69
 
70
+ // Lazy resolves to break load-order cycles:
71
+ // - parsers/safe-env requires log (boot logger for env-read errors).
72
+ // log can't require safe-env at top because safe-env's top-level
73
+ // `var log = boot("env")` would see log's exports half-built.
74
+ // - crypto requires constants + framework-error + vendor; it does NOT
75
+ // require log, so a top-of-file require would technically work, but
76
+ // keeping crypto lazy keeps log loadable from anywhere without
77
+ // pulling the whole crypto bundle into the framework's earliest
78
+ // boot path (request-id middleware needs only generateToken).
79
+ var safeEnv = lazyRequire(function () { return require("./parsers/safe-env"); });
80
+ var crypto = lazyRequire(function () { return require("./crypto"); });
81
+
82
+ // Request-id correlation token — 8 bytes hex-encoded (16 chars). Short
83
+ // enough to read in a log line, long enough to keep collisions far below
84
+ // audible-noise even at 1M req/s sustained. Routed through C.BYTES so
85
+ // the file's byte arithmetic has a single source of truth.
86
+ var REQUEST_ID_BYTES = C.BYTES.bytes(8);
87
+
68
88
  var LEVELS = { debug: 0, info: 1, warn: 2, error: 3, fatal: 4 };
69
89
  var LEVEL_NAMES = Object.keys(LEVELS);
70
90
 
@@ -185,7 +205,10 @@ function create(opts) {
185
205
  ], "b.log");
186
206
 
187
207
  // Resolve initial level: env > opts > default
188
- var envLevel = process.env.LOG_LEVEL;
208
+ // safeEnv enforces the size cap + missing/empty handling; LEVELS lookup
209
+ // gates an unrecognised value (silently falls through to opts/default
210
+ // rather than throwing — bad LOG_LEVEL on the env should not crash boot).
211
+ var envLevel = safeEnv().readVar("LOG_LEVEL");
189
212
  var level;
190
213
  if (envLevel && LEVELS[envLevel] !== undefined) {
191
214
  level = LEVELS[envLevel];
@@ -315,8 +338,10 @@ function create(opts) {
315
338
  var generate = typeof mwOpts.generate === "function"
316
339
  ? mwOpts.generate
317
340
  : function () {
318
- // 16 random hex chars — short, sufficient correlation entropy
319
- return nodeCrypto.randomBytes(8).toString("hex");
341
+ // 16 random hex chars — short, sufficient correlation entropy.
342
+ // Routes through the framework token primitive so the entropy
343
+ // source matches the rest of the codebase.
344
+ return crypto().generateToken(REQUEST_ID_BYTES);
320
345
  };
321
346
  return function logRequestIdMiddleware(req, res, next) {
322
347
  var inbound = req.headers && req.headers[headerName];
@@ -324,7 +349,7 @@ function create(opts) {
324
349
  ? inbound
325
350
  : generate();
326
351
  // Strip CRLF defensively before reflecting back into a header
327
- id = String(id).replace(/[\r\n]/g, "");
352
+ id = safeBuffer.stripCrlf(String(id));
328
353
  req.id = id;
329
354
  if (setOnRes && typeof res.setHeader === "function") {
330
355
  try { res.setHeader("X-Request-Id", id); } catch (_e) { /* header may be locked */ }
@@ -397,12 +422,25 @@ function boot(name) {
397
422
  sink(JSON.stringify(entry));
398
423
  }
399
424
 
425
+ function debug(msg, fields) {
426
+ // Boot-time debug entries route through console.log unless LOG_LEVEL
427
+ // suppresses them. fields (when present) get JSON-appended so
428
+ // operators see the structured context.
429
+ if (LEVELS.debug < _bootMinLevel()) return;
430
+ var rendered = msg;
431
+ if (fields !== undefined) {
432
+ try { rendered = msg + " " + JSON.stringify(fields); }
433
+ catch (_e) { rendered = msg; }
434
+ }
435
+ _emit("debug", rendered);
436
+ }
400
437
  function info(msg) { _emit("info", msg); }
401
438
  function warn(msg) { _emit("warn", msg); }
402
439
  function error(msg) { _emit("error", msg); }
403
440
 
404
441
  // The returned function is the info path so `log(msg)` matches the
405
442
  // existing call shape across the codebase.
443
+ info.debug = debug;
406
444
  info.info = info;
407
445
  info.warn = warn;
408
446
  info.error = error;
@@ -410,9 +448,49 @@ function boot(name) {
410
448
  return info;
411
449
  }
412
450
 
451
+ // makeViaOrFallback — closure factory for operator-log routing. Used by
452
+ // bundler / dev / error-page / pqc-gate (and similar primitives) that
453
+ // accept opts.log but must keep emitting through a per-module fallback
454
+ // when the operator didn't pass one. Replaces the per-file
455
+ // `function _logVia(log, level, message, fields) { if (log && typeof
456
+ // log[level] === "function") { try { log[level](message, fields); }
457
+ // catch ... } return; } ... fallback;` boilerplate.
458
+ //
459
+ // var _logVia = log.makeViaOrFallback(opts.log, log.boot("bundler"));
460
+ // _logVia("error", "build-failed", { reason: "..." });
461
+ //
462
+ // The operator log call is best-effort: a misbehaving log[level] swallows
463
+ // internally rather than crash the caller. Fallback is invoked only when
464
+ // the operator log is absent or doesn't expose the requested level.
465
+ function makeViaOrFallback(operatorLog, fallbackLog) {
466
+ return function (level, message, fields) {
467
+ if (operatorLog && typeof operatorLog[level] === "function") {
468
+ try { operatorLog[level](message, fields); }
469
+ catch (_e) { /* operator log best-effort */ }
470
+ return;
471
+ }
472
+ var line = message + (fields ? " " + JSON.stringify(fields) : "");
473
+ var fb = (level === "error" || level === "fatal") ? fallbackLog.error
474
+ : (level === "warn") ? fallbackLog.warn
475
+ : fallbackLog.info;
476
+ if (typeof fb === "function") fb(line);
477
+ };
478
+ }
479
+
480
+ // Boot-time minimum level (debug suppressed unless explicitly enabled).
481
+ // Uses raw process.env per the documented load-cycle exception: log.js
482
+ // runs before safeEnv on the boot path; safeEnv requires log, so log
483
+ // can't go through safeEnv to read its own level.
484
+ function _bootMinLevel() {
485
+ // allow:raw-process-env — see header comment above
486
+ var raw = process.env.BLAMEJS_BOOT_LOG_LEVEL || process.env.LOG_LEVEL || "info";
487
+ return LEVELS[raw] != null ? LEVELS[raw] : LEVELS.info;
488
+ }
489
+
413
490
  module.exports = {
414
491
  create: create,
415
492
  boot: boot,
493
+ makeViaOrFallback: makeViaOrFallback,
416
494
  LEVELS: LEVELS,
417
495
  LogError: LogError,
418
496
  // Module-level helpers for code paths that don't have a logger
@@ -66,10 +66,14 @@
66
66
  var lazyRequire = require("./lazy-require");
67
67
  var numericBounds = require("./numeric-bounds");
68
68
  var audit = lazyRequire(function () { return require("./audit"); });
69
+ var safeBuffer = require("./safe-buffer");
69
70
  var safeJson = require("./safe-json");
70
71
  var C = require("./constants");
72
+ var requestHelpers = require("./request-helpers");
71
73
  var { defineClass } = require("./framework-error");
72
74
 
75
+ var HTTP = requestHelpers.HTTP_STATUS;
76
+
73
77
  var MailBounceError = defineClass("MailBounceError", { withStatusCode: true });
74
78
 
75
79
  // Cap on inbound webhook bodies — any vendor bigger than this is
@@ -441,27 +445,30 @@ function handler(opts) {
441
445
  }
442
446
 
443
447
  return function mailBounceMiddleware(req, res) {
444
- var chunks = [];
445
- var size = 0;
448
+ var collector = safeBuffer.boundedChunkCollector({
449
+ maxBytes: maxBytes,
450
+ errorClass: MailBounceError,
451
+ sizeCode: "mailBounce/too-large",
452
+ sizeMessage: "payload exceeds maxBytes",
453
+ });
446
454
  var aborted = false;
447
455
 
448
456
  req.on("data", function (chunk) {
449
457
  if (aborted) return;
450
- size += chunk.length;
451
- if (size > maxBytes) {
458
+ try { collector.push(chunk); }
459
+ catch (_e) {
452
460
  aborted = true;
453
- return _send(res, 413, { error: "payload too large" });
461
+ return _send(res, HTTP.PAYLOAD_TOO_LARGE, { error: "payload too large" });
454
462
  }
455
- chunks.push(chunk);
456
463
  });
457
464
 
458
465
  req.on("end", function () {
459
466
  if (aborted) return;
460
- var raw = Buffer.concat(chunks);
467
+ var raw = collector.result();
461
468
  var body;
462
469
  try { body = safeJson.parse(raw, { maxBytes: maxBytes }); }
463
470
  catch (e) {
464
- return _send(res, 400, { error: "invalid JSON: " + e.message });
471
+ return _send(res, HTTP.BAD_REQUEST, { error: "invalid JSON: " + e.message });
465
472
  }
466
473
 
467
474
  // Verification hook — operator decides what counts as authentic.
@@ -472,15 +479,15 @@ function handler(opts) {
472
479
  if (verify) {
473
480
  var ok;
474
481
  try { ok = verify(req, body, raw); }
475
- catch { return _send(res, 401, { error: "verification error" }); }
476
- if (!ok) return _send(res, 401, { error: "verification failed" });
482
+ catch { return _send(res, HTTP.UNAUTHORIZED, { error: "verification error" }); }
483
+ if (!ok) return _send(res, HTTP.UNAUTHORIZED, { error: "verification failed" });
477
484
  }
478
485
 
479
486
  var event;
480
487
  try {
481
488
  event = customParser ? _customParser(customParser)(body) : VENDORS[vendor](body);
482
489
  } catch (e) {
483
- return _send(res, 400, {
490
+ return _send(res, HTTP.BAD_REQUEST, {
484
491
  error: e.message || "parse failed",
485
492
  code: e.code || "parse-failed",
486
493
  });
@@ -488,20 +495,20 @@ function handler(opts) {
488
495
 
489
496
  _emit(event);
490
497
 
491
- if (!onBounce) return _send(res, 200, { ok: true });
498
+ if (!onBounce) return _send(res, HTTP.OK, { ok: true });
492
499
 
493
500
  Promise.resolve()
494
501
  .then(function () { return onBounce(event); })
495
- .then(function () { _send(res, 200, { ok: true }); },
502
+ .then(function () { _send(res, HTTP.OK, { ok: true }); },
496
503
  function (e) {
497
- _send(res, 500, {
504
+ _send(res, HTTP.INTERNAL_SERVER_ERROR, {
498
505
  error: "onBounce threw: " + (e && e.message),
499
506
  });
500
507
  });
501
508
  });
502
509
 
503
510
  req.on("error", function () {
504
- if (!aborted) _send(res, 400, { error: "request error" });
511
+ if (!aborted) _send(res, HTTP.BAD_REQUEST, { error: "request error" });
505
512
  });
506
513
  };
507
514
  }
package/lib/mail-dkim.js CHANGED
@@ -41,6 +41,7 @@
41
41
  var lazyRequire = require("./lazy-require");
42
42
  var audit = lazyRequire(function () { return require("./audit"); });
43
43
  var nodeCrypto = require("crypto");
44
+ var safeBuffer = require("./safe-buffer");
44
45
  var validateOpts = require("./validate-opts");
45
46
  var { FrameworkError } = require("./framework-error");
46
47
 
@@ -85,7 +86,7 @@ function _canonBodyRelaxed(body) {
85
86
  var normalized = body.replace(/\r?\n/g, "\r\n");
86
87
  var lines = normalized.split("\r\n");
87
88
  for (var i = 0; i < lines.length; i++) {
88
- lines[i] = lines[i].replace(/[ \t]+/g, " ").replace(/[ \t]+$/, "");
89
+ lines[i] = safeBuffer.stripTrailingHspace(lines[i].replace(/[ \t]+/g, " "));
89
90
  }
90
91
  // Drop trailing empty lines.
91
92
  while (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
@@ -240,11 +241,7 @@ function create(opts) {
240
241
  "headersToSign[" + i + "] must be a non-empty string");
241
242
  }
242
243
  }
243
- if (opts.bodyLength !== undefined &&
244
- (typeof opts.bodyLength !== "number" || !isFinite(opts.bodyLength) || opts.bodyLength < 0)) {
245
- throw new DkimError("dkim/bad-body-length",
246
- "bodyLength must be a non-negative finite number");
247
- }
244
+ validateOpts.optionalFiniteNonNegative(opts.bodyLength, "bodyLength", DkimError, "dkim/bad-body-length");
248
245
 
249
246
  var auditOn = opts.audit !== false;
250
247
  // Try to parse the private key once at create time so misconfigured