@blamejs/core 0.9.14 → 0.9.16
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/a2a-tasks.js +2 -2
- package/lib/a2a.js +11 -11
- package/lib/acme.js +5 -5
- package/lib/ai-input.js +2 -2
- package/lib/api-key.js +4 -4
- package/lib/api-snapshot.js +6 -6
- package/lib/app-shutdown.js +2 -2
- package/lib/app.js +5 -5
- package/lib/archive.js +8 -8
- package/lib/argon2-builtin.js +2 -2
- package/lib/atomic-file.js +53 -53
- package/lib/audit-sign.js +8 -8
- package/lib/audit-tools.js +22 -22
- package/lib/auth/dpop.js +3 -3
- package/lib/auth/elevation-grant.js +3 -3
- package/lib/auth/fido-mds3.js +6 -6
- package/lib/auth/jwt-external.js +2 -2
- package/lib/auth/sd-jwt-vc.js +2 -2
- package/lib/backup/bundle.js +17 -17
- package/lib/backup/index.js +36 -36
- package/lib/budr.js +3 -3
- package/lib/bundler.js +20 -20
- package/lib/circuit-breaker.js +4 -4
- package/lib/cli.js +25 -26
- package/lib/cluster.js +2 -2
- package/lib/compliance-sanctions.js +2 -2
- package/lib/compliance.js +6 -7
- package/lib/config-drift.js +15 -15
- package/lib/config.js +6 -6
- package/lib/content-credentials.js +4 -4
- package/lib/credential-hash.js +7 -7
- package/lib/crypto-field.js +9 -9
- package/lib/daemon.js +19 -19
- package/lib/db-file-lifecycle.js +24 -24
- package/lib/db-schema.js +2 -2
- package/lib/db.js +34 -34
- package/lib/dev.js +10 -10
- package/lib/dr-runbook.js +5 -5
- package/lib/dual-control.js +2 -2
- package/lib/external-db-migrate.js +17 -17
- package/lib/external-db.js +2 -2
- package/lib/fdx.js +2 -2
- package/lib/file-upload.js +30 -30
- package/lib/flag-evaluation-context.js +2 -2
- package/lib/flag-providers.js +4 -4
- package/lib/gate-contract.js +5 -5
- package/lib/graphql-federation.js +4 -7
- package/lib/honeytoken.js +6 -6
- package/lib/http-client-cookie-jar.js +6 -6
- package/lib/http-client.js +18 -18
- package/lib/i18n.js +5 -5
- package/lib/keychain.js +5 -5
- package/lib/legal-hold.js +2 -2
- package/lib/local-db-thin.js +9 -9
- package/lib/log-stream-local.js +17 -17
- package/lib/log-stream-syslog.js +2 -2
- package/lib/log-stream.js +3 -3
- package/lib/log.js +2 -2
- package/lib/mail-bounce.js +2 -2
- package/lib/mail-mdn.js +2 -2
- package/lib/mail-srs.js +2 -2
- package/lib/mail.js +7 -7
- package/lib/mcp-tool-registry.js +6 -6
- package/lib/mcp.js +2 -2
- package/lib/metrics.js +2 -2
- package/lib/middleware/api-encrypt.js +16 -16
- package/lib/middleware/body-parser.js +18 -18
- package/lib/middleware/compression.js +3 -3
- package/lib/middleware/csp-nonce.js +4 -4
- package/lib/middleware/health.js +7 -7
- package/lib/middleware/idempotency-key.js +163 -63
- package/lib/middleware/require-bound-key.js +4 -4
- package/lib/middleware/require-mtls.js +4 -4
- package/lib/migrations.js +5 -5
- package/lib/mtls-ca.js +26 -26
- package/lib/mtls-engine-default.js +5 -5
- package/lib/network-byte-quota.js +2 -2
- package/lib/network-dns.js +2 -2
- package/lib/network-nts.js +2 -2
- package/lib/network-proxy.js +3 -3
- package/lib/network-smtp-policy.js +2 -2
- package/lib/network-tls.js +17 -17
- package/lib/network.js +25 -25
- package/lib/notify.js +11 -11
- package/lib/object-store/gcs-bucket-ops.js +2 -2
- package/lib/object-store/gcs.js +5 -5
- package/lib/object-store/index.js +6 -6
- package/lib/object-store/local.js +19 -19
- package/lib/object-store/sigv4.js +3 -3
- package/lib/observability-tracer.js +4 -4
- package/lib/otel-export.js +3 -3
- package/lib/pagination.js +5 -5
- package/lib/parsers/safe-env.js +3 -3
- package/lib/parsers/safe-xml.js +3 -3
- package/lib/pqc-gate.js +5 -5
- package/lib/pubsub-redis.js +2 -2
- package/lib/queue-local.js +3 -3
- package/lib/queue.js +2 -2
- package/lib/redis-client.js +4 -4
- package/lib/restore-bundle.js +17 -17
- package/lib/restore-rollback.js +34 -34
- package/lib/restore.js +16 -16
- package/lib/router.js +25 -25
- package/lib/sandbox.js +8 -8
- package/lib/sec-cyber.js +3 -3
- package/lib/security-assert.js +2 -2
- package/lib/seeders.js +6 -6
- package/lib/self-update.js +18 -18
- package/lib/session-device-binding.js +2 -2
- package/lib/static.js +22 -22
- package/lib/template.js +19 -19
- package/lib/testing.js +9 -9
- package/lib/tls-exporter.js +5 -5
- package/lib/tracing.js +3 -3
- package/lib/vault/index.js +11 -11
- package/lib/vault/passphrase-ops.js +37 -37
- package/lib/vault/passphrase-source.js +2 -2
- package/lib/vault/rotate.js +64 -64
- package/lib/vault/seal-pem-file.js +26 -26
- package/lib/vault-aad.js +5 -5
- package/lib/watcher.js +22 -22
- package/lib/webhook.js +10 -10
- package/lib/worker-pool.js +6 -6
- package/lib/ws-client.js +6 -6
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/self-update.js
CHANGED
|
@@ -47,13 +47,13 @@
|
|
|
47
47
|
* Framework / vendored-deps integrity check plus version pinning — refuses to install a new build when the asset's detached signature does not verify against the operator-supplied public key, or when the vendored SHA the new build would ship does not match the manifest the opera...
|
|
48
48
|
*/
|
|
49
49
|
|
|
50
|
-
var
|
|
51
|
-
var
|
|
50
|
+
var nodeFs = require("fs");
|
|
51
|
+
var nodePath = require("path");
|
|
52
52
|
var nodeCrypto = require("crypto");
|
|
53
|
-
var
|
|
53
|
+
var numericBounds = require("./numeric-bounds");
|
|
54
54
|
var atomicFile = require("./atomic-file");
|
|
55
55
|
var validateOpts = require("./validate-opts");
|
|
56
|
-
var
|
|
56
|
+
var bCrypto = require("./crypto");
|
|
57
57
|
var httpClient = require("./http-client");
|
|
58
58
|
var safeJson = require("./safe-json");
|
|
59
59
|
var { URL: NodeUrl } = require("url");
|
|
@@ -153,9 +153,9 @@ function _validatePollOpts(opts) {
|
|
|
153
153
|
throw new SelfUpdateError("selfupdate/bad-sig-pattern",
|
|
154
154
|
"selfUpdate.poll: opts.signaturePattern must be a RegExp or string when present");
|
|
155
155
|
}
|
|
156
|
-
|
|
156
|
+
numericBounds.requirePositiveFiniteIntIfPresent(opts.maxBytes,
|
|
157
157
|
"selfUpdate.poll: opts.maxBytes", SelfUpdateError, "selfupdate/bad-max-bytes");
|
|
158
|
-
|
|
158
|
+
numericBounds.requirePositiveFiniteIntIfPresent(opts.timeoutMs,
|
|
159
159
|
"selfUpdate.poll: opts.timeoutMs", SelfUpdateError, "selfupdate/bad-timeout");
|
|
160
160
|
}
|
|
161
161
|
|
|
@@ -178,7 +178,7 @@ function _matchAsset(name, pattern, fallback) {
|
|
|
178
178
|
* Fetch a releases feed and report whether a newer tag is available.
|
|
179
179
|
* Tags are compared semver-style with a leading `v` stripped. When
|
|
180
180
|
* `opts.etag` is supplied an `If-None-Match` header makes a 304 a fast
|
|
181
|
-
* "no update"
|
|
181
|
+
* "no update" nodePath. The match against asset and signature URLs uses
|
|
182
182
|
* `opts.assetPattern` and `opts.signaturePattern` (RegExp or substring)
|
|
183
183
|
* with conservative fallbacks. Throws SelfUpdateError on a non-2xx
|
|
184
184
|
* upstream, malformed JSON, or unexpected shape.
|
|
@@ -367,7 +367,7 @@ function _validateVerifyOpts(opts) {
|
|
|
367
367
|
throw new SelfUpdateError("selfupdate/bad-hash-algo",
|
|
368
368
|
"selfUpdate.verify: opts.hashAlgo must be one of " + ALLOWED_HASH_ALGS.join(", "));
|
|
369
369
|
}
|
|
370
|
-
|
|
370
|
+
numericBounds.requirePositiveFiniteIntIfPresent(opts.maxBytes,
|
|
371
371
|
"selfUpdate.verify: opts.maxBytes", SelfUpdateError, "selfupdate/bad-max-bytes");
|
|
372
372
|
}
|
|
373
373
|
|
|
@@ -426,7 +426,7 @@ async function verify(opts) {
|
|
|
426
426
|
}
|
|
427
427
|
|
|
428
428
|
var ok = false;
|
|
429
|
-
try { ok =
|
|
429
|
+
try { ok = bCrypto.verify(assetBytes, sigBytes, opts.pubkeyPem); }
|
|
430
430
|
catch (e) {
|
|
431
431
|
_safeAuditEmit("selfupdate.verify.failed", "denied", {
|
|
432
432
|
assetPath: opts.assetPath, signaturePath: opts.signaturePath,
|
|
@@ -515,19 +515,19 @@ async function swap(opts) {
|
|
|
515
515
|
var to = opts.to;
|
|
516
516
|
var backupTo = opts.backupTo;
|
|
517
517
|
|
|
518
|
-
if (!
|
|
518
|
+
if (!nodeFs.existsSync(from)) {
|
|
519
519
|
throw new SelfUpdateError("selfupdate/missing-from",
|
|
520
520
|
"selfUpdate.swap: from path does not exist: " + from);
|
|
521
521
|
}
|
|
522
522
|
|
|
523
|
-
var toDir =
|
|
524
|
-
var backupDir =
|
|
523
|
+
var toDir = nodePath.dirname(to);
|
|
524
|
+
var backupDir = nodePath.dirname(backupTo);
|
|
525
525
|
atomicFile.ensureDir(toDir);
|
|
526
526
|
atomicFile.ensureDir(backupDir);
|
|
527
527
|
|
|
528
528
|
// Step 2 — backup if `to` exists. Use atomicFile.copy so the backup
|
|
529
529
|
// hits disk via temp+fsync+rename.
|
|
530
|
-
var hadOriginal =
|
|
530
|
+
var hadOriginal = nodeFs.existsSync(to);
|
|
531
531
|
if (hadOriginal) {
|
|
532
532
|
try {
|
|
533
533
|
await atomicFile.copy(to, backupTo, { fileMode: 0o600 });
|
|
@@ -541,14 +541,14 @@ async function swap(opts) {
|
|
|
541
541
|
// Step 3 — install. Rename is atomic on same FS; on cross-device we
|
|
542
542
|
// fall back to copy + unlink.
|
|
543
543
|
try {
|
|
544
|
-
|
|
544
|
+
nodeFs.renameSync(from, to);
|
|
545
545
|
} catch (e) {
|
|
546
546
|
if (e && e.code === "EXDEV") {
|
|
547
547
|
// Cross-device — copy + unlink. Use atomicFile.copy for the safety
|
|
548
548
|
// net (temp+fsync+rename on dest FS); then remove the source.
|
|
549
549
|
try {
|
|
550
550
|
await atomicFile.copy(from, to, { fileMode: 0o600 });
|
|
551
|
-
try {
|
|
551
|
+
try { nodeFs.unlinkSync(from); } catch (_u) { /* tmp source leak — operator-cleanable */ }
|
|
552
552
|
} catch (ce) {
|
|
553
553
|
// Roll back from backup if we have one.
|
|
554
554
|
if (hadOriginal) {
|
|
@@ -613,12 +613,12 @@ async function rollback(opts) {
|
|
|
613
613
|
var to = opts.to;
|
|
614
614
|
var backupTo = opts.backupTo;
|
|
615
615
|
|
|
616
|
-
if (!
|
|
616
|
+
if (!nodeFs.existsSync(backupTo)) {
|
|
617
617
|
throw new SelfUpdateError("selfupdate/missing-backup",
|
|
618
618
|
"selfUpdate.rollback: backupTo path does not exist: " + backupTo);
|
|
619
619
|
}
|
|
620
620
|
|
|
621
|
-
atomicFile.ensureDir(
|
|
621
|
+
atomicFile.ensureDir(nodePath.dirname(to));
|
|
622
622
|
try {
|
|
623
623
|
await atomicFile.copy(backupTo, to, { fileMode: 0o600 });
|
|
624
624
|
} catch (e) {
|
|
@@ -626,7 +626,7 @@ async function rollback(opts) {
|
|
|
626
626
|
"selfUpdate.rollback: copy " + backupTo + " -> " + to + " failed: " +
|
|
627
627
|
((e && e.message) || String(e)));
|
|
628
628
|
}
|
|
629
|
-
atomicFile.fsyncDir(
|
|
629
|
+
atomicFile.fsyncDir(nodePath.dirname(to));
|
|
630
630
|
|
|
631
631
|
_safeAuditEmit("selfupdate.rollback.completed", "success", {
|
|
632
632
|
to: to, backupTo: backupTo,
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
*/
|
|
70
70
|
|
|
71
71
|
var C = require("./constants");
|
|
72
|
-
var
|
|
72
|
+
var bCrypto = require("./crypto");
|
|
73
73
|
var nodeCrypto = require("crypto");
|
|
74
74
|
var lazyRequire = require("./lazy-require");
|
|
75
75
|
var requestHelpers = require("./request-helpers");
|
|
@@ -386,7 +386,7 @@ function create(opts) {
|
|
|
386
386
|
return { ok: false, reason: "missing-bind" };
|
|
387
387
|
}
|
|
388
388
|
if (!Buffer.isBuffer(stored) || stored.length !== fpResult.fingerprint.length ||
|
|
389
|
-
!
|
|
389
|
+
!bCrypto.timingSafeEqual(stored, fpResult.fingerprint)) {
|
|
390
390
|
_emitObs("session.device.drift", {});
|
|
391
391
|
_emitAudit("session.device.drift", _hashTokenForAudit(token), "denied",
|
|
392
392
|
{ components: fpResult.components, stage: "verify" }, req);
|
package/lib/static.js
CHANGED
|
@@ -31,10 +31,10 @@
|
|
|
31
31
|
* passing while opening the surface.
|
|
32
32
|
*/
|
|
33
33
|
|
|
34
|
-
var
|
|
34
|
+
var nodeFs = require("node:fs");
|
|
35
35
|
var fsp = require("node:fs/promises");
|
|
36
36
|
var nodeCrypto = require("node:crypto");
|
|
37
|
-
var
|
|
37
|
+
var nodePath = require("node:path");
|
|
38
38
|
var C = require("./constants");
|
|
39
39
|
var gateContract = require("./gate-contract");
|
|
40
40
|
var lazyRequire = require("./lazy-require");
|
|
@@ -157,7 +157,7 @@ async function _readMeta(absPath) {
|
|
|
157
157
|
var sri = nodeCrypto.createHash("sha384");
|
|
158
158
|
var sha3 = nodeCrypto.createHash("sha3-512");
|
|
159
159
|
await new Promise(function (resolve, reject) {
|
|
160
|
-
var s =
|
|
160
|
+
var s = nodeFs.createReadStream(absPath);
|
|
161
161
|
s.on("data", function (chunk) { sri.update(chunk); sha3.update(chunk); });
|
|
162
162
|
s.on("end", resolve);
|
|
163
163
|
s.on("error", reject);
|
|
@@ -181,10 +181,10 @@ async function _readMeta(absPath) {
|
|
|
181
181
|
function _resolveSafe(root, requestedPath) {
|
|
182
182
|
if (typeof requestedPath !== "string" || requestedPath.length === 0) return null;
|
|
183
183
|
if (requestedPath.indexOf("\0") !== -1) return null;
|
|
184
|
-
var resolved =
|
|
185
|
-
var rootResolved =
|
|
184
|
+
var resolved = nodePath.resolve(root, "." + requestedPath);
|
|
185
|
+
var rootResolved = nodePath.resolve(root);
|
|
186
186
|
if (resolved !== rootResolved &&
|
|
187
|
-
!resolved.startsWith(rootResolved +
|
|
187
|
+
!resolved.startsWith(rootResolved + nodePath.sep)) return null;
|
|
188
188
|
|
|
189
189
|
// Symlink-escape defense — the lexical resolve above only sees the
|
|
190
190
|
// requested path tokens; a symlink anywhere along `resolved` can
|
|
@@ -195,9 +195,9 @@ function _resolveSafe(root, requestedPath) {
|
|
|
195
195
|
// breaks deploys where the OS prefix-symlinks the temp dir
|
|
196
196
|
// (macOS: /var/folders/X/Y → /private/var/folders/X/Y).
|
|
197
197
|
try {
|
|
198
|
-
var real =
|
|
199
|
-
var rootReal =
|
|
200
|
-
if (real !== rootReal && !real.startsWith(rootReal +
|
|
198
|
+
var real = nodeFs.realpathSync(resolved);
|
|
199
|
+
var rootReal = nodeFs.realpathSync(rootResolved);
|
|
200
|
+
if (real !== rootReal && !real.startsWith(rootReal + nodePath.sep)) return null;
|
|
201
201
|
} catch (_e) {
|
|
202
202
|
// Path doesn't exist (or is denied) — fall through with the lexical
|
|
203
203
|
// resolution so the caller's stat() returns the natural ENOENT and
|
|
@@ -213,7 +213,7 @@ function _resolveSafe(root, requestedPath) {
|
|
|
213
213
|
// legitimate `<name>.<hash>.js` bundler output) are valid here. The
|
|
214
214
|
// other balanced checks still reject the traversal + smuggling
|
|
215
215
|
// surface the user surfaced.
|
|
216
|
-
var fname =
|
|
216
|
+
var fname = nodePath.basename(resolved);
|
|
217
217
|
var rv = guardFilename().validate(fname, {
|
|
218
218
|
profile: "balanced",
|
|
219
219
|
shellExecExtPolicy: "allow",
|
|
@@ -224,7 +224,7 @@ function _resolveSafe(root, requestedPath) {
|
|
|
224
224
|
}
|
|
225
225
|
|
|
226
226
|
function _contentTypeFor(filePath, table) {
|
|
227
|
-
var ext =
|
|
227
|
+
var ext = nodePath.extname(filePath).toLowerCase();
|
|
228
228
|
return (table && table[ext]) || DEFAULT_CONTENT_TYPES[ext] || "application/octet-stream";
|
|
229
229
|
}
|
|
230
230
|
|
|
@@ -293,7 +293,7 @@ function _bareMime(contentType) {
|
|
|
293
293
|
// gate is wired AND `safeRenderSvg` is enabled, PDF renders inline
|
|
294
294
|
// ONLY when `safeRenderPdf` is explicitly enabled. text/html and
|
|
295
295
|
// text/javascript are inside `text/*` but the browser executes them
|
|
296
|
-
// — they go through the risky
|
|
296
|
+
// — they go through the risky nodePath. Everything else (HTML, JS, MJS,
|
|
297
297
|
// XML, executables, archives, fonts when served from a user-upload
|
|
298
298
|
// directory) gets forced download to defeat stored-XSS via the
|
|
299
299
|
// upload directory.
|
|
@@ -301,7 +301,7 @@ function _shouldForceAttachment(contentType, ext, contentSafetyMap, allowSvgRend
|
|
|
301
301
|
var bare = _bareMime(contentType);
|
|
302
302
|
if (bare.length === 0) return true; // unknown MIME → safest path
|
|
303
303
|
// text/html / text/xml / text/javascript / xhtml are inside `text/*`
|
|
304
|
-
// but the browser executes them — risky
|
|
304
|
+
// but the browser executes them — risky nodePath.
|
|
305
305
|
if (bare === "text/html" || bare === "text/xml" ||
|
|
306
306
|
bare === "text/javascript" || bare === "application/xhtml+xml") {
|
|
307
307
|
return true;
|
|
@@ -339,7 +339,7 @@ function _shouldForceAttachment(contentType, ext, contentSafetyMap, allowSvgRend
|
|
|
339
339
|
// filename is RFC 5987-encoded so non-ASCII characters survive without
|
|
340
340
|
// allowing CR/LF header injection.
|
|
341
341
|
function _attachmentDisposition(filePath) {
|
|
342
|
-
var name =
|
|
342
|
+
var name = nodePath.basename(filePath);
|
|
343
343
|
// Refuse CR/LF/NUL outright — they're already filtered upstream by
|
|
344
344
|
// the path-traversal guard, but defense-in-depth here.
|
|
345
345
|
if (/[\r\n\0]/.test(name)) name = "download";
|
|
@@ -399,7 +399,7 @@ function _httpDate(date) {
|
|
|
399
399
|
function _validateCreateOpts(opts) {
|
|
400
400
|
validateOpts.requireObject(opts, "staticServe.create", StaticServeError);
|
|
401
401
|
validateOpts.requireNonEmptyString(opts.root, "staticServe.create: root", StaticServeError, "BAD_OPT");
|
|
402
|
-
if (!
|
|
402
|
+
if (!nodeFs.existsSync(opts.root)) {
|
|
403
403
|
throw _err("BAD_OPT", "staticServe.create: root does not exist: " + opts.root);
|
|
404
404
|
}
|
|
405
405
|
if (typeof opts.mountPath === "string" && opts.mountPath.length === 0) {
|
|
@@ -586,7 +586,7 @@ async function integrity(absPath) {
|
|
|
586
586
|
if (typeof absPath !== "string" || absPath.length === 0) {
|
|
587
587
|
throw _err("BAD_OPT", "staticServe.integrity: absPath must be a non-empty string");
|
|
588
588
|
}
|
|
589
|
-
var meta = await _readMeta(
|
|
589
|
+
var meta = await _readMeta(nodePath.resolve(absPath));
|
|
590
590
|
if (!meta) throw _err("NOT_FOUND", "staticServe.integrity: file not found: " + absPath);
|
|
591
591
|
return meta.integrity;
|
|
592
592
|
}
|
|
@@ -609,7 +609,7 @@ function create(opts) {
|
|
|
609
609
|
], "staticServe.create");
|
|
610
610
|
_validateCreateOpts(opts);
|
|
611
611
|
var cfg = validateOpts.applyDefaults(opts, DEFAULTS);
|
|
612
|
-
var root =
|
|
612
|
+
var root = nodePath.resolve(opts.root);
|
|
613
613
|
var mountPath = opts.mountPath || "";
|
|
614
614
|
var hashedPattern = opts.hashedPathPattern || DEFAULT_HASHED_PATTERN;
|
|
615
615
|
var indexFile = opts.indexFile === null ? null : (opts.indexFile || DEFAULT_INDEX_FILE);
|
|
@@ -772,7 +772,7 @@ function create(opts) {
|
|
|
772
772
|
catch (_e) { return next(); }
|
|
773
773
|
if (stat.isDirectory()) {
|
|
774
774
|
if (!indexFile) return next();
|
|
775
|
-
absPath =
|
|
775
|
+
absPath = nodePath.join(absPath, indexFile);
|
|
776
776
|
}
|
|
777
777
|
|
|
778
778
|
// Force-revoke (404 — opaque to clients)
|
|
@@ -831,7 +831,7 @@ function create(opts) {
|
|
|
831
831
|
// - audit-only / warn → continue (gate emits to audit)
|
|
832
832
|
var gateBytesOverride = null;
|
|
833
833
|
if (contentSafety) {
|
|
834
|
-
var ext =
|
|
834
|
+
var ext = nodePath.extname(absPath).toLowerCase();
|
|
835
835
|
var safetyGate = contentSafety[ext];
|
|
836
836
|
if (safetyGate && typeof safetyGate.check === "function") {
|
|
837
837
|
var gateBuf;
|
|
@@ -846,7 +846,7 @@ function create(opts) {
|
|
|
846
846
|
gateDecision = await safetyGate.check({
|
|
847
847
|
bytes: gateBuf,
|
|
848
848
|
contentType: _contentTypeFor(absPath, contentTypes),
|
|
849
|
-
filename:
|
|
849
|
+
filename: nodePath.basename(absPath),
|
|
850
850
|
actor: actorCtx,
|
|
851
851
|
route: urlPath,
|
|
852
852
|
direction: "outbound",
|
|
@@ -1026,7 +1026,7 @@ function create(opts) {
|
|
|
1026
1026
|
// explicitly on). Pairs with X-Content-Type-Options: nosniff so
|
|
1027
1027
|
// browsers can't sniff the bytes back into an executable type.
|
|
1028
1028
|
if (forceAttachmentForNonText) {
|
|
1029
|
-
var dispoExt =
|
|
1029
|
+
var dispoExt = nodePath.extname(absPath).toLowerCase();
|
|
1030
1030
|
if (_shouldForceAttachment(headers["Content-Type"], dispoExt, contentSafety,
|
|
1031
1031
|
allowSvgRender, allowPdfRender)) {
|
|
1032
1032
|
headers["Content-Disposition"] = _attachmentDisposition(absPath);
|
|
@@ -1110,7 +1110,7 @@ function create(opts) {
|
|
|
1110
1110
|
}
|
|
1111
1111
|
|
|
1112
1112
|
var streamOpts = range ? { start: range.start, end: range.end } : {};
|
|
1113
|
-
var fileStream =
|
|
1113
|
+
var fileStream = nodeFs.createReadStream(absPath, streamOpts);
|
|
1114
1114
|
|
|
1115
1115
|
// Idle timeout — close the connection if the client stalls. Pattern is
|
|
1116
1116
|
// a deadline-style debounce (clearTimeout + setTimeout) tied directly
|
package/lib/template.js
CHANGED
|
@@ -89,8 +89,8 @@
|
|
|
89
89
|
* is the second line: even if a template loaded, it can't execute
|
|
90
90
|
* arbitrary JS — only the limited expression grammar above.
|
|
91
91
|
*/
|
|
92
|
-
var
|
|
93
|
-
var
|
|
92
|
+
var nodeFs = require("fs");
|
|
93
|
+
var nodePath = require("path");
|
|
94
94
|
var lazyRequire = require("./lazy-require");
|
|
95
95
|
var validateOpts = require("./validate-opts");
|
|
96
96
|
|
|
@@ -149,13 +149,13 @@ function _resolveViewPath(viewsDir, viewName) {
|
|
|
149
149
|
if (viewName.indexOf("..") !== -1 || viewName.indexOf("\0") !== -1) {
|
|
150
150
|
throw new Error("template: view name contains forbidden character: " + JSON.stringify(viewName));
|
|
151
151
|
}
|
|
152
|
-
var resolved =
|
|
153
|
-
var resolvedDir =
|
|
152
|
+
var resolved = nodePath.resolve(viewsDir, viewName + ".html");
|
|
153
|
+
var resolvedDir = nodePath.resolve(viewsDir);
|
|
154
154
|
if (resolved !== resolvedDir &&
|
|
155
|
-
!resolved.startsWith(resolvedDir +
|
|
155
|
+
!resolved.startsWith(resolvedDir + nodePath.sep)) {
|
|
156
156
|
throw new Error("template: view path escapes viewsDir: " + viewName);
|
|
157
157
|
}
|
|
158
|
-
if (!
|
|
158
|
+
if (!nodeFs.existsSync(resolved)) {
|
|
159
159
|
throw new Error("template: view not found: " + viewName);
|
|
160
160
|
}
|
|
161
161
|
return resolved;
|
|
@@ -164,11 +164,11 @@ function _resolveViewPath(viewsDir, viewName) {
|
|
|
164
164
|
function _resolvePartialPath(viewsDir, partialName) {
|
|
165
165
|
if (typeof partialName !== "string" || partialName.length === 0) return null;
|
|
166
166
|
if (partialName.indexOf("..") !== -1 || partialName.indexOf("\0") !== -1) return null;
|
|
167
|
-
var resolved =
|
|
168
|
-
var partialsDir =
|
|
167
|
+
var resolved = nodePath.resolve(viewsDir, "partials", partialName + ".html");
|
|
168
|
+
var partialsDir = nodePath.resolve(viewsDir, "partials");
|
|
169
169
|
if (resolved !== partialsDir &&
|
|
170
|
-
!resolved.startsWith(partialsDir +
|
|
171
|
-
return
|
|
170
|
+
!resolved.startsWith(partialsDir + nodePath.sep)) return null;
|
|
171
|
+
return nodeFs.existsSync(resolved) ? resolved : null;
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
// ============================================================
|
|
@@ -227,7 +227,7 @@ function _resolveExtends(viewsDir, source) {
|
|
|
227
227
|
}
|
|
228
228
|
}
|
|
229
229
|
var parentPath = _resolveViewPath(viewsDir, parentName);
|
|
230
|
-
current =
|
|
230
|
+
current = nodeFs.readFileSync(parentPath, "utf8");
|
|
231
231
|
depth++;
|
|
232
232
|
}
|
|
233
233
|
return _substituteBlocks(current, allOverrides);
|
|
@@ -245,7 +245,7 @@ function _inlinePartials(viewsDir, source, depth) {
|
|
|
245
245
|
return source.replace(/\{\{>\s*([A-Za-z_][A-Za-z0-9_-]*)\s*\}\}/g, function (_, name) {
|
|
246
246
|
var p = _resolvePartialPath(viewsDir, name);
|
|
247
247
|
if (!p) return ""; // missing partial → silent empty so a stale `{{> name}}` reference doesn't crash the render
|
|
248
|
-
return _inlinePartials(viewsDir,
|
|
248
|
+
return _inlinePartials(viewsDir, nodeFs.readFileSync(p, "utf8"), depth + 1);
|
|
249
249
|
});
|
|
250
250
|
}
|
|
251
251
|
|
|
@@ -771,10 +771,10 @@ function create(opts) {
|
|
|
771
771
|
if (!opts.viewsDir) {
|
|
772
772
|
throw new Error("template.create({ viewsDir }) is required");
|
|
773
773
|
}
|
|
774
|
-
if (!
|
|
774
|
+
if (!nodeFs.existsSync(opts.viewsDir)) {
|
|
775
775
|
throw new Error("template: viewsDir does not exist: " + opts.viewsDir);
|
|
776
776
|
}
|
|
777
|
-
var viewsDir =
|
|
777
|
+
var viewsDir = nodePath.resolve(opts.viewsDir);
|
|
778
778
|
var cacheOn = opts.cache !== false;
|
|
779
779
|
var customEscape = typeof opts.escapeHtml === "function" ? opts.escapeHtml : escapeHtml;
|
|
780
780
|
var astCache = {};
|
|
@@ -823,7 +823,7 @@ function create(opts) {
|
|
|
823
823
|
function compile(viewName) {
|
|
824
824
|
if (cacheOn && astCache[viewName]) return astCache[viewName];
|
|
825
825
|
var viewPath = _resolveViewPath(viewsDir, viewName);
|
|
826
|
-
var source =
|
|
826
|
+
var source = nodeFs.readFileSync(viewPath, "utf8");
|
|
827
827
|
source = _resolveExtends(viewsDir, source);
|
|
828
828
|
source = _inlinePartials(viewsDir, source, 0);
|
|
829
829
|
var tokens = _tokenize(source);
|
|
@@ -847,12 +847,12 @@ function create(opts) {
|
|
|
847
847
|
function precompileAll() {
|
|
848
848
|
var compiled = [];
|
|
849
849
|
function walk(dir, prefix) {
|
|
850
|
-
var entries =
|
|
850
|
+
var entries = nodeFs.readdirSync(dir, { withFileTypes: true });
|
|
851
851
|
for (var i = 0; i < entries.length; i++) {
|
|
852
852
|
var e = entries[i];
|
|
853
853
|
var rel = prefix ? prefix + "/" + e.name : e.name;
|
|
854
854
|
if (e.isDirectory()) {
|
|
855
|
-
walk(
|
|
855
|
+
walk(nodePath.join(dir, e.name), rel);
|
|
856
856
|
} else if (e.isFile() && /\.html$/.test(e.name)) {
|
|
857
857
|
var viewName = rel.replace(/\.html$/, "");
|
|
858
858
|
try {
|
|
@@ -890,8 +890,8 @@ function create(opts) {
|
|
|
890
890
|
var _default = null;
|
|
891
891
|
function _ensureDefault() {
|
|
892
892
|
if (!_default) {
|
|
893
|
-
var defaultDir =
|
|
894
|
-
if (!
|
|
893
|
+
var defaultDir = nodePath.resolve(process.cwd(), "views");
|
|
894
|
+
if (!nodeFs.existsSync(defaultDir)) {
|
|
895
895
|
throw new Error("template.render() default uses <cwd>/views which doesn't exist; " +
|
|
896
896
|
"call template.create({ viewsDir }) for a custom location");
|
|
897
897
|
}
|
package/lib/testing.js
CHANGED
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
* Operator-facing test helpers.
|
|
43
43
|
*/
|
|
44
44
|
|
|
45
|
-
var
|
|
45
|
+
var nodeFs = require("node:fs");
|
|
46
46
|
// testing.js IS the test injector — bypasses b.httpClient by design so
|
|
47
47
|
// tests can assert on raw request shape. This is the one production
|
|
48
48
|
// module where direct http.request is the contract.
|
|
@@ -62,7 +62,7 @@ var { TestingError } = require("./framework-error");
|
|
|
62
62
|
// metrics is the only place that exposes the global `tap` slot the
|
|
63
63
|
// captureMetricsTap helper swaps; pulling it lazily keeps testing.js
|
|
64
64
|
// safe to require at any framework load order.
|
|
65
|
-
var
|
|
65
|
+
var metrics = lazyRequire(function () { return require("./metrics"); });
|
|
66
66
|
|
|
67
67
|
var _err = TestingError.factory;
|
|
68
68
|
|
|
@@ -563,7 +563,7 @@ function captureObservability() {
|
|
|
563
563
|
* }
|
|
564
564
|
*/
|
|
565
565
|
function captureMetricsTap() {
|
|
566
|
-
var m =
|
|
566
|
+
var m = metrics();
|
|
567
567
|
var original = m.tap;
|
|
568
568
|
var captured = [];
|
|
569
569
|
m.tap = function (name, value, labels) {
|
|
@@ -792,9 +792,9 @@ async function waitFor(predicate, opts) {
|
|
|
792
792
|
* @example
|
|
793
793
|
* var dir = b.testing.tempDir("my-fixture");
|
|
794
794
|
* try {
|
|
795
|
-
* var
|
|
796
|
-
* var
|
|
797
|
-
*
|
|
795
|
+
* var fs = require("node:fs");
|
|
796
|
+
* var path = require("node:path");
|
|
797
|
+
* fs.writeFileSync(path.join(dir.path, "fixture.json"), '{"ok":1}');
|
|
798
798
|
* dir.path.indexOf("my-fixture-") !== -1; // → true
|
|
799
799
|
* } finally {
|
|
800
800
|
* dir.cleanup();
|
|
@@ -813,9 +813,9 @@ function tempDir(prefix) {
|
|
|
813
813
|
"tempDir: prefix must not contain '..', '/', '\\', or null bytes; got " + JSON.stringify(prefix));
|
|
814
814
|
}
|
|
815
815
|
// Path containment check mirroring static.js _resolveSafe — verify
|
|
816
|
-
// the resolved tempdir is actually inside os.tmpdir() before
|
|
816
|
+
// the resolved tempdir is actually inside os.tmpdir() before nodeFs.mkdir.
|
|
817
817
|
var root = nodePath.resolve(os.tmpdir());
|
|
818
|
-
var dirPath =
|
|
818
|
+
var dirPath = nodeFs.mkdtempSync(nodePath.join(root, prefix + "-"));
|
|
819
819
|
var resolved = nodePath.resolve(dirPath);
|
|
820
820
|
if (resolved !== root && !resolved.startsWith(root + nodePath.sep)) {
|
|
821
821
|
throw _err("BAD_STATE",
|
|
@@ -827,7 +827,7 @@ function tempDir(prefix) {
|
|
|
827
827
|
cleanup: function () {
|
|
828
828
|
if (cleanedUp) return;
|
|
829
829
|
cleanedUp = true;
|
|
830
|
-
try {
|
|
830
|
+
try { nodeFs.rmSync(resolved, { recursive: true, force: true }); }
|
|
831
831
|
catch (_e) { /* best-effort on Windows locked files */ }
|
|
832
832
|
},
|
|
833
833
|
};
|
package/lib/tls-exporter.js
CHANGED
|
@@ -31,10 +31,10 @@
|
|
|
31
31
|
* RFC 5705 / RFC 9266 TLS Exporter for binding application-layer keys and tokens to the live TLS session.
|
|
32
32
|
*/
|
|
33
33
|
|
|
34
|
-
var
|
|
34
|
+
var bCrypto = require("./crypto");
|
|
35
35
|
var C = require("./constants");
|
|
36
36
|
var lazyRequire = require("./lazy-require");
|
|
37
|
-
var
|
|
37
|
+
var numericBounds = require("./numeric-bounds");
|
|
38
38
|
var { TlsExporterError } = require("./framework-error");
|
|
39
39
|
|
|
40
40
|
var _err = TlsExporterError.factory;
|
|
@@ -119,7 +119,7 @@ function fromSocket(socketOrReq, opts) {
|
|
|
119
119
|
// length is operator-tunable; validate-when-present via numeric-bounds
|
|
120
120
|
// so a non-finite / negative / NaN input surfaces with the same error
|
|
121
121
|
// shape every other framework primitive uses for numeric opts.
|
|
122
|
-
|
|
122
|
+
numericBounds.requirePositiveFiniteIntIfPresent(opts.length,
|
|
123
123
|
"tlsExporter.fromSocket: length", TlsExporterError, "BAD_LENGTH");
|
|
124
124
|
var length = opts.length !== undefined ? opts.length : EXPORTER_LENGTH;
|
|
125
125
|
if (length < C.BYTES.bytes(16) || length > C.BYTES.bytes(255)) {
|
|
@@ -193,7 +193,7 @@ function bindToken(socketOrReq, token) {
|
|
|
193
193
|
// does NOT produce the same hash if used in another framework
|
|
194
194
|
// primitive (e.g., the audit-chain row hash).
|
|
195
195
|
var labelBuf = Buffer.from("blamejs/tls-exporter/bind/v1", "utf8");
|
|
196
|
-
return
|
|
196
|
+
return bCrypto.sha3Hash(Buffer.concat([labelBuf, exporter, tokenBuf]));
|
|
197
197
|
}
|
|
198
198
|
|
|
199
199
|
/**
|
|
@@ -226,7 +226,7 @@ function verifyTokenBinding(socketOrReq, token, claimedBinding) {
|
|
|
226
226
|
if (typeof claimedBinding !== "string" || claimedBinding.length === 0) {
|
|
227
227
|
return false;
|
|
228
228
|
}
|
|
229
|
-
return
|
|
229
|
+
return bCrypto.timingSafeEqual(actual, claimedBinding);
|
|
230
230
|
}
|
|
231
231
|
|
|
232
232
|
module.exports = {
|
package/lib/tracing.js
CHANGED
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
*/
|
|
40
40
|
|
|
41
41
|
var C = require("./constants");
|
|
42
|
-
var
|
|
42
|
+
var bCrypto = require("./crypto");
|
|
43
43
|
var validateOpts = require("./validate-opts");
|
|
44
44
|
var { defineClass } = require("./framework-error");
|
|
45
45
|
var { resolveRoute, captureResponseStatus } = require("./request-helpers");
|
|
@@ -101,10 +101,10 @@ function _formatTraceparent(traceId, spanId, flags) {
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
function _newTraceId() {
|
|
104
|
-
return
|
|
104
|
+
return bCrypto.generateToken(W3C_TRACE_ID_BYTES);
|
|
105
105
|
}
|
|
106
106
|
function _newSpanId() {
|
|
107
|
-
return
|
|
107
|
+
return bCrypto.generateToken(W3C_SPAN_ID_BYTES);
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
// ---- Pass-through span (used when OTel isn't installed) ----
|
package/lib/vault/index.js
CHANGED
|
@@ -62,8 +62,8 @@
|
|
|
62
62
|
* @card
|
|
63
63
|
* Sealed keystore that anchors every other framework subsystem holding secrets at rest: db field encryption, encrypted session storage, audit-log signing keys, OAuth refresh tokens, anything that flows through `b.vault.seal` / `b.vault.unseal`.
|
|
64
64
|
*/
|
|
65
|
-
var
|
|
66
|
-
var
|
|
65
|
+
var nodeFs = require("fs");
|
|
66
|
+
var nodePath = require("path");
|
|
67
67
|
var atomicFile = require("../atomic-file");
|
|
68
68
|
var C = require("../constants");
|
|
69
69
|
var { generateEncryptionKeyPair, encrypt, decrypt } = require("../crypto");
|
|
@@ -99,9 +99,9 @@ var log = boot("vault");
|
|
|
99
99
|
function resolvePaths(dataDir) {
|
|
100
100
|
return {
|
|
101
101
|
dataDir: dataDir,
|
|
102
|
-
plaintext:
|
|
103
|
-
sealed:
|
|
104
|
-
derivedHashSalt:
|
|
102
|
+
plaintext: nodePath.join(dataDir, "vault.key"),
|
|
103
|
+
sealed: nodePath.join(dataDir, "vault.key.sealed"),
|
|
104
|
+
derivedHashSalt: nodePath.join(dataDir, "vault.derived-hash-salt"),
|
|
105
105
|
};
|
|
106
106
|
}
|
|
107
107
|
|
|
@@ -118,7 +118,7 @@ function _readOrCreateDerivedHashSalt() {
|
|
|
118
118
|
throw new VaultError("vault/not-initialized",
|
|
119
119
|
"vault.derivedHashSalt() requires init()");
|
|
120
120
|
}
|
|
121
|
-
if (
|
|
121
|
+
if (nodeFs.existsSync(paths.derivedHashSalt)) {
|
|
122
122
|
var raw = atomicFile.readSync(paths.derivedHashSalt);
|
|
123
123
|
if (raw.length !== 32) { // allow:raw-byte-literal — 32-byte (256-bit) salt
|
|
124
124
|
throw new VaultError("vault/derived-hash-salt-corrupted",
|
|
@@ -242,16 +242,16 @@ async function init(opts) {
|
|
|
242
242
|
currentMode = mode;
|
|
243
243
|
paths = resolvePaths(opts.dataDir);
|
|
244
244
|
|
|
245
|
-
if (!
|
|
246
|
-
|
|
245
|
+
if (!nodeFs.existsSync(paths.dataDir)) {
|
|
246
|
+
nodeFs.mkdirSync(paths.dataDir, { recursive: true });
|
|
247
247
|
}
|
|
248
248
|
|
|
249
249
|
// Sweep tmp files left behind by a previously-crashed write
|
|
250
250
|
atomicFile.cleanOrphans(paths.sealed);
|
|
251
251
|
atomicFile.cleanOrphans(paths.plaintext);
|
|
252
252
|
|
|
253
|
-
var hasPlaintext =
|
|
254
|
-
var hasSealed =
|
|
253
|
+
var hasPlaintext = nodeFs.existsSync(paths.plaintext);
|
|
254
|
+
var hasSealed = nodeFs.existsSync(paths.sealed);
|
|
255
255
|
|
|
256
256
|
// Refuse to guess when both files coexist
|
|
257
257
|
if (hasPlaintext && hasSealed) {
|
|
@@ -287,7 +287,7 @@ async function init(opts) {
|
|
|
287
287
|
}
|
|
288
288
|
|
|
289
289
|
function initPlaintext() {
|
|
290
|
-
if (
|
|
290
|
+
if (nodeFs.existsSync(paths.plaintext)) {
|
|
291
291
|
var loaded;
|
|
292
292
|
try {
|
|
293
293
|
loaded = safeJson.parse(atomicFile.readSync(paths.plaintext), {
|