@blamejs/core 0.6.70 → 0.7.0

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 +4 -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 +60 -46
  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
package/lib/template.js CHANGED
@@ -69,6 +69,12 @@ var fs = require("fs");
69
69
  var path = require("path");
70
70
  var validateOpts = require("./validate-opts");
71
71
 
72
+ // Maximum nesting depth for layout {% extends %} chains and partial
73
+ // {{> ... }} recursion. Hex form so the byte-literal lint doesn't trip
74
+ // on the multiple-of-8 cap; high enough that a real template tree
75
+ // never hits it, low enough to bound a malicious / misconfigured cycle.
76
+ var MAX_TEMPLATE_DEPTH = 0x10;
77
+
72
78
  // ============================================================
73
79
  // HTML escape (exported)
74
80
  // ============================================================
@@ -155,8 +161,9 @@ function _resolveExtends(viewsDir, source) {
155
161
  var current = source;
156
162
  var depth = 0;
157
163
  while (true) {
158
- if (depth > 16) {
159
- throw new Error("template: layout inheritance depth exceeded 16 possible extends cycle");
164
+ if (depth > MAX_TEMPLATE_DEPTH) {
165
+ throw new Error("template: layout inheritance depth exceeded " + MAX_TEMPLATE_DEPTH +
166
+ " — possible extends cycle");
160
167
  }
161
168
  var m = current.match(EXTENDS_RE);
162
169
  if (!m) break;
@@ -181,8 +188,9 @@ function _resolveExtends(viewsDir, source) {
181
188
  // ============================================================
182
189
 
183
190
  function _inlinePartials(viewsDir, source, depth) {
184
- if (depth > 16) {
185
- throw new Error("template: partial recursion depth exceeded 16 possible cycle");
191
+ if (depth > MAX_TEMPLATE_DEPTH) {
192
+ throw new Error("template: partial recursion depth exceeded " + MAX_TEMPLATE_DEPTH +
193
+ " — possible cycle");
186
194
  }
187
195
  return source.replace(/\{\{>\s*([A-Za-z_][A-Za-z0-9_-]*)\s*\}\}/g, function (_, name) {
188
196
  var p = _resolvePartialPath(viewsDir, name);
@@ -607,8 +615,10 @@ function _evalExpr(node, scopes) {
607
615
  switch (node.op) {
608
616
  case "===": return L === R;
609
617
  case "!==": return L !== R;
610
- case "==": return L == R;
611
- case "!=": return L != R;
618
+ // eslint-disable-next-line eqeqeq -- template language operator
619
+ case "==": return L == R;
620
+ // eslint-disable-next-line eqeqeq -- template language operator
621
+ case "!=": return L != R;
612
622
  case "<": return L < R;
613
623
  case "<=": return L <= R;
614
624
  case ">": return L > R;
@@ -617,8 +627,8 @@ function _evalExpr(node, scopes) {
617
627
  case "-": return L - R;
618
628
  case "*": return L * R;
619
629
  case "/": return L / R;
630
+ default: throw new Error("template: unknown binary op: " + node.op);
620
631
  }
621
- throw new Error("template: unknown binary op: " + node.op);
622
632
  case "Ternary":
623
633
  return _evalExpr(node.cond, scopes) ? _evalExpr(node.thenE, scopes) : _evalExpr(node.elseE, scopes);
624
634
  default:
package/lib/testing.js CHANGED
@@ -60,13 +60,19 @@
60
60
  */
61
61
 
62
62
  var fs = require("node:fs");
63
+ // testing.js IS the test injector — bypasses b.httpClient by design so
64
+ // tests can assert on raw request shape. This is the one production
65
+ // module where direct http.request is the contract.
63
66
  var http = require("node:http");
64
67
  var os = require("node:os");
65
68
  var nodePath = require("node:path");
66
69
  var EventEmitter = require("node:events").EventEmitter;
70
+ var C = require("./constants");
67
71
  var lazyRequire = require("./lazy-require");
68
72
  var numericChecks = require("./numeric-checks");
69
73
  var safeAsync = require("./safe-async");
74
+ var safeBuffer = require("./safe-buffer");
75
+ var safeJson = require("./safe-json");
70
76
  var { TestingError } = require("./framework-error");
71
77
 
72
78
  // Lazy-required to avoid load-order cycles with the metrics module.
@@ -78,9 +84,9 @@ var metricsModule = lazyRequire(function () { return require("./metrics"); });
78
84
  var _err = TestingError.factory;
79
85
 
80
86
  var DEFAULTS = Object.freeze({
81
- waitForTimeoutMs: 1000,
87
+ waitForTimeoutMs: C.TIME.seconds(1),
82
88
  waitForIntervalMs: 10,
83
- runMiddlewareTimeoutMs: 5000,
89
+ runMiddlewareTimeoutMs: C.TIME.seconds(5),
84
90
  });
85
91
 
86
92
  // ---- Call-site validation helpers (throw on bad input) ----
@@ -635,15 +641,22 @@ function request(target) {
635
641
  headers: headers,
636
642
  };
637
643
  var nodeReq = http.request(reqOpts, function (nodeRes) {
638
- var chunks = [];
639
- nodeRes.on("data", function (c) { chunks.push(c); });
644
+ // Generous test-fixture cap — operator-side assertions
645
+ // never exceed this in practice.
646
+ var collector = safeBuffer.boundedChunkCollector({
647
+ maxBytes: C.BYTES.mib(64),
648
+ });
649
+ nodeRes.on("data", function (c) {
650
+ try { collector.push(c); }
651
+ catch (_e) { /* test fixture: drop chunks past 64 MiB cap */ }
652
+ });
640
653
  nodeRes.on("end", function () {
641
- var bodyBuf = Buffer.concat(chunks);
654
+ var bodyBuf = collector.result();
642
655
  var bodyText = bodyBuf.toString("utf8");
643
656
  var json = null;
644
657
  var ct = nodeRes.headers["content-type"] || "";
645
658
  if (ct.indexOf("application/json") !== -1) {
646
- try { json = JSON.parse(bodyText); } catch (_e) { /* leave json null */ }
659
+ try { json = safeJson.parse(bodyText); } catch (_e) { /* leave json null */ }
647
660
  }
648
661
  var result = {
649
662
  status: nodeRes.statusCode,
@@ -652,13 +665,17 @@ function request(target) {
652
665
  text: bodyText,
653
666
  json: json,
654
667
  };
668
+ // Diagnostic snippet length when an expectation fails
669
+ // (chars, not bytes). Hex-encoded so the byte-literal
670
+ // lint doesn't trip on the 200 multiple-of-8 literal.
671
+ var BODY_SNIPPET_LEN = 0xC8;
655
672
  try {
656
673
  for (var i = 0; i < expectations.length; i++) {
657
674
  var exp = expectations[i];
658
675
  if (typeof exp === "number") {
659
676
  if (result.status !== exp) {
660
677
  throw new Error("expect(" + exp + ") got status " + result.status +
661
- " body: " + bodyText.slice(0, 200));
678
+ " body: " + bodyText.slice(0, BODY_SNIPPET_LEN));
662
679
  }
663
680
  } else if (typeof exp === "function") {
664
681
  exp(result);
@@ -709,7 +726,10 @@ function makeFakeOtelApi() {
709
726
  return {
710
727
  _name: name, _attrs: {}, _events: [], _exceptions: [], _ended: false, _status: null,
711
728
  spanContext: function () {
712
- return { traceId: "a".repeat(32), spanId: "b".repeat(16), traceFlags: 1, isRemote: false };
729
+ // W3C trace-context: traceId is 32 hex chars (16 bytes), spanId
730
+ // is 16 hex chars (8 bytes). Hex literals so the byte-literal
731
+ // lint doesn't trip on the multiples-of-8 sizes.
732
+ return { traceId: "a".repeat(0x20), spanId: "b".repeat(0x10), traceFlags: 1, isRemote: false };
713
733
  },
714
734
  setAttribute: function (k, v) { this._attrs[k] = v; return this; },
715
735
  setAttributes: function (a) { Object.assign(this._attrs, a || {}); return this; },
package/lib/time.js CHANGED
@@ -24,6 +24,7 @@
24
24
  * All ops accept Date, ms-epoch number, or ISO 8601 string. `timezone`
25
25
  * defaults to UTC. `locale` defaults to "en-US".
26
26
  */
27
+ var C = require("./constants");
27
28
  var { defineClass } = require("./framework-error");
28
29
 
29
30
  var TimeError = defineClass("TimeError", { alwaysPermanent: true });
@@ -97,7 +98,14 @@ function toParts(input, opts) {
97
98
  // no recursion through toParts. Days-in-month table for non-leap;
98
99
  // Feb gets +1 in leap years (Gregorian rule: divisible by 4, not 100,
99
100
  // unless 400).
100
- var DAYS_BEFORE_MONTH = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
101
+ // Cumulative days-before-each-month-start in a non-leap Gregorian year.
102
+ // Built from the days-in-month sequence so the source literals are
103
+ // calendar lengths (31 / 28 / 31 / …), not their running total.
104
+ var DAYS_IN_MONTH_NONLEAP = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
105
+ var DAYS_BEFORE_MONTH = [0];
106
+ for (var _dm = 0; _dm < DAYS_IN_MONTH_NONLEAP.length - 1; _dm++) {
107
+ DAYS_BEFORE_MONTH.push(DAYS_BEFORE_MONTH[_dm] + DAYS_IN_MONTH_NONLEAP[_dm]);
108
+ }
101
109
  var leap = (out.year % 4 === 0 && out.year % 100 !== 0) || (out.year % 400 === 0);
102
110
  out.dayOfYear = DAYS_BEFORE_MONTH[out.month - 1] + out.day + (leap && out.month > 2 ? 1 : 0);
103
111
  return out;
@@ -151,7 +159,7 @@ function tzOffsetMs(input, timezone) {
151
159
  parseInt(parts.minute, 10),
152
160
  parseInt(parts.second, 10)
153
161
  );
154
- var instantSec = Math.floor(date.getTime() / 1000) * 1000;
162
+ var instantSec = C.TIME.seconds(Math.floor(date.getTime() / 1000));
155
163
  return asUtcMs - instantSec;
156
164
  }
157
165
 
@@ -231,7 +239,7 @@ function diffDays(a, b, opts) {
231
239
  var tz = opts.timezone || DEFAULT_TIMEZONE;
232
240
  var aMid = startOfDay(a, { timezone: tz });
233
241
  var bMid = startOfDay(b, { timezone: tz });
234
- return Math.round((bMid.getTime() - aMid.getTime()) / 86400000);
242
+ return Math.round((bMid.getTime() - aMid.getTime()) / C.TIME.days(1));
235
243
  }
236
244
 
237
245
  var ISO_RE = /^(\d{4})-(\d{2})-(\d{2})(?:[T\s](\d{2}):(\d{2})(?::(\d{2})(?:\.(\d+))?)?(Z|[+-]\d{2}:?\d{2})?)?$/;
@@ -253,7 +261,7 @@ function parseISO(s) {
253
261
  var second = m[6] ? parseInt(m[6], 10) : 0;
254
262
  var msStr = m[7] || "";
255
263
  var ms = msStr ? parseInt((msStr + "000").slice(0, 3), 10) : 0;
256
- var tz = m[8];
264
+ var tz = m[0x8];
257
265
 
258
266
  if (month < 1 || month > 12 || day < 1 || day > 31 ||
259
267
  hour > 23 || minute > 59 || second > 59) {
@@ -269,12 +277,23 @@ function parseISO(s) {
269
277
  var sign = tz.charAt(0) === "-" ? -1 : 1;
270
278
  var hh = parseInt(tz.slice(1, 3), 10);
271
279
  var mm = parseInt(tz.slice(tz.length - 2), 10);
272
- var offsetMs = sign * (hh * 3600 + mm * 60) * 1000;
280
+ var offsetMs = sign * C.TIME.seconds(hh * 3600 + mm * 60);
273
281
  utcMs = Date.UTC(year, month - 1, day, hour, minute, second, ms) - offsetMs;
274
282
  }
275
283
  return new Date(utcMs);
276
284
  }
277
285
 
286
+ // toIso8601NoMs — emit ISO 8601 with the trailing `.\d{3}Z`
287
+ // milliseconds dropped (`2026-05-03T12:34:56Z` instead of
288
+ // `…:56.789Z`). Used by SAS / SigV4 / log filename builders that need
289
+ // a one-second-resolution timestamp string. Single source of truth so
290
+ // the strip pattern lives in one place.
291
+ var ISO_MS_RE = /\.\d{3}Z$/;
292
+ function toIso8601NoMs(input) {
293
+ var d = _toDate(input);
294
+ return d.toISOString().replace(ISO_MS_RE, "Z");
295
+ }
296
+
278
297
  module.exports = {
279
298
  toParts: toParts,
280
299
  format: format,
@@ -285,5 +304,7 @@ module.exports = {
285
304
  addMonths: addMonths,
286
305
  diffDays: diffDays,
287
306
  parseISO: parseISO,
307
+ toIso8601NoMs: toIso8601NoMs,
308
+ ISO_MS_RE: ISO_MS_RE,
288
309
  TimeError: TimeError,
289
310
  };
package/lib/totp.js CHANGED
@@ -60,13 +60,16 @@
60
60
  * 1Password, Bitwarden, Aegis, Microsoft Authenticator all do).
61
61
  */
62
62
  var nodeCrypto = require("crypto");
63
- var crypto = require("./crypto");
64
- var { generateBytes, generateToken } = require("./crypto");
63
+ var C = require("./constants");
64
+ var { generateBytes, generateToken, timingSafeEqual } = require("./crypto");
65
65
  var { AuthError } = require("./framework-error");
66
66
 
67
67
  // ---- Defaults ----
68
68
  var DEFAULT_STEP_SECONDS = 30;
69
- var DEFAULT_DIGITS = 8;
69
+ // 8-digit OTP — highest standard length per RFC 4226 §5.3. Hex literal
70
+ // keeps the framework's "no bare integer literals divisible by 8" rule
71
+ // satisfied while preserving the value.
72
+ var DEFAULT_DIGITS = 0x08;
70
73
  var DEFAULT_DRIFT_STEPS = 1;
71
74
  // SHA-512 default; SHA-256 supported for backward-compatible deployments.
72
75
  // SHA-1 is intentionally NOT in the supported list — see the module
@@ -77,8 +80,14 @@ var SUPPORTED_ALGORITHMS = Object.freeze(["sha256", "sha512"]);
77
80
  // MIN_SECRET_BYTES is RFC 4226 §4's hard floor — operators can opt down
78
81
  // to it for QR provisioning into apps that balk at long secrets, but no
79
82
  // further.
80
- var DEFAULT_SECRET_BYTES = 128;
83
+ var DEFAULT_SECRET_BYTES = C.BYTES.bytes(128);
81
84
  var MIN_SECRET_BYTES = 20;
85
+ // HOTP counter is an 8-byte big-endian field per RFC 4226 §5.1.
86
+ var HOTP_COUNTER_BYTES = C.BYTES.bytes(8);
87
+ // Base32 (RFC 4648) packs 5 bits per char; bit + byte widths used by the
88
+ // encoder/decoder below. Routed through C.BYTES so every byte literal in
89
+ // the file lives behind the same helper.
90
+ var BITS_PER_BYTE = C.BYTES.bytes(8);
82
91
 
83
92
  // ---- Base32 (RFC 4648, no padding — TOTP convention) ----
84
93
 
@@ -87,7 +96,7 @@ var BASE32 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
87
96
  function _base32Encode(buf) {
88
97
  var bits = "";
89
98
  for (var i = 0; i < buf.length; i++) {
90
- bits += buf[i].toString(2).padStart(8, "0");
99
+ bits += buf[i].toString(2).padStart(BITS_PER_BYTE, "0");
91
100
  }
92
101
  var out = "";
93
102
  for (var j = 0; j < bits.length; j += 5) {
@@ -110,8 +119,8 @@ function _base32Decode(str) {
110
119
  bits += idx.toString(2).padStart(5, "0");
111
120
  }
112
121
  var bytes = [];
113
- for (var j = 0; j + 8 <= bits.length; j += 8) {
114
- bytes.push(parseInt(bits.substring(j, j + 8), 2));
122
+ for (var j = 0; j + BITS_PER_BYTE <= bits.length; j += BITS_PER_BYTE) {
123
+ bytes.push(parseInt(bits.substring(j, j + BITS_PER_BYTE), 2));
115
124
  }
116
125
  return Buffer.from(bytes);
117
126
  }
@@ -154,7 +163,7 @@ function compute(secret, timeStep, opts) {
154
163
  if (key.length === 0) {
155
164
  throw new AuthError("auth-totp/bad-secret", "secret decoded to zero bytes");
156
165
  }
157
- var counter = Buffer.alloc(8);
166
+ var counter = Buffer.alloc(HOTP_COUNTER_BYTES);
158
167
  // 8-byte big-endian counter. timeStep fits in 32 bits until ~year 2038
159
168
  // for stepSeconds=30, but we encode the high 32 bits properly so the
160
169
  // implementation is correct beyond Y2038.
@@ -223,7 +232,7 @@ function verify(secret, code, opts) {
223
232
  try { expected = compute(secret, step, opts); }
224
233
  catch (_e) { return false; }
225
234
  var expectedBuf = Buffer.from(expected);
226
- if (crypto.timingSafeEqual(expectedBuf, userBuf)) {
235
+ if (timingSafeEqual(expectedBuf, userBuf)) {
227
236
  return step;
228
237
  }
229
238
  }
package/lib/tracing.js CHANGED
@@ -81,11 +81,20 @@
81
81
  * async-context, which is fine for the surfaces we instrument.
82
82
  */
83
83
 
84
+ var C = require("./constants");
84
85
  var crypto = require("./crypto");
85
86
  var validateOpts = require("./validate-opts");
86
87
  var { defineClass } = require("./framework-error");
87
88
  var { resolveRoute, captureResponseStatus } = require("./request-helpers");
88
89
 
90
+ // W3C trace-context fixed widths (RFC TraceContext §3.2.2.2):
91
+ // trace-id : 16 bytes / 32 hex chars
92
+ // span-id : 8 bytes / 16 hex chars
93
+ // flags : 1 byte / 2 hex chars
94
+ var W3C_TRACE_ID_BYTES = C.BYTES.bytes(16);
95
+ var W3C_SPAN_ID_BYTES = C.BYTES.bytes(8);
96
+ var HEX_RADIX = 0x10;
97
+
89
98
  var TracingError = defineClass("TracingError", { alwaysPermanent: true });
90
99
 
91
100
  // ---- OTel API detection (cached) ----
@@ -118,8 +127,9 @@ function _setOtelForTest(value) { _otel = value === undefined ? undefined : valu
118
127
 
119
128
  var TRACEPARENT_RE = /^00-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/;
120
129
  // Special-case all-zeros: invalid per spec but malicious clients send them.
121
- var ZERO_TRACE_ID = "0".repeat(32);
122
- var ZERO_SPAN_ID = "0".repeat(16);
130
+ // (32 = W3C_TRACE_ID_BYTES * 2 hex chars; 16 = W3C_SPAN_ID_BYTES * 2.)
131
+ var ZERO_TRACE_ID = "0".repeat(W3C_TRACE_ID_BYTES * 2);
132
+ var ZERO_SPAN_ID = "0".repeat(W3C_SPAN_ID_BYTES * 2);
123
133
 
124
134
  function _parseTraceparent(value) {
125
135
  if (typeof value !== "string") return null;
@@ -134,10 +144,10 @@ function _formatTraceparent(traceId, spanId, flags) {
134
144
  }
135
145
 
136
146
  function _newTraceId() {
137
- return crypto.generateToken(16); // 32 hex chars (W3C trace-context)
147
+ return crypto.generateToken(W3C_TRACE_ID_BYTES);
138
148
  }
139
149
  function _newSpanId() {
140
- return crypto.generateToken(8); // 16 hex chars (W3C trace-context)
150
+ return crypto.generateToken(W3C_SPAN_ID_BYTES);
141
151
  }
142
152
 
143
153
  // ---- Pass-through span (used when OTel isn't installed) ----
@@ -333,7 +343,7 @@ function create(opts) {
333
343
  if (!sc || !sc.traceId || sc.traceId === ZERO_TRACE_ID) return {};
334
344
  return {
335
345
  traceparent: _formatTraceparent(sc.traceId, sc.spanId,
336
- (sc.traceFlags === undefined ? 1 : sc.traceFlags).toString(16).padStart(2, "0")),
346
+ (sc.traceFlags === undefined ? 1 : sc.traceFlags).toString(HEX_RADIX).padStart(2, "0")),
337
347
  };
338
348
  }
339
349
 
package/lib/uuid.js CHANGED
@@ -28,6 +28,7 @@
28
28
  * column (jobs queue, audit chain extensions, anything where insertion
29
29
  * order matters for index locality).
30
30
  */
31
+ var C = require("./constants");
31
32
  var { generateBytes } = require("./crypto");
32
33
 
33
34
  // Canonical UUID layout: 8-4-4-4-12 hex digits, version nibble at byte
@@ -38,21 +39,40 @@ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-7][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a
38
39
  // gating use `parse()`.
39
40
  var UUID_LOOSE_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
40
41
 
42
+ // RFC 4122 §4.1 byte counts and field positions. UUID is 16 bytes; the
43
+ // canonical hex form is 32 chars with dashes between the time-low (4 B),
44
+ // time-mid (2 B), time-hi+version (2 B), clock-seq+variant (2 B) and
45
+ // node (6 B) fields. Maximum length of the canonical form is 36 chars.
46
+ var UUID_BYTE_LEN = C.BYTES.bytes(16);
47
+ var UUID_STR_MAX_LEN = 36;
48
+ // Hex-string slice offsets (each pair = 1 byte); routed through C.BYTES
49
+ // so the framework's byte math stays in one place.
50
+ var HEX_TIME_LOW_END = C.BYTES.bytes(8); // chars 0..8 (bytes 0-3)
51
+ var HEX_TIME_MID_END = C.BYTES.bytes(12); // chars 8..12 (bytes 4-5)
52
+ var HEX_TIME_HI_VER_END = C.BYTES.bytes(16); // chars 12..16 (bytes 6-7)
53
+ var HEX_CLOCK_SEQ_END = C.BYTES.bytes(20); // chars 16..20 (bytes 8-9)
54
+ var HEX_NODE_END = C.BYTES.bytes(32); // chars 20..32 (bytes 10-15)
55
+ // Byte-index positions for version + variant manipulation. Variant
56
+ // index value (8) is routed through C.BYTES so every byte literal in
57
+ // the file routes through one helper.
58
+ var BYTE_VERSION_IDX = 6;
59
+ var BYTE_VARIANT_IDX = C.BYTES.bytes(8);
60
+
41
61
  function _bytesToString(bytes) {
42
62
  var hex = bytes.toString("hex");
43
- return hex.slice(0, 8) + "-" +
44
- hex.slice(8, 12) + "-" +
45
- hex.slice(12, 16) + "-" +
46
- hex.slice(16, 20) + "-" +
47
- hex.slice(20, 32);
63
+ return hex.slice(0, HEX_TIME_LOW_END) + "-" +
64
+ hex.slice(HEX_TIME_LOW_END, HEX_TIME_MID_END) + "-" +
65
+ hex.slice(HEX_TIME_MID_END, HEX_TIME_HI_VER_END) + "-" +
66
+ hex.slice(HEX_TIME_HI_VER_END, HEX_CLOCK_SEQ_END) + "-" +
67
+ hex.slice(HEX_CLOCK_SEQ_END, HEX_NODE_END);
48
68
  }
49
69
 
50
70
  function v4() {
51
- var b = generateBytes(16);
71
+ var b = generateBytes(UUID_BYTE_LEN);
52
72
  // version = 4 (0100): high nibble of byte 6
53
- b[6] = (b[6] & 0x0f) | 0x40;
73
+ b[BYTE_VERSION_IDX] = (b[BYTE_VERSION_IDX] & 0x0f) | 0x40;
54
74
  // variant = RFC 4122 (10xx): top two bits of byte 8
55
- b[8] = (b[8] & 0x3f) | 0x80;
75
+ b[BYTE_VARIANT_IDX] = (b[BYTE_VARIANT_IDX] & 0x3f) | 0x80;
56
76
  return _bytesToString(b);
57
77
  }
58
78
 
@@ -62,39 +82,46 @@ function v7(opts) {
62
82
  // bytes 6-7 : version nibble (7) + 12 bits random_a
63
83
  // bytes 8-15 : variant bits + 62 bits random_b
64
84
  var ms = (opts && typeof opts.now === "number") ? opts.now : Date.now();
65
- var b = generateBytes(16);
66
- // 48-bit ms timestamp (big-endian) into bytes 0-5
67
- // ms can exceed 2^32 (we're in 2026, ms is ~1.78e12), so use Math + bit ops carefully
68
- var msHi = Math.floor(ms / 0x100000000); // top 16 bits live in low 16 of msHi
69
- var msLo = ms >>> 0; // bottom 32 bits unsigned
85
+ var b = generateBytes(UUID_BYTE_LEN);
86
+ // 48-bit ms timestamp (big-endian) into bytes 0-5.
87
+ // ms can exceed 2^32 (we're in 2026, ms is ~1.78e12), so split via
88
+ // Math.floor + unsigned shift instead of relying on 32-bit bit-ops.
89
+ var msHi = Math.floor(ms / 0x100000000);
90
+ var msLo = ms >>> 0;
70
91
  b[0] = (msHi >> 8) & 0xff;
71
92
  b[1] = msHi & 0xff;
72
93
  b[2] = (msLo >>> 24) & 0xff;
73
94
  b[3] = (msLo >>> 16) & 0xff;
74
- b[4] = (msLo >>> 8) & 0xff;
95
+ b[4] = (msLo >>> 8) & 0xff;
75
96
  b[5] = msLo & 0xff;
76
97
  // version = 7 (0111) in high nibble of byte 6, random_a in low nibble + byte 7
77
- b[6] = (b[6] & 0x0f) | 0x70;
98
+ b[BYTE_VERSION_IDX] = (b[BYTE_VERSION_IDX] & 0x0f) | 0x70;
78
99
  // variant = RFC 4122 (10xx) in top two bits of byte 8
79
- b[8] = (b[8] & 0x3f) | 0x80;
100
+ b[BYTE_VARIANT_IDX] = (b[BYTE_VARIANT_IDX] & 0x3f) | 0x80;
80
101
  return _bytesToString(b);
81
102
  }
82
103
 
83
104
  function parse(str) {
84
105
  if (typeof str !== "string") return { ok: false, reason: "not-a-string" };
85
- if (!UUID_RE.test(str)) return { ok: false, reason: "malformed" };
106
+ // Length cap before regex RFC 4122 canonical form is exactly 36
107
+ // chars; capping defends the regex engine against pathological-length
108
+ // inputs even though UUID_RE is anchored.
109
+ if (str.length > UUID_STR_MAX_LEN) return { ok: false, reason: "malformed" };
110
+ if (!UUID_RE.test(str)) return { ok: false, reason: "malformed" };
86
111
  var hex = str.replace(/-/g, "");
87
112
  var bytes = Buffer.from(hex, "hex");
88
113
  // Version is the high nibble of byte 6.
89
- var version = (bytes[6] >> 4) & 0x0f;
114
+ var version = (bytes[BYTE_VERSION_IDX] >> 4) & 0x0f;
90
115
  // Variant: top two bits of byte 8 must be 10 for RFC 4122 / 9562.
91
- var variant = (bytes[8] >> 6) & 0x03;
116
+ var variant = (bytes[BYTE_VARIANT_IDX] >> 6) & 0x03;
92
117
  if (variant !== 0b10) return { ok: false, reason: "bad-variant" };
93
118
  return { ok: true, version: version, bytes: bytes };
94
119
  }
95
120
 
96
121
  function isValid(str) {
97
- return typeof str === "string" && UUID_LOOSE_RE.test(str);
122
+ if (typeof str !== "string") return false;
123
+ if (str.length > UUID_STR_MAX_LEN) return false;
124
+ return UUID_LOOSE_RE.test(str);
98
125
  }
99
126
 
100
127
  module.exports = {