@blamejs/core 0.6.61 → 0.6.62
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 +2 -0
- package/lib/safe-schema.js +11 -0
- package/lib/safe-url.js +20 -0
- package/package.json +1 -1
- package/sbom.cyclonedx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,8 @@ upgrading across more than a few patches at a time.
|
|
|
8
8
|
|
|
9
9
|
## v0.6.x
|
|
10
10
|
|
|
11
|
+
- **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.
|
|
12
|
+
|
|
11
13
|
- **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
14
|
|
|
13
15
|
- **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.
|
package/lib/safe-schema.js
CHANGED
|
@@ -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
package/sbom.cyclonedx.json
CHANGED
|
@@ -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:
|
|
5
|
+
"serialNumber": "urn:uuid:3ba1ea7f-af06-4625-bebf-f91c81f11992",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-05-03T13:
|
|
8
|
+
"timestamp": "2026-05-03T13:56:09.684Z",
|
|
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.
|
|
22
|
+
"bom-ref": "@blamejs/core@0.6.62",
|
|
23
23
|
"type": "library",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.6.
|
|
25
|
+
"version": "0.6.62",
|
|
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.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/core@0.6.62",
|
|
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.
|
|
57
|
+
"ref": "@blamejs/core@0.6.62",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|