@blamejs/core 0.17.14 → 0.17.15
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/middleware/body-parser.js +19 -4
- 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.17.x
|
|
10
10
|
|
|
11
|
+
- v0.17.15 (2026-07-23) — **The standalone multipart parser honors storage: memory and every other documented upload knob.** b.parsers.multipart(req, opts) is documented as a thin wrapper over the same engine b.middleware.bodyParser drives, but its option resolver carried a hand-maintained passthrough list that had drifted: storage ("disk" | "memory") and filenameCharsets were silently dropped, so a serverless / read-only-filesystem handler passing storage: "memory" got disk mode instead and threw when the parser tried to open a temp file. The passthrough is now derived from the parser's own defaults, so every documented knob reaches the standalone path and a future knob cannot silently vanish, and an invalid storage value throws at the call the same way the middleware does instead of falling through to disk. **Fixed:** *b.parsers.multipart honors storage: memory and filenameCharsets* — The standalone multipart parser's option resolver passed through a hand-maintained subset of the multipart knobs, omitting storage and filenameCharsets: b.parsers.multipart(req, { storage: "memory" }) silently used disk mode and then threw on a read-only / serverless filesystem, and a filename*=ISO-8859-1'' part could not be opted in via filenameCharsets. The passthrough is now derived from the parser's DEFAULTS so every documented knob reaches the standalone path (only the maxBytes/maxFiles aliases and the dispatch-only contentTypes are excluded), and an invalid storage value throws a TypeError at the call rather than falling back to disk -- matching b.middleware.bodyParser.
|
|
12
|
+
|
|
11
13
|
- 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
14
|
|
|
13
15
|
- 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.
|
|
@@ -1651,12 +1651,27 @@ function _resolveStandaloneMultipartOpts(opts, ct) {
|
|
|
1651
1651
|
null, { permanent: true, statusCode: HTTP_STATUS.BAD_REQUEST });
|
|
1652
1652
|
resolved.fileCount = mf;
|
|
1653
1653
|
}
|
|
1654
|
-
// Pass
|
|
1655
|
-
//
|
|
1656
|
-
|
|
1657
|
-
|
|
1654
|
+
// Pass through EVERY documented multipart knob the middleware honors —
|
|
1655
|
+
// parsers.multipart is a thin wrapper, not a feature subset. Derive the
|
|
1656
|
+
// passthrough from DEFAULTS.multipart rather than a hand-maintained list so a
|
|
1657
|
+
// knob added to the middleware can never silently vanish on the standalone
|
|
1658
|
+
// path (the drift that dropped `storage` + `filenameCharsets`). Only the keys
|
|
1659
|
+
// the standalone surfaces under a different name (totalSize←maxBytes,
|
|
1660
|
+
// fileCount←maxFiles) or that are dispatch-only (contentTypes — the standalone
|
|
1661
|
+
// requires multipart/form-data) are excluded.
|
|
1662
|
+
var STANDALONE_MULTIPART_EXCLUDE = { totalSize: true, fileCount: true, contentTypes: true };
|
|
1663
|
+
Object.keys(DEFAULTS.multipart).forEach(function (k) {
|
|
1664
|
+
if (STANDALONE_MULTIPART_EXCLUDE[k]) return;
|
|
1658
1665
|
if (opts[k] !== undefined) resolved[k] = opts[k];
|
|
1659
1666
|
});
|
|
1667
|
+
// storage is an operator-config enum — reject a typo at the call the same way
|
|
1668
|
+
// b.middleware.bodyParser does (a bad value must not silently fall back to
|
|
1669
|
+
// disk mode and then throw deep in the parser on a read-only FS).
|
|
1670
|
+
if (resolved.storage !== "disk" && resolved.storage !== "memory") {
|
|
1671
|
+
throw new TypeError(
|
|
1672
|
+
"parsers.multipart: storage must be \"disk\" or \"memory\" (got " +
|
|
1673
|
+
JSON.stringify(resolved.storage) + ")");
|
|
1674
|
+
}
|
|
1660
1675
|
// ct is the parsed Content-Type; required for the boundary parameter.
|
|
1661
1676
|
if (!ct || typeof ct.type !== "string" || ct.type !== "multipart/form-data") {
|
|
1662
1677
|
throw new BodyParserError("body-parser/standalone-not-multipart",
|
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:de6a5295-f203-43a2-9beb-1d4c36015b11",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-07-
|
|
8
|
+
"timestamp": "2026-07-24T02:18:59.855Z",
|
|
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.17.
|
|
22
|
+
"bom-ref": "@blamejs/core@0.17.15",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.17.
|
|
25
|
+
"version": "0.17.15",
|
|
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.17.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/core@0.17.15",
|
|
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.17.
|
|
57
|
+
"ref": "@blamejs/core@0.17.15",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|