@blamejs/core 0.18.10 → 0.18.11

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 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.11 (2026-08-03) — **A static file that is a symlink at the final path component now fails closed with a clean 404 instead of an uncaught error; broader DNS, field-crypto, and static-server coverage.** The static server refuses a symlink at the final path component (its O_NOFOLLOW TOCTOU defense) — but that refusal now falls back to a clean 404 / NOT_FOUND rather than propagating an uncaught error that hung the request or surfaced a raw ELOOP from staticServe.integrity(). Alongside the fix, error and edge branches of b.network.dns, b.cryptoField, and b.staticServe come under explicit test. **Changed:** *Error- and edge-path tests for the DNS, field-crypto, and static-server primitives* — b.network.dns (DoH oversized-body handling, DoT mid-query connection reset and socket reuse, cache expiry, system-resolver error paths), b.cryptoField (tampered / relocated / downgraded cell fail-closed nulling, key-row AAD and rowId resolution, unseal rate-cap configuration, on-read upgrade branches), and b.staticServe (integrity and confinement edge cases, idle-timeout teardown, the final-component symlink refusal above) now have explicit tests for their boundary branches. An unused internal date-formatting helper was removed. **Fixed:** *A final-component symlink (or a mid-hash read error) on a static file fails closed with a clean 404* — When computing a served file's SRI and ETag, the static server opens it with O_NOFOLLOW so a symlink swapped in at the final path component after the in-root check cannot be followed (a TOCTOU / CWE-22 / CWE-367 defense). On POSIX that open raises ELOOP for a symlinked target — the common atomic-deploy pattern where a stable name links to a hashed file (latest.js pointing at app.<hash>.js). That error, and any read error mid-hash, previously propagated uncaught: the middleware request hung with no response, and staticServe.integrity() threw a raw ELOOP instead of its documented NOT_FOUND. It now falls back exactly as a read error does — the middleware returns 404 (falls through) and integrity() throws NOT_FOUND. The symlink is still not served; the refusal is just clean. Windows is unaffected (O_NOFOLLOW is a no-op there).
12
+
11
13
  - v0.18.10 (2026-08-03) — **Encrypted-at-rest boot fails closed on a truncated db.enc instead of silently starting fresh over it; broader SAML and database test coverage.** A database running encrypted at rest whose db.enc snapshot is truncated below a valid envelope (an interrupted external copy or backup restore, a full tmpfs, filesystem corruption) no longer boots onto a fresh empty database that the next flush would persist over the corrupt copy — it now fails closed so the operator can restore from backup. Alongside the fix, adversarial and edge branches of b.auth.saml and b.db come under explicit test, and the vendored @blamejs/pki toolkit is refreshed. **Changed:** *Verification- and edge-path tests for the SAML SP and database primitives* — b.auth.saml (the XML-signature Reference handling with no URI or no Transforms, a foreign-namespace Conditions element failing closed to no-audience-restriction, RSA-OAEP SHA-384/SHA-512 key transport with AES-128-GCM assertion decryption, ed25519 sign/verify dispatch, and the single-logout build/parse paths) and b.db (encrypted-boot key and envelope fallbacks, the truncated-snapshot refusal above, flush-to-disk after close, the audit-chain rollback check against a non-empty log, and the boot-time NTP check thresholds) now have explicit tests for their boundary branches. · *Vendored @blamejs/pki refreshed to 0.3.29* — The vendored @blamejs/pki toolkit that backs the mutual-TLS CA engine (X.509 / CRL / PKCS#12 signing) is refreshed from 0.3.28 to 0.3.29. The upstream change is in that toolkit's ACME client and Retry-After parser, which the CA engine does not call, so there is no behaviour change to the certificate-authority surface — this is a supply-chain currency refresh. **Fixed:** *Encrypted boot fails closed on a truncated db.enc rather than starting fresh over it* — When a database is encrypted at rest and its durable db.enc snapshot exists but is too short to be a valid AEAD envelope — a truncation from an interrupted external copy or backup restore, a full tmpfs, or filesystem corruption — boot silently opened a fresh empty database. The next flush or close then persisted that empty database over the corrupt-but-present snapshot, masking the corruption and destroying the operator's chance to recover. Boot now fails closed with db/enc-truncated; restore db.enc from backup, or remove it to intentionally start empty. A valid snapshot is always written atomically and is never this short, so no healthy deployment is affected. **Detectors:** *Truncated db.enc must fail closed, not start fresh* — A source guard keeps the encrypted-boot path throwing on an under-length db.enc rather than returning to open a fresh database — so a truncated durable snapshot can never again be silently overwritten by the next flush.
12
14
 
13
15
  - 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.
package/lib/static.js CHANGED
@@ -243,21 +243,31 @@ async function _readMeta(root, candidate) {
243
243
  // chunk avoids re-reading the file.
244
244
  var sri = nodeCrypto.createHash("sha384");
245
245
  var sha3 = nodeCrypto.createHash("sha3-512");
246
- await new Promise(function (resolve, reject) {
247
- // The path is the confined output of `_assertInsideRoot(root, candidate)`
248
- // above (lexical resolve + root-prefix containment). Open it O_NOFOLLOW and
249
- // stream from that fd: lexical confinement still leaves a window where a
250
- // symlink swapped in at the final component after the check would be
251
- // followed by a plain createReadStream (CWE-22 / CWE-367); O_NOFOLLOW refuses
252
- // it. The open throws synchronously on a symlink (ELOOP) / missing file
253
- // reject the hash promise so the caller falls back exactly as on a read error.
254
- var s;
255
- try { s = nodeFs.createReadStream(absPath, { fd: atomicFile.openNoFollowSync(absPath) }); }
256
- catch (e) { reject(e); return; }
257
- s.on("data", function (chunk) { sri.update(chunk); sha3.update(chunk); });
258
- s.on("end", resolve);
259
- s.on("error", reject);
260
- });
246
+ try {
247
+ await new Promise(function (resolve, reject) {
248
+ // The path is the confined output of `_assertInsideRoot(root, candidate)`
249
+ // above (lexical resolve + root-prefix containment). Open it O_NOFOLLOW and
250
+ // stream from that fd: lexical confinement still leaves a window where a
251
+ // symlink swapped in at the final component after the check would be
252
+ // followed by a plain createReadStream (CWE-22 / CWE-367); O_NOFOLLOW refuses
253
+ // it. The open throws synchronously on a symlink (ELOOP) / missing file, and
254
+ // a read I/O error rejects mid-stream.
255
+ var s;
256
+ try { s = nodeFs.createReadStream(absPath, { fd: atomicFile.openNoFollowSync(absPath) }); }
257
+ catch (e) { reject(e); return; }
258
+ s.on("data", function (chunk) { sri.update(chunk); sha3.update(chunk); });
259
+ s.on("end", resolve);
260
+ s.on("error", reject);
261
+ });
262
+ } catch (_hashErr) {
263
+ // Fall back exactly as on the fsp.stat failure above: return null so the
264
+ // caller 404s / falls through. Without this the O_NOFOLLOW ELOOP (a
265
+ // final-component symlink — the atomic-deploy `latest.js -> app.<hash>.js`
266
+ // pattern on POSIX) or a mid-stream read error would propagate as an
267
+ // uncaught rejection — hanging the request from the middleware, or surfacing
268
+ // a raw ELOOP from integrity() instead of the documented NOT_FOUND.
269
+ return null;
270
+ }
261
271
  var sriDigest = sri.digest("base64");
262
272
  var sha3Hex = sha3.digest("hex");
263
273
 
@@ -512,10 +522,6 @@ function _parseRangeHeader(header, size) {
512
522
  return { start: start, end: end, length: end - start + 1 };
513
523
  }
514
524
 
515
- function _httpDate(date) {
516
- return (date instanceof Date ? date : new Date(date)).toUTCString();
517
- }
518
-
519
525
  function _validateCreateOpts(opts) {
520
526
  // Declarative per-field validation. shape() runs requireObject(opts)
521
527
  // itself (same "opts must be an object" / BAD_OPT contract the inline
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/core",
3
- "version": "0.18.10",
3
+ "version": "0.18.11",
4
4
  "description": "The Node framework that owns its stack.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "blamejs contributors",
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:52e564cd-e4bc-440f-bd6c-a12a68c4102e",
5
+ "serialNumber": "urn:uuid:64a46779-1870-427d-919a-0e19529a6497",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-08-03T20:26:23.786Z",
8
+ "timestamp": "2026-08-03T22:09:50.815Z",
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.10",
22
+ "bom-ref": "@blamejs/core@0.18.11",
23
23
  "type": "application",
24
24
  "name": "blamejs",
25
- "version": "0.18.10",
25
+ "version": "0.18.11",
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.10",
29
+ "purl": "pkg:npm/%40blamejs/core@0.18.11",
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.10",
57
+ "ref": "@blamejs/core@0.18.11",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]