@blamejs/core 0.17.11 → 0.17.13
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 +4 -0
- package/NOTICE +1 -1
- package/index.js +2 -0
- package/lib/app-shutdown.js +23 -20
- package/lib/audit.js +7 -5
- package/lib/content-digest.js +2 -1
- package/lib/crypto.js +252 -8
- package/lib/daemon.js +268 -24
- package/lib/db-declare-view.js +2 -2
- package/lib/db.js +4 -1
- package/lib/dsr.js +1 -1
- package/lib/i18n-messageformat.js +3 -2
- package/lib/log-stream-otlp-grpc.js +5 -1
- package/lib/log-stream-otlp.js +5 -1
- package/lib/log-stream.js +5 -2
- package/lib/middleware/bot-guard.js +5 -9
- package/lib/outbox.js +1 -1
- package/lib/pid-probe.js +55 -0
- package/lib/pqc-agent.js +8 -1
- package/lib/redact.js +54 -0
- package/lib/safe-object.js +80 -0
- package/lib/self-update-standalone-verifier.js +74 -27
- package/lib/self-update.js +497 -87
- package/lib/ssrf-guard.js +52 -0
- package/lib/vendor/MANIFEST.json +11 -11
- package/lib/vendor/public-suffix-list.dat +7 -41
- package/lib/vendor/public-suffix-list.data.js +5269 -5285
- package/lib/watcher.js +89 -17
- package/lib/webhook-dispatcher.js +1 -1
- package/lib/ws-client.js +17 -0
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/watcher.js
CHANGED
|
@@ -74,6 +74,14 @@ var DEFAULT_POLL_MAX_FILES = 50000;
|
|
|
74
74
|
// Operators with legitimate high-churn directories raise this via opts.
|
|
75
75
|
var DEFAULT_MAX_PENDING = 10000; // pending-event queue cap
|
|
76
76
|
|
|
77
|
+
// Native fs.watch runtime-error codes that leave the underlying handle dead —
|
|
78
|
+
// change detection is permanently off, not a transient blip. Classified as a
|
|
79
|
+
// fatal watcher/handle-dead so a wrapper can recreate/alert rather than ignore.
|
|
80
|
+
var HANDLE_DEAD_CODES = ["EPERM", "EBADF", "ENOSPC", "EMFILE", "ENFILE", "EACCES"];
|
|
81
|
+
// Windows MAX_PATH is 260; a path approaching it needs the \\?\ long-path
|
|
82
|
+
// prefix for lstat to resolve it (below the threshold, plain paths are fine).
|
|
83
|
+
var WIN32_LONG_PATH_THRESHOLD = 250;
|
|
84
|
+
|
|
77
85
|
// ---- glob-style matcher ----
|
|
78
86
|
//
|
|
79
87
|
// Supports three shapes per entry:
|
|
@@ -133,7 +141,7 @@ function _matchGlobBasename(parts, base) {
|
|
|
133
141
|
return true;
|
|
134
142
|
}
|
|
135
143
|
|
|
136
|
-
function _compileIgnore(patterns) {
|
|
144
|
+
function _compileIgnore(patterns, caseFold) {
|
|
137
145
|
if (!Array.isArray(patterns) || patterns.length === 0) {
|
|
138
146
|
return function () { return false; };
|
|
139
147
|
}
|
|
@@ -154,26 +162,33 @@ function _compileIgnore(patterns) {
|
|
|
154
162
|
throw new WatcherError("watcher/bad-ignore",
|
|
155
163
|
"watcher.create: ignore[" + i + "] exceeds " + MAX_IGNORE_STAR_COUNT + "-wildcard cap");
|
|
156
164
|
}
|
|
157
|
-
|
|
165
|
+
// When ignoreCaseFold is on, fold the pattern to lower case at compile time
|
|
166
|
+
// and the walked path at match time, so the walk-prune aligns with a
|
|
167
|
+
// case-insensitive consumer's own filter (an on-disk 'Node_Modules' is
|
|
168
|
+
// pruned by a 'node_modules/**' ignore, not stat'd toward pollMaxFiles).
|
|
169
|
+
// Length + wildcard caps stay measured on the original pattern.
|
|
170
|
+
var pk = caseFold ? p.toLowerCase() : p;
|
|
171
|
+
if (pk.indexOf("**") !== -1) {
|
|
158
172
|
// dir/** prefix-match — strip the trailing **; reject `**` mid-pattern.
|
|
159
|
-
if (!/^[^*]*\/?\*\*$/.test(
|
|
173
|
+
if (!/^[^*]*\/?\*\*$/.test(pk)) {
|
|
160
174
|
throw new WatcherError("watcher/bad-ignore",
|
|
161
175
|
"watcher.create: ignore[" + i + "] '**' is only supported as a trailing dir/** prefix form");
|
|
162
176
|
}
|
|
163
|
-
var prefix =
|
|
177
|
+
var prefix = pk.replace(/\/?\*\*$/, "");
|
|
164
178
|
compiled.push({ kind: "prefix", value: prefix });
|
|
165
179
|
} else if (starCount > 0) {
|
|
166
|
-
compiled.push({ kind: "glob", value: _parseGlobBasename(
|
|
180
|
+
compiled.push({ kind: "glob", value: _parseGlobBasename(pk) });
|
|
167
181
|
} else {
|
|
168
|
-
compiled.push({ kind: "exact", value:
|
|
182
|
+
compiled.push({ kind: "exact", value: pk });
|
|
169
183
|
}
|
|
170
184
|
}
|
|
171
185
|
return function (relPath) {
|
|
172
|
-
var
|
|
173
|
-
var
|
|
186
|
+
var matchPath = caseFold ? relPath.toLowerCase() : relPath;
|
|
187
|
+
var base = nodePath.basename(matchPath);
|
|
188
|
+
var normalized = matchPath.split(nodePath.sep).join("/");
|
|
174
189
|
for (var j = 0; j < compiled.length; j += 1) {
|
|
175
190
|
var c = compiled[j];
|
|
176
|
-
if (c.kind === "exact" && (c.value ===
|
|
191
|
+
if (c.kind === "exact" && (c.value === matchPath || c.value === normalized)) return true;
|
|
177
192
|
if (c.kind === "prefix" && (normalized === c.value || normalized.indexOf(c.value + "/") === 0)) return true;
|
|
178
193
|
if (c.kind === "glob" && _matchGlobBasename(c.value, base)) return true;
|
|
179
194
|
}
|
|
@@ -312,6 +327,21 @@ function _validateOpts(opts) {
|
|
|
312
327
|
throw new WatcherError("watcher/bad-ignore",
|
|
313
328
|
"watcher.create: ignore must be an array of glob patterns");
|
|
314
329
|
}
|
|
330
|
+
validateOpts.optionalBoolean(opts.ignoreCaseFold, "ignoreCaseFold", WatcherError, "watcher/bad-ignore");
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function _lstatLongPathSafe(fullPath) {
|
|
334
|
+
// Windows MAX_PATH (260): lstatSync on a path at/over the limit without the
|
|
335
|
+
// \\?\ long-path prefix spuriously throws ENOENT, which _normalizeAndDispatch
|
|
336
|
+
// would misread as a delete. The prefix needs a backslashed absolute path
|
|
337
|
+
// (the watcher root is realpathSync.native'd → absolute) and disables path
|
|
338
|
+
// normalization, which is safe here — fullPath has no relative components. A
|
|
339
|
+
// genuinely-missing file still ENOENTs on the plain-path fallback → real delete.
|
|
340
|
+
if (process.platform === "win32" && fullPath.length >= WIN32_LONG_PATH_THRESHOLD && fullPath.indexOf("\\\\?\\") !== 0) {
|
|
341
|
+
try { return nodeFs.lstatSync("\\\\?\\" + fullPath); }
|
|
342
|
+
catch (_e) { /* fall through to the plain path so a real ENOENT resurfaces */ }
|
|
343
|
+
}
|
|
344
|
+
return nodeFs.lstatSync(fullPath);
|
|
315
345
|
}
|
|
316
346
|
|
|
317
347
|
function create(opts) {
|
|
@@ -339,7 +369,7 @@ function create(opts) {
|
|
|
339
369
|
var onChange = opts.onChange || function () {};
|
|
340
370
|
var onDelete = opts.onDelete || function () {};
|
|
341
371
|
var onError = opts.onError || function () {};
|
|
342
|
-
var isIgnored = _compileIgnore(opts.ignore);
|
|
372
|
+
var isIgnored = _compileIgnore(opts.ignore, opts.ignoreCaseFold === true);
|
|
343
373
|
var auditOn = opts.audit !== false;
|
|
344
374
|
|
|
345
375
|
// Pre-flight: root must exist and be a directory.
|
|
@@ -375,13 +405,33 @@ function create(opts) {
|
|
|
375
405
|
try { onError(err); } catch (_e) { /* operator error handler must not crash the watcher */ }
|
|
376
406
|
}
|
|
377
407
|
|
|
408
|
+
// Native-backend (fs.watch) error handler. A runtime error whose code leaves
|
|
409
|
+
// the handle dead (EPERM after the watched root is deleted/recreated on
|
|
410
|
+
// Windows, EBADF, inotify exhaustion) permanently stops detection — classify
|
|
411
|
+
// it fatal so a consumer can react (recreate/alert) rather than treat it as a
|
|
412
|
+
// transient blip. Non-handle-dead errors pass through unclassified.
|
|
413
|
+
function _handleBackendError(err) {
|
|
414
|
+
if (err && HANDLE_DEAD_CODES.indexOf(err.code) !== -1) {
|
|
415
|
+
var dead = new WatcherError("watcher/handle-dead",
|
|
416
|
+
"watcher: native watch handle died (" + err.code + ") — change detection has " +
|
|
417
|
+
"permanently stopped; recreate the watcher to resume: " + (err.message || String(err)));
|
|
418
|
+
dead.fatal = true;
|
|
419
|
+
dead.cause = err;
|
|
420
|
+
_safeError(dead);
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
_safeError(err);
|
|
424
|
+
}
|
|
425
|
+
|
|
378
426
|
function _normalizeAndDispatch(relPath) {
|
|
379
427
|
if (stopped) return;
|
|
380
428
|
if (isIgnored(relPath)) return;
|
|
381
429
|
var fullPath = nodePath.join(root, relPath);
|
|
382
|
-
// lstat (NOT stat) — refuses to follow symlinks out of root.
|
|
430
|
+
// lstat (NOT stat) — refuses to follow symlinks out of root. Long-path-safe
|
|
431
|
+
// so a Windows path over MAX_PATH isn't misread as a delete (issue: a deep
|
|
432
|
+
// change delivered to onDelete instead of onChange).
|
|
383
433
|
var lst;
|
|
384
|
-
try { lst =
|
|
434
|
+
try { lst = _lstatLongPathSafe(fullPath); }
|
|
385
435
|
catch (e) {
|
|
386
436
|
if (e && e.code === "ENOENT") {
|
|
387
437
|
// Path is gone — delete event. Type unknown by the time we
|
|
@@ -456,10 +506,23 @@ function create(opts) {
|
|
|
456
506
|
var absDir = relDir === "" ? root : nodePath.join(root, relDir);
|
|
457
507
|
var entries;
|
|
458
508
|
try { entries = nodeFs.readdirSync(absDir, { withFileTypes: true }); }
|
|
459
|
-
catch (
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
509
|
+
catch (rootErr) {
|
|
510
|
+
if (relDir === "") {
|
|
511
|
+
// The ROOT itself is unreadable (unmounted NFS/SMB/USB volume, or a
|
|
512
|
+
// deleted root). Returning an empty snapshot would diff as a delete
|
|
513
|
+
// of EVERY tracked entry — a mass-delete a consumer can't tell apart
|
|
514
|
+
// from a real rm -rf. Surface a distinct fatal root-lost signal and
|
|
515
|
+
// abort the walk; _pollTick's catch keeps the prior snapshot, so no
|
|
516
|
+
// spurious onDelete fires.
|
|
517
|
+
var lost = new WatcherError("watcher/root-lost",
|
|
518
|
+
"watcher.poll: root '" + root + "' is no longer readable: " +
|
|
519
|
+
((rootErr && rootErr.message) || String(rootErr)));
|
|
520
|
+
lost.fatal = true;
|
|
521
|
+
lost.cause = rootErr;
|
|
522
|
+
throw lost;
|
|
523
|
+
}
|
|
524
|
+
// Inner dir vanished mid-walk between the parent listing and the
|
|
525
|
+
// descent. Skip — the next tick's diff surfaces the deletion.
|
|
463
526
|
continue;
|
|
464
527
|
}
|
|
465
528
|
for (var i = 0; i < entries.length; i += 1) {
|
|
@@ -544,7 +607,7 @@ function create(opts) {
|
|
|
544
607
|
if (rel === "" || rel === ".") return;
|
|
545
608
|
_enqueue(rel);
|
|
546
609
|
});
|
|
547
|
-
watcherHandle.on("error", function (err) {
|
|
610
|
+
watcherHandle.on("error", function (err) { _handleBackendError(err); });
|
|
548
611
|
} catch (e) {
|
|
549
612
|
if (e && (e.code === "ERR_FEATURE_UNAVAILABLE_ON_PLATFORM" || e.code === "ENOSYS")) {
|
|
550
613
|
throw new WatcherError("watcher/recursive-unsupported",
|
|
@@ -609,10 +672,19 @@ function create(opts) {
|
|
|
609
672
|
root: root,
|
|
610
673
|
mode: mode,
|
|
611
674
|
_flushForTest: _flushForTest,
|
|
675
|
+
// Test seam — drives the native-backend error classification path (the OS
|
|
676
|
+
// killing the handle isn't deterministically reproducible). Not part of the
|
|
677
|
+
// operator contract.
|
|
678
|
+
_simulateBackendErrorForTest: _handleBackendError,
|
|
612
679
|
};
|
|
613
680
|
}
|
|
614
681
|
|
|
615
682
|
module.exports = {
|
|
616
683
|
create: create,
|
|
617
684
|
WatcherError: WatcherError,
|
|
685
|
+
// Exported so a consumer pre-filtering operator ignore patterns (to keep one
|
|
686
|
+
// bad pattern from aborting the whole watcher) can align with the caps
|
|
687
|
+
// create() enforces, instead of pinning hand-copied mirror constants.
|
|
688
|
+
MAX_IGNORE_PATTERN_LEN: MAX_IGNORE_PATTERN_LEN,
|
|
689
|
+
MAX_IGNORE_STAR_COUNT: MAX_IGNORE_STAR_COUNT,
|
|
618
690
|
};
|
|
@@ -72,7 +72,7 @@ var HTTP_OK_MAX = 300;
|
|
|
72
72
|
function _validateTableName(name, label) {
|
|
73
73
|
validateOpts.requireNonEmptyString(name, label, WebhookDispatcherError, "webhook-dispatcher/bad-opts");
|
|
74
74
|
// safeSql.quoteIdentifier refuses an injection-bearing name at construction.
|
|
75
|
-
safeSql.quoteIdentifier(name);
|
|
75
|
+
safeSql.quoteIdentifier(name, undefined, { allowReserved: true }); // parity with b.db.from()
|
|
76
76
|
return name;
|
|
77
77
|
}
|
|
78
78
|
|
package/lib/ws-client.js
CHANGED
|
@@ -268,6 +268,7 @@ function connect(target, opts) {
|
|
|
268
268
|
// performs the (async) check + pinning before the dial and reruns it on
|
|
269
269
|
// every reconnect, so a urlFor-swapped target is validated too.
|
|
270
270
|
client._prepareDial().then(function () {
|
|
271
|
+
if (client._closed) return; // close() during the async SSRF re-resolve retires the dial
|
|
271
272
|
client._dial();
|
|
272
273
|
}).catch(function (e) {
|
|
273
274
|
setImmediate(function () { client._handleSocketError(e); });
|
|
@@ -366,6 +367,10 @@ class WsClient extends EventEmitter {
|
|
|
366
367
|
}
|
|
367
368
|
|
|
368
369
|
_dial() {
|
|
370
|
+
// Guard the shared dial funnel: a close() during the pending _prepareDial
|
|
371
|
+
// (initial dial) or the reconnect backoff sets _closed; without this check
|
|
372
|
+
// the resolved continuation would open a socket + heartbeat nobody owns.
|
|
373
|
+
if (this._closed) return;
|
|
369
374
|
var self = this;
|
|
370
375
|
this._readyState = "connecting";
|
|
371
376
|
|
|
@@ -883,6 +888,18 @@ class WsClient extends EventEmitter {
|
|
|
883
888
|
this._closed = true;
|
|
884
889
|
return;
|
|
885
890
|
}
|
|
891
|
+
if (this._readyState === "connecting") {
|
|
892
|
+
// No socket is established yet (the dial is still in _prepareDial's SSRF
|
|
893
|
+
// re-resolve or the handshake). There is no peer to exchange a graceful
|
|
894
|
+
// close frame with, so the 1000ms grace window buys nothing — and leaving
|
|
895
|
+
// _closed false across it lets a slow _prepareDial resolve into _dial()
|
|
896
|
+
// and open a socket nobody owns. Retire immediately (sets _closed and
|
|
897
|
+
// destroys any partial socket).
|
|
898
|
+
code = (typeof code === "number") ? code : CLOSE_NORMAL;
|
|
899
|
+
reason = (typeof reason === "string") ? reason : "";
|
|
900
|
+
this._teardown(code, reason, false);
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
886
903
|
code = (typeof code === "number") ? code : CLOSE_NORMAL;
|
|
887
904
|
reason = (typeof reason === "string") ? reason : "";
|
|
888
905
|
// RFC 6455 §5.5: control frames must be <= 125 bytes total. The
|
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:dd71bb9a-bc83-498e-b1da-0abc600f88d2",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-07-
|
|
8
|
+
"timestamp": "2026-07-23T20:20:36.895Z",
|
|
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.13",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.17.
|
|
25
|
+
"version": "0.17.13",
|
|
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.13",
|
|
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.13",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|