@blamejs/core 0.18.8 → 0.18.9
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/http-client.js +9 -12
- package/package.json +1 -1
- package/sbom.cdx.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.18.x
|
|
10
10
|
|
|
11
|
+
- v0.18.9 (2026-08-02) — **Broader error- and edge-path test coverage for the CLI, mail, and HTTP-client primitives.** Error, edge, and fallback branches of b.cli, b.mail, and b.httpClient are now under explicit test. No behaviour changes: exercising every path confirmed each already behaves per its documented contract. One dead internal parameter on the HTTP-client cache path — never supplied by any caller — was removed. **Changed:** *Error- and edge-path tests for the CLI, mail, and HTTP-client primitives* — b.cli (the api-snapshot capture/compare load-error paths and the restore-apply round trip), b.mail (reverse-DNS forward-confirmation and failure fallbacks, SMTP UTF-8 and binary-MIME detection on edge inputs, AUTH LOGIN with an empty password, and audit-emit failures being swallowed without masking the delivery outcome), and b.httpClient (the HTTPS-connect failure classification and the HTTP/2 synchronous-request-throw path) now have explicit tests for their boundary branches. Behaviour is unchanged.
|
|
12
|
+
|
|
11
13
|
- v0.18.8 (2026-08-02) — **SSRF-safe local-transport HTTP, timestamped-HMAC webhook verification, the OAuth client-credentials grant, trusted-proxy identity headers, and a public keyed-HMAC primitive; plus a fix for a filterless overflow update that could rewrite every row.** Five general-purpose primitives land: talking HTTP to a co-located daemon over a socket the SSRF gate cannot protect, verifying the single-header timestamped-HMAC webhook scheme, the machine-to-machine OAuth client_credentials grant with a cached auto-renewing token manager, and peer-gated trust of a reverse proxy's identity headers. b.crypto.hmac exposes the framework's keyed HMAC (SHA3-512 by default; weaker digests available explicitly for external-scheme interop) and replaces b.crypto.hmacSha3. Separately, an update() on a collection whose field lives in the overflow store, called with no filter, no longer slips past the unconditional-write guard and silently rewrites every row. **Added:** *b.localHttp — SSRF-safe HTTP over a local transport* — b.localHttp.create({ socketPath }) and b.localHttp.request() speak HTTP to a co-located daemon over a Unix domain socket, a Windows named pipe, or a loopback TCP peer with a bearer token — the transports an SSRF gate cannot protect, because there is no network host to steer. It deliberately does not route through b.httpClient (DNS + the SSRF gate): a socket-path request cannot be pointed at an arbitrary IP, so that surface is exactly what it exists to bypass safely. It refuses any non-loopback TCP host, never forwards Origin or Referer to the daemon, bounds the response body, and sets the caller's Host header verbatim. Use it for a local control-plane API — a sidecar's endpoint, a container-runtime socket, a metrics port. · *b.webhookHmac.verify — timestamped-HMAC webhook verification* — Verifies the single-header timestamped-HMAC webhook scheme — t=<unix-seconds>,v1=<hmac-hex> signed over <timestamp>.<raw-body> — used by Stripe, Tailscale, and others. It refuses a timestamp outside the tolerance window (replay defense), checks every v1 value so a rotated signing secret verifies with no downtime, compares in constant time, and ignores signature versions it does not recognize. Pass the exact bytes received, not a parsed-then-re-serialized JSON body, or the HMAC will not reproduce. This is distinct from b.standardWebhooks, which verifies the three-header StandardWebhooks scheme. · *b.auth.oauth.clientCredentials + clientCredentialsManager — the client_credentials grant* — The RFC 6749 §4.4 client_credentials grant for machine-to-machine authentication with no user present: clientCredentials() POSTs grant_type=client_credentials and returns the access token — never a refresh token, per the RFC. It authenticates with the client id/secret via client_secret_post (default) or client_secret_basic (tokenEndpointAuthMethod), sends no scope unless you supply one (the authorization-code scope default is not applied to a machine-to-machine request), and a client_credentials-only client needs no redirectUri at create. clientCredentialsManager() wraps it in an in-memory cache that fetches on first use, re-fetches a configurable window before expiry, collapses concurrent callers onto one in-flight request, and — throughout a 429 backoff window (honoring the server's Retry-After when present) — serves the still-valid cached token or fails fast without another network call, rather than hammering the authorization server. The client's tokenEndpointAuthMethod (client_secret_post / client_secret_basic) applies uniformly to every token-endpoint request, not just this grant. · *b.requestHelpers.trustedIdentityHeaders — peer-gated proxy identity headers* — Resolves an identity-injecting reverse proxy's header family — Cloudflare Access Cf-Access-*, oauth2-proxy X-Forwarded-User, Tailscale Serve Tailscale-User-*, or any custom mapping — under the same trusted-proxy peer-gate as b.requestHelpers.trustedClientIp. It is the identity-header mirror of the X-Forwarded-For discipline: the family is trusted only when the immediate socket peer is a configured trusted proxy; from any other peer the headers are stripped from req.headers so downstream code cannot read a forged identity. With no gate configured it fails closed and always strips. A naive trust of these headers is a full impersonation bypass; this closes it by construction rather than opening a second, looser trust path. · *b.crypto.hmac — a public keyed-HMAC primitive* — b.crypto.hmac(key, data, algorithm = "sha3-512") exposes the framework's keyed HMAC with a PQC-first SHA3-512 default. Weaker digests (sha256, sha384, sha512, sha3-256, sha3-384) are available by naming them explicitly, for interop with an external scheme that pins one — a provider's SHA-256 webhook signature, for example; SHA-1 and MD5 are refused. Key and data accept a Buffer or a string; the result is a lowercase hex digest. **Removed:** *b.crypto.hmacSha3 (folded into b.crypto.hmac)* — b.crypto.hmacSha3 is removed. b.crypto.hmac already defaults to SHA3-512, so replace b.crypto.hmacSha3(key, data) with b.crypto.hmac(key, data) — the same algorithm and the same output. **Fixed:** *A filterless update on an overflow-backed collection no longer rewrites every row* — An update() on a collection whose target field lives in the overflow (JSON) store, called with no filter conditions — an empty or absent query — slipped past the unconditional-write guard that protects real-column updates and silently rewrote every row: the internal per-row writes each carry a WHERE by row id, so the guard never saw a match-everything update. Such an update is now refused with the same "call where(...) first" error as a real-column update. Provide an explicit filter for the rows you intend to change. **Detectors:** *Overflow-store update re-asserts the unconditional-write refusal* — A source guard keeps the overflow-store update path re-checking, on its read query, that a filter is present — so a filterless overflow update cannot regress to silently rewriting every row.
|
|
12
14
|
|
|
13
15
|
- v0.18.7 (2026-08-02) — **STARTTLS idle-timeout no longer injects plaintext into the encrypted channel; broader mail-server test coverage.** A STARTTLS upgrade on the POP3 / IMAP / ManageSieve / SMTP-submission / MX servers left the pre-upgrade plain-socket idle-timeout handler armed, so a post-handshake idle wrote a plaintext timeout reply into the now-encrypted stream (the peer saw a TLS decode error / reset). The shared socket-upgrade path now strips that handler so the idle timeout is delivered encrypted. Alongside the fix, error and edge branches of b.mail.server.pop3, b.mail.server.imap, and b.mail.auth are now under test. Except for the fix, behaviour is unchanged. **Changed:** *Error- and edge-path tests for the POP3, IMAP, and mail-authentication primitives* — b.mail.server.pop3 (the STLS upgrade and post-handshake idle timeout, pipelined-command draining, AUTH / APOP / USER-PASS branches, and store-fallback and tenant-refusal shapes), b.mail.server.imap (APPEND / CATENATE literal handling, command-dispatch and guard-error branches, and the LOGIN / NOTIFY paths), and b.mail.auth (SPF macro expansion and record parsing, iprev forward-confirm, and dual-CIDR a/mx parsing) now have explicit tests for their boundary paths. **Security:** *STARTTLS idle timeout is delivered over the encrypted channel, not injected as plaintext* — The POP3, IMAP, ManageSieve, SMTP-submission, and MX servers arm a plain-socket idle timer whose handler writes a plaintext idle-timeout reply and closes the connection. After a STARTTLS upgrade the shared socket-upgrade path stripped only the plain socket's data listeners, so that plaintext handler survived the wrap and, on a post-handshake idle, injected cleartext into the now-encrypted stream — the peer observed a TLS decode error or connection reset instead of a clean encrypted timeout reply. The upgrade path now strips the plain socket's timeout listeners and disarms its idle timer, so the upgraded TLS socket owns the idle timeout and replies over the encrypted channel. **Detectors:** *STARTTLS upgrade strips the plaintext idle-timeout handler* — A source guard keeps the shared socket-upgrade path stripping the plain socket's timeout listeners alongside its data listeners, so a plaintext idle-timeout reply cannot survive into the encrypted channel.
|
package/lib/http-client.js
CHANGED
|
@@ -1080,19 +1080,16 @@ function _runWithCache(opts, maxRedirects, runAfter) {
|
|
|
1080
1080
|
try { got = cache._lookup(method, opts.url, requestHeaders); }
|
|
1081
1081
|
catch (_e) { got = null; }
|
|
1082
1082
|
|
|
1083
|
-
function _doNetwork(
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
headers: Object.assign({}, opts.headers || {}, extraReqHeaders),
|
|
1088
|
-
});
|
|
1089
|
-
}
|
|
1083
|
+
function _doNetwork() {
|
|
1084
|
+
// Conditional revalidation headers (If-None-Match / If-Modified-Since)
|
|
1085
|
+
// are built and applied on the separate _revalidate path, so a cache
|
|
1086
|
+
// miss / malformed-entry refetch issues the request opts verbatim.
|
|
1090
1087
|
if (maxRedirects === null || maxRedirects === 0) {
|
|
1091
|
-
return _requestSingle(
|
|
1092
|
-
return { finalOpts:
|
|
1088
|
+
return _requestSingle(opts).then(function (res) {
|
|
1089
|
+
return { finalOpts: opts, res: res };
|
|
1093
1090
|
});
|
|
1094
1091
|
}
|
|
1095
|
-
return _requestWithRedirects(
|
|
1092
|
+
return _requestWithRedirects(opts, maxRedirects);
|
|
1096
1093
|
}
|
|
1097
1094
|
|
|
1098
1095
|
// 2. Miss → network → maybe store.
|
|
@@ -1101,7 +1098,7 @@ function _runWithCache(opts, maxRedirects, runAfter) {
|
|
|
1101
1098
|
catch (_e) { /* drop-silent */ }
|
|
1102
1099
|
try { cache._obsEvent("httpclient.cache.miss", 1, { method: method }); }
|
|
1103
1100
|
catch (_e) { /* drop-silent */ }
|
|
1104
|
-
return _doNetwork(
|
|
1101
|
+
return _doNetwork().then(function (boxed) {
|
|
1105
1102
|
_maybeStore(cache, method, opts.url, requestHeaders, boxed.res);
|
|
1106
1103
|
return runAfter(boxed.finalOpts, _withCacheHeaders(boxed.res, "MISS", undefined, cache.statusHeader));
|
|
1107
1104
|
});
|
|
@@ -1115,7 +1112,7 @@ function _runWithCache(opts, maxRedirects, runAfter) {
|
|
|
1115
1112
|
// Malformed entry — drop it, treat as miss.
|
|
1116
1113
|
try { cache.invalidate(method, opts.url, requestHeaders); }
|
|
1117
1114
|
catch (_e2) { /* drop-silent */ }
|
|
1118
|
-
return _doNetwork(
|
|
1115
|
+
return _doNetwork().then(function (boxed) {
|
|
1119
1116
|
_maybeStore(cache, method, opts.url, requestHeaders, boxed.res);
|
|
1120
1117
|
return runAfter(boxed.finalOpts, _withCacheHeaders(boxed.res, "MISS", undefined, cache.statusHeader));
|
|
1121
1118
|
});
|
package/package.json
CHANGED
package/sbom.cdx.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:d511bc0a-e738-46e4-be3a-f731ed144487",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-08-
|
|
8
|
+
"timestamp": "2026-08-02T13:57:50.020Z",
|
|
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.18.
|
|
22
|
+
"bom-ref": "@blamejs/core@0.18.9",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.18.
|
|
25
|
+
"version": "0.18.9",
|
|
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.18.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/core@0.18.9",
|
|
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.18.
|
|
57
|
+
"ref": "@blamejs/core@0.18.9",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|