@blamejs/core 0.6.61 → 0.6.63

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.
package/CHANGELOG.md CHANGED
@@ -8,6 +8,10 @@ upgrading across more than a few patches at a time.
8
8
 
9
9
  ## v0.6.x
10
10
 
11
+ - **0.6.63** (2026-05-03) — `b.parsers.toml.parse` enforces `maxDepth` on dotted-key paths. Pre-fix the parser walked arbitrarily deep table headers (`[a.b.c.d.e…]`) without applying the existing `maxDepth` (which only ran inside `_parseValue` for inline tables / arrays); a 10,000-segment path built a tree deep enough to stack-overflow the recursive `_normalize` walker post-parse. An attacker submitting a malicious TOML config to `b.config` would crash the framework at boot or whenever the file was reloaded. Now `_parseDottedKey` rejects a path with more than `maxDepth` segments via the same `toml/too-deep` error code already used for value-side depth. **Tests** — 4 new layer-0 assertions: 99-segment path under default depth accepts, 150-segment path rejects with `toml/too-deep`, 10K-segment path rejects (no stack overflow), `maxDepth: 1000` opt allows a 500-segment path. Smoke 7222 → 7226 / wiki e2e 178 / Linux 85.6s / eslint clean / shellcheck clean.
12
+
13
+ - **0.6.62** (2026-05-03) — URL-length DoS-shape gap closed in two places. **`b.safeSchema.string().url()`** was regex-only with no length cap, accepting arbitrarily long matching strings — same DoS class as v0.6.61's `.email()` gap; an operator chaining `.url()` on a request body would feed multi-megabyte URLs into downstream HTTP clients / SSRF gates / log lines. Now rejects with `string/url-too-long` at 8193+ chars. **`b.safeUrl.parse`** had the same gap at the framework's primary URL-validation surface (used by httpClient + ssrfGuard + every operator-supplied URL). The 8 KB cap now applies BEFORE handing the string to Node's `new URL()` parser; operator-supplied URLs feeding `b.httpClient.request({ url })` from request bodies / webhook configs are bounded. Operators with legitimate non-standard use (proxies, tunnels with embedded payloads) override via `opts.maxUrlLength`. Both bounds reference RFC 7230 §3.1.1's 8000-octet recommendation. **Tests** — 4 new layer-0 assertions on `string().url()` (8 KB / 8193 / 100K / error code) + 4 on `safeUrl.parse` (8192 accept / 8193 reject / error code / `maxUrlLength` opt override). Smoke 7215 → 7222 / wiki e2e 178 / Linux 85.5s / per-primitive integration 16 files / eslint clean / shellcheck clean.
14
+
11
15
  - **0.6.61** (2026-05-03) — `b.safeSchema.string().email()` enforces RFC 5321 §4.5.3.1.3's 254-character cap on the address. Pre-fix the validator was the regex-only `/^[^\s@]+@[^\s@]+\.[^\s@]+$/`, which accepts arbitrarily long matching strings — an operator chaining `.email()` on a request body was open to a DoS shape (50 KB email passes validation, downstream DB writes / log lines unbounded). Now `.email()` rejects with `string/email-too-long` at 255+ chars before the regex even runs, with a message naming the RFC. Operators with a legitimate non-RFC reason for longer addresses skip `.email()` and chain `.regex(custom)` directly. **Tests** — 4 new layer-0 assertions: 254-char address accepts, 255 / 500 char addresses reject, error code `string/email-too-long` exposed for operator handler routing. Smoke 7211 → 7215 / wiki e2e 178 / Linux 85.8s / eslint clean / shellcheck clean.
12
16
 
13
17
  - **0.6.60** (2026-05-03) — `b.pagination.encodeCursor` correctness fixes for non-plain types in cursor state. The `_canonicalize` walker did `if (typeof === "object") Object.keys(...)`, which silently lost data on three classes of input: **Date** silently encoded as `{}` (Date instances have no own enumerable keys); **Buffer / Uint8Array** silently encoded as `{"0":97,"1":98,…}` (indexed-key serialisation); **circular references** stack-overflowed instead of throwing a clean error. After encode → decode the operator's data was either gone or unrecognisable. The fix: Date now serialises to its ISO string (matches stdlib `JSON.stringify` semantics — round-trips through encode/decode as the ISO string operators expect); Buffer / Uint8Array / Map / Set / RegExp throw `pagination/bad-state` with a message naming the offending constructor and pointing operators at the right conversion (`"convert to a plain primitive (string / number / iso-string) first"`); circular references throw `pagination/bad-state` cleanly via WeakSet cycle detection. **Tests** — 7 new layer-0 assertions: Date round-trip preserved, all 5 non-plain types reject cleanly, circular reference rejects without stack overflow. Smoke 7204→7211 / wiki e2e 178 / Linux 85.3s / eslint clean / shellcheck clean.
@@ -209,6 +209,15 @@ function parse(input, opts) {
209
209
  throw _err("forbidden key '" + seg + "'", "toml/poisoned-key");
210
210
  }
211
211
  segments.push(seg);
212
+ // Cap dotted-key depth at maxDepth — same bound as `_parseValue`.
213
+ // Without this, a table header `[a.b.c.d…]` with thousands of
214
+ // segments builds a tree deep enough to stack-overflow the
215
+ // post-parse `_normalize` walker (which is recursive). The check
216
+ // sits at +1 over depth so a path of exactly maxDepth segments is
217
+ // still accepted (matches the inclusive bound in _parseValue).
218
+ if (segments.length > maxDepth) {
219
+ throw _err("dotted-key path exceeds maxDepth (" + maxDepth + ")", "toml/too-deep");
220
+ }
212
221
  }
213
222
  }
214
223
 
@@ -421,6 +421,17 @@ function _stringMethods(schema, spec) {
421
421
  };
422
422
  schema.url = function () {
423
423
  return chain(function (v, p) {
424
+ // RFC 9110 doesn't set a hard URL length, but RFC 7230 §3.1.1
425
+ // recommended 8000 octets and most HTTP origin servers + load
426
+ // balancers cap at 8 KB. Without this bound an operator chaining
427
+ // .url() on a request body was open to a 50 MB URL passing
428
+ // validation. Operators with a legitimate non-standard use
429
+ // (tunnels, proxies with embedded payloads) skip .url() and
430
+ // chain .regex(custom) directly.
431
+ if (v.length > 8192) {
432
+ return _fail(p, "string/url-too-long",
433
+ "must be a valid URL (max 8192 chars per RFC 7230 §3.1.1 guidance)");
434
+ }
424
435
  return URL_RE.test(v) ? { ok: true } :
425
436
  _fail(p, "string/url", "must be a valid URL");
426
437
  });
package/lib/safe-url.js CHANGED
@@ -75,17 +75,37 @@ function _makeError(errorClass, code, message) {
75
75
  return new errorClass(code, message, true);
76
76
  }
77
77
 
78
+ // RFC 7230 §3.1.1 recommended 8000 octets, RFC 9110 doesn't update.
79
+ // Most HTTP origin servers + load balancers cap at 8 KB. Operators with
80
+ // a legitimate non-standard use (proxies, tunnels with embedded
81
+ // payloads) override via opts.maxUrlLength.
82
+ var DEFAULT_MAX_URL_LENGTH = 8192;
83
+
78
84
  function parse(url, opts) {
79
85
  opts = opts || {};
80
86
  var allowed = Array.isArray(opts.allowedProtocols) && opts.allowedProtocols.length > 0
81
87
  ? opts.allowedProtocols
82
88
  : ALLOW_HTTP_TLS;
83
89
  var errClass = opts.errorClass;
90
+ var maxUrlLength = (typeof opts.maxUrlLength === "number" && opts.maxUrlLength > 0)
91
+ ? opts.maxUrlLength
92
+ : DEFAULT_MAX_URL_LENGTH;
84
93
 
85
94
  if (url == null || url === "") {
86
95
  throw _makeError(errClass, "safe-url/missing", "url is required");
87
96
  }
88
97
 
98
+ // Bound the URL string length BEFORE handing it to `new URL()`. Without
99
+ // this cap the framework would walk multi-megabyte URLs through Node's
100
+ // parser before the SSRF / protocol / userinfo gates even ran — a real
101
+ // DoS shape for operators feeding `b.httpClient.request({ url })` from
102
+ // request bodies / webhook configs.
103
+ if (typeof url === "string" && url.length > maxUrlLength) {
104
+ throw _makeError(errClass, "safe-url/too-long",
105
+ "URL exceeds " + maxUrlLength + " chars (got " + url.length +
106
+ "). RFC 7230 §3.1.1 recommends 8000; pass opts.maxUrlLength to override.");
107
+ }
108
+
89
109
  var parsed;
90
110
  if (url instanceof URL) {
91
111
  parsed = url;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/core",
3
- "version": "0.6.61",
3
+ "version": "0.6.63",
4
4
  "description": "The Node framework that owns its stack.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "blamejs contributors",
@@ -2,10 +2,10 @@
2
2
  "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
3
3
  "bomFormat": "CycloneDX",
4
4
  "specVersion": "1.5",
5
- "serialNumber": "urn:uuid:4c584992-71fc-4053-b097-fe6dc1dbb573",
5
+ "serialNumber": "urn:uuid:192ac8a0-4291-44dd-a03a-44af6b4cce65",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-05-03T13:46:27.072Z",
8
+ "timestamp": "2026-05-03T14:04:51.705Z",
9
9
  "lifecycles": [
10
10
  {
11
11
  "phase": "build"
@@ -19,14 +19,14 @@
19
19
  }
20
20
  ],
21
21
  "component": {
22
- "bom-ref": "@blamejs/core@0.6.61",
22
+ "bom-ref": "@blamejs/core@0.6.63",
23
23
  "type": "library",
24
24
  "name": "blamejs",
25
- "version": "0.6.61",
25
+ "version": "0.6.63",
26
26
  "scope": "required",
27
27
  "author": "blamejs contributors",
28
28
  "description": "The Node framework that owns its stack.",
29
- "purl": "pkg:npm/%40blamejs/core@0.6.61",
29
+ "purl": "pkg:npm/%40blamejs/core@0.6.63",
30
30
  "properties": [],
31
31
  "externalReferences": [
32
32
  {
@@ -54,7 +54,7 @@
54
54
  "components": [],
55
55
  "dependencies": [
56
56
  {
57
- "ref": "@blamejs/core@0.6.61",
57
+ "ref": "@blamejs/core@0.6.63",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]