@blamejs/core 0.7.62 → 0.7.64
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/router.js +42 -4
- package/lib/websocket.js +21 -5
- 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.7.x
|
|
10
10
|
|
|
11
|
+
- **0.7.64** (2026-05-06) — HTTP/2 + WebSocket DoS hardening. **HTTP/2 server caps** — `lib/router.js` `http2.createSecureServer` now ships framework-default hardening: `maxConcurrentStreams: 100` (CVE-2023-44487 Rapid Reset cap; Node default was 4294967295), `maxSessionMemory: 10` (MB), `maxHeaderListPairs: 100` (CVE-2024-27983 / CVE-2024-28182 CONTINUATION-flood cap), `maxSettings: 32`, `peerMaxConcurrentStreams: 100`, `unknownProtocolTimeout: 10s` (Slowloris-h2 variant). Operator-supplied `tlsOptions` override any of these. **Slowloris timeouts** — `server.headersTimeout = 60s`, `server.requestTimeout = 5min`, `server.keepAliveTimeout = 5s` set explicitly post-listen. The framework was previously relying on Node-version-shifting defaults; pinning brings older Node releases up to the modern bar. **WebSocket origin default** — *breaking change* (pre-1.0, no compat shim per CLAUDE.md): when `origins` is omitted from `b.websocket.create({ ... })`, the new default is **same-origin enforcement** (Origin header host must match Host header). Pre-0.7.64 the default was "accept all", which is the canonical Cross-Site WebSocket Hijacking (CSWSH) class. Operators needing cross-origin opt in via `origins: "*"` (with audited reason) or `origins: [...allowlist]`. Non-browser clients (no Origin header) continue to bypass — origin enforcement is a browser-class defense.
|
|
12
|
+
|
|
13
|
+
- **0.7.63** (2026-05-06) — gitleaks regex allowlist extended to cover JWT fixtures split across multiple string literals. The v0.7.62 regex only matched the full three-segment JWT compact-serialization shape; source files split long JWT fixtures across literals for line-length, so gitleaks saw individual `eyJ...`-prefixed base64url segments and still flagged them. Added a second allowlist regex matching any `eyJ`-prefixed segment of substantive length (`{20,}`). Same rationale: real signing keys never appear as `eyJ...` base64url tokens — they're PEM / DER / PKCS#8.
|
|
14
|
+
|
|
11
15
|
- **0.7.62** (2026-05-06) — gitleaks regex allowlist for JWT compact-serialization shape (`eyJ...header.eyJ...payload.signature`). The new `b.guardJwt` and `b.guardAuth` test fixtures legitimately embed JWT-shaped strings as benign + hostile inputs; gitleaks' default `generic-api-key` rule fires on the high-entropy base64url segments and refuses every release tag with the fixtures present. Real signing keys never appear in compact serialization shape — they're PEM / DER / PKCS#8 — so this allowlist doesn't suppress detection of actual key leaks. Allowlist regex added under the existing "Doc-string credential-shaped placeholders" block in `.gitleaks.toml`. No code change.
|
|
12
16
|
|
|
13
17
|
- **0.7.61** (2026-05-06) — eslint cleanup in `lib/guard-regex.js` and `lib/guard-shell.js`. The `no-useless-escape` rule (eslint v9+) flagged unnecessary backslashes inside regex character classes — `*`, `+`, `?`, `[` don't need escaping when they appear inside `[...]`. Behavior unchanged: regex semantics are identical with or without the escapes (the engine treats both forms as the literal character). The framework's CI gate runs eslint with `--max-warnings 0`; this slice unblocks the CI lint job that's been failing on tag pushes since v0.7.53. No operator-facing behavior change.
|
package/lib/router.js
CHANGED
|
@@ -626,10 +626,32 @@ class Router {
|
|
|
626
626
|
// accept both. enableConnectProtocol: true is what enables h2
|
|
627
627
|
// WebSocket (RFC 8441) — clients refuse to issue Extended CONNECT
|
|
628
628
|
// until they see this in the server's SETTINGS frame.
|
|
629
|
+
// Framework-default HTTP/2 hardening — operator-supplied
|
|
630
|
+
// tlsOptions can override any of these.
|
|
631
|
+
//
|
|
632
|
+
// maxConcurrentStreams: cap concurrent streams per session (Node
|
|
633
|
+
// default is 4294967295 — way too high; CVE-2023-44487 Rapid
|
|
634
|
+
// Reset relies on the unbounded default).
|
|
635
|
+
// maxSessionMemory: 10 MB cap per session (Node default; explicit).
|
|
636
|
+
// maxHeaderListPairs: 100 header pairs max (Node default 128;
|
|
637
|
+
// tightened — CVE-2024-27983 / CVE-2024-28182 CONTINUATION
|
|
638
|
+
// flood relies on header-pair amplification).
|
|
639
|
+
// maxSettings: cap SETTINGS-frame entries.
|
|
640
|
+
// peerMaxConcurrentStreams: cap how many streams the peer is
|
|
641
|
+
// willing to accept (limits server-initiated push, which the
|
|
642
|
+
// framework doesn't use).
|
|
643
|
+
// unknownProtocolTimeout: 10s — drop sessions stuck in protocol-
|
|
644
|
+
// detection (Slowloris-h2 variant).
|
|
629
645
|
server = http2.createSecureServer(Object.assign({
|
|
630
|
-
allowHTTP1:
|
|
631
|
-
ALPNProtocols:
|
|
632
|
-
settings:
|
|
646
|
+
allowHTTP1: true,
|
|
647
|
+
ALPNProtocols: ["h2", "http/1.1"],
|
|
648
|
+
settings: { enableConnectProtocol: true },
|
|
649
|
+
maxConcurrentStreams: 100, // allow:raw-byte-literal — CVE-2023-44487 Rapid Reset cap
|
|
650
|
+
maxSessionMemory: 10, // allow:raw-byte-literal — MB cap (Node default explicit)
|
|
651
|
+
maxHeaderListPairs: 100, // allow:raw-byte-literal — CVE-2024-27983 CONTINUATION-flood cap
|
|
652
|
+
maxSettings: 32, // allow:raw-byte-literal — SETTINGS-frame entry ceiling
|
|
653
|
+
peerMaxConcurrentStreams: 100, // allow:raw-byte-literal — peer-side stream cap
|
|
654
|
+
unknownProtocolTimeout: C.TIME.seconds(10),
|
|
633
655
|
}, tlsOptions), requestHandler);
|
|
634
656
|
} else {
|
|
635
657
|
// Cleartext path is h1-only. Operators wanting h2c on cleartext
|
|
@@ -710,7 +732,23 @@ class Router {
|
|
|
710
732
|
|
|
711
733
|
if (host) server.listen(port, host, cb);
|
|
712
734
|
else server.listen(port, cb);
|
|
713
|
-
|
|
735
|
+
// Slowloris / slow-read defenses. Node defaults shifted across
|
|
736
|
+
// versions; the framework pins them explicitly so operators on
|
|
737
|
+
// older Node releases get the modern bar.
|
|
738
|
+
//
|
|
739
|
+
// headersTimeout: 60s — time allotted for the entire request-line
|
|
740
|
+
// + header section. Slowloris's classic posture is a connection
|
|
741
|
+
// that trickles headers indefinitely.
|
|
742
|
+
// requestTimeout: 5min — total wall-clock for a request including
|
|
743
|
+
// body. Body-streaming uploads through fileUpload can take
|
|
744
|
+
// minutes; this is the operator-overridable ceiling.
|
|
745
|
+
// keepAliveTimeout: 5s — idle timeout between requests on a
|
|
746
|
+
// keep-alive connection.
|
|
747
|
+
// server.timeout: 5min — hardware/network timeout (legacy).
|
|
748
|
+
server.headersTimeout = C.TIME.seconds(60);
|
|
749
|
+
server.requestTimeout = C.TIME.minutes(5);
|
|
750
|
+
server.keepAliveTimeout = C.TIME.seconds(5);
|
|
751
|
+
server.timeout = C.TIME.minutes(5);
|
|
714
752
|
return server;
|
|
715
753
|
}
|
|
716
754
|
}
|
package/lib/websocket.js
CHANGED
|
@@ -221,12 +221,17 @@ function negotiateSubprotocol(req, supported) {
|
|
|
221
221
|
// origins shapes:
|
|
222
222
|
// array — strict allowlist, enforced
|
|
223
223
|
// "*" — explicit "accept all" (operator opt-in to no checking)
|
|
224
|
-
// null/undefined — same
|
|
225
|
-
//
|
|
226
|
-
//
|
|
227
|
-
//
|
|
224
|
+
// null/undefined — DEFAULT: same-origin (Origin host matches Host
|
|
225
|
+
// header). The pre-0.7.64 default was "accept all" —
|
|
226
|
+
// flipped here because cross-site WebSocket
|
|
227
|
+
// hijacking (CSWSH) is a real attacker capability
|
|
228
|
+
// against any browser-targeted WebSocket route, and
|
|
229
|
+
// same-origin is the safe default. Operators
|
|
230
|
+
// needing cross-origin opt in explicitly via
|
|
231
|
+
// `origins: "*"` (with audited reason) or
|
|
232
|
+
// `origins: [...allowlist]`.
|
|
228
233
|
function isOriginAllowed(req, origins) {
|
|
229
|
-
if (
|
|
234
|
+
if (origins === "*") return true;
|
|
230
235
|
var origin = (req.headers || {}).origin;
|
|
231
236
|
// Non-browser clients (curl, server-to-server, native apps) don't
|
|
232
237
|
// send Origin. Origin enforcement only meaningfully applies to
|
|
@@ -234,6 +239,17 @@ function isOriginAllowed(req, origins) {
|
|
|
234
239
|
// the operator's network ACL / auth middleware, not Origin.
|
|
235
240
|
if (!origin) return true;
|
|
236
241
|
if (Array.isArray(origins)) return origins.indexOf(origin) !== -1;
|
|
242
|
+
// Default: same-origin. Compare the Origin header's hostname against
|
|
243
|
+
// the Host header. Operators behind a TLS-terminating LB pass the
|
|
244
|
+
// canonical Host through (or set `origins: [...]` explicitly).
|
|
245
|
+
if (!origins) {
|
|
246
|
+
var host = (req.headers || {}).host;
|
|
247
|
+
if (!host) return false;
|
|
248
|
+
var originHost;
|
|
249
|
+
try { originHost = new URL(origin).host; } // allow:raw-new-url — comparing browser-supplied Origin header against Host; safeUrl.parse adds policy filtering that isn't appropriate for exact host comparison
|
|
250
|
+
catch (_e) { return false; }
|
|
251
|
+
return originHost === host;
|
|
252
|
+
}
|
|
237
253
|
return false;
|
|
238
254
|
}
|
|
239
255
|
|
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:186ac1cf-f222-4ea2-81b9-ad8a89f1849e",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-05-06T00:
|
|
8
|
+
"timestamp": "2026-05-06T00:58:07.750Z",
|
|
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.7.
|
|
22
|
+
"bom-ref": "@blamejs/core@0.7.64",
|
|
23
23
|
"type": "library",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.7.
|
|
25
|
+
"version": "0.7.64",
|
|
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.7.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/core@0.7.64",
|
|
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.7.
|
|
57
|
+
"ref": "@blamejs/core@0.7.64",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|