@blamejs/core 0.17.13 → 0.17.14

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.17.x
10
10
 
11
+ - v0.17.14 (2026-07-23) — **The framework's file watchers are hardened against an uncatchable process abort on Windows 8.3 short-name paths.** fs.watch aborts the whole Node process -- an uncatchable libuv assertion in the directory-change backend -- when the watched path is reached through a Windows 8.3 short-name component (the shape of a path under C:\Users\SOMEUS~1\... or a short-named temp directory). The recursive file watcher already expanded the short name before watching; this closes the same gap everywhere else the framework watches a path: the feature-flag file watcher now stat-polls its config file instead of directory-watching it, and the dev-server and bundler watchers expand the short name before watching. Three structural detectors lock the invariant in so it cannot regress in a way a behavioral test cannot reach (the abort only fires on a real short-name path, which a host with 8.3 generation disabled cannot reproduce). **Changed:** *Public Suffix List refreshed to the current upstream snapshot* — The vendored Mozilla Public Suffix List is updated to the current upstream snapshot, so organizational-domain derivation reflects the latest registry delegations wherever the framework draws a registrable-domain boundary -- DMARC identifier alignment, BIMI issuer scoping, cookie-scope confinement, and same-site policy. The .data.js carrier is regenerated and re-signed across all four integrity layers. **Fixed:** *The feature-flag file watcher no longer aborts on a Windows short-name config path* — b.flags localFile provider with watch enabled directory-watched the config file via fs.watch, which aborts the process (uncatchable) when the config path resolves through a Windows 8.3 short-name component -- its surrounding try/catch cannot recover from a libuv abort(). It now stat-polls the file (StatWatcher opens no directory-change handle, so it is immune to the path form) and reloads on change with sub-second latency. · *The dev-server and bundler watchers expand a short-name path before watching* — b.dev and b.bundler watch a source directory or file via fs.watch; on Windows a path reached through an 8.3 short-name component aborts libuv. Both now resolve the real long-form path (realpathSync.native) before the watch, matching the guard the recursive b.watcher already applied. **Detectors:** *Filesystem-watch short-name safety and pid-liveness composition are enforced structurally* — Three codebase-patterns detectors close invariants a behavioral test cannot assert (the underlying abort is environment-specific to a real 8.3 short-name path): any lib fs.watch must expand the short name via realpathSync.native in the same file (or use the StatWatcher path instead); the daemon's Windows cooperative-stop sentinel must poll a synchronous existsSync rather than watch the filesystem; and a signal-0 process-liveness probe must compose the shared pid-probe classifier rather than re-roll process.kill(pid, 0) with its own EPERM/ESRCH interpretation.
12
+
11
13
  - v0.17.13 (2026-07-23) — **A cross-surface hardening sweep: the log pipeline redacts secrets embedded in a message string, the SSRF guard gains a hostname-aware loopback classifier, crypto gains an RFC 7469 SPKI pin, and a batch of cross-platform lifecycle bugs across the watcher, self-update, daemon, and WebSocket-client primitives are fixed.** The structured-log pipeline redacted metadata but shipped the free-text message verbatim, so a secret interpolated into a log message reached file and remote sinks unscrubbed; a new in-place free-text redactor closes that at the emit chokepoint and every sink. The SSRF guard grows a hostname-aware loopback classifier so consumers stop re-deriving the localhost/127.0.0.1/::1 triad, crypto grows an RFC 7469 SubjectPublicKeyInfo pin plus a pin-verifying checkServerIdentity builder, and the self-update verifier now accepts the same SHA3-512 / IEEE-P1363 ECDSA signatures its standalone counterpart does. Alongside: cross-platform lifecycle fixes in the recursive watcher (long-path stat, lost-root signalling, dead-handle classification, case-folded ignore matching), the self-updater (Windows in-use-binary replacement, asset-to-signature pairing, post-install probation), the daemon (detached-spawn failure reporting, a Windows cooperative-stop channel, a read-only status probe, a pid-lock adopt window), and the WebSocket client (a close during a slow initial dial no longer opens an orphan socket). **Added:** *New primitives across the watcher, self-update, daemon, crypto, SSRF, redaction, and object surfaces* — b.watcher exports its ignore-pattern length and wildcard caps so a consumer pre-filter aligns with what create() enforces. b.selfUpdate gains a post-install probation lifecycle (beginProbation / confirmHealthy / evaluateOnBoot) that distinguishes a clean stop from a crash and decides a next-boot rollback. b.daemon gains a read-only status() PID-probe and, on Windows, a cooperative stop-request channel so stop() can drain the graceful-shutdown orchestration instead of a hard terminate. b.crypto adds spkiPin / spkiPinVerifier (above). b.ssrfGuard adds isLoopbackHost / isExactLoopbackName (above). b.redact adds redactText (above). And b.safeObject.ownProp / ownSet provide one prototype-pollution-safe own-property get/set that the framework's interpolators compose instead of re-rolling a guarded read. **Changed:** *Public Suffix List refreshed to the current upstream snapshot* — The vendored Mozilla Public Suffix List is updated to the current upstream snapshot, so organizational-domain derivation reflects the latest registry delegations wherever the framework draws a registrable-domain boundary -- DMARC identifier alignment, BIMI issuer scoping, cookie-scope confinement, and same-site policy. The .data.js carrier is regenerated and re-signed across all four integrity layers. **Fixed:** *Recursive watcher: long paths, lost roots, dead handles, case-folded ignores* — b.watcher no longer misreads a change on a Windows path over MAX_PATH as a delete (the post-event stat is long-path-aware); a poll walk that can no longer read the ROOT surfaces a distinct fatal `watcher/root-lost` signal and keeps its prior snapshot instead of diffing an empty tree as a delete of every tracked entry; a native-handle error whose code leaves change detection permanently off is classified fatal (`watcher/handle-dead`) so a wrapper can recreate rather than treat it as transient; and an opt-in `ignoreCaseFold` folds the walk-prune to match a case-insensitive filesystem so an on-disk `Node_Modules` is pruned by a `node_modules/**` ignore. · *Self-update: Windows in-use-binary replacement and asset-to-signature pairing* — swap/rollback install by moving the existing target aside and placing the new bytes at the freed path, so a self-updating Windows daemon can replace its own running image (which the OS locks against an overwrite-in-place); the backup and rollback share the same relocation. And poll now pairs the returned signature to the returned asset by the detached-signature name relationship rather than two independent first-matches, failing closed when the pairing is ambiguous, so a release with several sidecars per binary can no longer hand a foreign signature to the verifier. · *Daemon: detached-spawn failure is reported, pid-lock adopts without a gap* — b.daemon.start's detached mode validated the child pid before writing the pidfile and now subscribes the child's async error so a failed spawn (a bad command) throws instead of returning success and writing an `undefined` pid. And appShutdown's pid-lock adopts an existing pidfile that already records the current process id (the detached parent-handoff) instead of unlinking and recreating it -- closing a window in which the pidfile briefly did not exist and a concurrent stop misread a running daemon as not-running. · *WebSocket client: a close during a slow initial dial opens no orphan socket* — b.wsClient.connect ran its post-SSRF-resolve dial continuation even when close() was called during the (asynchronous, over-a-second) SSRF re-resolve, opening a socket and heartbeat nobody owned. The reconnect path already re-checked the closed flag after its await; the initial dial now does too (guarded at the shared dial funnel), and a close() while still connecting retires immediately rather than leaving the flag unset across the graceful-close window. · *crypto hash error preserves its errno; PQC agent mirrors the curve into TLS groups* — b.crypto.hashFilesParallel now preserves the underlying fs error's `code` / `errno` / `path` on the pre-open stat failure (and gives the symlink-refusal, non-regular-file, and size-cap rejections stable codes) so a caller can tell a benign save-then-delete race from a real read failure without string-matching a message. And b.pqcAgent.create mirrors a caller's `ecdhCurve` into the TLS `groups` option, so narrowing or reordering the curve preference is actually advertised on builds that honor `groups`. · *Reserved-word identifier parity for operator-supplied table and column names* — Several sites that quote an operator- or schema-supplied identifier -- CSV export, the outbox and webhook table-name guards, the DSR ticket store, the Postgres row-level-security emitter, and the view declarator -- rejected a schema-valid name that collides with a SQL keyword, stricter than b.db.from() which permits it. Those sites now match db.from()'s parity (still failing closed on shape, length, null bytes, and the reserved table prefix). **Security:** *The log pipeline redacts secrets embedded in a message string* — b.logStream.emit ran the redactor over structured `meta` but wrote the free-text `message` verbatim, so a credential interpolated into a log line -- a JWT, an AWS access key, a URL-embedded password -- reached file, webhook, syslog, CloudWatch, and OTLP sinks unscrubbed. And the built-in value detectors are whole-value anchored, so even redacting the message with the structured redactor would miss a token embedded mid-sentence. A new `b.redact.redactText` scrubs credentials in place with word-boundary matching (PEM blocks, JWTs, AWS keys, URL-userinfo passwords, bearer tokens, key=secret assignments, SSN/EIN, Luhn-valid PANs) while keeping the surrounding message, deliberately excluding the high-entropy shape detector that would eat ordinary identifiers in prose. It is applied at the emit chokepoint (covering every sink) and at the OTLP encoders' direct path, and is drop-safe on the hot path. · *The SSRF guard gains a hostname-aware loopback classifier* — b.ssrfGuard.classify recognized only IP literals, so `localhost`, `*.localhost`, and bracketed IPv6 forms were not classified as loopback and every consumer re-derived the strip-brackets / strip-trailing-dot / special-case triad by hand. `b.ssrfGuard.isLoopbackHost` composes canonicalization plus classification plus the RFC 6761 reserved names (`localhost` and `*.localhost`); a companion `b.ssrfGuard.isExactLoopbackName` omits the subdomain reservation for the OAuth loopback-redirect exception, which must accept only the exact `localhost` name and never widen to a `*.localhost` an attacker could register. Neither resolves DNS. · *Crypto gains an RFC 7469 SPKI pin and a pin-verifying identity check* — There was no primitive producing the HPKP / RFC 7469 pin -- the base64 SHA-256 over a certificate's SubjectPublicKeyInfo -- so consumers hand-derived it at both the verify and the report site, where drift mints a never-matching pin. `b.crypto.spkiPin` returns that pin (composing the certificate's SPKI export), and `b.crypto.spkiPinVerifier` returns a checkServerIdentity-compatible function that runs the RFC 9525 hostname check first, then constant-time-compares the peer's SPKI pin against a supplied set (requiring the RFC 7469 backup pin). The pin binds the certificate's long-term key, not the ephemeral post-quantum key-exchange group. · *The self-update verifier accepts the release-signing ECDSA form* — b.selfUpdate.verify delegated to an algorithm-agnostic verify that, for an EC P-384 key, pinned neither the SHA3-512 digest nor the raw IEEE-P1363 signature encoding the release pipeline emits -- so a sidecar signed the way the standalone verifier and install script expect only verified through that standalone path, even inside a fully-installed framework. verify now routes through the same streaming SHA3-512 / DER-or-P1363 verifier, collapsing the two into one acceptance set and dropping the whole-asset in-memory buffer.
12
14
 
13
15
  - v0.17.12 (2026-07-22) — **The vendored Public Suffix List is refreshed to the current upstream snapshot.** The vendored Mozilla Public Suffix List is updated to the current upstream build, so organizational-domain derivation for DMARC identifier alignment, BIMI issuer scoping, cookie-scope confinement, and same-site policy reflects the current registry delegations. The data module is regenerated and re-signed across all four integrity layers, the manifest hashes are refreshed, and the NOTICE attribution date is updated. **Security:** *Public Suffix List refreshed to the current upstream snapshot* — The vendored Mozilla Public Suffix List is updated to the current upstream snapshot, so organizational-domain derivation reflects the current registry delegations everywhere the framework draws a registrable-domain boundary -- DMARC identifier alignment, BIMI issuer scoping, cookie-scope confinement, and same-site policy. The precise upstream commit and version are recorded in the signed data file and the vendor manifest. The .data.js carrier is regenerated and re-signed across all four integrity layers (SHA-256 + SHA3-512 + SLH-DSA-SHAKE-256f signature + in-payload canary), the manifest hashes are refreshed, and the NOTICE attribution date is updated.
package/lib/bundler.js CHANGED
@@ -276,7 +276,11 @@ function create(opts) {
276
276
 
277
277
  // Test seam: tests pass a fake watcher so we don't actually nodeFs.watch
278
278
  var watchFn = opts._watch || function (dirOrFile, wopts, listener) {
279
- return nodeFs.watch(dirOrFile, wopts, listener);
279
+ // realpathSync.native expands an 8.3 short-name component before the watch;
280
+ // fs.watch aborts libuv (uncatchable) on a Windows short-name path.
281
+ var target = dirOrFile;
282
+ try { target = nodeFs.realpathSync.native(dirOrFile); } catch (_e) { /* keep raw path */ }
283
+ return nodeFs.watch(target, wopts, listener);
280
284
  };
281
285
  var setTimeoutFn = opts._setTimeout || setTimeout;
282
286
  var clearTimeoutFn = opts._clearTimeout || clearTimeout;
package/lib/dev.js CHANGED
@@ -200,7 +200,11 @@ function create(opts) {
200
200
  return childProcess().spawn(cmd, sargs, sopts);
201
201
  };
202
202
  var watchFn = opts._watch || function (dir, wopts, listener) {
203
- return nodeFs.watch(dir, wopts, listener);
203
+ // realpathSync.native expands an 8.3 short-name component before the watch;
204
+ // fs.watch aborts libuv (uncatchable) on a Windows short-name path.
205
+ var target = dir;
206
+ try { target = nodeFs.realpathSync.native(dir); } catch (_e) { /* keep raw path */ }
207
+ return nodeFs.watch(target, wopts, listener);
204
208
  };
205
209
  var setTimeoutFn = opts._setTimeout || setTimeout;
206
210
  var clearTimeoutFn = opts._clearTimeout || clearTimeout;
@@ -127,7 +127,7 @@ function memory(opts) {
127
127
 
128
128
  function localFile(opts) {
129
129
  opts = opts || {};
130
- validateOpts(opts, ["path", "watch"], "flag.providers.localFile");
130
+ validateOpts(opts, ["path", "watch", "_watch"], "flag.providers.localFile");
131
131
  validateOpts.requireNonEmptyString(opts.path,
132
132
  "providers.localFile: path", FlagError, "flag/bad-provider");
133
133
  var raw;
@@ -157,22 +157,34 @@ function localFile(opts) {
157
157
  var provider = _makeProvider("local-file", parsed.flags);
158
158
  provider._path = opts.path;
159
159
  if (opts.watch === true) {
160
- try {
161
- nodeFs.watch(opts.path, { persistent: false }, function () {
162
- try {
163
- var nextRaw = atomicFile.fdSafeReadSync(opts.path, { maxBytes: C.BYTES.mib(1), encoding: "utf8" });
164
- var nextParsed = safeJson.parse(nextRaw, { maxBytes: C.BYTES.mib(1) });
165
- if (nextParsed && nextParsed.flags) {
166
- for (var k in nextParsed.flags) {
167
- if (Object.prototype.hasOwnProperty.call(nextParsed.flags, k)) {
168
- _validateFlagSpec(k, nextParsed.flags[k]);
169
- }
160
+ // Re-read the flags from disk on a file change. Drop-silent on the hot
161
+ // path: a mid-write / transiently-invalid file must not throw out of the
162
+ // watcher callback.
163
+ var reload = function () {
164
+ try {
165
+ var nextRaw = atomicFile.fdSafeReadSync(opts.path, { maxBytes: C.BYTES.mib(1), encoding: "utf8" });
166
+ var nextParsed = safeJson.parse(nextRaw, { maxBytes: C.BYTES.mib(1) });
167
+ if (nextParsed && nextParsed.flags) {
168
+ for (var k in nextParsed.flags) {
169
+ if (Object.prototype.hasOwnProperty.call(nextParsed.flags, k)) {
170
+ _validateFlagSpec(k, nextParsed.flags[k]);
170
171
  }
171
- provider._replace(nextParsed.flags);
172
172
  }
173
- } catch (_e) { /* drop-silent on hot-path reload */ }
174
- });
175
- } catch (_w) { /* watch unavailable - non-fatal */ }
173
+ provider._replace(nextParsed.flags);
174
+ }
175
+ } catch (_e) { /* drop-silent on hot-path reload */ }
176
+ };
177
+ // Test seam: a fake watcher (opts._watch) lets a test drive reload
178
+ // deterministically. The default STAT-POLLS the file (StatWatcher), NOT
179
+ // fs.watch: fs.watch aborts libuv — an uncatchable src/win/fs-event.c
180
+ // assertion — when opts.path resolves through a Windows 8.3 short-name
181
+ // component (e.g. a path under C:\Users\SOMEUS~1\...). StatWatcher never
182
+ // opens a directory-change handle, so it is immune to the path form; a ~1s
183
+ // poll is ample latency for a feature-flag hot reload.
184
+ var watchFn = opts._watch || function (p, onChange) {
185
+ nodeFs.watchFile(p, { persistent: false, interval: C.TIME.seconds(1) }, function () { onChange(); });
186
+ };
187
+ try { watchFn(opts.path, reload); } catch (_w) { /* watch unavailable - non-fatal */ }
176
188
  }
177
189
  return provider;
178
190
  }
@@ -18,7 +18,7 @@
18
18
  "hashes": {
19
19
  "server": "sha256:2b30a26f728c5349f4c4b47834f862a4f77393b1224fc12b22abe3ce2cfab78f"
20
20
  },
21
- "refreshedAt": "2026-07-23T17:16:44.262Z"
21
+ "refreshedAt": "2026-07-23T22:29:23.692Z"
22
22
  },
23
23
  "@noble/curves": {
24
24
  "version": "2.2.0",
@@ -40,7 +40,7 @@
40
40
  "hashes": {
41
41
  "server": "sha256:2880c288b1285ef51d356d057bee6f0c8a00de36638cf47b47617e8c1faf10d5"
42
42
  },
43
- "refreshedAt": "2026-07-23T17:16:44.262Z"
43
+ "refreshedAt": "2026-07-23T22:29:23.692Z"
44
44
  },
45
45
  "@noble/post-quantum": {
46
46
  "version": "0.6.1",
@@ -71,7 +71,7 @@
71
71
  "hashes": {
72
72
  "server": "sha256:f9c94094b3c10fe73dac5343289da582454ea6053494fab2bf66099d9103d6c3"
73
73
  },
74
- "refreshedAt": "2026-07-23T17:16:44.262Z"
74
+ "refreshedAt": "2026-07-23T22:29:23.692Z"
75
75
  },
76
76
  "@simplewebauthn/server": {
77
77
  "version": "13.3.2",
@@ -94,7 +94,7 @@
94
94
  "hashes": {
95
95
  "server": "sha256:e83195dc9f189385da9c856ef38843f4466f93ea8f3d7fc2efcb1e1b18da6f20"
96
96
  },
97
- "refreshedAt": "2026-07-23T17:16:44.262Z"
97
+ "refreshedAt": "2026-07-23T22:29:23.692Z"
98
98
  },
99
99
  "SecLists-common-passwords-top-10000": {
100
100
  "version": "10k-most-common (master)",
@@ -114,7 +114,7 @@
114
114
  },
115
115
  "runtime_artifact": "lib/vendor/common-passwords-top-10000.data.js",
116
116
  "integrity_layers": "sha256 + sha3-512 + SLH-DSA-SHAKE-256f signature + in-payload canary (where applicable)",
117
- "refreshedAt": "2026-07-23T17:16:44.262Z"
117
+ "refreshedAt": "2026-07-23T22:29:23.692Z"
118
118
  },
119
119
  "bimi-trust-anchors": {
120
120
  "version": "operator-managed",
@@ -139,7 +139,7 @@
139
139
  },
140
140
  "runtime_artifact": "lib/vendor/bimi-trust-anchors.data.js",
141
141
  "integrity_layers": "sha256 + sha3-512 + SLH-DSA-SHAKE-256f signature + in-payload canary (where applicable)",
142
- "refreshedAt": "2026-07-23T17:16:44.262Z"
142
+ "refreshedAt": "2026-07-23T22:29:23.692Z"
143
143
  },
144
144
  "publicsuffix-list": {
145
145
  "version": "master",
@@ -154,12 +154,12 @@
154
154
  "bundler": "curl https://publicsuffix.org/list/public_suffix_list.dat",
155
155
  "bundledAt": "2026-07-23T00:00:00Z",
156
156
  "hashes": {
157
- "server": "sha256:da358965c19dfbc44ad7fc4ba769203a61e13be67314645e502300c2e52cac21",
158
- "data_js": "sha256:ce0071657db1097831ccafeb5a282214d2a06dcbab3ae4f4053c60b987223d5b"
157
+ "server": "sha256:04220f6e1c20c9af1edfcd891dd4663b2c92a020b13e698dd13b3630573520ca",
158
+ "data_js": "sha256:52428436de68a79537db52f2b0cb7ac0a1cc3e6397db87533c6e2f1bd2e12087"
159
159
  },
160
160
  "runtime_artifact": "lib/vendor/public-suffix-list.data.js",
161
161
  "integrity_layers": "sha256 + sha3-512 + SLH-DSA-SHAKE-256f signature + in-payload canary (where applicable)",
162
- "refreshedAt": "2026-07-23T17:16:44.262Z"
162
+ "refreshedAt": "2026-07-23T22:29:23.692Z"
163
163
  },
164
164
  "peculiar-pki": {
165
165
  "version": "2.0.0+pkijs-3.4.0",
@@ -190,7 +190,7 @@
190
190
  "hashes": {
191
191
  "server": "sha256:2307ef65e070757ffb13442b377e45efb9fa1a10432d9b39618387720ab990ed"
192
192
  },
193
- "refreshedAt": "2026-07-23T17:16:44.262Z"
193
+ "refreshedAt": "2026-07-23T22:29:23.692Z"
194
194
  }
195
195
  }
196
196
  }
@@ -5,8 +5,8 @@
5
5
  // Please pull this list from, and only from https://publicsuffix.org/list/public_suffix_list.dat,
6
6
  // rather than any other VCS sites. Pulling from any other URL is not guaranteed to be supported.
7
7
 
8
- // VERSION: 2026-07-23_05-57-33_UTC
9
- // COMMIT: 438c5e02a9db7870300cb3108258aeefc8467c36
8
+ // VERSION: 2026-07-23_18-24-27_UTC
9
+ // COMMIT: 93b4eb174b2da9ee3f6effc363b579a96a65c93a
10
10
 
11
11
  // Instructions on pulling and using this list can be found at https://publicsuffix.org/list/.
12
12
 
@@ -15205,6 +15205,10 @@ us.platform.sh
15205
15205
  *.platformsh.site
15206
15206
  *.tst.site
15207
15207
 
15208
+ // Playcode : https://playcode.io
15209
+ // Submitted by Ruslan Ianberdin <security@playcode.io>
15210
+ playcode.site
15211
+
15208
15212
  // Pley AB : https://www.pley.com/
15209
15213
  // Submitted by Henning Pohl <infra@pley.com>
15210
15214
  pley.games