@blamejs/core 0.6.59 → 0.6.61
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 +4 -0
- package/lib/pagination.js +27 -3
- package/lib/safe-schema.js +11 -0
- package/package.json +1 -1
- package/sbom.cyclonedx.json +6 -6
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.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
|
+
|
|
13
|
+
- **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.
|
|
14
|
+
|
|
11
15
|
- **0.6.59** (2026-05-03) — HTTP/2 session teardown deduplication + http-client sweep. v0.6.58's inline `session.close() + session.destroy()` block in `lib/log-stream-otlp-grpc.js` was the right fix for the OTLP-gRPC sink hang, but the same bug class lives in `lib/http-client.js` — the h2 transport pool had 5 call sites running the bare `session.close()` (`_resetTransports`, the ALPN-fallback path, the h2 connect-error path, the h2c connect-error path, the idle-timeout handler, and `_resetForTest`) all of which leak the underlying TCP socket on idle / error / fallback paths in exactly the same way. New `lib/http2-teardown.js` exports `tearDownH2Session(session)` which performs the close()-then-destroy() routine; `lib/http-client.js` and `lib/log-stream-otlp-grpc.js` both import it. The OTLP-gRPC v0.6.58 inline block is removed in favour of the shared helper. **Tests** — verified in a node:24-alpine docker container: smoke 7204 checks in 85.3 seconds. Wiki e2e 178 / eslint clean / shellcheck clean.
|
|
12
16
|
|
|
13
17
|
- **0.6.58** (2026-05-03) — `b.logStream` OTLP-gRPC sink hang fix that has been silently breaking the npm-publish gate since v0.6.38. The sink's `close()` was calling `session.close()` (HTTP/2 *graceful* close — waits for in-flight streams before freeing the socket) but never `session.destroy()`. The graceful close completed but the underlying TCP socket stayed connected, blocking the test fixture's `server.close()` indefinitely on Linux CI runners. Same bug also added ~120 seconds of lingering latency to every local smoke run on Windows (smoke 196s → 76s after fix). The fix calls `session.close()` then `session.destroy()`; by the time we reach close(), all buffered records have been flushed via the awaited `inflightPromise` + final `_doExport`, so destroy() is structurally safe. **Operator impact** — the npm registry has been stuck at v0.6.37 since 2026-05-02; every tag from v0.6.38 → v0.6.57 timed out at the publish workflow's smoke step. With this fix the publish should reach `npm publish`. No layer-0 or integration test changes — the existing `log-stream-otlp-grpc.test.js` round-trip was passing locally because tests use small batches and the lingering socket happens to terminate before the test driver's overall timeout, but it was leaving the process unable to exit cleanly until the OS-level TCP timeout fired. Smoke 7204 / wiki e2e 178 / integration 16 files / eslint clean / shellcheck clean.
|
package/lib/pagination.js
CHANGED
|
@@ -108,13 +108,37 @@ var DEFAULT_MAX_LIMIT = 100;
|
|
|
108
108
|
|
|
109
109
|
// Canonical JSON — sorted keys at every depth. Mirrors safe-schema /
|
|
110
110
|
// audit-tools so verifier and producer hash exactly the same bytes.
|
|
111
|
-
|
|
111
|
+
//
|
|
112
|
+
// Cursor state must contain only plain data (string, number, boolean,
|
|
113
|
+
// null, plain object, array, Date). Buffer / typed-arrays / Map / Set /
|
|
114
|
+
// RegExp are rejected because the prior `Object.keys` walk silently
|
|
115
|
+
// serialised them as `{}` (Date) or `{"0":97,"1":98,…}` (Buffer) — the
|
|
116
|
+
// operator's data round-tripped through encode/decode as garbage. Date
|
|
117
|
+
// gets an explicit ISO-string conversion (matches stdlib JSON.stringify).
|
|
118
|
+
// Circular references throw cleanly instead of stack-overflowing.
|
|
119
|
+
function _canonicalize(value, seen) {
|
|
112
120
|
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
113
|
-
if (
|
|
121
|
+
if (value instanceof Date) return JSON.stringify(value.toISOString());
|
|
122
|
+
if (Buffer.isBuffer(value) || value instanceof Uint8Array ||
|
|
123
|
+
value instanceof Map || value instanceof Set ||
|
|
124
|
+
value instanceof RegExp) {
|
|
125
|
+
throw new PaginationError("pagination/bad-state",
|
|
126
|
+
"cursor state cannot contain " + value.constructor.name +
|
|
127
|
+
"; convert to a plain primitive (string / number / iso-string) first");
|
|
128
|
+
}
|
|
129
|
+
seen = seen || new WeakSet();
|
|
130
|
+
if (seen.has(value)) {
|
|
131
|
+
throw new PaginationError("pagination/bad-state",
|
|
132
|
+
"cursor state contains a circular reference");
|
|
133
|
+
}
|
|
134
|
+
seen.add(value);
|
|
135
|
+
if (Array.isArray(value)) {
|
|
136
|
+
return "[" + value.map(function (v) { return _canonicalize(v, seen); }).join(",") + "]";
|
|
137
|
+
}
|
|
114
138
|
var keys = Object.keys(value).sort();
|
|
115
139
|
var parts = [];
|
|
116
140
|
for (var i = 0; i < keys.length; i++) {
|
|
117
|
-
parts.push(JSON.stringify(keys[i]) + ":" + _canonicalize(value[keys[i]]));
|
|
141
|
+
parts.push(JSON.stringify(keys[i]) + ":" + _canonicalize(value[keys[i]], seen));
|
|
118
142
|
}
|
|
119
143
|
return "{" + parts.join(",") + "}";
|
|
120
144
|
}
|
package/lib/safe-schema.js
CHANGED
|
@@ -404,6 +404,17 @@ function _stringMethods(schema, spec) {
|
|
|
404
404
|
};
|
|
405
405
|
schema.email = function () {
|
|
406
406
|
return chain(function (v, p) {
|
|
407
|
+
// RFC 5321 §4.5.3.1.3 — max forward-path is 256 octets including
|
|
408
|
+
// angle brackets, so the address itself is bounded at 254 chars.
|
|
409
|
+
// Without this cap an operator chaining .email() on a request body
|
|
410
|
+
// is open to a DoS shape (50 KB email -> downstream DB writes
|
|
411
|
+
// unbounded string columns, log lines, etc.). Operators with a
|
|
412
|
+
// legitimate non-RFC reason for longer emails skip .email() and
|
|
413
|
+
// chain .regex(custom) directly.
|
|
414
|
+
if (v.length > 254) {
|
|
415
|
+
return _fail(p, "string/email-too-long",
|
|
416
|
+
"must be a valid email address (max 254 chars per RFC 5321)");
|
|
417
|
+
}
|
|
407
418
|
return EMAIL_RE.test(v) ? { ok: true } :
|
|
408
419
|
_fail(p, "string/email", "must be a valid email address");
|
|
409
420
|
});
|
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:4c584992-71fc-4053-b097-fe6dc1dbb573",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-05-03T13:
|
|
8
|
+
"timestamp": "2026-05-03T13:46:27.072Z",
|
|
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.61",
|
|
23
23
|
"type": "library",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.6.
|
|
25
|
+
"version": "0.6.61",
|
|
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.61",
|
|
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.61",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|