Skip to content

Commit b94ea34

Browse files
authored
Merge pull request #9 from capsulerun/feature/V0.1.0/sandbox-dynamic-package-imports
Prepare package import manage in sandbox
2 parents 8b89222 + aca2c9c commit b94ea34

File tree

2 files changed

+84
-21
lines changed

2 files changed

+84
-21
lines changed

wasm-sandboxes/js/sandbox.ts

Lines changed: 60 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,62 @@ function wasmRelative(cwd: string, filePath: string): string {
88
return path.resolve(cwd, filePath).replace(/^\//, '');
99
}
1010

11+
function resolveNodeModule(fromPath: string, id: string): string | null {
12+
let dir = path.dirname('/' + fromPath);
13+
14+
while (true) {
15+
const base = wasmRelative('/', path.join(dir, 'node_modules', id));
16+
const candidates = [base, base + '.js', base + '/index.js'];
17+
18+
const pkgJson = base + '/package.json';
19+
20+
if (fs.existsSync(pkgJson)) {
21+
try {
22+
const pkg = JSON.parse(fs.readFileSync(pkgJson, 'utf-8') as string);
23+
const main = pkg.main || 'index.js';
24+
candidates.unshift(wasmRelative('/', path.join(dir, 'node_modules', id, main)));
25+
} catch {}
26+
}
27+
28+
for (const candidate of candidates) {
29+
if (fs.existsSync(candidate)) return candidate;
30+
}
31+
32+
const parent = path.dirname(dir);
33+
if (parent === dir) return null;
34+
35+
dir = parent;
36+
}
37+
}
38+
39+
const makeRequire = (fromPath: string) => (id: string) => {
40+
let depPath: string;
41+
42+
if (!id.startsWith('.') && !id.startsWith('/')) {
43+
const resolved = resolveNodeModule(fromPath, id);
44+
45+
if (!resolved) throw new Error(`Cannot find module '${id}'`);
46+
47+
depPath = resolved;
48+
} else {
49+
const base = wasmRelative('/', path.resolve('/' + path.dirname(fromPath), id));
50+
51+
depPath = fs.existsSync(base) ? base
52+
: fs.existsSync(base + '.js') ? base + '.js'
53+
: base + '/index.js';
54+
}
55+
56+
const src = fs.readFileSync(depPath, 'utf-8') as string;
57+
const m = { exports: {} as any };
58+
const fn = new Function('module', 'exports', 'require', '__filename', '__dirname', src);
59+
60+
fn(m, m.exports, makeRequire(depPath), depPath, path.dirname(depPath));
61+
62+
return m.exports;
63+
};
64+
1165
const executeFile = task(
12-
{ name: "executeFile", compute: "MEDIUM", ram: "512MB" },
66+
{ name: "executeFile", compute: "MEDIUM", ram: "512MB", allowedHosts: ["*"] },
1367
async (state: State, filePath: string, args: string[]) => {
1468
const capturedOutput: string[] = [];
1569
const relPath = wasmRelative(state.cwd, filePath);
@@ -28,18 +82,6 @@ const executeFile = task(
2882
try {
2983
const code = fs.readFileSync(relPath, 'utf-8') as string;
3084
const mod = { exports: {} as any };
31-
32-
const makeRequire = (fromPath: string) => (id: string) => {
33-
const base = wasmRelative('/', path.resolve('/' + path.dirname(fromPath), id));
34-
const depPath = fs.existsSync(base) ? base
35-
: fs.existsSync(base + '.js') ? base + '.js'
36-
: base + '/index.js';
37-
const src = fs.readFileSync(depPath, 'utf-8') as string;
38-
const m = { exports: {} as any };
39-
const fn = new Function('module', 'exports', 'require', '__filename', '__dirname', src);
40-
fn(m, m.exports, makeRequire(depPath), depPath, path.dirname(depPath));
41-
return m.exports;
42-
};
4385
const customRequire = makeRequire(relPath);
4486

4587
const fn = new Function('module', 'exports', 'require', '__filename', '__dirname', code);
@@ -60,7 +102,7 @@ const executeFile = task(
60102

61103

62104
const executeCode = task(
63-
{ name: "executeCode", compute: "LOW", ram: "256MB" },
105+
{ name: "executeCode", compute: "LOW", ram: "256MB", allowedHosts: ["*"] },
64106
async (state: State, code: string): Promise<unknown> => {
65107
process.chdir(state.cwd);
66108
const capturedOutput: string[] = [];
@@ -70,14 +112,16 @@ const executeCode = task(
70112
capturedOutput.push(args.map(arg => String(arg)).join(' '));
71113
};
72114

115+
const require = makeRequire(wasmRelative(state.cwd, '.'));
116+
73117
try {
74118
let result;
75119
try {
76120
result = eval(code);
77121
} catch (e) {
78122
if (e instanceof SyntaxError && e.message.includes("return")) {
79-
const fn = new Function(code);
80-
result = fn();
123+
const fn = new Function('require', code);
124+
result = fn(require);
81125
} else {
82126
throw e;
83127
}

wasm-sandboxes/python/sandbox.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@
66
from dataclasses import dataclass
77
from capsule import task
88

9+
# Need to import them at build time
10+
import socket
11+
import ssl
12+
import urllib.request
13+
import urllib.parse
14+
import urllib.error
15+
import urllib.response
16+
import http.client
17+
import http.cookiejar
918

1019
@dataclass
1120
class State:
@@ -28,12 +37,16 @@ def wasm_relative(cwd: str, file_path: str) -> str:
2837
return joined.lstrip("/")
2938

3039

31-
@task(name="executeFile", compute="MEDIUM", ram="512MB")
40+
@task(name="executeFile", compute="MEDIUM", ram="512MB", allowed_hosts=["*"])
3241
def execute_file(state: str, file_path: str, args: list[str]):
33-
parsed_state = State.from_json(state)
34-
rel_path = wasm_relative(parsed_state.cwd, file_path)
42+
state = State.from_json(state)
43+
rel_path = wasm_relative(state.cwd, file_path)
3544
file_dir = os.path.dirname(rel_path) or "."
3645

46+
site_packages = os.path.join(state.cwd, "site-packages")
47+
if site_packages not in sys.path:
48+
sys.path.insert(0, site_packages)
49+
3750
captured_output = StringIO()
3851
old_stdout = sys.stdout
3952
old_argv = sys.argv
@@ -72,8 +85,14 @@ def execute_file(state: str, file_path: str, args: list[str]):
7285
return public_result if public_result else None
7386

7487

75-
@task(name="executeCode", compute="LOW", ram="256MB")
76-
def execute_code(_state: str, code: str):
88+
@task(name="executeCode", compute="LOW", ram="256MB", allowed_hosts=["*"])
89+
def execute_code(state: str, code: str):
90+
state = State.from_json(state)
91+
92+
site_packages = os.path.join(state.cwd, "site-packages")
93+
if site_packages not in sys.path:
94+
sys.path.insert(0, site_packages)
95+
7796
tree = ast.parse(code)
7897

7998
if not tree.body:

0 commit comments

Comments
 (0)