@secure-exec/nodejs 0.2.0-rc.1
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/LICENSE +191 -0
- package/README.md +7 -0
- package/dist/bindings.d.ts +31 -0
- package/dist/bindings.js +67 -0
- package/dist/bridge/active-handles.d.ts +22 -0
- package/dist/bridge/active-handles.js +112 -0
- package/dist/bridge/child-process.d.ts +99 -0
- package/dist/bridge/child-process.js +672 -0
- package/dist/bridge/dispatch.d.ts +2 -0
- package/dist/bridge/dispatch.js +40 -0
- package/dist/bridge/fs.d.ts +502 -0
- package/dist/bridge/fs.js +3307 -0
- package/dist/bridge/index.d.ts +10 -0
- package/dist/bridge/index.js +41 -0
- package/dist/bridge/module.d.ts +75 -0
- package/dist/bridge/module.js +325 -0
- package/dist/bridge/network.d.ts +1093 -0
- package/dist/bridge/network.js +8651 -0
- package/dist/bridge/os.d.ts +13 -0
- package/dist/bridge/os.js +256 -0
- package/dist/bridge/polyfills.d.ts +9 -0
- package/dist/bridge/polyfills.js +67 -0
- package/dist/bridge/process.d.ts +121 -0
- package/dist/bridge/process.js +1382 -0
- package/dist/bridge/whatwg-url.d.ts +67 -0
- package/dist/bridge/whatwg-url.js +712 -0
- package/dist/bridge-contract.d.ts +774 -0
- package/dist/bridge-contract.js +172 -0
- package/dist/bridge-handlers.d.ts +199 -0
- package/dist/bridge-handlers.js +4263 -0
- package/dist/bridge-loader.d.ts +9 -0
- package/dist/bridge-loader.js +87 -0
- package/dist/bridge-setup.d.ts +1 -0
- package/dist/bridge-setup.js +3 -0
- package/dist/bridge.js +21652 -0
- package/dist/builtin-modules.d.ts +25 -0
- package/dist/builtin-modules.js +312 -0
- package/dist/default-network-adapter.d.ts +13 -0
- package/dist/default-network-adapter.js +351 -0
- package/dist/driver.d.ts +87 -0
- package/dist/driver.js +191 -0
- package/dist/esm-compiler.d.ts +14 -0
- package/dist/esm-compiler.js +68 -0
- package/dist/execution-driver.d.ts +37 -0
- package/dist/execution-driver.js +977 -0
- package/dist/host-network-adapter.d.ts +7 -0
- package/dist/host-network-adapter.js +279 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +23 -0
- package/dist/isolate-bootstrap.d.ts +86 -0
- package/dist/isolate-bootstrap.js +125 -0
- package/dist/ivm-compat.d.ts +7 -0
- package/dist/ivm-compat.js +31 -0
- package/dist/kernel-runtime.d.ts +58 -0
- package/dist/kernel-runtime.js +535 -0
- package/dist/module-access.d.ts +75 -0
- package/dist/module-access.js +606 -0
- package/dist/module-resolver.d.ts +8 -0
- package/dist/module-resolver.js +150 -0
- package/dist/os-filesystem.d.ts +42 -0
- package/dist/os-filesystem.js +161 -0
- package/dist/package-bundler.d.ts +36 -0
- package/dist/package-bundler.js +497 -0
- package/dist/polyfills.d.ts +17 -0
- package/dist/polyfills.js +97 -0
- package/dist/worker-adapter.d.ts +21 -0
- package/dist/worker-adapter.js +34 -0
- package/package.json +123 -0
|
@@ -0,0 +1,606 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as fsSync from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { createEaccesError } from "@secure-exec/core/internal/shared/errors";
|
|
5
|
+
import { O_TRUNC } from "@secure-exec/core";
|
|
6
|
+
const MODULE_ACCESS_INVALID_CONFIG = "ERR_MODULE_ACCESS_INVALID_CONFIG";
|
|
7
|
+
const MODULE_ACCESS_OUT_OF_SCOPE = "ERR_MODULE_ACCESS_OUT_OF_SCOPE";
|
|
8
|
+
const MODULE_ACCESS_NATIVE_ADDON = "ERR_MODULE_ACCESS_NATIVE_ADDON";
|
|
9
|
+
const SANDBOX_APP_ROOT = "/root";
|
|
10
|
+
const SANDBOX_NODE_MODULES_ROOT = `${SANDBOX_APP_ROOT}/node_modules`;
|
|
11
|
+
const VIRTUAL_DIR_MODE = 0o040755;
|
|
12
|
+
function toVirtualPath(value) {
|
|
13
|
+
if (!value || value === ".")
|
|
14
|
+
return "/";
|
|
15
|
+
const normalized = path.posix.normalize(value.startsWith("/") ? value : `/${value}`);
|
|
16
|
+
if (normalized.length > 1 && normalized.endsWith("/")) {
|
|
17
|
+
return normalized.slice(0, -1);
|
|
18
|
+
}
|
|
19
|
+
return normalized;
|
|
20
|
+
}
|
|
21
|
+
function isWithinPath(candidate, parent) {
|
|
22
|
+
const relative = path.relative(parent, candidate);
|
|
23
|
+
return (relative === "" ||
|
|
24
|
+
(!relative.startsWith("..") && !path.isAbsolute(relative)));
|
|
25
|
+
}
|
|
26
|
+
function startsWithPath(value, prefix) {
|
|
27
|
+
return value === prefix || value.startsWith(`${prefix}/`);
|
|
28
|
+
}
|
|
29
|
+
function createEnoentError(syscall, targetPath) {
|
|
30
|
+
const error = new Error(`ENOENT: no such file or directory, ${syscall} '${targetPath}'`);
|
|
31
|
+
error.code = "ENOENT";
|
|
32
|
+
error.path = targetPath;
|
|
33
|
+
error.syscall = syscall;
|
|
34
|
+
return error;
|
|
35
|
+
}
|
|
36
|
+
function createModuleAccessError(code, message) {
|
|
37
|
+
const error = new Error(`${code}: ${message}`);
|
|
38
|
+
error.code = code;
|
|
39
|
+
return error;
|
|
40
|
+
}
|
|
41
|
+
function createVirtualDirStat() {
|
|
42
|
+
const now = Date.now();
|
|
43
|
+
return {
|
|
44
|
+
mode: VIRTUAL_DIR_MODE,
|
|
45
|
+
size: 4096,
|
|
46
|
+
isDirectory: true,
|
|
47
|
+
isSymbolicLink: false,
|
|
48
|
+
atimeMs: now,
|
|
49
|
+
mtimeMs: now,
|
|
50
|
+
ctimeMs: now,
|
|
51
|
+
birthtimeMs: now,
|
|
52
|
+
ino: 0,
|
|
53
|
+
nlink: 2,
|
|
54
|
+
uid: 0,
|
|
55
|
+
gid: 0,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function normalizeOverlayPath(pathValue) {
|
|
59
|
+
return toVirtualPath(pathValue);
|
|
60
|
+
}
|
|
61
|
+
function isNativeAddonPath(pathValue) {
|
|
62
|
+
return pathValue.endsWith(".node");
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Walk the host node_modules directory and its pnpm virtual-store, resolving
|
|
66
|
+
* symlink targets to build the full set of allowed host paths. This prevents
|
|
67
|
+
* symlink-based escapes from the overlay projection.
|
|
68
|
+
*/
|
|
69
|
+
function collectOverlayAllowedRoots(hostNodeModulesRoot) {
|
|
70
|
+
const roots = new Set([hostNodeModulesRoot]);
|
|
71
|
+
const symlinkScanRoots = [hostNodeModulesRoot, path.join(hostNodeModulesRoot, ".pnpm", "node_modules")];
|
|
72
|
+
const addSymlinkTarget = (entryPath) => {
|
|
73
|
+
try {
|
|
74
|
+
const target = fsSync.realpathSync(entryPath);
|
|
75
|
+
roots.add(target);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// Ignore broken symlinks.
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
const scanDirForSymlinks = (scanRoot) => {
|
|
82
|
+
let entries = [];
|
|
83
|
+
try {
|
|
84
|
+
entries = fsSync.readdirSync(scanRoot, { withFileTypes: true });
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
for (const entry of entries) {
|
|
90
|
+
const entryPath = path.join(scanRoot, entry.name);
|
|
91
|
+
if (entry.isSymbolicLink()) {
|
|
92
|
+
addSymlinkTarget(entryPath);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (entry.isDirectory() && entry.name.startsWith("@")) {
|
|
96
|
+
let scopedEntries = [];
|
|
97
|
+
try {
|
|
98
|
+
scopedEntries = fsSync.readdirSync(entryPath, { withFileTypes: true });
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
for (const scopedEntry of scopedEntries) {
|
|
104
|
+
if (!scopedEntry.isSymbolicLink())
|
|
105
|
+
continue;
|
|
106
|
+
addSymlinkTarget(path.join(entryPath, scopedEntry.name));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
for (const scanRoot of symlinkScanRoots) {
|
|
112
|
+
scanDirForSymlinks(scanRoot);
|
|
113
|
+
}
|
|
114
|
+
return [...roots];
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Union filesystem that overlays host `node_modules` (read-only) onto a base
|
|
118
|
+
* VFS. Sandbox code sees `/root/node_modules/...` which maps to the host's
|
|
119
|
+
* real `<cwd>/node_modules/...`. Write operations to the overlay throw EACCES.
|
|
120
|
+
* Symlinks are resolved and validated against the allowed-roots allowlist to
|
|
121
|
+
* prevent path-traversal escapes. Native `.node` addons are rejected.
|
|
122
|
+
*/
|
|
123
|
+
export class ModuleAccessFileSystem {
|
|
124
|
+
baseFileSystem;
|
|
125
|
+
hostNodeModulesRoot;
|
|
126
|
+
overlayAllowedRoots;
|
|
127
|
+
constructor(baseFileSystem, options) {
|
|
128
|
+
this.baseFileSystem = baseFileSystem;
|
|
129
|
+
const cwdInput = options.cwd ?? process.cwd();
|
|
130
|
+
if (options.cwd !== undefined && !path.isAbsolute(options.cwd)) {
|
|
131
|
+
throw createModuleAccessError(MODULE_ACCESS_INVALID_CONFIG, `moduleAccess.cwd must be an absolute path, got '${options.cwd}'`);
|
|
132
|
+
}
|
|
133
|
+
const cwd = path.resolve(cwdInput);
|
|
134
|
+
const nodeModulesPath = path.join(cwd, "node_modules");
|
|
135
|
+
try {
|
|
136
|
+
this.hostNodeModulesRoot = fsSync.realpathSync(nodeModulesPath);
|
|
137
|
+
this.overlayAllowedRoots = collectOverlayAllowedRoots(this.hostNodeModulesRoot);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
this.hostNodeModulesRoot = null;
|
|
141
|
+
this.overlayAllowedRoots = [];
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
isWithinAllowedOverlayRoots(canonicalPath) {
|
|
145
|
+
return this.overlayAllowedRoots.some((root) => isWithinPath(canonicalPath, root));
|
|
146
|
+
}
|
|
147
|
+
isSyntheticPath(virtualPath) {
|
|
148
|
+
if (virtualPath === "/" || virtualPath === SANDBOX_APP_ROOT) {
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
if (virtualPath === SANDBOX_NODE_MODULES_ROOT) {
|
|
152
|
+
return this.hostNodeModulesRoot !== null;
|
|
153
|
+
}
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
syntheticChildren(pathValue) {
|
|
157
|
+
const entries = new Map();
|
|
158
|
+
if (pathValue === "/") {
|
|
159
|
+
entries.set("app", true);
|
|
160
|
+
}
|
|
161
|
+
if (pathValue === SANDBOX_APP_ROOT && this.hostNodeModulesRoot !== null) {
|
|
162
|
+
entries.set("node_modules", true);
|
|
163
|
+
}
|
|
164
|
+
return entries;
|
|
165
|
+
}
|
|
166
|
+
isReadOnlyProjectionPath(virtualPath) {
|
|
167
|
+
return startsWithPath(virtualPath, SANDBOX_NODE_MODULES_ROOT);
|
|
168
|
+
}
|
|
169
|
+
shouldMergeBase(pathValue) {
|
|
170
|
+
return (pathValue === "/" ||
|
|
171
|
+
pathValue === SANDBOX_APP_ROOT ||
|
|
172
|
+
!startsWithPath(pathValue, SANDBOX_NODE_MODULES_ROOT));
|
|
173
|
+
}
|
|
174
|
+
overlayHostPathFor(virtualPath) {
|
|
175
|
+
if (!startsWithPath(virtualPath, SANDBOX_NODE_MODULES_ROOT)) {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
if (!this.hostNodeModulesRoot) {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
if (virtualPath === SANDBOX_NODE_MODULES_ROOT) {
|
|
182
|
+
return this.hostNodeModulesRoot;
|
|
183
|
+
}
|
|
184
|
+
const relative = path.posix
|
|
185
|
+
.relative(SANDBOX_NODE_MODULES_ROOT, virtualPath)
|
|
186
|
+
.replace(/^\/+/, "");
|
|
187
|
+
if (!relative) {
|
|
188
|
+
return this.hostNodeModulesRoot;
|
|
189
|
+
}
|
|
190
|
+
return path.join(this.hostNodeModulesRoot, ...relative.split("/"));
|
|
191
|
+
}
|
|
192
|
+
prepareOpenSync(pathValue, flags) {
|
|
193
|
+
const virtualPath = normalizeOverlayPath(pathValue);
|
|
194
|
+
if (this.isReadOnlyProjectionPath(virtualPath)) {
|
|
195
|
+
throw createEaccesError((flags & O_TRUNC) !== 0 ? "truncate" : "write", virtualPath);
|
|
196
|
+
}
|
|
197
|
+
const syncBase = this.baseFileSystem;
|
|
198
|
+
return syncBase?.prepareOpenSync?.(virtualPath, flags) ?? false;
|
|
199
|
+
}
|
|
200
|
+
/** Translate a sandbox path to the corresponding host path (for sync module resolution). */
|
|
201
|
+
toHostPath(sandboxPath) {
|
|
202
|
+
return this.overlayHostPathFor(normalizeOverlayPath(sandboxPath));
|
|
203
|
+
}
|
|
204
|
+
/** Translate a host path back to the sandbox path (reverse of toHostPath). */
|
|
205
|
+
toSandboxPath(hostPath) {
|
|
206
|
+
if (this.hostNodeModulesRoot && isWithinPath(hostPath, this.hostNodeModulesRoot)) {
|
|
207
|
+
const relative = path.relative(this.hostNodeModulesRoot, hostPath);
|
|
208
|
+
return path.posix.join(SANDBOX_NODE_MODULES_ROOT, ...relative.split(path.sep));
|
|
209
|
+
}
|
|
210
|
+
return hostPath;
|
|
211
|
+
}
|
|
212
|
+
async resolveOverlayHostPath(virtualPath, syscall) {
|
|
213
|
+
if (isNativeAddonPath(virtualPath)) {
|
|
214
|
+
throw createModuleAccessError(MODULE_ACCESS_NATIVE_ADDON, `native addon '${virtualPath}' is not supported for module overlay`);
|
|
215
|
+
}
|
|
216
|
+
const hostPath = this.overlayHostPathFor(virtualPath);
|
|
217
|
+
if (!hostPath) {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
try {
|
|
221
|
+
const canonical = await fs.realpath(hostPath);
|
|
222
|
+
if (!this.hostNodeModulesRoot ||
|
|
223
|
+
!this.isWithinAllowedOverlayRoots(canonical)) {
|
|
224
|
+
throw createModuleAccessError(MODULE_ACCESS_OUT_OF_SCOPE, `resolved path for '${virtualPath}' escapes allowed overlay roots`);
|
|
225
|
+
}
|
|
226
|
+
if (isNativeAddonPath(canonical)) {
|
|
227
|
+
throw createModuleAccessError(MODULE_ACCESS_NATIVE_ADDON, `native addon '${virtualPath}' is not supported for module overlay`);
|
|
228
|
+
}
|
|
229
|
+
return canonical;
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
const err = error;
|
|
233
|
+
if (err?.code === "ENOENT") {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
if (err?.code === MODULE_ACCESS_OUT_OF_SCOPE) {
|
|
237
|
+
throw err;
|
|
238
|
+
}
|
|
239
|
+
if (err?.code === MODULE_ACCESS_NATIVE_ADDON) {
|
|
240
|
+
throw err;
|
|
241
|
+
}
|
|
242
|
+
if (err?.code === "EACCES") {
|
|
243
|
+
throw createEaccesError(syscall, virtualPath);
|
|
244
|
+
}
|
|
245
|
+
throw err;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
async readMergedDir(pathValue) {
|
|
249
|
+
const entries = this.syntheticChildren(pathValue);
|
|
250
|
+
const overlayHostPath = await this.resolveOverlayHostPath(pathValue, "scandir");
|
|
251
|
+
if (overlayHostPath) {
|
|
252
|
+
const hostEntries = await fs.readdir(overlayHostPath, { withFileTypes: true });
|
|
253
|
+
for (const entry of hostEntries) {
|
|
254
|
+
entries.set(entry.name, entry.isDirectory());
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (this.baseFileSystem && this.shouldMergeBase(pathValue)) {
|
|
258
|
+
try {
|
|
259
|
+
const baseEntries = await this.baseFileSystem.readDirWithTypes(pathValue);
|
|
260
|
+
for (const entry of baseEntries) {
|
|
261
|
+
if (!entries.has(entry.name)) {
|
|
262
|
+
entries.set(entry.name, entry.isDirectory);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
// Ignore base fs misses for synthetic and overlay-facing reads.
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
if (entries.size === 0 && !this.isSyntheticPath(pathValue)) {
|
|
271
|
+
throw createEnoentError("scandir", pathValue);
|
|
272
|
+
}
|
|
273
|
+
return entries;
|
|
274
|
+
}
|
|
275
|
+
async fallbackReadFile(pathValue) {
|
|
276
|
+
if (!this.baseFileSystem) {
|
|
277
|
+
throw createEnoentError("open", pathValue);
|
|
278
|
+
}
|
|
279
|
+
return this.baseFileSystem.readFile(pathValue);
|
|
280
|
+
}
|
|
281
|
+
async fallbackReadTextFile(pathValue) {
|
|
282
|
+
if (!this.baseFileSystem) {
|
|
283
|
+
throw createEnoentError("open", pathValue);
|
|
284
|
+
}
|
|
285
|
+
return this.baseFileSystem.readTextFile(pathValue);
|
|
286
|
+
}
|
|
287
|
+
async fallbackReadDir(pathValue) {
|
|
288
|
+
if (!this.baseFileSystem) {
|
|
289
|
+
throw createEnoentError("scandir", pathValue);
|
|
290
|
+
}
|
|
291
|
+
return this.baseFileSystem.readDir(pathValue);
|
|
292
|
+
}
|
|
293
|
+
async fallbackReadDirWithTypes(pathValue) {
|
|
294
|
+
if (!this.baseFileSystem) {
|
|
295
|
+
throw createEnoentError("scandir", pathValue);
|
|
296
|
+
}
|
|
297
|
+
return this.baseFileSystem.readDirWithTypes(pathValue);
|
|
298
|
+
}
|
|
299
|
+
async fallbackWriteFile(pathValue, content) {
|
|
300
|
+
if (!this.baseFileSystem) {
|
|
301
|
+
throw createEnoentError("write", pathValue);
|
|
302
|
+
}
|
|
303
|
+
return this.baseFileSystem.writeFile(pathValue, content);
|
|
304
|
+
}
|
|
305
|
+
async fallbackCreateDir(pathValue) {
|
|
306
|
+
if (!this.baseFileSystem) {
|
|
307
|
+
throw createEnoentError("mkdir", pathValue);
|
|
308
|
+
}
|
|
309
|
+
return this.baseFileSystem.createDir(pathValue);
|
|
310
|
+
}
|
|
311
|
+
async fallbackMkdir(pathValue) {
|
|
312
|
+
if (!this.baseFileSystem) {
|
|
313
|
+
throw createEnoentError("mkdir", pathValue);
|
|
314
|
+
}
|
|
315
|
+
return this.baseFileSystem.mkdir(pathValue);
|
|
316
|
+
}
|
|
317
|
+
async fallbackExists(pathValue) {
|
|
318
|
+
if (!this.baseFileSystem) {
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
return this.baseFileSystem.exists(pathValue);
|
|
322
|
+
}
|
|
323
|
+
async fallbackStat(pathValue) {
|
|
324
|
+
if (!this.baseFileSystem) {
|
|
325
|
+
throw createEnoentError("stat", pathValue);
|
|
326
|
+
}
|
|
327
|
+
return this.baseFileSystem.stat(pathValue);
|
|
328
|
+
}
|
|
329
|
+
async fallbackRemoveFile(pathValue) {
|
|
330
|
+
if (!this.baseFileSystem) {
|
|
331
|
+
throw createEnoentError("unlink", pathValue);
|
|
332
|
+
}
|
|
333
|
+
return this.baseFileSystem.removeFile(pathValue);
|
|
334
|
+
}
|
|
335
|
+
async fallbackRemoveDir(pathValue) {
|
|
336
|
+
if (!this.baseFileSystem) {
|
|
337
|
+
throw createEnoentError("rmdir", pathValue);
|
|
338
|
+
}
|
|
339
|
+
return this.baseFileSystem.removeDir(pathValue);
|
|
340
|
+
}
|
|
341
|
+
async fallbackRename(oldPath, newPath) {
|
|
342
|
+
if (!this.baseFileSystem) {
|
|
343
|
+
throw createEnoentError("rename", `${oldPath} -> ${newPath}`);
|
|
344
|
+
}
|
|
345
|
+
return this.baseFileSystem.rename(oldPath, newPath);
|
|
346
|
+
}
|
|
347
|
+
async readFile(pathValue) {
|
|
348
|
+
const virtualPath = normalizeOverlayPath(pathValue);
|
|
349
|
+
const hostPath = await this.resolveOverlayHostPath(virtualPath, "open");
|
|
350
|
+
if (hostPath) {
|
|
351
|
+
return fs.readFile(hostPath);
|
|
352
|
+
}
|
|
353
|
+
if (startsWithPath(virtualPath, SANDBOX_NODE_MODULES_ROOT)) {
|
|
354
|
+
throw createEnoentError("open", virtualPath);
|
|
355
|
+
}
|
|
356
|
+
return this.fallbackReadFile(virtualPath);
|
|
357
|
+
}
|
|
358
|
+
async readTextFile(pathValue) {
|
|
359
|
+
const virtualPath = normalizeOverlayPath(pathValue);
|
|
360
|
+
const hostPath = await this.resolveOverlayHostPath(virtualPath, "open");
|
|
361
|
+
if (hostPath) {
|
|
362
|
+
return fs.readFile(hostPath, "utf8");
|
|
363
|
+
}
|
|
364
|
+
if (startsWithPath(virtualPath, SANDBOX_NODE_MODULES_ROOT)) {
|
|
365
|
+
throw createEnoentError("open", virtualPath);
|
|
366
|
+
}
|
|
367
|
+
return this.fallbackReadTextFile(virtualPath);
|
|
368
|
+
}
|
|
369
|
+
async readDir(pathValue) {
|
|
370
|
+
const virtualPath = normalizeOverlayPath(pathValue);
|
|
371
|
+
if (this.isSyntheticPath(virtualPath) ||
|
|
372
|
+
startsWithPath(virtualPath, SANDBOX_NODE_MODULES_ROOT)) {
|
|
373
|
+
const entries = await this.readMergedDir(virtualPath);
|
|
374
|
+
return Array.from(entries.keys()).sort((left, right) => left.localeCompare(right));
|
|
375
|
+
}
|
|
376
|
+
return this.fallbackReadDir(virtualPath);
|
|
377
|
+
}
|
|
378
|
+
async readDirWithTypes(pathValue) {
|
|
379
|
+
const virtualPath = normalizeOverlayPath(pathValue);
|
|
380
|
+
if (this.isSyntheticPath(virtualPath) ||
|
|
381
|
+
startsWithPath(virtualPath, SANDBOX_NODE_MODULES_ROOT)) {
|
|
382
|
+
const entries = await this.readMergedDir(virtualPath);
|
|
383
|
+
return Array.from(entries.entries())
|
|
384
|
+
.map(([name, isDirectory]) => ({ name, isDirectory }))
|
|
385
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
386
|
+
}
|
|
387
|
+
return this.fallbackReadDirWithTypes(virtualPath);
|
|
388
|
+
}
|
|
389
|
+
async writeFile(pathValue, content) {
|
|
390
|
+
const virtualPath = normalizeOverlayPath(pathValue);
|
|
391
|
+
if (this.isReadOnlyProjectionPath(virtualPath)) {
|
|
392
|
+
throw createEaccesError("write", virtualPath);
|
|
393
|
+
}
|
|
394
|
+
return this.fallbackWriteFile(virtualPath, content);
|
|
395
|
+
}
|
|
396
|
+
async createDir(pathValue) {
|
|
397
|
+
const virtualPath = normalizeOverlayPath(pathValue);
|
|
398
|
+
if (this.isReadOnlyProjectionPath(virtualPath)) {
|
|
399
|
+
throw createEaccesError("mkdir", virtualPath);
|
|
400
|
+
}
|
|
401
|
+
return this.fallbackCreateDir(virtualPath);
|
|
402
|
+
}
|
|
403
|
+
async mkdir(pathValue, _options) {
|
|
404
|
+
const virtualPath = normalizeOverlayPath(pathValue);
|
|
405
|
+
if (this.isReadOnlyProjectionPath(virtualPath)) {
|
|
406
|
+
throw createEaccesError("mkdir", virtualPath);
|
|
407
|
+
}
|
|
408
|
+
return this.fallbackMkdir(virtualPath);
|
|
409
|
+
}
|
|
410
|
+
async exists(pathValue) {
|
|
411
|
+
const virtualPath = normalizeOverlayPath(pathValue);
|
|
412
|
+
if (this.isSyntheticPath(virtualPath)) {
|
|
413
|
+
return true;
|
|
414
|
+
}
|
|
415
|
+
const hostPath = await this.resolveOverlayHostPath(virtualPath, "access");
|
|
416
|
+
if (hostPath) {
|
|
417
|
+
return true;
|
|
418
|
+
}
|
|
419
|
+
if (startsWithPath(virtualPath, SANDBOX_NODE_MODULES_ROOT)) {
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
return this.fallbackExists(virtualPath);
|
|
423
|
+
}
|
|
424
|
+
async stat(pathValue) {
|
|
425
|
+
const virtualPath = normalizeOverlayPath(pathValue);
|
|
426
|
+
if (this.isSyntheticPath(virtualPath)) {
|
|
427
|
+
const hostPath = await this.resolveOverlayHostPath(virtualPath, "stat");
|
|
428
|
+
if (!hostPath) {
|
|
429
|
+
return createVirtualDirStat();
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
const hostPath = await this.resolveOverlayHostPath(virtualPath, "stat");
|
|
433
|
+
if (hostPath) {
|
|
434
|
+
const info = await fs.stat(hostPath);
|
|
435
|
+
return {
|
|
436
|
+
mode: info.mode,
|
|
437
|
+
size: info.size,
|
|
438
|
+
isDirectory: info.isDirectory(),
|
|
439
|
+
isSymbolicLink: false,
|
|
440
|
+
atimeMs: info.atimeMs,
|
|
441
|
+
mtimeMs: info.mtimeMs,
|
|
442
|
+
ctimeMs: info.ctimeMs,
|
|
443
|
+
birthtimeMs: info.birthtimeMs,
|
|
444
|
+
ino: info.ino,
|
|
445
|
+
nlink: info.nlink,
|
|
446
|
+
uid: info.uid,
|
|
447
|
+
gid: info.gid,
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
if (startsWithPath(virtualPath, SANDBOX_NODE_MODULES_ROOT)) {
|
|
451
|
+
throw createEnoentError("stat", virtualPath);
|
|
452
|
+
}
|
|
453
|
+
return this.fallbackStat(virtualPath);
|
|
454
|
+
}
|
|
455
|
+
async removeFile(pathValue) {
|
|
456
|
+
const virtualPath = normalizeOverlayPath(pathValue);
|
|
457
|
+
if (this.isReadOnlyProjectionPath(virtualPath)) {
|
|
458
|
+
throw createEaccesError("unlink", virtualPath);
|
|
459
|
+
}
|
|
460
|
+
return this.fallbackRemoveFile(virtualPath);
|
|
461
|
+
}
|
|
462
|
+
async removeDir(pathValue) {
|
|
463
|
+
const virtualPath = normalizeOverlayPath(pathValue);
|
|
464
|
+
if (this.isReadOnlyProjectionPath(virtualPath)) {
|
|
465
|
+
throw createEaccesError("rmdir", virtualPath);
|
|
466
|
+
}
|
|
467
|
+
return this.fallbackRemoveDir(virtualPath);
|
|
468
|
+
}
|
|
469
|
+
async rename(oldPath, newPath) {
|
|
470
|
+
const oldVirtualPath = normalizeOverlayPath(oldPath);
|
|
471
|
+
const newVirtualPath = normalizeOverlayPath(newPath);
|
|
472
|
+
if (this.isReadOnlyProjectionPath(oldVirtualPath) ||
|
|
473
|
+
this.isReadOnlyProjectionPath(newVirtualPath)) {
|
|
474
|
+
throw createEaccesError("rename", `${oldVirtualPath} -> ${newVirtualPath}`);
|
|
475
|
+
}
|
|
476
|
+
return this.fallbackRename(oldVirtualPath, newVirtualPath);
|
|
477
|
+
}
|
|
478
|
+
async symlink(target, linkPath) {
|
|
479
|
+
const virtualPath = normalizeOverlayPath(linkPath);
|
|
480
|
+
if (this.isReadOnlyProjectionPath(virtualPath)) {
|
|
481
|
+
throw createEaccesError("symlink", virtualPath);
|
|
482
|
+
}
|
|
483
|
+
if (!this.baseFileSystem)
|
|
484
|
+
throw createEnoentError("symlink", virtualPath);
|
|
485
|
+
return this.baseFileSystem.symlink(target, virtualPath);
|
|
486
|
+
}
|
|
487
|
+
async readlink(path) {
|
|
488
|
+
const virtualPath = normalizeOverlayPath(path);
|
|
489
|
+
if (!this.baseFileSystem)
|
|
490
|
+
throw createEnoentError("readlink", virtualPath);
|
|
491
|
+
return this.baseFileSystem.readlink(virtualPath);
|
|
492
|
+
}
|
|
493
|
+
async lstat(path) {
|
|
494
|
+
const virtualPath = normalizeOverlayPath(path);
|
|
495
|
+
if (this.isSyntheticPath(virtualPath)) {
|
|
496
|
+
return createVirtualDirStat();
|
|
497
|
+
}
|
|
498
|
+
const hostPath = await this.resolveOverlayHostPath(virtualPath, "lstat");
|
|
499
|
+
if (hostPath) {
|
|
500
|
+
const info = await fs.lstat(hostPath);
|
|
501
|
+
return {
|
|
502
|
+
mode: info.mode,
|
|
503
|
+
size: info.size,
|
|
504
|
+
isDirectory: info.isDirectory(),
|
|
505
|
+
isSymbolicLink: info.isSymbolicLink(),
|
|
506
|
+
atimeMs: info.atimeMs,
|
|
507
|
+
mtimeMs: info.mtimeMs,
|
|
508
|
+
ctimeMs: info.ctimeMs,
|
|
509
|
+
birthtimeMs: info.birthtimeMs,
|
|
510
|
+
ino: info.ino,
|
|
511
|
+
nlink: info.nlink,
|
|
512
|
+
uid: info.uid,
|
|
513
|
+
gid: info.gid,
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
if (startsWithPath(virtualPath, SANDBOX_NODE_MODULES_ROOT)) {
|
|
517
|
+
throw createEnoentError("lstat", virtualPath);
|
|
518
|
+
}
|
|
519
|
+
if (!this.baseFileSystem)
|
|
520
|
+
throw createEnoentError("lstat", virtualPath);
|
|
521
|
+
return this.baseFileSystem.lstat(virtualPath);
|
|
522
|
+
}
|
|
523
|
+
async link(oldPath, newPath) {
|
|
524
|
+
const oldVirtualPath = normalizeOverlayPath(oldPath);
|
|
525
|
+
const newVirtualPath = normalizeOverlayPath(newPath);
|
|
526
|
+
if (this.isReadOnlyProjectionPath(newVirtualPath)) {
|
|
527
|
+
throw createEaccesError("link", newVirtualPath);
|
|
528
|
+
}
|
|
529
|
+
if (!this.baseFileSystem)
|
|
530
|
+
throw createEnoentError("link", oldVirtualPath);
|
|
531
|
+
return this.baseFileSystem.link(oldVirtualPath, newVirtualPath);
|
|
532
|
+
}
|
|
533
|
+
async chmod(path, mode) {
|
|
534
|
+
const virtualPath = normalizeOverlayPath(path);
|
|
535
|
+
if (this.isReadOnlyProjectionPath(virtualPath)) {
|
|
536
|
+
throw createEaccesError("chmod", virtualPath);
|
|
537
|
+
}
|
|
538
|
+
if (!this.baseFileSystem)
|
|
539
|
+
throw createEnoentError("chmod", virtualPath);
|
|
540
|
+
return this.baseFileSystem.chmod(virtualPath, mode);
|
|
541
|
+
}
|
|
542
|
+
async chown(path, uid, gid) {
|
|
543
|
+
const virtualPath = normalizeOverlayPath(path);
|
|
544
|
+
if (this.isReadOnlyProjectionPath(virtualPath)) {
|
|
545
|
+
throw createEaccesError("chown", virtualPath);
|
|
546
|
+
}
|
|
547
|
+
if (!this.baseFileSystem)
|
|
548
|
+
throw createEnoentError("chown", virtualPath);
|
|
549
|
+
return this.baseFileSystem.chown(virtualPath, uid, gid);
|
|
550
|
+
}
|
|
551
|
+
async utimes(path, atime, mtime) {
|
|
552
|
+
const virtualPath = normalizeOverlayPath(path);
|
|
553
|
+
if (this.isReadOnlyProjectionPath(virtualPath)) {
|
|
554
|
+
throw createEaccesError("utimes", virtualPath);
|
|
555
|
+
}
|
|
556
|
+
if (!this.baseFileSystem)
|
|
557
|
+
throw createEnoentError("utimes", virtualPath);
|
|
558
|
+
return this.baseFileSystem.utimes(virtualPath, atime, mtime);
|
|
559
|
+
}
|
|
560
|
+
async truncate(path, length) {
|
|
561
|
+
const virtualPath = normalizeOverlayPath(path);
|
|
562
|
+
if (this.isReadOnlyProjectionPath(virtualPath)) {
|
|
563
|
+
throw createEaccesError("truncate", virtualPath);
|
|
564
|
+
}
|
|
565
|
+
if (!this.baseFileSystem)
|
|
566
|
+
throw createEnoentError("truncate", virtualPath);
|
|
567
|
+
return this.baseFileSystem.truncate(virtualPath, length);
|
|
568
|
+
}
|
|
569
|
+
async realpath(pathValue) {
|
|
570
|
+
const virtualPath = normalizeOverlayPath(pathValue);
|
|
571
|
+
if (this.isSyntheticPath(virtualPath)) {
|
|
572
|
+
return virtualPath;
|
|
573
|
+
}
|
|
574
|
+
const hostPath = await this.resolveOverlayHostPath(virtualPath, "realpath");
|
|
575
|
+
if (hostPath) {
|
|
576
|
+
return virtualPath;
|
|
577
|
+
}
|
|
578
|
+
if (startsWithPath(virtualPath, SANDBOX_NODE_MODULES_ROOT)) {
|
|
579
|
+
throw createEnoentError("realpath", virtualPath);
|
|
580
|
+
}
|
|
581
|
+
if (!this.baseFileSystem)
|
|
582
|
+
throw createEnoentError("realpath", virtualPath);
|
|
583
|
+
return this.baseFileSystem.realpath(virtualPath);
|
|
584
|
+
}
|
|
585
|
+
async pread(pathValue, offset, length) {
|
|
586
|
+
const virtualPath = normalizeOverlayPath(pathValue);
|
|
587
|
+
const hostPath = await this.resolveOverlayHostPath(virtualPath, "open");
|
|
588
|
+
if (hostPath) {
|
|
589
|
+
const handle = await fs.open(hostPath, "r");
|
|
590
|
+
try {
|
|
591
|
+
const buf = new Uint8Array(length);
|
|
592
|
+
const { bytesRead } = await handle.read(buf, 0, length, offset);
|
|
593
|
+
return buf.slice(0, bytesRead);
|
|
594
|
+
}
|
|
595
|
+
finally {
|
|
596
|
+
await handle.close();
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
if (startsWithPath(virtualPath, SANDBOX_NODE_MODULES_ROOT)) {
|
|
600
|
+
throw createEnoentError("open", virtualPath);
|
|
601
|
+
}
|
|
602
|
+
if (!this.baseFileSystem)
|
|
603
|
+
throw createEnoentError("open", virtualPath);
|
|
604
|
+
return this.baseFileSystem.pread(virtualPath, offset, length);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { DriverDeps } from "./isolate-bootstrap.js";
|
|
2
|
+
type ResolverDeps = Pick<DriverDeps, "filesystem" | "packageTypeCache" | "moduleFormatCache" | "isolateJsonPayloadLimitBytes" | "resolutionCache">;
|
|
3
|
+
export declare function getNearestPackageType(deps: ResolverDeps, filePath: string): Promise<"module" | "commonjs" | null>;
|
|
4
|
+
export declare function getModuleFormat(deps: ResolverDeps, filePath: string, sourceCode?: string): Promise<"esm" | "cjs" | "json">;
|
|
5
|
+
export declare function shouldRunAsESM(deps: ResolverDeps, code: string, filePath?: string): Promise<boolean>;
|
|
6
|
+
export declare function resolveReferrerDirectory(deps: Pick<DriverDeps, "filesystem">, referrerPath: string): Promise<string>;
|
|
7
|
+
export declare function resolveESMPath(deps: Pick<DriverDeps, "filesystem" | "resolutionCache">, specifier: string, referrerPath: string): Promise<string | null>;
|
|
8
|
+
export {};
|