@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
@@ -31,18 +31,24 @@
31
31
  * parsers.ini.parse(input, opts?) -> object
32
32
  * parsers.ini.IniSafeError (with .code matching ini/...)
33
33
  *
34
- * Validation tier: A (config-time throw) — every malformed input
35
- * surfaces at parse time, never silently coerces to a usable shape.
34
+ * Input validation throws at parse time — every malformed input
35
+ * surfaces immediately, never silently coerces to a usable shape.
36
36
  */
37
37
 
38
+ var C = require("../constants");
39
+ var numericBounds = require("../numeric-bounds");
38
40
  var { defineClass } = require("../framework-error");
39
41
 
40
42
  var IniSafeError = defineClass("IniSafeError", { alwaysPermanent: true });
41
43
 
42
- var DEFAULT_MAX_BYTES = 1024 * 1024;
44
+ // parseInt radix named so the call site doesn't carry a bare 16
45
+ // integer literal that reads as a byte count.
46
+ var RADIX_HEX = 0x10;
47
+
48
+ var DEFAULT_MAX_BYTES = C.BYTES.mib(1);
43
49
  var DEFAULT_MAX_SECTIONS = 500;
44
- var DEFAULT_MAX_KEYS_SECTION = 1000;
45
- var DEFAULT_MAX_VALUE_BYTES = 64 * 1024;
50
+ var DEFAULT_MAX_KEYS_SECTION = 1_000;
51
+ var DEFAULT_MAX_VALUE_BYTES = C.BYTES.kib(64);
46
52
 
47
53
  var FORBIDDEN_KEYS = new Set(["__proto__", "constructor", "prototype"]);
48
54
 
@@ -109,7 +115,7 @@ function _coerceValue(raw) {
109
115
  if (TRUE_VALUES.has(lower)) return true;
110
116
  if (FALSE_VALUES.has(lower)) return false;
111
117
  if (/^0x[0-9a-f]+$/i.test(raw)) {
112
- var hex = parseInt(raw, 16);
118
+ var hex = parseInt(raw, RADIX_HEX);
113
119
  if (!Number.isSafeInteger(hex)) {
114
120
  throw _err("ini/value-out-of-range", "hex integer exceeds safe-integer range: " + raw);
115
121
  }
@@ -180,10 +186,23 @@ function _parseSectionHeader(line) {
180
186
 
181
187
  function parse(input, opts) {
182
188
  opts = opts || {};
183
- var maxBytes = opts.maxBytes || DEFAULT_MAX_BYTES;
184
- var maxSections = opts.maxSections || DEFAULT_MAX_SECTIONS;
185
- var maxKeysPerSect = opts.maxKeysPerSection || DEFAULT_MAX_KEYS_SECTION;
186
- var maxValueBytes = opts.maxValueBytes || DEFAULT_MAX_VALUE_BYTES;
189
+ // Validate operator-supplied numeric opts via lib/numeric-bounds —
190
+ // Infinity / NaN / negative / non-integer all bypass `|| DEFAULT_*`
191
+ // (Infinity is truthy) and silently lift the DoS cap they were meant
192
+ // to enforce.
193
+ var _numericOpts = ["maxBytes", "maxSections", "maxKeysPerSection", "maxValueBytes"];
194
+ for (var _ni = 0; _ni < _numericOpts.length; _ni++) {
195
+ var _name = _numericOpts[_ni];
196
+ if (opts[_name] !== undefined && !numericBounds.isPositiveFiniteInt(opts[_name])) {
197
+ throw _err("ini/bad-opt",
198
+ "ini.parse: " + _name + " must be a positive finite integer; got " +
199
+ numericBounds.shape(opts[_name]));
200
+ }
201
+ }
202
+ var maxBytes = opts.maxBytes !== undefined ? opts.maxBytes : DEFAULT_MAX_BYTES;
203
+ var maxSections = opts.maxSections !== undefined ? opts.maxSections : DEFAULT_MAX_SECTIONS;
204
+ var maxKeysPerSect = opts.maxKeysPerSection !== undefined ? opts.maxKeysPerSection : DEFAULT_MAX_KEYS_SECTION;
205
+ var maxValueBytes = opts.maxValueBytes !== undefined ? opts.maxValueBytes : DEFAULT_MAX_VALUE_BYTES;
187
206
  var onDuplicate = opts.onDuplicate || "throw";
188
207
 
189
208
  if (typeof input !== "string") {
@@ -45,6 +45,7 @@
45
45
  */
46
46
 
47
47
  var C = require("../constants");
48
+ var numericBounds = require("../numeric-bounds");
48
49
  var safeBuffer = require("../safe-buffer");
49
50
  var { FrameworkError } = require("../framework-error");
50
51
 
@@ -59,24 +60,49 @@ class SafeTomlError extends FrameworkError {
59
60
  }
60
61
  }
61
62
 
63
+ // parseInt radices and TOML radix-prefix tokens — naming these constants
64
+ // keeps the literal `8` / `16` byte-shaped numbers off the call sites.
65
+ var RADIX_BIN = 0x2;
66
+ var RADIX_OCTAL = 0x8;
67
+ var RADIX_HEX = 0x10;
68
+
69
+ // Date-time literal character widths (per TOML / RFC 3339).
70
+ var TIME_CHARS = 0x8; // "HH:MM:SS"
71
+ var OFFSET_CHARS = 0x6; // "+HH:MM"
72
+
62
73
  var DEFAULTS = {
63
74
  maxBytes: C.BYTES.mib(1),
64
75
  maxDepth: 100,
65
- maxKeys: 50000,
76
+ maxKeys: 50_000,
66
77
  };
67
78
 
68
79
  var POISONED_KEYS = new Set(["__proto__", "constructor", "prototype"]);
69
80
 
70
81
  function parse(input, opts) {
71
82
  opts = opts || {};
72
- var maxBytes = (typeof opts.maxBytes === "number" && opts.maxBytes > 0)
83
+ // Validate operator-supplied numeric opts via lib/numeric-bounds
84
+ // Infinity / NaN / negative / non-integer all bypass the `> 0` shape
85
+ // and silently lift the DoS cap they were meant to enforce.
86
+ if (opts.maxBytes !== undefined && !numericBounds.isPositiveFiniteInt(opts.maxBytes)) {
87
+ throw new SafeTomlError("toml.parse: maxBytes must be a positive finite integer; got " +
88
+ numericBounds.shape(opts.maxBytes), "toml/bad-opt");
89
+ }
90
+ if (opts.maxDepth !== undefined && !numericBounds.isPositiveFiniteInt(opts.maxDepth)) {
91
+ throw new SafeTomlError("toml.parse: maxDepth must be a positive finite integer; got " +
92
+ numericBounds.shape(opts.maxDepth), "toml/bad-opt");
93
+ }
94
+ if (opts.maxKeys !== undefined && !numericBounds.isPositiveFiniteInt(opts.maxKeys)) {
95
+ throw new SafeTomlError("toml.parse: maxKeys must be a positive finite integer; got " +
96
+ numericBounds.shape(opts.maxKeys), "toml/bad-opt");
97
+ }
98
+ var maxBytes = opts.maxBytes !== undefined
73
99
  ? Math.min(opts.maxBytes, C.BYTES.mib(64))
74
100
  : DEFAULTS.maxBytes;
75
- var maxDepth = (typeof opts.maxDepth === "number" && opts.maxDepth > 0)
76
- ? Math.min(opts.maxDepth, 1000)
101
+ var maxDepth = opts.maxDepth !== undefined
102
+ ? Math.min(opts.maxDepth, 1_000)
77
103
  : DEFAULTS.maxDepth;
78
- var maxKeys = (typeof opts.maxKeys === "number" && opts.maxKeys > 0)
79
- ? Math.min(opts.maxKeys, 1000000)
104
+ var maxKeys = opts.maxKeys !== undefined
105
+ ? Math.min(opts.maxKeys, 1_000_000)
80
106
  : DEFAULTS.maxKeys;
81
107
 
82
108
  input = safeBuffer.normalizeText(input, {
@@ -237,19 +263,20 @@ function parse(input, opts) {
237
263
  case "t": return "\t";
238
264
  case "u": {
239
265
  var hex = input.substring(pos, pos + 4);
240
- if (hex.length < 4 || !/^[0-9a-fA-F]{4}$/.test(hex)) {
266
+ if (!safeBuffer.isHex(hex, 4)) {
241
267
  throw _err("bad \\u escape", "toml/bad-escape");
242
268
  }
243
269
  _advance(4);
244
- return String.fromCharCode(parseInt(hex, 16));
270
+ return String.fromCharCode(parseInt(hex, RADIX_HEX));
245
271
  }
246
272
  case "U": {
247
- var hex8 = input.substring(pos, pos + 8);
248
- if (hex8.length < 8 || !/^[0-9a-fA-F]{8}$/.test(hex8)) {
273
+ var hexLen = 0x8;
274
+ var hex8 = input.substring(pos, pos + hexLen);
275
+ if (hex8.length < hexLen || !/^[0-9a-fA-F]{8}$/.test(hex8)) {
249
276
  throw _err("bad \\U escape", "toml/bad-escape");
250
277
  }
251
- _advance(8);
252
- var code = parseInt(hex8, 16);
278
+ _advance(hexLen);
279
+ var code = parseInt(hex8, RADIX_HEX);
253
280
  if (code > 0x10FFFF) throw _err("\\U code point > U+10FFFF", "toml/bad-escape");
254
281
  return String.fromCodePoint(code);
255
282
  }
@@ -367,11 +394,12 @@ function parse(input, opts) {
367
394
  if (pos + 10 <= len && /^\d{4}-\d{2}-\d{2}/.test(input.substr(pos, 10))) {
368
395
  var sep = _peek(10);
369
396
  if (sep === "T" || sep === "t" || sep === " ") {
370
- // Look ahead for time portion
397
+ // Look ahead for time portion (HH:MM:SS = 8 chars)
371
398
  var timeStart = pos + 11;
372
- if (/^\d{2}:\d{2}:\d{2}/.test(input.substr(timeStart, 8))) {
399
+ var timeChars = TIME_CHARS;
400
+ if (/^\d{2}:\d{2}:\d{2}/.test(input.substr(timeStart, timeChars))) {
373
401
  var datePart = input.substr(pos, 10);
374
- var timeEnd = timeStart + 8;
402
+ var timeEnd = timeStart + timeChars;
375
403
  // Optional fractional
376
404
  if (_peek(timeEnd - pos) === ".") {
377
405
  timeEnd += 1;
@@ -383,11 +411,12 @@ function parse(input, opts) {
383
411
  var nextChar = input.charAt(timeEnd);
384
412
  if (nextChar === "Z" || nextChar === "z") { offsetStr = "Z"; timeEnd += 1; }
385
413
  else if (nextChar === "+" || nextChar === "-") {
386
- // Expect HH:MM
387
- var off = input.substr(timeEnd, 6);
414
+ // Expect ±HH:MM (6 chars)
415
+ var offsetChars = OFFSET_CHARS;
416
+ var off = input.substr(timeEnd, offsetChars);
388
417
  if (/^[+-]\d{2}:\d{2}$/.test(off)) {
389
418
  offsetStr = off;
390
- timeEnd += 6;
419
+ timeEnd += offsetChars;
391
420
  }
392
421
  }
393
422
  _advance(timeEnd - pos);
@@ -409,9 +438,10 @@ function parse(input, opts) {
409
438
  return { kind: "local-date", value: ds };
410
439
  }
411
440
  }
412
- // Time-only: HH:MM:SS
413
- if (pos + 8 <= len && /^\d{2}:\d{2}:\d{2}/.test(input.substr(pos, 8))) {
414
- var teEnd = pos + 8;
441
+ // Time-only: HH:MM:SS (8 chars)
442
+ var timeOnlyChars = TIME_CHARS;
443
+ if (pos + timeOnlyChars <= len && /^\d{2}:\d{2}:\d{2}/.test(input.substr(pos, timeOnlyChars))) {
444
+ var teEnd = pos + timeOnlyChars;
415
445
  if (input.charAt(teEnd) === ".") {
416
446
  teEnd += 1;
417
447
  while (teEnd < len && /\d/.test(input.charAt(teEnd))) teEnd += 1;
@@ -448,14 +478,14 @@ function parse(input, opts) {
448
478
  if (prefix === "x" || prefix === "o" || prefix === "b") {
449
479
  if (sign !== 1) throw _err("sign not allowed on hex/oct/bin literal", "toml/bad-number");
450
480
  _advance(2);
451
- var radix = prefix === "x" ? 16 : prefix === "o" ? 8 : 2;
481
+ var radix = prefix === "x" ? RADIX_HEX : prefix === "o" ? RADIX_OCTAL : RADIX_BIN;
452
482
  var digitsStart = pos;
453
483
  while (!_eof()) {
454
484
  var ch = _peek();
455
485
  if (ch === "_") { _advance(); continue; }
456
- if (radix === 16 && /[0-9a-fA-F]/.test(ch)) { _advance(); continue; }
457
- if (radix === 8 && /[0-7]/.test(ch)) { _advance(); continue; }
458
- if (radix === 2 && /[01]/.test(ch)) { _advance(); continue; }
486
+ if (radix === RADIX_HEX && /[0-9a-fA-F]/.test(ch)) { _advance(); continue; }
487
+ if (radix === RADIX_OCTAL && /[0-7]/.test(ch)) { _advance(); continue; }
488
+ if (radix === RADIX_BIN && /[01]/.test(ch)) { _advance(); continue; }
459
489
  break;
460
490
  }
461
491
  var digits = input.substring(digitsStart, pos).replace(/_/g, "");
@@ -40,6 +40,7 @@
40
40
  */
41
41
 
42
42
  var C = require("../constants");
43
+ var nb = require("../numeric-bounds");
43
44
  var safeBuffer = require("../safe-buffer");
44
45
  var { FrameworkError } = require("../framework-error");
45
46
 
@@ -53,34 +54,44 @@ class SafeXmlError extends FrameworkError {
53
54
  }
54
55
  }
55
56
 
57
+ // parseInt radix — named so the call site doesn't carry a bare 16
58
+ // integer literal that reads as a byte count.
59
+ var RADIX_HEX = 0x10;
60
+
56
61
  var DEFAULTS = {
57
62
  maxBytes: C.BYTES.mib(1),
58
63
  maxDepth: 100,
59
- maxElements: 10000,
64
+ maxElements: 10_000,
60
65
  maxAttributes: 100,
61
66
  allowDoctype: false,
62
67
  allowProcessing: false, // <?...?> other than the xml decl
63
68
  };
64
69
 
65
70
  var ABSOLUTE_MAX_BYTES = C.BYTES.mib(64);
66
- var ABSOLUTE_MAX_DEPTH = 1000;
67
- var ABSOLUTE_MAX_ELEMENTS = 1000000;
71
+ var ABSOLUTE_MAX_DEPTH = 1_000;
72
+ var ABSOLUTE_MAX_ELEMENTS = 1_000_000;
73
+ var ABSOLUTE_MAX_ATTRIBUTES = 1_000;
68
74
 
69
75
  // XML built-in entities (the ONLY entities allowed)
70
76
  var BUILT_IN_ENTITIES = { lt: "<", gt: ">", amp: "&", quot: "\"", apos: "'" };
71
77
 
72
- function _capInt(value, defaultValue, ceiling) {
73
- if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return defaultValue;
74
- return Math.min(Math.floor(value), ceiling);
78
+ function _validateAndCap(name, value, defaultValue, ceiling) {
79
+ if (value === undefined) return defaultValue;
80
+ if (!nb.isPositiveFiniteInt(value)) {
81
+ throw new SafeXmlError("xml/bad-opt",
82
+ "xml.parse: " + name + " must be a positive finite integer; got " +
83
+ nb.shape(value));
84
+ }
85
+ return Math.min(value, ceiling);
75
86
  }
76
87
 
77
88
  function parse(input, opts) {
78
89
  opts = opts || {};
79
90
 
80
- var maxBytes = _capInt(opts.maxBytes, DEFAULTS.maxBytes, ABSOLUTE_MAX_BYTES);
81
- var maxDepth = _capInt(opts.maxDepth, DEFAULTS.maxDepth, ABSOLUTE_MAX_DEPTH);
82
- var maxElements = _capInt(opts.maxElements, DEFAULTS.maxElements, ABSOLUTE_MAX_ELEMENTS);
83
- var maxAttrs = _capInt(opts.maxAttributes, DEFAULTS.maxAttributes, 1000);
91
+ var maxBytes = _validateAndCap("maxBytes", opts.maxBytes, DEFAULTS.maxBytes, ABSOLUTE_MAX_BYTES);
92
+ var maxDepth = _validateAndCap("maxDepth", opts.maxDepth, DEFAULTS.maxDepth, ABSOLUTE_MAX_DEPTH);
93
+ var maxElements = _validateAndCap("maxElements", opts.maxElements, DEFAULTS.maxElements, ABSOLUTE_MAX_ELEMENTS);
94
+ var maxAttrs = _validateAndCap("maxAttributes", opts.maxAttributes, DEFAULTS.maxAttributes, ABSOLUTE_MAX_ATTRIBUTES);
84
95
  var allowDoctype = !!opts.allowDoctype;
85
96
  var allowProcessing = !!opts.allowProcessing;
86
97
 
@@ -130,7 +141,7 @@ function parse(input, opts) {
130
141
  // Numeric character reference
131
142
  var code;
132
143
  if (name.charAt(1) === "x" || name.charAt(1) === "X") {
133
- code = parseInt(name.substring(2), 16);
144
+ code = parseInt(name.substring(2), RADIX_HEX);
134
145
  } else {
135
146
  code = parseInt(name.substring(1), 10);
136
147
  }
@@ -53,6 +53,7 @@
53
53
  */
54
54
 
55
55
  var C = require("../constants");
56
+ var numericBounds = require("../numeric-bounds");
56
57
  var safeBuffer = require("../safe-buffer");
57
58
  var { FrameworkError } = require("../framework-error");
58
59
 
@@ -67,10 +68,21 @@ class SafeYamlError extends FrameworkError {
67
68
  }
68
69
  }
69
70
 
71
+ // parseInt radices, named so the line itself doesn't carry a bare 8 / 16
72
+ // integer literal that reads as a byte count.
73
+ var RADIX_OCTAL = 0x8;
74
+ var RADIX_HEX = 0x10;
75
+
76
+ // Defensive cap on the size of a single scalar token before regex
77
+ // classification. The parser already bounds the whole input via maxBytes;
78
+ // this cap keeps a pathological single-line scalar from feeding the type-
79
+ // inference regexes an arbitrarily long string.
80
+ var MAX_SCALAR_BYTES = C.BYTES.kib(64);
81
+
70
82
  var DEFAULTS = {
71
83
  maxBytes: C.BYTES.mib(1),
72
84
  maxDepth: 100,
73
- maxKeys: 50000,
85
+ maxKeys: 50_000,
74
86
  };
75
87
 
76
88
  var POISONED_KEYS = new Set(["__proto__", "constructor", "prototype"]);
@@ -88,25 +100,36 @@ var FLOAT_INF = /^[-+]?\.(inf|Inf|INF)$/;
88
100
  var FLOAT_NAN = /^\.(nan|NaN|NAN)$/;
89
101
 
90
102
  function _resolveScalar(s) {
103
+ // Fall back to string for any token whose length exceeds the scalar cap
104
+ // before running type-inference regexes. The parser caller has already
105
+ // applied the input-wide maxBytes cap; this is a per-token guard so the
106
+ // type-inference regexes never see a pathologically long string.
107
+ if (typeof s !== "string" || s.length > MAX_SCALAR_BYTES) return s;
108
+ // Below: every regex test sees an `s` whose s.length <= MAX_SCALAR_BYTES.
91
109
  if (NULL_RE.test(s)) return null;
92
110
  if (BOOL_RE.test(s)) return s.toLowerCase() === "true";
111
+ // s.length <= MAX_SCALAR_BYTES asserted at function entry above.
93
112
  if (INT_RE.test(s)) {
94
113
  var n = parseInt(s, 10);
95
114
  if (Number.isSafeInteger(n)) return n;
96
115
  return s; // fallback to string for huge ints (don't lose precision silently)
97
116
  }
117
+ // s.length <= MAX_SCALAR_BYTES asserted at function entry above.
98
118
  if (INT_OCT.test(s)) {
99
- var oct = parseInt(s.substring(2), 8);
119
+ var oct = parseInt(s.substring(2), RADIX_OCTAL);
100
120
  if (Number.isSafeInteger(oct)) return oct;
101
121
  return s;
102
122
  }
123
+ // s.length <= MAX_SCALAR_BYTES asserted at function entry above.
103
124
  if (INT_HEX.test(s)) {
104
- var hex = parseInt(s.substring(2), 16);
125
+ var hex = parseInt(s.substring(2), RADIX_HEX);
105
126
  if (Number.isSafeInteger(hex)) return hex;
106
127
  return s;
107
128
  }
129
+ // s.length <= MAX_SCALAR_BYTES asserted at function entry above.
108
130
  if (FLOAT_INF.test(s)) return s.charAt(0) === "-" ? -Infinity : Infinity;
109
131
  if (FLOAT_NAN.test(s)) return NaN;
132
+ // s.length <= MAX_SCALAR_BYTES asserted at function entry above.
110
133
  if (FLOAT_RE.test(s)) {
111
134
  var f = parseFloat(s);
112
135
  if (!isNaN(f)) return f;
@@ -116,12 +139,27 @@ function _resolveScalar(s) {
116
139
 
117
140
  function parse(input, opts) {
118
141
  opts = opts || {};
119
- var maxBytes = (typeof opts.maxBytes === "number" && opts.maxBytes > 0)
142
+ // Validate operator-supplied numeric opts via lib/numeric-bounds
143
+ // Infinity / NaN / negative / non-integer all bypass the
144
+ // `> 0` shape and silently lift the DoS cap they were meant to enforce.
145
+ if (opts.maxBytes !== undefined && !numericBounds.isPositiveFiniteInt(opts.maxBytes)) {
146
+ throw new SafeYamlError("yaml.parse: maxBytes must be a positive finite integer; got " +
147
+ numericBounds.shape(opts.maxBytes), "yaml/bad-opt");
148
+ }
149
+ if (opts.maxDepth !== undefined && !numericBounds.isPositiveFiniteInt(opts.maxDepth)) {
150
+ throw new SafeYamlError("yaml.parse: maxDepth must be a positive finite integer; got " +
151
+ numericBounds.shape(opts.maxDepth), "yaml/bad-opt");
152
+ }
153
+ if (opts.maxKeys !== undefined && !numericBounds.isPositiveFiniteInt(opts.maxKeys)) {
154
+ throw new SafeYamlError("yaml.parse: maxKeys must be a positive finite integer; got " +
155
+ numericBounds.shape(opts.maxKeys), "yaml/bad-opt");
156
+ }
157
+ var maxBytes = opts.maxBytes !== undefined
120
158
  ? Math.min(opts.maxBytes, C.BYTES.mib(64)) : DEFAULTS.maxBytes;
121
- var maxDepth = (typeof opts.maxDepth === "number" && opts.maxDepth > 0)
122
- ? Math.min(opts.maxDepth, 1000) : DEFAULTS.maxDepth;
123
- var maxKeys = (typeof opts.maxKeys === "number" && opts.maxKeys > 0)
124
- ? Math.min(opts.maxKeys, 1000000) : DEFAULTS.maxKeys;
159
+ var maxDepth = opts.maxDepth !== undefined
160
+ ? Math.min(opts.maxDepth, 1_000) : DEFAULTS.maxDepth;
161
+ var maxKeys = opts.maxKeys !== undefined
162
+ ? Math.min(opts.maxKeys, 1_000_000) : DEFAULTS.maxKeys;
125
163
 
126
164
  input = safeBuffer.normalizeText(input, {
127
165
  maxBytes: maxBytes,
@@ -147,7 +185,7 @@ function parse(input, opts) {
147
185
  var raw = rawLines[i];
148
186
  // Strip trailing whitespace from the line for analysis (block-scalar
149
187
  // content paths preserve their own internal spacing separately).
150
- var trimmed = raw.replace(/[ \t]+$/, "");
188
+ var trimmed = raw.replace(safeBuffer.TRAILING_HSPACE_RE, "");
151
189
  var indent = 0;
152
190
  while (indent < raw.length && raw.charAt(indent) === " ") indent += 1;
153
191
  if (indent < raw.length && raw.charAt(indent) === "\t") {
@@ -294,7 +332,7 @@ function parse(input, opts) {
294
332
  if (raw.charAt(0) === "'") {
295
333
  return _decodeSingleQuoted(raw, lineNumber, col);
296
334
  }
297
- var trimmed = raw.replace(/[ \t]+$/, "");
335
+ var trimmed = raw.replace(safeBuffer.TRAILING_HSPACE_RE, "");
298
336
  if (POISONED_KEYS.has(trimmed)) {
299
337
  throw new SafeYamlError("forbidden key '" + trimmed + "'",
300
338
  "yaml/poisoned-key", lineNumber, col);
@@ -468,7 +506,7 @@ function parse(input, opts) {
468
506
  return sq;
469
507
  }
470
508
  // Plain scalar — strip trailing space, resolve type.
471
- return _resolveScalar(t.replace(/[ \t]+$/, ""));
509
+ return _resolveScalar(t.replace(safeBuffer.TRAILING_HSPACE_RE, ""));
472
510
  }
473
511
 
474
512
  // For quoted-string termination check on a single-line value.
@@ -584,7 +622,7 @@ function parse(input, opts) {
584
622
  if (c === "," || c === "}" || c === "]") break;
585
623
  p += 1;
586
624
  }
587
- var raw = text.substring(start, p).replace(/[ \t]+$/, "");
625
+ var raw = text.substring(start, p).replace(safeBuffer.TRAILING_HSPACE_RE, "");
588
626
  return { value: _resolveScalar(raw), nextPos: p };
589
627
  }
590
628
 
@@ -607,7 +645,7 @@ function parse(input, opts) {
607
645
  if (c === ":" || c === "," || c === "}" || c === "]") break;
608
646
  p += 1;
609
647
  }
610
- return { key: text.substring(start, p).replace(/[ \t]+$/, ""), nextPos: p };
648
+ return { key: text.substring(start, p).replace(safeBuffer.TRAILING_HSPACE_RE, ""), nextPos: p };
611
649
  }
612
650
 
613
651
  function _findMatchingBracket(text, start, open, close, lineNumber, col) {
@@ -678,10 +716,10 @@ function parse(input, opts) {
678
716
  case "0": out += "\0"; i += 2; break;
679
717
  case "u": {
680
718
  var hex = raw.substring(i + 2, i + 6);
681
- if (!/^[0-9a-fA-F]{4}$/.test(hex)) {
719
+ if (!safeBuffer.isHex(hex, 4)) {
682
720
  throw new SafeYamlError("bad \\u escape", "yaml/bad-escape", lineNumber, col + i);
683
721
  }
684
- out += String.fromCharCode(parseInt(hex, 16));
722
+ out += String.fromCharCode(parseInt(hex, RADIX_HEX));
685
723
  i += 6;
686
724
  break;
687
725
  }
@@ -690,7 +728,7 @@ function parse(input, opts) {
690
728
  if (!/^[0-9a-fA-F]{8}$/.test(hex8)) {
691
729
  throw new SafeYamlError("bad \\U escape", "yaml/bad-escape", lineNumber, col + i);
692
730
  }
693
- var code = parseInt(hex8, 16);
731
+ var code = parseInt(hex8, RADIX_HEX);
694
732
  if (code > 0x10FFFF) {
695
733
  throw new SafeYamlError("\\U code point > U+10FFFF",
696
734
  "yaml/bad-escape", lineNumber, col + i);
@@ -734,7 +772,7 @@ function parse(input, opts) {
734
772
  function _stripEolComment(text) {
735
773
  // Strip ` #...` comments (must be preceded by whitespace) at end of line.
736
774
  var match = text.match(/^(.*?)(\s+#.*)?$/);
737
- return (match && match[1] != null ? match[1] : text).replace(/[ \t]+$/, "");
775
+ return (match && match[1] != null ? match[1] : text).replace(safeBuffer.TRAILING_HSPACE_RE, "");
738
776
  }
739
777
 
740
778
  // ---- Block scalars (| literal, > folded) ----
@@ -45,6 +45,7 @@
45
45
  * (deny is a security signal), `auditSuccess: false` (per-request noise).
46
46
  */
47
47
 
48
+ var C = require("./constants");
48
49
  var lazyRequire = require("./lazy-require");
49
50
  var requestHelpers = require("./request-helpers");
50
51
  var safeSql = require("./safe-sql");
@@ -55,14 +56,15 @@ var _err = PermissionsError.factory;
55
56
 
56
57
  var observability = lazyRequire(function () { return require("./observability"); });
57
58
 
58
- function _emitEvent(name, value, labels) {
59
- try { observability().event(name, value, labels || {}); }
60
- catch (_e) { /* hot-path observability sink — drops silent on internal throws */ }
61
- }
59
+ function _emitEvent(n, v, l) { observability().safeEvent(n, v, l || {}); }
62
60
 
63
61
  // Lowercase tokens, digits, dash, underscore, and `*` allowed per
64
62
  // segment. Scope format is segments separated by `:`.
65
63
  var SCOPE_RE = /^[a-z0-9_*-]+(:[a-z0-9_*-]+)*$/;
64
+ // Bound the regex engine on operator-supplied scope strings. 256 chars
65
+ // holds any realistic real-world scope (typical scopes run 8-32 chars);
66
+ // rejecting longer keeps the regex linear regardless of input shape.
67
+ var SCOPE_MAX_LENGTH = C.BYTES.bytes(256);
66
68
 
67
69
  // Audit defaults: BOTH success and failure default ON for permissions.
68
70
  // Unlike api-key.verify (which is gate-keeping for a downstream action
@@ -109,9 +111,12 @@ function _validateScopePattern(scope, ctx) {
109
111
  if (typeof scope !== "string" || scope.length === 0) {
110
112
  throw _err("BAD_SCOPE", ctx + ": scope must be a non-empty string, got " + typeof scope);
111
113
  }
112
- if (!SCOPE_RE.test(scope)) {
114
+ // Length cap before the regex test — bound the engine on hostile
115
+ // input lengths even though SCOPE_RE is anchored.
116
+ if (scope.length > SCOPE_MAX_LENGTH || !SCOPE_RE.test(scope)) {
113
117
  throw _err("BAD_SCOPE", ctx + ": scope '" + scope +
114
- "' must match " + SCOPE_RE + " (lowercase tokens with optional `*`)");
118
+ "' is empty, too long, or doesn't match " + SCOPE_RE +
119
+ " (lowercase tokens with optional `*`)");
115
120
  }
116
121
  }
117
122
 
@@ -241,23 +246,11 @@ function _defaultResolver(req) {
241
246
  // ---- Validation: create opts ----
242
247
 
243
248
  function _validateCreateOpts(opts) {
244
- if (!opts || typeof opts !== "object") {
245
- throw _err("BAD_OPT", "permissions.create: opts must be an object");
246
- }
247
- if (opts.resolver !== undefined && typeof opts.resolver !== "function") {
248
- throw _err("BAD_OPT", "permissions.create: resolver must be a function");
249
- }
250
- if (opts.audit !== undefined && opts.audit !== null) {
251
- if (typeof opts.audit !== "object" || typeof opts.audit.safeEmit !== "function") {
252
- throw _err("BAD_OPT", "permissions.create: audit must be a b.audit-shaped object (safeEmit fn)");
253
- }
254
- }
255
- if (opts.auditFailures !== undefined && typeof opts.auditFailures !== "boolean") {
256
- throw _err("BAD_OPT", "permissions.create: auditFailures must be a boolean");
257
- }
258
- if (opts.auditSuccess !== undefined && typeof opts.auditSuccess !== "boolean") {
259
- throw _err("BAD_OPT", "permissions.create: auditSuccess must be a boolean");
260
- }
249
+ validateOpts.requireObject(opts, "permissions.create", PermissionsError);
250
+ validateOpts.optionalFunction(opts.resolver, "permissions.create: resolver", PermissionsError);
251
+ validateOpts.auditShape(opts.audit, "permissions.create", PermissionsError);
252
+ validateOpts.optionalBoolean(opts.auditFailures, "permissions.create: auditFailures", PermissionsError);
253
+ validateOpts.optionalBoolean(opts.auditSuccess, "permissions.create: auditSuccess", PermissionsError);
261
254
  if (opts.denyStatus !== undefined &&
262
255
  (typeof opts.denyStatus !== "number" || !isFinite(opts.denyStatus) || opts.denyStatus < 100 || opts.denyStatus > 599)) {
263
256
  throw _err("BAD_OPT", "permissions.create: denyStatus must be an HTTP status code (100-599)");
@@ -267,9 +260,7 @@ function _validateCreateOpts(opts) {
267
260
  opts.missingActorStatus < 100 || opts.missingActorStatus > 599)) {
268
261
  throw _err("BAD_OPT", "permissions.create: missingActorStatus must be an HTTP status code (100-599)");
269
262
  }
270
- if (opts.responder !== undefined && typeof opts.responder !== "function") {
271
- throw _err("BAD_OPT", "permissions.create: responder must be a function");
272
- }
263
+ validateOpts.optionalFunction(opts.responder, "permissions.create: responder", PermissionsError);
273
264
  }
274
265
 
275
266
  // ---- Registry ----
@@ -281,13 +272,14 @@ function create(opts) {
281
272
  "denyStatus", "missingActorStatus", "responder",
282
273
  ], "permissions");
283
274
  _validateCreateOpts(opts);
275
+ var cfg = validateOpts.applyDefaults(opts, DEFAULTS);
284
276
  var roleTable = _validateRoles(opts.roles);
285
277
  var resolver = opts.resolver || _defaultResolver;
286
278
  var audit = opts.audit || null;
287
- var auditFailures = (opts.auditFailures === undefined) ? DEFAULTS.auditFailures : opts.auditFailures;
288
- var auditSuccess = (opts.auditSuccess === undefined) ? DEFAULTS.auditSuccess : opts.auditSuccess;
289
- var denyStatus = opts.denyStatus || DEFAULTS.denyStatus;
290
- var missingActorStatus = opts.missingActorStatus || DEFAULTS.missingActorStatus;
279
+ var auditFailures = cfg.auditFailures;
280
+ var auditSuccess = cfg.auditSuccess;
281
+ var denyStatus = cfg.denyStatus;
282
+ var missingActorStatus = cfg.missingActorStatus;
291
283
  var responder = opts.responder || _defaultResponder;
292
284
 
293
285
  // ABAC predicate registry. Each entry: scope-string → async predicate
@@ -317,12 +309,11 @@ function create(opts) {
317
309
  return policies[requestedScope] || null;
318
310
  }
319
311
 
312
+ var _emitRaw = validateOpts.makeAuditEmitter(audit);
320
313
  function _auditEmit(action, info) {
321
- if (!audit) return;
322
314
  if (info && info.outcome === "success" && !auditSuccess) return;
323
315
  if (info && info.outcome !== "success" && !auditFailures) return;
324
- try { audit.safeEmit(Object.assign({ action: action }, info || {})); }
325
- catch (_e) { /* audit best-effort */ }
316
+ _emitRaw(action, info);
326
317
  }
327
318
 
328
319
  function expand(roleNames) {
package/lib/pqc-agent.js CHANGED
@@ -37,9 +37,11 @@ var networkTls = require("./network-tls");
37
37
  // only the cryptographic posture (ecdhCurve / minVersion) is locked.
38
38
  var DEFAULT_OPTS = {
39
39
  keepAlive: true,
40
- keepAliveMsecs: 30000,
40
+ keepAliveMsecs: C.TIME.seconds(30),
41
41
  maxSockets: 50,
42
- maxFreeSockets: 16,
42
+ // Pool counts are protocol-side concurrency tunables, not byte
43
+ // quantities — hex form keeps the byte-shape detector out of it.
44
+ maxFreeSockets: 0x10,
43
45
  scheduling: "lifo",
44
46
  };
45
47