@blamejs/core 0.9.14 → 0.9.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 +1 -0
- 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/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/config-drift.js +15 -15
- package/lib/content-credentials.js +4 -4
- package/lib/credential-hash.js +3 -3
- 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 +35 -35
- 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 +2 -2
- package/lib/external-db.js +2 -2
- package/lib/fdx.js +2 -2
- package/lib/file-upload.js +30 -30
- 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 +9 -9
- 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/mail-bounce.js +2 -2
- package/lib/mail-mdn.js +2 -2
- package/lib/mail-srs.js +2 -2
- package/lib/mail.js +4 -4
- 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 +16 -16
- 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/migrations.js +3 -3
- package/lib/mtls-ca.js +26 -26
- package/lib/mtls-engine-default.js +5 -5
- 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 +13 -13
- package/lib/notify.js +3 -3
- 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-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 +18 -18
- package/lib/restore-rollback.js +34 -34
- package/lib/restore.js +16 -16
- package/lib/router.js +13 -13
- package/lib/sandbox.js +8 -8
- package/lib/sec-cyber.js +3 -3
- package/lib/security-assert.js +2 -2
- package/lib/seeders.js +4 -4
- 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 +7 -7
- 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/watcher.js +23 -23
- package/lib/webhook.js +10 -10
- package/lib/worker-pool.js +6 -6
- package/lib/ws-client.js +4 -4
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/restore.js
CHANGED
|
@@ -51,11 +51,11 @@
|
|
|
51
51
|
* manual recovery)
|
|
52
52
|
*/
|
|
53
53
|
|
|
54
|
-
var
|
|
54
|
+
var nodeFs = require("fs");
|
|
55
55
|
var os = require("os");
|
|
56
|
-
var
|
|
56
|
+
var nodePath = require("path");
|
|
57
57
|
var C = require("./constants");
|
|
58
|
-
var
|
|
58
|
+
var bCrypto = require("./crypto");
|
|
59
59
|
var numericChecks = require("./numeric-checks");
|
|
60
60
|
var restoreBundle = require("./restore-bundle");
|
|
61
61
|
var restoreRollback = require("./restore-rollback");
|
|
@@ -128,11 +128,11 @@ function create(opts) {
|
|
|
128
128
|
while (stack.length > 0) {
|
|
129
129
|
var current = stack.pop();
|
|
130
130
|
var entries;
|
|
131
|
-
try { entries =
|
|
131
|
+
try { entries = nodeFs.readdirSync(current, { withFileTypes: true }); }
|
|
132
132
|
catch (_e) { continue; }
|
|
133
133
|
for (var i = 0; i < entries.length; i++) {
|
|
134
134
|
var entry = entries[i];
|
|
135
|
-
var full =
|
|
135
|
+
var full = nodePath.join(current, entry.name);
|
|
136
136
|
if (entry.isDirectory()) {
|
|
137
137
|
stack.push(full);
|
|
138
138
|
} else if (entry.isFile()) {
|
|
@@ -141,7 +141,7 @@ function create(opts) {
|
|
|
141
141
|
return { tooManyFiles: true, fileCount: fileCount };
|
|
142
142
|
}
|
|
143
143
|
try {
|
|
144
|
-
totalBytes +=
|
|
144
|
+
totalBytes += nodeFs.statSync(full).size;
|
|
145
145
|
if (totalBytes > maxPulledBytes) {
|
|
146
146
|
return { tooManyBytes: true, totalBytes: totalBytes };
|
|
147
147
|
}
|
|
@@ -200,8 +200,8 @@ function create(opts) {
|
|
|
200
200
|
"inspect: bundle '" + bundleId + "' not in storage");
|
|
201
201
|
}
|
|
202
202
|
await _preflightBundleSize(bundleId);
|
|
203
|
-
var pullDir =
|
|
204
|
-
"blamejs-restore-inspect-" +
|
|
203
|
+
var pullDir = nodePath.join(os.tmpdir(),
|
|
204
|
+
"blamejs-restore-inspect-" + bCrypto.generateToken(4));
|
|
205
205
|
try {
|
|
206
206
|
await storage.readBundle(bundleId, pullDir);
|
|
207
207
|
var pulled = _walkPullDirFootprint(pullDir);
|
|
@@ -217,7 +217,7 @@ function create(opts) {
|
|
|
217
217
|
}
|
|
218
218
|
return restoreBundle.inspect({ bundleDir: pullDir });
|
|
219
219
|
} finally {
|
|
220
|
-
try {
|
|
220
|
+
try { nodeFs.rmSync(pullDir, { recursive: true, force: true }); } catch (_e) { /* best-effort tmpdir cleanup */ }
|
|
221
221
|
}
|
|
222
222
|
}
|
|
223
223
|
|
|
@@ -234,13 +234,13 @@ function create(opts) {
|
|
|
234
234
|
"run: bundle '" + bundleId + "' not in storage");
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
-
var pullId =
|
|
238
|
-
var pullDir =
|
|
239
|
-
var stagingDir =
|
|
237
|
+
var pullId = bCrypto.generateToken(4);
|
|
238
|
+
var pullDir = nodePath.join(os.tmpdir(), "blamejs-restore-pull-" + pullId);
|
|
239
|
+
var stagingDir = nodePath.join(os.tmpdir(), "blamejs-restore-staging-" + pullId);
|
|
240
240
|
|
|
241
241
|
function _cleanupTmp() {
|
|
242
|
-
try {
|
|
243
|
-
try {
|
|
242
|
+
try { nodeFs.rmSync(pullDir, { recursive: true, force: true }); } catch (_e) { /* best-effort tmpdir cleanup */ }
|
|
243
|
+
try { nodeFs.rmSync(stagingDir, { recursive: true, force: true }); } catch (_e) { /* best-effort tmpdir cleanup */ }
|
|
244
244
|
}
|
|
245
245
|
|
|
246
246
|
// 1. Pull bundle out of storage
|
|
@@ -319,7 +319,7 @@ function create(opts) {
|
|
|
319
319
|
} catch (e) {
|
|
320
320
|
// Pull dir is safe to clean (the source bundle is in storage);
|
|
321
321
|
// staging stays for manual recovery.
|
|
322
|
-
try {
|
|
322
|
+
try { nodeFs.rmSync(pullDir, { recursive: true, force: true }); } catch (_e) { /* best-effort tmpdir cleanup */ }
|
|
323
323
|
_emitAudit("restore.failure",
|
|
324
324
|
{ bundleId: bundleId, reason: "swap: " + ((e && e.message) || String(e)) },
|
|
325
325
|
"failure");
|
|
@@ -331,7 +331,7 @@ function create(opts) {
|
|
|
331
331
|
}
|
|
332
332
|
|
|
333
333
|
// 4. Clean up the pull dir (source bundle still in storage)
|
|
334
|
-
try {
|
|
334
|
+
try { nodeFs.rmSync(pullDir, { recursive: true, force: true }); } catch (_e) { /* best-effort tmpdir cleanup */ }
|
|
335
335
|
|
|
336
336
|
var summary = {
|
|
337
337
|
bundleId: bundleId,
|
package/lib/router.js
CHANGED
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
*/
|
|
35
35
|
var http = require("http");
|
|
36
36
|
var http2 = require("http2");
|
|
37
|
-
var
|
|
38
|
-
var
|
|
37
|
+
var nodeFs = require("fs");
|
|
38
|
+
var nodePath = require("path");
|
|
39
39
|
var C = require("./constants");
|
|
40
40
|
var requestHelpers = require("./request-helpers");
|
|
41
41
|
var lazyRequire = require("./lazy-require");
|
|
@@ -296,8 +296,8 @@ class Router {
|
|
|
296
296
|
this.routes = [];
|
|
297
297
|
this.middleware = [];
|
|
298
298
|
// WebSocket routes are kept separate from HTTP routes — they're
|
|
299
|
-
// matched on the upgrade / Extended CONNECT
|
|
300
|
-
// verb. Map<
|
|
299
|
+
// matched on the upgrade / Extended CONNECT nodePath, not on a method
|
|
300
|
+
// verb. Map<nodePath, { handler, opts }>.
|
|
301
301
|
this._wsRoutes = new Map();
|
|
302
302
|
// Active WebSocket connections opened through router.ws(). Tracked
|
|
303
303
|
// so router.closeWebSockets() can do a clean rolling-shutdown.
|
|
@@ -407,7 +407,7 @@ class Router {
|
|
|
407
407
|
// process.exit(0);
|
|
408
408
|
//
|
|
409
409
|
// Tests use the same primitive in teardown — no parallel cleanup
|
|
410
|
-
//
|
|
410
|
+
// nodePath, no h1-upgrade detached-socket workaround.
|
|
411
411
|
async closeWebSockets(opts) {
|
|
412
412
|
opts = opts || {};
|
|
413
413
|
var timeoutMs = typeof opts.timeoutMs === "number" ? opts.timeoutMs : C.TIME.seconds(5);
|
|
@@ -603,7 +603,7 @@ class Router {
|
|
|
603
603
|
|
|
604
604
|
// ---- WebSocket route registration ----
|
|
605
605
|
//
|
|
606
|
-
// ws(
|
|
606
|
+
// ws(nodePath, handler, opts?)
|
|
607
607
|
// path — exact match. Path-param patterns aren't supported on
|
|
608
608
|
// upgrade requests; operators that need dynamic paths
|
|
609
609
|
// register one ws route per stable shape.
|
|
@@ -1089,7 +1089,7 @@ class Router {
|
|
|
1089
1089
|
// server's emitter list clean for HTTP-only deployments.
|
|
1090
1090
|
if (self._wsRoutes.size > 0) {
|
|
1091
1091
|
// h1 upgrade event — fires for "Upgrade: websocket" from h1
|
|
1092
|
-
// clients. Routes by
|
|
1092
|
+
// clients. Routes by nodePath; refuses with 426 in h2-only mode.
|
|
1093
1093
|
server.on("upgrade", function (req, socket, head) {
|
|
1094
1094
|
var pathname = String(req.url || "/").split("?")[0];
|
|
1095
1095
|
var route = self._wsRoutes.get(pathname);
|
|
@@ -1201,18 +1201,18 @@ class Router {
|
|
|
1201
1201
|
*/
|
|
1202
1202
|
// Static file serving middleware
|
|
1203
1203
|
function serveStatic(dir) {
|
|
1204
|
-
var root =
|
|
1204
|
+
var root = nodePath.resolve(dir);
|
|
1205
1205
|
return (req, res, next) => {
|
|
1206
1206
|
if (req.method !== "GET") return next();
|
|
1207
1207
|
var rel = req.pathname;
|
|
1208
1208
|
if (rel.includes("\0")) return next();
|
|
1209
|
-
var filePath =
|
|
1209
|
+
var filePath = nodePath.resolve(nodePath.join(root, rel));
|
|
1210
1210
|
if (!filePath.startsWith(root)) return next();
|
|
1211
|
-
if (!
|
|
1211
|
+
if (!nodeFs.existsSync(filePath) || nodeFs.statSync(filePath).isDirectory()) return next();
|
|
1212
1212
|
|
|
1213
|
-
var ext =
|
|
1213
|
+
var ext = nodePath.extname(filePath).toLowerCase();
|
|
1214
1214
|
var mime = MIME_TYPES[ext] || "application/octet-stream";
|
|
1215
|
-
var stat =
|
|
1215
|
+
var stat = nodeFs.statSync(filePath);
|
|
1216
1216
|
var hasVersion = req.url && req.url.includes("?v=");
|
|
1217
1217
|
var cacheControl = hasVersion
|
|
1218
1218
|
? "public, max-age=31536000, immutable"
|
|
@@ -1222,7 +1222,7 @@ function serveStatic(dir) {
|
|
|
1222
1222
|
"Content-Length": stat.size,
|
|
1223
1223
|
"Cache-Control": cacheControl,
|
|
1224
1224
|
});
|
|
1225
|
-
|
|
1225
|
+
nodeFs.createReadStream(filePath).pipe(res);
|
|
1226
1226
|
};
|
|
1227
1227
|
}
|
|
1228
1228
|
|
package/lib/sandbox.js
CHANGED
|
@@ -78,11 +78,11 @@
|
|
|
78
78
|
* arbitrary source from the public internet.
|
|
79
79
|
*/
|
|
80
80
|
|
|
81
|
-
var
|
|
81
|
+
var nodePath = require("path");
|
|
82
82
|
var lazyRequire = require("./lazy-require");
|
|
83
83
|
var validateOpts = require("./validate-opts");
|
|
84
84
|
var numericBounds = require("./numeric-bounds");
|
|
85
|
-
var
|
|
85
|
+
var C = require("./constants");
|
|
86
86
|
var { SandboxError } = require("./framework-error");
|
|
87
87
|
|
|
88
88
|
var audit = lazyRequire(function () { return require("./audit"); });
|
|
@@ -110,14 +110,14 @@ var ALWAYS_AVAILABLE = Object.freeze([
|
|
|
110
110
|
"Promise", "Error", "TypeError", "RangeError", "RegExp",
|
|
111
111
|
]);
|
|
112
112
|
|
|
113
|
-
var WORKER_PATH =
|
|
113
|
+
var WORKER_PATH = nodePath.resolve(__dirname, "sandbox-worker.js");
|
|
114
114
|
|
|
115
115
|
// Default caps. Sourced from C.* helpers so the unit lives at the call site.
|
|
116
116
|
var DEFAULT_TIMEOUT_MS = 250;
|
|
117
|
-
var MAX_TIMEOUT_MS =
|
|
118
|
-
var DEFAULT_MAX_BYTES =
|
|
119
|
-
var MAX_MAX_BYTES =
|
|
120
|
-
var MIN_MAX_BYTES =
|
|
117
|
+
var MAX_TIMEOUT_MS = C.TIME.seconds(10);
|
|
118
|
+
var DEFAULT_MAX_BYTES = C.BYTES.mib(64);
|
|
119
|
+
var MAX_MAX_BYTES = C.BYTES.gib(1);
|
|
120
|
+
var MIN_MAX_BYTES = C.BYTES.mib(4);
|
|
121
121
|
|
|
122
122
|
function _validateAllowed(allowed) {
|
|
123
123
|
if (allowed === undefined || allowed === null) return [];
|
|
@@ -217,7 +217,7 @@ function run(opts) {
|
|
|
217
217
|
// floor so the worker can boot. Round each cap down to a MiB integer.
|
|
218
218
|
// Floors / caps are quanta of MiB chosen to fit a small embedded
|
|
219
219
|
// worker; passed straight to v8's resourceLimits.
|
|
220
|
-
var oneMib =
|
|
220
|
+
var oneMib = C.BYTES.mib(1);
|
|
221
221
|
// The MiB-unit caps below are integers passed directly to v8's
|
|
222
222
|
// resourceLimits (already typed in MiB by the v8 API), not byte
|
|
223
223
|
// counts - the constants helpers don't apply.
|
package/lib/sec-cyber.js
CHANGED
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
var audit = require("./audit");
|
|
70
70
|
var C = require("./constants");
|
|
71
71
|
var validateOpts = require("./validate-opts");
|
|
72
|
-
var
|
|
72
|
+
var numericBounds = require("./numeric-bounds");
|
|
73
73
|
var { defineClass } = require("./framework-error");
|
|
74
74
|
var SecCyberError = defineClass("SecCyberError", { alwaysPermanent: true });
|
|
75
75
|
|
|
@@ -104,9 +104,9 @@ function eightKArtifact(opts) {
|
|
|
104
104
|
"secCyber.eightKArtifact: registrant.name", SecCyberError, "BAD_REGISTRANT_NAME");
|
|
105
105
|
validateOpts.requireNonEmptyString(opts.registrant.cik,
|
|
106
106
|
"secCyber.eightKArtifact: registrant.cik", SecCyberError, "BAD_CIK");
|
|
107
|
-
|
|
107
|
+
numericBounds.requirePositiveFiniteIntIfPresent(opts.detectedAt,
|
|
108
108
|
"secCyber.eightKArtifact: detectedAt", SecCyberError, "BAD_DETECTED_AT");
|
|
109
|
-
|
|
109
|
+
numericBounds.requirePositiveFiniteIntIfPresent(opts.materialityDeterminedAt,
|
|
110
110
|
"secCyber.eightKArtifact: materialityDeterminedAt", SecCyberError, "BAD_MAT_AT");
|
|
111
111
|
|
|
112
112
|
if (FINDINGS.indexOf(opts.materialityFinding) === -1) {
|
package/lib/security-assert.js
CHANGED
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
* non-function extra entry, etc.) so the operator catches typos at
|
|
68
68
|
* boot, not at the moment they were trying to gate the boot.
|
|
69
69
|
*/
|
|
70
|
-
var
|
|
70
|
+
var nodeFs = require("fs");
|
|
71
71
|
var nodeTls = require("node:tls");
|
|
72
72
|
var lazyRequire = require("./lazy-require");
|
|
73
73
|
var safeEnv = require("./parsers/safe-env");
|
|
@@ -286,7 +286,7 @@ async function assertProduction(opts) {
|
|
|
286
286
|
if (typeof opts.dataDir === "string" && opts.dataDir.length > 0 && process.platform !== "win32") {
|
|
287
287
|
var maxMode = typeof opts.maxDataDirMode === "number" ? opts.maxDataDirMode : 0o750;
|
|
288
288
|
try {
|
|
289
|
-
var stat =
|
|
289
|
+
var stat = nodeFs.statSync(opts.dataDir);
|
|
290
290
|
var mode = stat.mode & 0o777;
|
|
291
291
|
if (mode > maxMode) {
|
|
292
292
|
failures.push({ ok: false, code: "security/datadir-permissions",
|
package/lib/seeders.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*
|
|
16
16
|
* module.exports = {
|
|
17
17
|
* description: "Create default admin user for local dev",
|
|
18
|
-
* // Optional — when omitted, the env is inferred from the
|
|
18
|
+
* // Optional — when omitted, the env is inferred from the nodePath.
|
|
19
19
|
* // When present, this seed only applies under one of these envs.
|
|
20
20
|
* envs: ["dev", "test"],
|
|
21
21
|
* // Default false — applied once and recorded in registry.
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
* applied state)
|
|
55
55
|
*/
|
|
56
56
|
|
|
57
|
-
var
|
|
57
|
+
var nodePath = require("path");
|
|
58
58
|
var atomicFile = require("./atomic-file");
|
|
59
59
|
var C = require("./constants");
|
|
60
60
|
var dbSchema = require("./db-schema");
|
|
@@ -161,7 +161,7 @@ function _resolveDb(opts) {
|
|
|
161
161
|
// ---- Directory walking + seed loading ----
|
|
162
162
|
|
|
163
163
|
function _envDir(rootDir, env) {
|
|
164
|
-
return
|
|
164
|
+
return nodePath.join(rootDir, env);
|
|
165
165
|
}
|
|
166
166
|
|
|
167
167
|
function _listSeedFiles(rootDir, env) {
|
|
@@ -171,7 +171,7 @@ function _listSeedFiles(rootDir, env) {
|
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
function _loadSeed(rootDir, env, file) {
|
|
174
|
-
var fullPath =
|
|
174
|
+
var fullPath = nodePath.join(_envDir(rootDir, env), file);
|
|
175
175
|
// Drop require cache for this path so a test rewriting a fixture
|
|
176
176
|
// between calls picks it up. Production restarts the process anyway.
|
|
177
177
|
try { delete require.cache[require.resolve(fullPath)]; } catch (_e) { /* not yet cached */ }
|
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
|