@blamejs/core 0.6.69 → 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 +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 +27 -20
  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 +17 -4
  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 +42 -17
  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
@@ -18,8 +18,8 @@
18
18
  */
19
19
  var { Readable } = require("stream");
20
20
  var { ObjectStoreError } = require("../framework-error");
21
- var httpClient = require("../http-client");
22
21
  var safeUrl = require("../safe-url");
22
+ var sharedRequest = require("./http-request");
23
23
  var authHeader = require("../auth-header");
24
24
 
25
25
  var _err = ObjectStoreError.factory;
@@ -36,16 +36,7 @@ function _authHeaders(authConfig) {
36
36
  }
37
37
 
38
38
  function _request(method, url, body, headers, opts) {
39
- return httpClient.request({
40
- method: method,
41
- url: url,
42
- headers: headers,
43
- body: body,
44
- idleTimeoutMs: opts && opts.timeoutMs,
45
- errorClass: ObjectStoreError,
46
- allowedProtocols: opts && opts.allowedProtocols,
47
- ...((opts && opts.allowInternal !== undefined) ? { allowInternal: opts.allowInternal } : {}),
48
- });
39
+ return sharedRequest(method, url, headers, body, opts);
49
40
  }
50
41
 
51
42
  function _keyToUrl(baseUrl, key) {
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ /**
3
+ * Shared HTTP-request helper for object-store backends.
4
+ *
5
+ * Every protocol backend (azure-blob / gcs / sigv4 / http-put)
6
+ * previously rolled an identical wrapper around httpClient.request that
7
+ * threaded the same five opts: idleTimeoutMs / maxResponseBytes /
8
+ * errorClass / allowedProtocols / allowInternal. Centralized here so a
9
+ * future change to the call shape (new opt, different error class
10
+ * default, etc.) is a one-file change.
11
+ *
12
+ * var sharedRequest = require("./http-request");
13
+ * sharedRequest("PUT", url, headers, body, { timeoutMs: 5000 });
14
+ *
15
+ * The errorClass defaults to ObjectStoreError; backends that need a
16
+ * different class pass `errorClass: SomeError` in opts.
17
+ */
18
+
19
+ var httpClient = require("../http-client");
20
+ var { ObjectStoreError } = require("../framework-error");
21
+
22
+ function request(method, url, headers, body, opts) {
23
+ opts = opts || {};
24
+ var req = {
25
+ method: method,
26
+ url: url,
27
+ headers: headers,
28
+ body: body,
29
+ idleTimeoutMs: opts.timeoutMs,
30
+ errorClass: opts.errorClass || ObjectStoreError,
31
+ allowedProtocols: opts.allowedProtocols,
32
+ };
33
+ if (opts.maxResponseBytes !== undefined) req.maxResponseBytes = opts.maxResponseBytes;
34
+ if (opts.allowInternal !== undefined) req.allowInternal = opts.allowInternal;
35
+ return httpClient.request(req);
36
+ }
37
+
38
+ module.exports = request;
@@ -35,7 +35,7 @@ var gcsProto = require("./gcs");
35
35
  var gcsBucketOps = require("./gcs-bucket-ops");
36
36
  var azureBlobProto = require("./azure-blob");
37
37
  var azureBlobBucketOps = require("./azure-blob-bucket-ops");
38
- var retryHelper = require("./retry");
38
+ var retryHelper = require("../retry");
39
39
  var protocolDispatcher = require("../protocol-dispatcher");
40
40
  var { ObjectStoreError } = require("../framework-error");
41
41
 
@@ -49,20 +49,31 @@
49
49
  * with codes (BUCKET_INVALID_NAME, INVALID_LIFECYCLE, INVALID_CORS_RULE,
50
50
  * BUCKET_ALREADY_OWNED, BUCKET_NOT_EMPTY, etc.).
51
51
  */
52
- var { URL } = require("url");
53
52
  var nodeCrypto = require("crypto");
54
53
  var C = require("../constants");
55
54
  var requestHelpers = require("../request-helpers");
56
55
  var sigv4 = require("./sigv4");
56
+ var sharedRequest = require("./http-request");
57
57
  var safeXml = require("../parsers/safe-xml");
58
58
  var safeUrl = require("../safe-url");
59
59
  var template = require("../template");
60
- var httpClient = require("../http-client");
61
60
  var validateOpts = require("../validate-opts");
62
61
  var { ObjectStoreError } = require("../framework-error");
63
62
 
64
63
  var _err = ObjectStoreError.factory;
65
64
 
65
+ // Internal URL builder — endpoint + path string from validated config.
66
+ // Routes through safeUrl.parse so the protocol allowlist + length cap
67
+ // apply uniformly. Cap is generous since presigned-style URLs with many
68
+ // query params can approach 8 KB.
69
+ function _internalUrl(input, allowedProtocols) {
70
+ return safeUrl.parse(input, {
71
+ allowedProtocols: allowedProtocols || safeUrl.ALLOW_HTTP_TLS,
72
+ errorClass: ObjectStoreError,
73
+ maxUrlLength: C.BYTES.kib(32),
74
+ });
75
+ }
76
+
66
77
  // S3 bucket-name rules (general purpose). Source: AWS docs
67
78
  // "Bucket naming rules". Lowercase letters, digits, hyphens; 3..63
68
79
  // chars; no consecutive dots; cannot end in -s3alias / -ol-s3 etc.
@@ -76,7 +87,9 @@ function _validateBucketName(name) {
76
87
  "bucket name must be a string of length 3..63, got " +
77
88
  (typeof name === "string" ? "length " + name.length : typeof name), true);
78
89
  }
79
- if (!BUCKET_NAME_RE.test(name)) {
90
+ // Length is bounded above (3..63) before the regex runs so the
91
+ // pattern can't be driven against an unbounded input.
92
+ if (name.length > 63 || !BUCKET_NAME_RE.test(name)) {
80
93
  throw _err("BUCKET_INVALID_NAME",
81
94
  "bucket name '" + name + "' violates S3 naming rules " +
82
95
  "(lowercase, digits, hyphens, dots — no leading/trailing punct)", true);
@@ -116,9 +129,11 @@ function _buildLifecycleXml(rules) {
116
129
  throw _err("INVALID_LIFECYCLE",
117
130
  "setLifecycle: rules must be a non-empty array", true);
118
131
  }
119
- if (rules.length > 1000) {
132
+ // S3 spec hard cap on lifecycle rules per bucket.
133
+ var MAX_LIFECYCLE_RULES = 1000;
134
+ if (rules.length > MAX_LIFECYCLE_RULES) {
120
135
  throw _err("INVALID_LIFECYCLE",
121
- "setLifecycle: maximum 1000 rules per bucket (S3 spec)", true);
136
+ "setLifecycle: maximum " + MAX_LIFECYCLE_RULES + " rules per bucket (S3 spec)", true);
122
137
  }
123
138
  var body = '<?xml version="1.0" encoding="UTF-8"?>';
124
139
  body += '<LifecycleConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">';
@@ -388,7 +403,7 @@ function _validateObjectKey(key) {
388
403
  if (typeof key !== "string" || key.length === 0) {
389
404
  throw _err("INVALID_KEY", "object key must be a non-empty string", true);
390
405
  }
391
- if (key.length > 1024) {
406
+ if (key.length > C.BYTES.kib(1)) {
392
407
  throw _err("INVALID_KEY", "object key exceeds 1024 bytes (S3 limit)", true);
393
408
  }
394
409
  }
@@ -442,17 +457,10 @@ function create(config) {
442
457
  var auditSuccess = config.auditSuccess !== false;
443
458
  var auditFailures = config.auditFailures !== false;
444
459
 
445
- function _emit(action, info) {
446
- if (!audit) return;
447
- try {
448
- audit.safeEmit(Object.assign({ action: action }, info));
449
- } catch (_e) { /* audit best-effort — never break the call */ }
450
- }
460
+ var _emit = validateOpts.makeAuditEmitter(audit);
451
461
 
452
462
  function _emitEvent(name, value, labels) {
453
- if (!observability || typeof observability.event !== "function") return;
454
- try { observability.event(name, value, labels || {}); }
455
- catch (_e) { /* observability best-effort */ }
463
+ if (observability) observability.safeEvent(name, value, labels || {});
456
464
  }
457
465
 
458
466
  function _actor(callerOpts) {
@@ -483,17 +491,15 @@ function create(config) {
483
491
  }
484
492
 
485
493
  function _bucketUrl(name, query) {
486
- var base;
494
+ var ub = _internalUrl(endpoint, allowedProtocols);
487
495
  if (pathStyle) {
488
- base = endpoint + "/" + name + "/";
496
+ ub.pathname = "/" + name + "/";
489
497
  } else {
490
- var ub = new URL(endpoint);
491
498
  ub.hostname = name + "." + ub.hostname;
492
499
  ub.pathname = "/";
493
- base = ub.toString();
494
- if (base.endsWith("/")) base = base.slice(0, -1) + "/";
495
500
  }
496
- return new URL(_appendQuery(base, query));
501
+ var base = ub.toString();
502
+ return _internalUrl(_appendQuery(base, query), allowedProtocols);
497
503
  }
498
504
 
499
505
  function _objectUrl(name, key, query) {
@@ -501,21 +507,20 @@ function create(config) {
501
507
  // separators in the key are preserved (S3 treats keys with slashes
502
508
  // as flat names, not directories).
503
509
  var encKey = key.split("/").map(encodeURIComponent).join("/");
504
- var base;
510
+ var uo = _internalUrl(endpoint, allowedProtocols);
505
511
  if (pathStyle) {
506
- base = endpoint + "/" + name + "/" + encKey;
512
+ uo.pathname = "/" + name + "/" + encKey;
507
513
  } else {
508
- var uo = new URL(endpoint);
509
514
  uo.hostname = name + "." + uo.hostname;
510
515
  uo.pathname = "/" + encKey;
511
- base = uo.toString();
512
516
  }
513
- return new URL(_appendQuery(base, query));
517
+ var base = uo.toString();
518
+ return _internalUrl(_appendQuery(base, query), allowedProtocols);
514
519
  }
515
520
 
516
521
  function _serviceUrl(query) {
517
522
  // ListBuckets — service-level, no bucket prefix.
518
- var u = new URL(endpoint);
523
+ var u = _internalUrl(endpoint, allowedProtocols);
519
524
  u.pathname = "/";
520
525
  if (query) {
521
526
  Object.keys(query).forEach(function (k) { u.searchParams.set(k, query[k]); });
@@ -538,16 +543,7 @@ function create(config) {
538
543
  }
539
544
 
540
545
  function _request(method, url, headers, body) {
541
- return httpClient.request({
542
- method: method,
543
- url: url,
544
- headers: headers,
545
- body: body,
546
- idleTimeoutMs: reqOpts.timeoutMs,
547
- errorClass: ObjectStoreError,
548
- allowedProtocols: reqOpts.allowedProtocols,
549
- ...((reqOpts.allowInternal !== undefined) ? { allowInternal: reqOpts.allowInternal } : {}),
550
- });
546
+ return sharedRequest(method, url, headers, body, reqOpts);
551
547
  }
552
548
 
553
549
  // ---- create ----
@@ -27,17 +27,31 @@ var nodeCrypto = require("crypto");
27
27
  var { URL } = require("url");
28
28
  var { Readable } = require("stream");
29
29
  var safeXml = require("../parsers/safe-xml");
30
+ var sharedRequest = require("./http-request");
30
31
  var C = require("../constants");
32
+ var nb = require("../numeric-bounds");
31
33
  var { ObjectStoreError } = require("../framework-error");
32
- var httpClient = require("../http-client");
33
34
  var safeUrl = require("../safe-url");
34
35
 
36
+ // Internal URL builder — endpoint + path string from validated config /
37
+ // framework-encoded keys. Routes through safeUrl.parse so the protocol
38
+ // allowlist + length cap apply uniformly. Presigned URLs with many
39
+ // query params can approach the 8 KB default; raise the cap to 32 KB
40
+ // so a worst-case multipart UploadPart URL still fits.
41
+ function _internalUrl(input, allowedProtocols) {
42
+ return safeUrl.parse(input, {
43
+ allowedProtocols: allowedProtocols || safeUrl.ALLOW_HTTP_TLS,
44
+ errorClass: ObjectStoreError,
45
+ maxUrlLength: C.BYTES.kib(32),
46
+ });
47
+ }
48
+
35
49
  // S3 list responses are commonly 1000-key paginated payloads — well above
36
50
  // the parser's default 1 MiB / 10K element ceilings. These overrides
37
51
  // give headroom for normal responses without uncapping the parser.
38
52
  var LIST_PARSE_OPTS = {
39
53
  maxBytes: C.BYTES.mib(8),
40
- maxElements: 50000,
54
+ maxElements: C.BYTES.bytes(50000),
41
55
  };
42
56
 
43
57
  function _arrayify(value) {
@@ -53,8 +67,8 @@ var ALGORITHM = "AWS4-HMAC-SHA256";
53
67
 
54
68
  // Presigned-URL expiry bounds. The SigV4 spec caps query-string-signed
55
69
  // URLs at 7 days; below 1 second is effectively unsignable.
56
- var PRESIGN_DEFAULT_EXPIRES_SECONDS = 15 * 60; // 15 minutes
57
- var PRESIGN_MAX_EXPIRES_SECONDS = 7 * 24 * 60 * 60; // 7 days (SigV4 hard cap)
70
+ var PRESIGN_DEFAULT_EXPIRES_SECONDS = C.TIME.minutes(15) / C.TIME.seconds(1); // 15 minutes
71
+ var PRESIGN_MAX_EXPIRES_SECONDS = C.TIME.days(7) / C.TIME.seconds(1); // 7 days (SigV4 hard cap)
58
72
  var PRESIGN_MIN_EXPIRES_SECONDS = 1;
59
73
 
60
74
  var _err = ObjectStoreError.factory;
@@ -152,9 +166,11 @@ function deriveSigningKey(secretAccessKey, dateStamp, region, service) {
152
166
  return hmacSha256(kService, "aws4_request");
153
167
  }
154
168
 
169
+ // SigV4 ISO compact format: YYYYMMDDTHHMMSSZ — slice indices fixed
170
+ // by AWS spec.
155
171
  function _formatAmzDate(d) {
156
172
  var iso = d.toISOString().replace(/[-:]/g, "");
157
- return iso.slice(0, 8) + "T" + iso.slice(9, 15) + "Z";
173
+ return iso.slice(0, C.BYTES.bytes(8)) + "T" + iso.slice(9, 15) + "Z";
158
174
  }
159
175
  function _formatDateStamp(d) {
160
176
  return d.toISOString().slice(0, 10).replace(/-/g, "");
@@ -164,7 +180,12 @@ function signRequest(opts) {
164
180
  var date = opts.date || new Date();
165
181
  var amzDate = _formatAmzDate(date);
166
182
  var dateStamp = _formatDateStamp(date);
167
- var url = opts.url instanceof URL ? opts.url : new URL(opts.url);
183
+ // signRequest is called by callers signing requests to operator-
184
+ // configured endpoints (cloudwatch, sqs, sns, custom S3-API stores).
185
+ // Honor opts.allowedProtocols so a test-fixture http:// endpoint still
186
+ // signs correctly while production https:// stays the default.
187
+ var url = opts.url instanceof URL ? opts.url
188
+ : _internalUrl(opts.url, opts.allowedProtocols);
168
189
  // service defaults to s3 for back-compat — every call site predating
169
190
  // v0.6.25 was object-store / S3. Other AWS services (logs, sqs, sns,
170
191
  // kinesis, etc.) pass opts.service explicitly. The credentialScope
@@ -200,19 +221,7 @@ function signRequest(opts) {
200
221
 
201
222
  // ---- HTTP request helper ----
202
223
 
203
- function _request(method, url, headers, body, opts) {
204
- return httpClient.request({
205
- method: method,
206
- url: url,
207
- headers: headers,
208
- body: body,
209
- idleTimeoutMs: opts && opts.timeoutMs,
210
- maxResponseBytes: opts && opts.maxResponseBytes,
211
- errorClass: ObjectStoreError,
212
- allowedProtocols: opts && opts.allowedProtocols,
213
- ...((opts && opts.allowInternal !== undefined) ? { allowInternal: opts.allowInternal } : {}),
214
- });
215
- }
224
+ var _request = sharedRequest;
216
225
 
217
226
  // ---- Multipart-upload constants ----
218
227
 
@@ -223,7 +232,7 @@ function _request(method, url, headers, body, opts) {
223
232
  var MIN_PART_SIZE_BYTES = C.BYTES.mib(5);
224
233
  // S3 spec ceiling on part count. CompleteMultipartUpload rejects
225
234
  // uploads with more than 10000 parts.
226
- var MAX_PARTS = 10000;
235
+ var MAX_PARTS = C.BYTES.bytes(10000);
227
236
  // Auto-multipart trigger: buffered bodies under this stay single-PUT.
228
237
  // Streams always go multipart since size isn't known up-front.
229
238
  var DEFAULT_MULTIPART_THRESHOLD_BYTES = C.BYTES.mib(64);
@@ -395,9 +404,9 @@ function create(config) {
395
404
  if (key.indexOf("\0") !== -1) throw _err("INVALID_KEY", "null byte in key", true);
396
405
  var encoded = key.split("/").map(function (s) { return awsUriEncode(s, true); }).join("/");
397
406
  if (pathStyle) {
398
- return new URL(endpoint + "/" + config.bucket + "/" + encoded);
407
+ return _internalUrl(endpoint + "/" + config.bucket + "/" + encoded, allowedProtocols);
399
408
  }
400
- var u = new URL(endpoint);
409
+ var u = _internalUrl(endpoint, allowedProtocols);
401
410
  u.hostname = config.bucket + "." + u.hostname;
402
411
  u.pathname = "/" + encoded;
403
412
  return u;
@@ -406,9 +415,9 @@ function create(config) {
406
415
  function _bucketUrl(searchParams) {
407
416
  var u;
408
417
  if (pathStyle) {
409
- u = new URL(endpoint + "/" + config.bucket + "/");
418
+ u = _internalUrl(endpoint + "/" + config.bucket + "/", allowedProtocols);
410
419
  } else {
411
- u = new URL(endpoint);
420
+ u = _internalUrl(endpoint, allowedProtocols);
412
421
  u.hostname = config.bucket + "." + u.hostname;
413
422
  u.pathname = "/";
414
423
  }
@@ -506,7 +515,7 @@ function create(config) {
506
515
  // still presents `uploads=` per AWS spec) so the signature path is
507
516
  // unchanged.
508
517
  var url = _keyToUrl(key);
509
- var initiateUrl = new URL(url.href + (url.search ? "&" : "?") + "uploads");
518
+ var initiateUrl = _internalUrl(url.href + (url.search ? "&" : "?") + "uploads", allowedProtocols);
510
519
  var initiateExtra = {
511
520
  "Content-Type": contentType,
512
521
  "Content-Length": "0",
@@ -530,7 +539,7 @@ function create(config) {
530
539
  // 2. Upload parts (concurrency-bounded)
531
540
  uploadedEtags = await _bounded(parts, partConcurrency, async function (partBuf, idx) {
532
541
  var partNumber = idx + 1;
533
- var partUrl = new URL(url.href);
542
+ var partUrl = _internalUrl(url.href, allowedProtocols);
534
543
  partUrl.searchParams.set("partNumber", String(partNumber));
535
544
  partUrl.searchParams.set("uploadId", uploadId);
536
545
  var partHeaders = _makeSigned("PUT", partUrl, sha256Hex(partBuf), {
@@ -546,7 +555,7 @@ function create(config) {
546
555
  });
547
556
 
548
557
  // 3. Complete
549
- var completeUrl = new URL(url.href);
558
+ var completeUrl = _internalUrl(url.href, allowedProtocols);
550
559
  completeUrl.searchParams.set("uploadId", uploadId);
551
560
  var completeBody = Buffer.from(_buildCompleteMultipartXml(uploadedEtags), "utf8");
552
561
  var completeHeaders = _makeSigned("POST", completeUrl, sha256Hex(completeBody), {
@@ -575,7 +584,7 @@ function create(config) {
575
584
  // Failures here are silently swallowed — the caller's original
576
585
  // error is what they need to see, not a secondary cleanup error.
577
586
  try {
578
- var abortUrl = new URL(url.href);
587
+ var abortUrl = _internalUrl(url.href, allowedProtocols);
579
588
  abortUrl.searchParams.set("uploadId", uploadId);
580
589
  var abortHeaders = _makeSigned("DELETE", abortUrl, sha256Hex(Buffer.alloc(0)));
581
590
  await _request("DELETE", abortUrl, abortHeaders, null, reqOpts);
@@ -648,7 +657,11 @@ function create(config) {
648
657
  // hint into real server-side enforcement.
649
658
  var headers = { host: url.host };
650
659
  if (opts.contentType) headers["content-type"] = opts.contentType;
651
- var signedHeadersStr = Object.keys(headers).sort().join(";");
660
+ // SigV4 SignedHeaders: lowercase, semicolon-joined, AWS-spec order.
661
+ var signedHeaderKeys = [];
662
+ for (var hk in headers) signedHeaderKeys.push(hk);
663
+ signedHeaderKeys.sort();
664
+ var signedHeadersStr = signedHeaderKeys.join(";");
652
665
 
653
666
  // Query-string SigV4: presigning carries auth in the URL itself.
654
667
  // The set order doesn't matter — canonicalQueryString sorts the
@@ -707,7 +720,12 @@ function create(config) {
707
720
  "POST-form policy enforces body size via the content-length-range condition; " +
708
721
  "use presignedUploadUrl if size enforcement is not needed", true);
709
722
  }
710
- var minBytes = (typeof opts.minBytes === "number" && opts.minBytes >= 0) ? opts.minBytes : 0;
723
+ if (opts.minBytes !== undefined && !nb.isNonNegativeFiniteInt(opts.minBytes)) {
724
+ throw _err("INVALID_MIN_BYTES",
725
+ "presignedUploadPolicy: minBytes must be a non-negative finite integer; got " +
726
+ nb.shape(opts.minBytes), true);
727
+ }
728
+ var minBytes = opts.minBytes !== undefined ? opts.minBytes : 0;
711
729
  var expiresIn = opts.expiresIn != null ? opts.expiresIn : PRESIGN_DEFAULT_EXPIRES_SECONDS;
712
730
  if (typeof expiresIn !== "number" ||
713
731
  expiresIn < PRESIGN_MIN_EXPIRES_SECONDS ||
@@ -830,6 +848,8 @@ module.exports = {
830
848
  canonicalHeaders: canonicalHeaders,
831
849
  awsUriEncode: awsUriEncode,
832
850
  sha256Hex: sha256Hex,
851
+ formatAmzDate: _formatAmzDate,
852
+ formatDateStamp: _formatDateStamp,
833
853
  SERVICE: SERVICE,
834
854
  ALGORITHM: ALGORITHM,
835
855
  };
@@ -133,8 +133,19 @@ function event(name, value, labels) {
133
133
  _safeMetricsTap(name, value, labels);
134
134
  }
135
135
 
136
+ // safeEvent — wraps `event` in a try/catch so callers on hot paths
137
+ // (per-request observability emits) can't crash the request that
138
+ // triggered them when the metrics registry has a misconfigured
139
+ // counter or label name. Replaces the per-file `_emitEvent` helper
140
+ // that 7+ modules previously duplicated.
141
+ function safeEvent(name, value, labels) {
142
+ try { event(name, value, labels); }
143
+ catch (_e) { /* hot-path observability sink — drops silent on internal throws */ }
144
+ }
145
+
136
146
  module.exports = {
137
- tap: tap,
138
- event: event,
139
- setTap: setTap,
147
+ tap: tap,
148
+ event: event,
149
+ safeEvent: safeEvent,
150
+ setTap: setTap,
140
151
  };
@@ -40,6 +40,8 @@
40
40
  * hand-rolled wire format with no compelling benefit at this scope.
41
41
  */
42
42
  var C = require("./constants");
43
+ var canonicalJson = require("./canonical-json");
44
+ var defaultHttpClient = require("./http-client");
43
45
  var safeAsync = require("./safe-async");
44
46
  var validateOpts = require("./validate-opts");
45
47
  var { defineClass } = require("./framework-error");
@@ -77,13 +79,19 @@ function _attrsToOtlp(attrs) {
77
79
  return out;
78
80
  }
79
81
 
80
- // Stable key per (name, attrs) so tap calls aggregate.
82
+ // Stable key per (name, attrs) so tap calls aggregate. Routes the
83
+ // sort through canonical-json so the framework has one canonical-sort
84
+ // source of truth for hash-input-shaped keys (the OTLP attribute
85
+ // encoding _attrsToOtlp emits the wire-format separately).
81
86
  function _bucketKey(name, attrs) {
82
87
  if (!attrs) return name + "|";
83
- var ks = Object.keys(attrs).sort();
84
- var parts = [name];
85
- for (var i = 0; i < ks.length; i++) parts.push(ks[i] + "=" + String(attrs[ks[i]]));
86
- return parts.join("|");
88
+ // Coerce to strings so canonical-json doesn't reject odd value types.
89
+ var coerced = {};
90
+ var rawKeys = Object.keys(attrs);
91
+ for (var i = 0; i < rawKeys.length; i++) {
92
+ coerced[rawKeys[i]] = String(attrs[rawKeys[i]]);
93
+ }
94
+ return name + "|" + canonicalJson.stringify(coerced);
87
95
  }
88
96
 
89
97
  function create(opts) {
@@ -92,14 +100,8 @@ function create(opts) {
92
100
  "endpoint", "headers", "serviceName", "intervalMs",
93
101
  "httpClient", "resourceAttributes", "scope",
94
102
  ], "otelExport.create");
95
- if (typeof opts.endpoint !== "string" || opts.endpoint.length === 0) {
96
- throw new OtelExportError("otel-export/bad-endpoint",
97
- "create: endpoint must be a non-empty URL");
98
- }
99
- if (typeof opts.serviceName !== "string" || opts.serviceName.length === 0) {
100
- throw new OtelExportError("otel-export/bad-service-name",
101
- "create: serviceName must be a non-empty string");
102
- }
103
+ validateOpts.requireNonEmptyString(opts.endpoint, "create: endpoint", OtelExportError, "otel-export/bad-endpoint");
104
+ validateOpts.requireNonEmptyString(opts.serviceName, "create: serviceName", OtelExportError, "otel-export/bad-service-name");
103
105
  var endpoint = opts.endpoint;
104
106
  var serviceName = opts.serviceName;
105
107
  var headers = opts.headers || {};
@@ -108,7 +110,7 @@ function create(opts) {
108
110
  throw new OtelExportError("otel-export/bad-interval",
109
111
  "create: intervalMs must be a non-negative finite number");
110
112
  }
111
- var httpClient = opts.httpClient || require("./http-client");
113
+ var httpClient = opts.httpClient || defaultHttpClient;
112
114
  var scopeName = (opts.scope && opts.scope.name) || "blamejs";
113
115
  var scopeVersion = (opts.scope && opts.scope.version) || "0.5.x";
114
116
  var resourceAttrs = Object.assign({ "service.name": serviceName },
package/lib/pagination.js CHANGED
@@ -96,16 +96,21 @@
96
96
  */
97
97
 
98
98
  var nodeCrypto = require("node:crypto");
99
+ var C = require("./constants");
99
100
  var canonicalJson = require("./canonical-json");
100
101
  var crypto = require("./crypto");
102
+ var nb = require("./numeric-bounds");
103
+ var safeJson = require("./safe-json");
104
+ var safeSql = require("./safe-sql");
101
105
  var { defineClass } = require("./framework-error");
102
106
 
103
107
  var PaginationError = defineClass("PaginationError", { alwaysPermanent: true });
104
108
 
105
109
  var CURSOR_VERSION = 1;
106
- var TAG_BYTES = 16; // 128-bit HMAC tag truncated from SHA3-512
110
+ var TAG_BYTES = C.BYTES.bytes(16); // 128-bit HMAC tag truncated from SHA3-512
107
111
  var DEFAULT_LIMIT = 25;
108
112
  var DEFAULT_MAX_LIMIT = 100;
113
+ var MAX_CURSOR_BYTES = C.BYTES.kib(8);
109
114
 
110
115
  // Canonical JSON via the shared lib/canonical-json walker with
111
116
  // bufferAs: "reject" — cursor state must contain only plain data, so
@@ -185,7 +190,7 @@ function decodeCursor(token, secret) {
185
190
  "cursor HMAC verification failed (tampered or wrong secret)");
186
191
  }
187
192
  var state;
188
- try { state = JSON.parse(json); }
193
+ try { state = safeJson.parse(json, { maxBytes: MAX_CURSOR_BYTES }); }
189
194
  catch (_e) {
190
195
  throw new PaginationError("pagination/bad-cursor", "cursor state JSON malformed");
191
196
  }
@@ -200,8 +205,12 @@ function decodeCursor(token, secret) {
200
205
  }
201
206
 
202
207
  function _resolveLimit(opts) {
203
- var max = (typeof opts.max === "number" && opts.max > 0) ? opts.max : DEFAULT_MAX_LIMIT;
204
- var def = (typeof opts.default === "number" && opts.default > 0) ? opts.default : DEFAULT_LIMIT;
208
+ nb.requirePositiveFiniteIntIfPresent(opts.max,
209
+ "max", PaginationError, "pagination/bad-opt");
210
+ nb.requirePositiveFiniteIntIfPresent(opts.default,
211
+ "default", PaginationError, "pagination/bad-opt");
212
+ var max = opts.max || DEFAULT_MAX_LIMIT;
213
+ var def = opts.default || DEFAULT_LIMIT;
205
214
  var requested = parseInt(opts.limit, 10);
206
215
  if (isNaN(requested) || requested < 1) requested = def;
207
216
  if (requested > max) requested = max;
@@ -247,9 +256,13 @@ function _normalizeOrderBy(opts) {
247
256
  "orderBy must be a string, array, or omitted; got " + typeof raw);
248
257
  }
249
258
  for (var i = 0; i < entries.length; i++) {
250
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(entries[i].column)) {
259
+ if (typeof entries[i].column !== "string" ||
260
+ entries[i].column.length === 0 ||
261
+ entries[i].column.length > safeSql.MAX_IDENTIFIER_LENGTH ||
262
+ !safeSql.DEFAULT_IDENTIFIER_RE.test(entries[i].column)) {
251
263
  throw new PaginationError("pagination/bad-orderby",
252
- "orderBy column must match /^[A-Za-z_][A-Za-z0-9_]*$/ (identifier-safe), got " +
264
+ "orderBy column must match " + safeSql.DEFAULT_IDENTIFIER_RE +
265
+ " and be 1.." + safeSql.MAX_IDENTIFIER_LENGTH + " chars, got " +
253
266
  JSON.stringify(entries[i].column));
254
267
  }
255
268
  }
@@ -405,9 +418,11 @@ async function offset(query, opts) {
405
418
  var orderBy = typeof opts.orderBy === "string" && opts.orderBy.length > 0 ? opts.orderBy : "_id";
406
419
  // Same identifier-only check on offset() as cursor() — orderBy passes
407
420
  // through to the db Query; throw at call site to prevent SQL injection.
408
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(orderBy)) {
421
+ if (orderBy.length > safeSql.MAX_IDENTIFIER_LENGTH ||
422
+ !safeSql.DEFAULT_IDENTIFIER_RE.test(orderBy)) {
409
423
  throw new PaginationError("pagination/bad-orderby",
410
- "offset: orderBy must match /^[A-Za-z_][A-Za-z0-9_]*$/ (identifier-safe), got " +
424
+ "offset: orderBy must match " + safeSql.DEFAULT_IDENTIFIER_RE +
425
+ " and be 1.." + safeSql.MAX_IDENTIFIER_LENGTH + " chars, got " +
411
426
  JSON.stringify(orderBy));
412
427
  }
413
428
  var direction = (opts.direction === "desc") ? "desc" : "asc";
@@ -65,10 +65,16 @@
65
65
  * Error types: each parser exports its own *SafeError class with .code
66
66
  * matching the format (xml/..., toml/..., ini/..., yaml/..., env/...).
67
67
  */
68
+ var safeEnv = require("./safe-env");
69
+ var safeIni = require("./safe-ini");
70
+ var safeToml = require("./safe-toml");
71
+ var safeXml = require("./safe-xml");
72
+ var safeYaml = require("./safe-yaml");
73
+
68
74
  module.exports = {
69
- xml: require("./safe-xml"),
70
- toml: require("./safe-toml"),
71
- yaml: require("./safe-yaml"),
72
- env: require("./safe-env"),
73
- ini: require("./safe-ini"),
75
+ xml: safeXml,
76
+ toml: safeToml,
77
+ yaml: safeYaml,
78
+ env: safeEnv,
79
+ ini: safeIni,
74
80
  };