From aca2c9c5e078896d195c45cd2ae39581e82c2243 Mon Sep 17 00:00:00 2001 From: Mavdol Date: Mon, 13 Apr 2026 16:52:24 +0200 Subject: [PATCH] Prepare package import manage in sandbox --- wasm-sandboxes/js/sandbox.ts | 76 +++++++++++++++++++++++++------- wasm-sandboxes/python/sandbox.py | 29 +++++++++--- 2 files changed, 84 insertions(+), 21 deletions(-) diff --git a/wasm-sandboxes/js/sandbox.ts b/wasm-sandboxes/js/sandbox.ts index ed793bb..9399527 100644 --- a/wasm-sandboxes/js/sandbox.ts +++ b/wasm-sandboxes/js/sandbox.ts @@ -8,8 +8,62 @@ function wasmRelative(cwd: string, filePath: string): string { return path.resolve(cwd, filePath).replace(/^\//, ''); } +function resolveNodeModule(fromPath: string, id: string): string | null { + let dir = path.dirname('/' + fromPath); + + while (true) { + const base = wasmRelative('/', path.join(dir, 'node_modules', id)); + const candidates = [base, base + '.js', base + '/index.js']; + + const pkgJson = base + '/package.json'; + + if (fs.existsSync(pkgJson)) { + try { + const pkg = JSON.parse(fs.readFileSync(pkgJson, 'utf-8') as string); + const main = pkg.main || 'index.js'; + candidates.unshift(wasmRelative('/', path.join(dir, 'node_modules', id, main))); + } catch {} + } + + for (const candidate of candidates) { + if (fs.existsSync(candidate)) return candidate; + } + + const parent = path.dirname(dir); + if (parent === dir) return null; + + dir = parent; + } +} + +const makeRequire = (fromPath: string) => (id: string) => { + let depPath: string; + + if (!id.startsWith('.') && !id.startsWith('/')) { + const resolved = resolveNodeModule(fromPath, id); + + if (!resolved) throw new Error(`Cannot find module '${id}'`); + + depPath = resolved; + } else { + const base = wasmRelative('/', path.resolve('/' + path.dirname(fromPath), id)); + + depPath = fs.existsSync(base) ? base + : fs.existsSync(base + '.js') ? base + '.js' + : base + '/index.js'; + } + + const src = fs.readFileSync(depPath, 'utf-8') as string; + const m = { exports: {} as any }; + const fn = new Function('module', 'exports', 'require', '__filename', '__dirname', src); + + fn(m, m.exports, makeRequire(depPath), depPath, path.dirname(depPath)); + + return m.exports; +}; + const executeFile = task( - { name: "executeFile", compute: "MEDIUM", ram: "512MB" }, + { name: "executeFile", compute: "MEDIUM", ram: "512MB", allowedHosts: ["*"] }, async (state: State, filePath: string, args: string[]) => { const capturedOutput: string[] = []; const relPath = wasmRelative(state.cwd, filePath); @@ -28,18 +82,6 @@ const executeFile = task( try { const code = fs.readFileSync(relPath, 'utf-8') as string; const mod = { exports: {} as any }; - - const makeRequire = (fromPath: string) => (id: string) => { - const base = wasmRelative('/', path.resolve('/' + path.dirname(fromPath), id)); - const depPath = fs.existsSync(base) ? base - : fs.existsSync(base + '.js') ? base + '.js' - : base + '/index.js'; - const src = fs.readFileSync(depPath, 'utf-8') as string; - const m = { exports: {} as any }; - const fn = new Function('module', 'exports', 'require', '__filename', '__dirname', src); - fn(m, m.exports, makeRequire(depPath), depPath, path.dirname(depPath)); - return m.exports; - }; const customRequire = makeRequire(relPath); const fn = new Function('module', 'exports', 'require', '__filename', '__dirname', code); @@ -60,7 +102,7 @@ const executeFile = task( const executeCode = task( - { name: "executeCode", compute: "LOW", ram: "256MB" }, + { name: "executeCode", compute: "LOW", ram: "256MB", allowedHosts: ["*"] }, async (state: State, code: string): Promise => { process.chdir(state.cwd); const capturedOutput: string[] = []; @@ -70,14 +112,16 @@ const executeCode = task( capturedOutput.push(args.map(arg => String(arg)).join(' ')); }; + const require = makeRequire(wasmRelative(state.cwd, '.')); + try { let result; try { result = eval(code); } catch (e) { if (e instanceof SyntaxError && e.message.includes("return")) { - const fn = new Function(code); - result = fn(); + const fn = new Function('require', code); + result = fn(require); } else { throw e; } diff --git a/wasm-sandboxes/python/sandbox.py b/wasm-sandboxes/python/sandbox.py index 803de67..9130e1a 100644 --- a/wasm-sandboxes/python/sandbox.py +++ b/wasm-sandboxes/python/sandbox.py @@ -6,6 +6,15 @@ from dataclasses import dataclass from capsule import task +# Need to import them at build time +import socket +import ssl +import urllib.request +import urllib.parse +import urllib.error +import urllib.response +import http.client +import http.cookiejar @dataclass class State: @@ -28,12 +37,16 @@ def wasm_relative(cwd: str, file_path: str) -> str: return joined.lstrip("/") -@task(name="executeFile", compute="MEDIUM", ram="512MB") +@task(name="executeFile", compute="MEDIUM", ram="512MB", allowed_hosts=["*"]) def execute_file(state: str, file_path: str, args: list[str]): - parsed_state = State.from_json(state) - rel_path = wasm_relative(parsed_state.cwd, file_path) + state = State.from_json(state) + rel_path = wasm_relative(state.cwd, file_path) file_dir = os.path.dirname(rel_path) or "." + site_packages = os.path.join(state.cwd, "site-packages") + if site_packages not in sys.path: + sys.path.insert(0, site_packages) + captured_output = StringIO() old_stdout = sys.stdout old_argv = sys.argv @@ -72,8 +85,14 @@ def execute_file(state: str, file_path: str, args: list[str]): return public_result if public_result else None -@task(name="executeCode", compute="LOW", ram="256MB") -def execute_code(_state: str, code: str): +@task(name="executeCode", compute="LOW", ram="256MB", allowed_hosts=["*"]) +def execute_code(state: str, code: str): + state = State.from_json(state) + + site_packages = os.path.join(state.cwd, "site-packages") + if site_packages not in sys.path: + sys.path.insert(0, site_packages) + tree = ast.parse(code) if not tree.body: