@blamejs/core 0.7.0 → 0.7.1
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/websocket.js +38 -6
- 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.7.x
|
|
10
10
|
|
|
11
|
+
- **0.7.1** (2026-05-04) — `b.websocket` route opts gain `handshakeGuid` to override the RFC 6455 §1.3 magic string used in the `Sec-WebSocket-Accept` derivation. Default stays `258EAFA5-E914-47DA-95CA-C5AB0DC85B11`. Operators with closed-ecosystem clients running their own GUID (typical for migrations from frameworks that customize the handshake to namespace their own client family) drop `handshakeGuid: "<their-uuid>"` into the `router.ws(path, handler, opts)` opts and clients keep working unchanged. Throws at upgrade time if the override is malformed — UUID-shape regex with a 64-char length cap before the regex test, so a typo produces a clear error instead of silently producing a `Sec-WebSocket-Accept` the client can't match. **Tests** — 3 new layer-0 assertions in `test/00-primitives.js` (custom GUID produces different accept key, empty / null falls back to RFC default, malformed handshakeGuid rejected at config time). Smoke 7338 → 7341 / wiki e2e 178 / Linux container smoke 7341 (145s) / eslint clean / shellcheck clean.
|
|
12
|
+
|
|
11
13
|
- **0.7.0** (2026-05-04) — Codebase-patterns hardening sweep + primitive consolidation. The duplicate-block detector in `test/layer-0-primitives/codebase-patterns.test.js` ran at MIN_DISTINCT_FILES=3 and surfaced ~50 inline-shape clusters that had proliferated across lib/ — the kind of soft drift that's invisible at higher thresholds and hides re-introduction of bug classes the framework already swept once. **New primitive families** consolidated those clusters down: opts validation (`validateOpts.requireObject` / `auditShape` / `observabilityShape` / `applyDefaults` / `optional{Boolean,Function,PositiveInt,FiniteNonNegative,PositiveFinite,NonEmptyString}` / `requireNonEmptyString` / `makeAuditEmitter`), async coordination (`safeAsync.safeInvoke` / `makeDropCallback` / `makeScheduledFlush`), and SQL execution (`dbSchema.runInTransaction` / `runSqlOnHandle`). Plus `numericBounds.requireXFiniteIntIfPresent` for opt-time numeric-shape gates that throw via the caller's framework-error class, `log.makeViaOrFallback` for operator-log routing with per-module fallback, `observability.safeEvent` for hot-path drop-silent emission, `safeBuffer.HEX_RE`/`CRLF_RE`/`TRAILING_HSPACE_RE` regex constants + `isHex` / `hasCrlf` / `stripCrlf` / `stripTrailingHspace` helpers, `time.toIso8601NoMs`, `migrationFiles.MIGRATION_FILE_RE` / `isMigrationFileName` (shared filename grammar across migrations / seeders / external-db-migrate), and `lib/object-store/http-request.js` as a shared HTTP request helper across azure-blob / gcs / sigv4 / http-put. Roughly 80 inline call sites across 50+ files refactored to route through the new primitives; ~1500 lines of duplicate inline shapes eliminated. **Catalog gate** — `KNOWN_ANTIPATTERNS` in the codebase-patterns test now has 24 entries firing at n=1, so future code re-introducing any of the registered inline shapes (even one new file) fails the gate immediately. Pre-v0.7.0 the duplicate-block detector required n>=3 to fire; the catalog closes the gap by registering each extracted primitive's inline shape so it can't drift back in. **Cluster allowlist** — `KNOWN_CLUSTERS` allowlist (n>=3 detector) has 25 entries with documented structural reasons (parser error class signatures don't fit the framework's `(code, message)` contract; framework-convention shapes like middleware factories; future consolidation candidates). **Cleanup** — deleted dead re-export shims `lib/object-store/retry.js` (re-exported `lib/retry.js`) and `lib/auth/totp.js` (re-exported `lib/totp.js`); both fit the rule "pre-v1 frameworks have no operators to compatibly upgrade — every legacy fallback is dead code." Renamed `lib/internal-sha1-hibp.js` → `lib/framework-sha1-hibp.js` to match the lib naming convention (the `internal-` prefix wasn't in the convention's five-bucket list; `framework-` is the canonical "restricted-use" bucket alongside `framework-error.js` / `framework-schema.js`). **No operator-facing API breakage** — `b.objectStoreRetry` was removed from the public surface (operators use `b.retry` directly, which has been the canonical primitive since v0.2.24). **Eslint config tightened** — added `eqeqeq` (with the `null` exception for the `== null` null-or-undefined idiom), `no-throw-literal`, `no-promise-executor-return`, `default-case`, `no-loss-of-precision`. The previous config was hiding 11 real errors: 8 sites of `return resolve()` / `return done()` inside `new Promise(executor)` (return value silently discarded) across app / dev / http-client / mail / router / wiki/integration / 00-primitives, plus a missing default-case + 2 intentional template-language `==` / `!=` operators in template.js's binary-op evaluator (now allowed via inline `// eslint-disable-next-line eqeqeq -- template language operator`). Also adds `structuredClone` to the Node-globals list so db.js's deep-clone calls don't trip `no-undef`. **Release-workflow gate added** — Linux container smoke `docker run --rm -v "/$(pwd):/blamejs" -w //blamejs node:24-alpine node test/smoke.js` is now part of the release flow. Catches lingering-handle bugs that pass on Windows / macOS but hang or error on Linux CI. **Tests** — smoke 7338 / Linux container smoke 7338 (148s) / wiki e2e 178 / per-primitive integration 16 files / wiki integration green / eslint clean / shellcheck clean.
|
|
12
14
|
|
|
13
15
|
## v0.6.x
|
package/lib/websocket.js
CHANGED
|
@@ -94,9 +94,20 @@ var { boot } = require("./log");
|
|
|
94
94
|
var HTTP = requestHelpers.HTTP_STATUS;
|
|
95
95
|
var log = boot("websocket");
|
|
96
96
|
|
|
97
|
-
// RFC 6455 §1.3
|
|
97
|
+
// RFC 6455 §1.3 — the standard handshake GUID. Operators running
|
|
98
|
+
// closed-ecosystem clients with a custom magic string pass their own
|
|
99
|
+
// via opts.handshakeGuid on the route; the framework's default stays
|
|
100
|
+
// the RFC value so RFC-compliant clients work out of the box.
|
|
98
101
|
var GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
|
99
102
|
|
|
103
|
+
// UUID-shape (8-4-4-4-12 hex) for opts.handshakeGuid validation. The
|
|
104
|
+
// SHA-1 used in the handshake is NOT a security primitive (RFC 6455
|
|
105
|
+
// requires it as a protocol marker), so the GUID itself doesn't need
|
|
106
|
+
// to be cryptographically random — but it must match the client's
|
|
107
|
+
// expected value byte-for-byte. Length + format check at config time
|
|
108
|
+
// catches the typo class.
|
|
109
|
+
var GUID_RE = /^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$/;
|
|
110
|
+
|
|
100
111
|
var OPCODE_CONTINUATION = 0x0;
|
|
101
112
|
var OPCODE_TEXT = 0x1;
|
|
102
113
|
var OPCODE_BINARY = 0x2;
|
|
@@ -165,11 +176,13 @@ class WebSocketError extends FrameworkError {
|
|
|
165
176
|
|
|
166
177
|
// ---- Handshake helpers ----
|
|
167
178
|
|
|
168
|
-
function computeAcceptKey(secWebSocketKey) {
|
|
179
|
+
function computeAcceptKey(secWebSocketKey, handshakeGuid) {
|
|
169
180
|
// SHA-1 required by RFC 6455 §1.3 — see file-level note 2 above.
|
|
170
181
|
// This is a protocol marker, not a security primitive.
|
|
182
|
+
// handshakeGuid defaults to the RFC value; operators with custom
|
|
183
|
+
// closed-ecosystem clients override per-route via opts.handshakeGuid.
|
|
171
184
|
var hash = nodeCrypto.createHash("sha1");
|
|
172
|
-
hash.update(String(secWebSocketKey) + GUID);
|
|
185
|
+
hash.update(String(secWebSocketKey) + (handshakeGuid || GUID));
|
|
173
186
|
return hash.digest("base64");
|
|
174
187
|
}
|
|
175
188
|
|
|
@@ -224,12 +237,12 @@ function isOriginAllowed(req, origins) {
|
|
|
224
237
|
return false;
|
|
225
238
|
}
|
|
226
239
|
|
|
227
|
-
function buildUpgradeResponse(secWebSocketKey, subprotocol, extensionHeader) {
|
|
240
|
+
function buildUpgradeResponse(secWebSocketKey, subprotocol, extensionHeader, handshakeGuid) {
|
|
228
241
|
var lines = [
|
|
229
242
|
"HTTP/1.1 101 Switching Protocols",
|
|
230
243
|
"Upgrade: websocket",
|
|
231
244
|
"Connection: Upgrade",
|
|
232
|
-
"Sec-WebSocket-Accept: " + computeAcceptKey(secWebSocketKey),
|
|
245
|
+
"Sec-WebSocket-Accept: " + computeAcceptKey(secWebSocketKey, handshakeGuid),
|
|
233
246
|
];
|
|
234
247
|
if (subprotocol) lines.push("Sec-WebSocket-Protocol: " + subprotocol);
|
|
235
248
|
if (extensionHeader) lines.push("Sec-WebSocket-Extensions: " + extensionHeader);
|
|
@@ -834,6 +847,25 @@ class WebSocketConnection extends EventEmitter {
|
|
|
834
847
|
function handleUpgrade(req, socket, head, opts) {
|
|
835
848
|
opts = opts || {};
|
|
836
849
|
|
|
850
|
+
// Throw-at-config-time on the optional GUID override. A typo here
|
|
851
|
+
// would produce a Sec-WebSocket-Accept the client can't match,
|
|
852
|
+
// breaking the upgrade in a way that's hard to diagnose; the format
|
|
853
|
+
// check at the top of handleUpgrade catches it loudly. Empty /
|
|
854
|
+
// undefined falls through to the RFC default in computeAcceptKey.
|
|
855
|
+
var GUID_MAX_LENGTH = C.BYTES.bytes(64); // allow:raw-byte-literal — UUID is 36 chars; 64 is a tolerant upper bound for the regex engine.
|
|
856
|
+
if (opts.handshakeGuid !== undefined && opts.handshakeGuid !== null) {
|
|
857
|
+
// Length cap before the regex test — UUIDs are exactly 36 chars so
|
|
858
|
+
// a > GUID_MAX_LENGTH input never matches the format and shouldn't
|
|
859
|
+
// reach the regex engine. Bounds the engine on hostile input
|
|
860
|
+
// regardless of the GUID_RE shape.
|
|
861
|
+
if (typeof opts.handshakeGuid !== "string" ||
|
|
862
|
+
opts.handshakeGuid.length > GUID_MAX_LENGTH ||
|
|
863
|
+
!GUID_RE.test(opts.handshakeGuid)) {
|
|
864
|
+
throw new Error("websocket.handleUpgrade: handshakeGuid must be a UUID-shaped string (8-4-4-4-12 hex with dashes), got " +
|
|
865
|
+
JSON.stringify(opts.handshakeGuid));
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
|
|
837
869
|
// Validate handshake first — refusing here writes a plain HTTP/1.1
|
|
838
870
|
// response and closes the socket, matching what the upgrade-event
|
|
839
871
|
// consumer would expect for a malformed request.
|
|
@@ -865,7 +897,7 @@ function handleUpgrade(req, socket, head, opts) {
|
|
|
865
897
|
try {
|
|
866
898
|
socket.write(buildUpgradeResponse(
|
|
867
899
|
req.headers["sec-websocket-key"], subprotocol,
|
|
868
|
-
pmd ? pmd.responseHeader : null));
|
|
900
|
+
pmd ? pmd.responseHeader : null, opts.handshakeGuid));
|
|
869
901
|
} catch (err) {
|
|
870
902
|
log.error("failed to write upgrade response: " + err.message);
|
|
871
903
|
try { socket.destroy(); } catch (_e) { /* socket already destroyed */ }
|
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:818ba232-b5b3-42aa-9167-55dbae7228f0",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-05-
|
|
8
|
+
"timestamp": "2026-05-04T03:26:40.519Z",
|
|
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.1",
|
|
23
23
|
"type": "library",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.7.
|
|
25
|
+
"version": "0.7.1",
|
|
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.1",
|
|
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.1",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|