just-bash 2.11.8 → 2.11.10

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.
@@ -9,7 +9,6 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
9
9
  import { createRequire } from "node:module";
10
10
  import { dirname } from "node:path";
11
11
  import { parentPort, workerData } from "node:worker_threads";
12
- import { loadPyodide } from "pyodide";
13
12
 
14
13
  // src/security/blocked-globals.ts
15
14
  function getBlockedGlobals() {
@@ -84,6 +83,13 @@ function getBlockedGlobals() {
84
83
  strategy: "throw",
85
84
  reason: "process.dlopen allows loading native addons"
86
85
  },
86
+ {
87
+ prop: "getBuiltinModule",
88
+ target: process,
89
+ violationType: "process_get_builtin_module",
90
+ strategy: "throw",
91
+ reason: "process.getBuiltinModule allows loading native Node.js modules (fs, child_process, vm)"
92
+ },
87
93
  // Note: process.mainModule is handled specially in defense-in-depth-box.ts
88
94
  // and worker-defense-in-depth.ts because it may be undefined in ESM contexts
89
95
  // but we still want to block both reading and setting it.
@@ -1529,21 +1535,60 @@ var SyncFsBackend = class {
1529
1535
  };
1530
1536
 
1531
1537
  // src/commands/python3/worker.ts
1532
- var pyodideInstance = null;
1533
- var pyodideLoading = null;
1538
+ import { readFileSync } from "node:fs";
1534
1539
  var require2 = createRequire(import.meta.url);
1535
- var pyodideIndexURL = `${dirname(require2.resolve("pyodide/pyodide.mjs"))}/`;
1536
- async function getPyodide() {
1537
- if (pyodideInstance) {
1538
- return pyodideInstance;
1539
- }
1540
- if (pyodideLoading) {
1541
- return pyodideLoading;
1540
+ try {
1541
+ const NodeModule = require2("node:module");
1542
+ if (typeof NodeModule._load === "function") {
1543
+ const originalLoad = NodeModule._load;
1544
+ const blockedModules = /* @__PURE__ */ new Set([
1545
+ "child_process",
1546
+ "node:child_process",
1547
+ "cluster",
1548
+ "node:cluster",
1549
+ "dgram",
1550
+ "node:dgram",
1551
+ "dns",
1552
+ "node:dns",
1553
+ "net",
1554
+ "node:net",
1555
+ "tls",
1556
+ "node:tls",
1557
+ "vm",
1558
+ "node:vm",
1559
+ "v8",
1560
+ "node:v8",
1561
+ "inspector",
1562
+ "node:inspector",
1563
+ "inspector/promises",
1564
+ "node:inspector/promises",
1565
+ "trace_events",
1566
+ "node:trace_events",
1567
+ "perf_hooks",
1568
+ "node:perf_hooks",
1569
+ "worker_threads",
1570
+ "node:worker_threads"
1571
+ ]);
1572
+ NodeModule._load = function(request, ...rest) {
1573
+ if (blockedModules.has(request)) {
1574
+ throw new Error(
1575
+ `[Defense-in-depth] require('${request}') is blocked in worker context`
1576
+ );
1577
+ }
1578
+ return originalLoad.apply(this, [request, ...rest]);
1579
+ };
1542
1580
  }
1543
- pyodideLoading = loadPyodide({ indexURL: pyodideIndexURL });
1544
- pyodideInstance = await pyodideLoading;
1545
- return pyodideInstance;
1581
+ } catch {
1582
+ }
1583
+ var cpythonDir;
1584
+ try {
1585
+ cpythonDir = dirname(
1586
+ require2.resolve("../../../vendor/cpython-emscripten/python.cjs")
1587
+ );
1588
+ } catch (_e) {
1589
+ cpythonDir = dirname(import.meta.url).replace("file://", "") + "/../../../vendor/cpython-emscripten";
1546
1590
  }
1591
+ var stdlibZipPath = `${cpythonDir}/python313.zip`;
1547
1592
  function createHOSTFS(backend, FS, PATH) {
1548
1593
  const ERRNO_CODES = {
1549
1594
  EPERM: 63,
@@ -1809,108 +1854,438 @@ function createHOSTFS(backend, FS, PATH) {
1809
1854
  };
1810
1855
  return HOSTFS;
1811
1856
  }
1812
- async function runPython(input) {
1813
- const backend = new SyncFsBackend(input.sharedBuffer);
1814
- let pyodide;
1815
- try {
1816
- pyodide = await getPyodide();
1817
- } catch (e) {
1818
- return {
1819
- success: false,
1820
- error: `Failed to load Pyodide: ${e.message}`
1821
- };
1822
- }
1823
- pyodide.setStdout({ batched: () => {
1824
- } });
1825
- pyodide.setStderr({ batched: () => {
1826
- } });
1827
- try {
1828
- pyodide.runPython(`
1829
- import sys
1830
- if hasattr(sys.stdout, 'flush'):
1831
- sys.stdout.flush()
1832
- if hasattr(sys.stderr, 'flush'):
1833
- sys.stderr.flush()
1834
- `);
1835
- } catch (_e) {
1836
- }
1837
- pyodide.setStdout({
1838
- batched: (text) => {
1839
- backend.writeStdout(`${text}
1840
- `);
1841
- }
1842
- });
1843
- pyodide.setStderr({
1844
- batched: (text) => {
1845
- backend.writeStderr(`${text}
1846
- `);
1847
- }
1848
- });
1849
- const FS = pyodide.FS;
1850
- const PATH = pyodide.PATH;
1851
- const HOSTFS = createHOSTFS(backend, FS, PATH);
1852
- try {
1853
- try {
1854
- pyodide.runPython(`import os; os.chdir('/')`);
1855
- } catch (_e) {
1856
- }
1857
- try {
1858
- FS.mkdir("/host");
1859
- } catch (_e) {
1860
- }
1861
- try {
1862
- FS.unmount("/host");
1863
- } catch (_e) {
1864
- }
1865
- FS.mount(HOSTFS, { root: "/" }, "/host");
1866
- } catch (e) {
1867
- return {
1868
- success: false,
1869
- error: `Failed to mount HOSTFS: ${e.message}`
1870
- };
1871
- }
1872
- try {
1873
- pyodide.runPython(`
1874
- import sys
1875
- if '_jb_http_bridge' in sys.modules:
1876
- del sys.modules['_jb_http_bridge']
1877
- if 'jb_http' in sys.modules:
1878
- del sys.modules['jb_http']
1879
- `);
1880
- } catch (_e) {
1881
- }
1882
- pyodide.registerJsModule("_jb_http_bridge", {
1883
- request: (url, method, headersJson, body) => {
1884
- try {
1885
- const headers = headersJson ? JSON.parse(headersJson) : void 0;
1886
- const result = backend.httpRequest(url, {
1887
- method: method || "GET",
1888
- headers,
1889
- body: body || void 0
1890
- });
1891
- return JSON.stringify(result);
1892
- } catch (e) {
1893
- return JSON.stringify({ error: e.message });
1894
- }
1895
- }
1896
- });
1857
+ function generateSetupCode(input) {
1897
1858
  const envSetup = Object.entries(input.env).map(([key, value]) => {
1898
1859
  return `os.environ[${JSON.stringify(key)}] = ${JSON.stringify(value)}`;
1899
1860
  }).join("\n");
1900
1861
  const argv0 = input.scriptPath || "python3";
1901
1862
  const argvList = [argv0, ...input.args].map((arg) => JSON.stringify(arg)).join(", ");
1902
- try {
1903
- await pyodide.runPythonAsync(`
1863
+ return `
1904
1864
  import os
1905
1865
  import sys
1906
- import builtins
1907
1866
  import json
1908
1867
 
1909
1868
  ${envSetup}
1910
1869
 
1911
1870
  sys.argv = [${argvList}]
1912
1871
 
1913
- # Create jb_http module for HTTP requests
1872
+ # Path redirection: redirect /absolute paths to /host mount
1873
+ def _should_redirect(path):
1874
+ return (isinstance(path, str) and
1875
+ path.startswith('/') and
1876
+ not path.startswith('/lib') and
1877
+ not path.startswith('/proc') and
1878
+ not path.startswith('/host') and
1879
+ not path.startswith('/_jb_http'))
1880
+
1881
+ # builtins.open
1882
+ import builtins
1883
+ _orig_open = builtins.open
1884
+ def _redir_open(path, mode='r', *args, **kwargs):
1885
+ if _should_redirect(path):
1886
+ path = '/host' + path
1887
+ return _orig_open(path, mode, *args, **kwargs)
1888
+ builtins.open = _redir_open
1889
+
1890
+ # os file operations
1891
+ _orig_listdir = os.listdir
1892
+ def _redir_listdir(path='.'):
1893
+ if _should_redirect(path):
1894
+ path = '/host' + path
1895
+ return _orig_listdir(path)
1896
+ os.listdir = _redir_listdir
1897
+
1898
+ _orig_exists = os.path.exists
1899
+ def _redir_exists(path):
1900
+ if _should_redirect(path):
1901
+ path = '/host' + path
1902
+ return _orig_exists(path)
1903
+ os.path.exists = _redir_exists
1904
+
1905
+ _orig_isfile = os.path.isfile
1906
+ def _redir_isfile(path):
1907
+ if _should_redirect(path):
1908
+ path = '/host' + path
1909
+ return _orig_isfile(path)
1910
+ os.path.isfile = _redir_isfile
1911
+
1912
+ _orig_isdir = os.path.isdir
1913
+ def _redir_isdir(path):
1914
+ if _should_redirect(path):
1915
+ path = '/host' + path
1916
+ return _orig_isdir(path)
1917
+ os.path.isdir = _redir_isdir
1918
+
1919
+ _orig_stat = os.stat
1920
+ def _redir_stat(path, *args, **kwargs):
1921
+ if _should_redirect(path):
1922
+ path = '/host' + path
1923
+ return _orig_stat(path, *args, **kwargs)
1924
+ os.stat = _redir_stat
1925
+
1926
+ _orig_mkdir = os.mkdir
1927
+ def _redir_mkdir(path, *args, **kwargs):
1928
+ if _should_redirect(path):
1929
+ path = '/host' + path
1930
+ return _orig_mkdir(path, *args, **kwargs)
1931
+ os.mkdir = _redir_mkdir
1932
+
1933
+ _orig_makedirs = os.makedirs
1934
+ def _redir_makedirs(path, *args, **kwargs):
1935
+ if _should_redirect(path):
1936
+ path = '/host' + path
1937
+ return _orig_makedirs(path, *args, **kwargs)
1938
+ os.makedirs = _redir_makedirs
1939
+
1940
+ _orig_remove = os.remove
1941
+ def _redir_remove(path, *args, **kwargs):
1942
+ if _should_redirect(path):
1943
+ path = '/host' + path
1944
+ return _orig_remove(path, *args, **kwargs)
1945
+ os.remove = _redir_remove
1946
+
1947
+ _orig_rmdir = os.rmdir
1948
+ def _redir_rmdir(path, *args, **kwargs):
1949
+ if _should_redirect(path):
1950
+ path = '/host' + path
1951
+ return _orig_rmdir(path, *args, **kwargs)
1952
+ os.rmdir = _redir_rmdir
1953
+
1954
+ _orig_getcwd = os.getcwd
1955
+ def _redir_getcwd():
1956
+ cwd = _orig_getcwd()
1957
+ if cwd.startswith('/host'):
1958
+ return cwd[5:]
1959
+ return cwd
1960
+ os.getcwd = _redir_getcwd
1961
+
1962
+ _orig_chdir = os.chdir
1963
+ def _redir_chdir(path):
1964
+ if _should_redirect(path):
1965
+ path = '/host' + path
1966
+ return _orig_chdir(path)
1967
+ os.chdir = _redir_chdir
1968
+
1969
+ # glob
1970
+ import glob as _glob_module
1971
+ _orig_glob = _glob_module.glob
1972
+ def _redir_glob(pathname, *args, **kwargs):
1973
+ if _should_redirect(pathname):
1974
+ pathname = '/host' + pathname
1975
+ return _orig_glob(pathname, *args, **kwargs)
1976
+ _glob_module.glob = _redir_glob
1977
+
1978
+ _orig_iglob = _glob_module.iglob
1979
+ def _redir_iglob(pathname, *args, **kwargs):
1980
+ if _should_redirect(pathname):
1981
+ pathname = '/host' + pathname
1982
+ return _orig_iglob(pathname, *args, **kwargs)
1983
+ _glob_module.iglob = _redir_iglob
1984
+
1985
+ # os.walk
1986
+ _orig_walk = os.walk
1987
+ def _redir_walk(top, *args, **kwargs):
1988
+ redirected = False
1989
+ if _should_redirect(top):
1990
+ top = '/host' + top
1991
+ redirected = True
1992
+ for dirpath, dirnames, filenames in _orig_walk(top, *args, **kwargs):
1993
+ if redirected and dirpath.startswith('/host'):
1994
+ dirpath = dirpath[5:] if len(dirpath) > 5 else '/'
1995
+ yield dirpath, dirnames, filenames
1996
+ os.walk = _redir_walk
1997
+
1998
+ # os.scandir
1999
+ _orig_scandir = os.scandir
2000
+ def _redir_scandir(path='.'):
2001
+ if _should_redirect(path):
2002
+ path = '/host' + path
2003
+ return _orig_scandir(path)
2004
+ os.scandir = _redir_scandir
2005
+
2006
+ # io.open
2007
+ import io as _io_module
2008
+ _io_module.open = builtins.open
2009
+
2010
+ # shutil
2011
+ import shutil as _shutil_module
2012
+
2013
+ _orig_shutil_copy = _shutil_module.copy
2014
+ def _redir_shutil_copy(src, dst, *args, **kwargs):
2015
+ if _should_redirect(src): src = '/host' + src
2016
+ if _should_redirect(dst): dst = '/host' + dst
2017
+ return _orig_shutil_copy(src, dst, *args, **kwargs)
2018
+ _shutil_module.copy = _redir_shutil_copy
2019
+
2020
+ _orig_shutil_copy2 = _shutil_module.copy2
2021
+ def _redir_shutil_copy2(src, dst, *args, **kwargs):
2022
+ if _should_redirect(src): src = '/host' + src
2023
+ if _should_redirect(dst): dst = '/host' + dst
2024
+ return _orig_shutil_copy2(src, dst, *args, **kwargs)
2025
+ _shutil_module.copy2 = _redir_shutil_copy2
2026
+
2027
+ _orig_shutil_copyfile = _shutil_module.copyfile
2028
+ def _redir_shutil_copyfile(src, dst, *args, **kwargs):
2029
+ if _should_redirect(src): src = '/host' + src
2030
+ if _should_redirect(dst): dst = '/host' + dst
2031
+ return _orig_shutil_copyfile(src, dst, *args, **kwargs)
2032
+ _shutil_module.copyfile = _redir_shutil_copyfile
2033
+
2034
+ _orig_shutil_copytree = _shutil_module.copytree
2035
+ def _redir_shutil_copytree(src, dst, *args, **kwargs):
2036
+ if _should_redirect(src): src = '/host' + src
2037
+ if _should_redirect(dst): dst = '/host' + dst
2038
+ return _orig_shutil_copytree(src, dst, *args, **kwargs)
2039
+ _shutil_module.copytree = _redir_shutil_copytree
2040
+
2041
+ _orig_shutil_move = _shutil_module.move
2042
+ def _redir_shutil_move(src, dst, *args, **kwargs):
2043
+ if _should_redirect(src): src = '/host' + src
2044
+ if _should_redirect(dst): dst = '/host' + dst
2045
+ return _orig_shutil_move(src, dst, *args, **kwargs)
2046
+ _shutil_module.move = _redir_shutil_move
2047
+
2048
+ _orig_shutil_rmtree = _shutil_module.rmtree
2049
+ def _redir_shutil_rmtree(path, *args, **kwargs):
2050
+ if _should_redirect(path): path = '/host' + path
2051
+ return _orig_shutil_rmtree(path, *args, **kwargs)
2052
+ _shutil_module.rmtree = _redir_shutil_rmtree
2053
+
2054
+ # pathlib.Path
2055
+ from pathlib import Path
2056
+
2057
+ def _redirect_path(p):
2058
+ s = str(p)
2059
+ if _should_redirect(s):
2060
+ return Path('/host' + s)
2061
+ return p
2062
+
2063
+ Path._orig_stat = Path.stat
2064
+ def _path_stat(self, *args, **kwargs):
2065
+ return _redirect_path(self)._orig_stat(*args, **kwargs)
2066
+ Path.stat = _path_stat
2067
+
2068
+ Path._orig_exists = Path.exists
2069
+ def _path_exists(self):
2070
+ return _redirect_path(self)._orig_exists()
2071
+ Path.exists = _path_exists
2072
+
2073
+ Path._orig_is_file = Path.is_file
2074
+ def _path_is_file(self):
2075
+ return _redirect_path(self)._orig_is_file()
2076
+ Path.is_file = _path_is_file
2077
+
2078
+ Path._orig_is_dir = Path.is_dir
2079
+ def _path_is_dir(self):
2080
+ return _redirect_path(self)._orig_is_dir()
2081
+ Path.is_dir = _path_is_dir
2082
+
2083
+ Path._orig_open = Path.open
2084
+ def _path_open(self, *args, **kwargs):
2085
+ return _redirect_path(self)._orig_open(*args, **kwargs)
2086
+ Path.open = _path_open
2087
+
2088
+ Path._orig_read_text = Path.read_text
2089
+ def _path_read_text(self, *args, **kwargs):
2090
+ return _redirect_path(self)._orig_read_text(*args, **kwargs)
2091
+ Path.read_text = _path_read_text
2092
+
2093
+ Path._orig_read_bytes = Path.read_bytes
2094
+ def _path_read_bytes(self):
2095
+ return _redirect_path(self)._orig_read_bytes()
2096
+ Path.read_bytes = _path_read_bytes
2097
+
2098
+ Path._orig_write_text = Path.write_text
2099
+ def _path_write_text(self, *args, **kwargs):
2100
+ return _redirect_path(self)._orig_write_text(*args, **kwargs)
2101
+ Path.write_text = _path_write_text
2102
+
2103
+ Path._orig_write_bytes = Path.write_bytes
2104
+ def _path_write_bytes(self, data):
2105
+ return _redirect_path(self)._orig_write_bytes(data)
2106
+ Path.write_bytes = _path_write_bytes
2107
+
2108
+ Path._orig_mkdir = Path.mkdir
2109
+ def _path_mkdir(self, *args, **kwargs):
2110
+ return _redirect_path(self)._orig_mkdir(*args, **kwargs)
2111
+ Path.mkdir = _path_mkdir
2112
+
2113
+ Path._orig_rmdir = Path.rmdir
2114
+ def _path_rmdir(self):
2115
+ return _redirect_path(self)._orig_rmdir()
2116
+ Path.rmdir = _path_rmdir
2117
+
2118
+ Path._orig_unlink = Path.unlink
2119
+ def _path_unlink(self, *args, **kwargs):
2120
+ return _redirect_path(self)._orig_unlink(*args, **kwargs)
2121
+ Path.unlink = _path_unlink
2122
+
2123
+ Path._orig_iterdir = Path.iterdir
2124
+ def _path_iterdir(self):
2125
+ redirected = _redirect_path(self)
2126
+ for p in redirected._orig_iterdir():
2127
+ s = str(p)
2128
+ if s.startswith('/host'):
2129
+ yield Path(s[5:])
2130
+ else:
2131
+ yield p
2132
+ Path.iterdir = _path_iterdir
2133
+
2134
+ Path._orig_glob = Path.glob
2135
+ def _path_glob(self, pattern):
2136
+ redirected = _redirect_path(self)
2137
+ for p in redirected._orig_glob(pattern):
2138
+ s = str(p)
2139
+ if s.startswith('/host'):
2140
+ yield Path(s[5:])
2141
+ else:
2142
+ yield p
2143
+ Path.glob = _path_glob
2144
+
2145
+ Path._orig_rglob = Path.rglob
2146
+ def _path_rglob(self, pattern):
2147
+ redirected = _redirect_path(self)
2148
+ for p in redirected._orig_rglob(pattern):
2149
+ s = str(p)
2150
+ if s.startswith('/host'):
2151
+ yield Path(s[5:])
2152
+ else:
2153
+ yield p
2154
+ Path.rglob = _path_rglob
2155
+
2156
+ # Set cwd to host mount
2157
+ os.chdir('/host' + ${JSON.stringify(input.cwd)})
2158
+ `;
2159
+ }
2160
+ function createHTTPFS(backend, FS) {
2161
+ let lastResponse = null;
2162
+ const encoder = new TextEncoder();
2163
+ const decoder = new TextDecoder();
2164
+ const HTTPFS = {
2165
+ mount(_mount) {
2166
+ return HTTPFS.createNode(null, "/", 16877, 0);
2167
+ },
2168
+ createNode(parent, name, mode, dev) {
2169
+ const node = FS.createNode(parent, name, mode, dev);
2170
+ node.node_ops = HTTPFS.node_ops;
2171
+ node.stream_ops = HTTPFS.stream_ops;
2172
+ return node;
2173
+ },
2174
+ node_ops: {
2175
+ getattr(node) {
2176
+ const isDir = node.name === "/" || node.parent === node;
2177
+ return {
2178
+ dev: 1,
2179
+ ino: node.id,
2180
+ mode: isDir ? 16877 : 33206,
2181
+ nlink: 1,
2182
+ uid: 0,
2183
+ gid: 0,
2184
+ rdev: 0,
2185
+ size: lastResponse ? lastResponse.length : 0,
2186
+ atime: /* @__PURE__ */ new Date(),
2187
+ mtime: /* @__PURE__ */ new Date(),
2188
+ ctime: /* @__PURE__ */ new Date(),
2189
+ blksize: 4096,
2190
+ blocks: 0
2191
+ };
2192
+ },
2193
+ setattr(_node, _attr) {
2194
+ },
2195
+ lookup(parent, name) {
2196
+ return HTTPFS.createNode(parent, name, 33206);
2197
+ },
2198
+ mknod(parent, name, mode, _dev) {
2199
+ return HTTPFS.createNode(parent, name, mode);
2200
+ },
2201
+ rename() {
2202
+ },
2203
+ unlink() {
2204
+ },
2205
+ rmdir() {
2206
+ },
2207
+ readdir(_node) {
2208
+ return ["request"];
2209
+ },
2210
+ symlink() {
2211
+ },
2212
+ readlink(_node) {
2213
+ return "";
2214
+ }
2215
+ },
2216
+ stream_ops: {
2217
+ open(stream) {
2218
+ delete stream.hostContent;
2219
+ stream.hostModified = false;
2220
+ const accessMode = stream.flags & 3;
2221
+ const isRead = accessMode === 0;
2222
+ if (isRead && lastResponse) {
2223
+ stream.hostContent = lastResponse;
2224
+ }
2225
+ },
2226
+ close(stream) {
2227
+ if (stream.hostModified && stream.hostContent) {
2228
+ const reqJson = decoder.decode(stream.hostContent);
2229
+ try {
2230
+ const req = JSON.parse(reqJson);
2231
+ const result = backend.httpRequest(req.url, {
2232
+ method: req.method || "GET",
2233
+ headers: req.headers || void 0,
2234
+ body: req.body || void 0
2235
+ });
2236
+ lastResponse = encoder.encode(JSON.stringify(result));
2237
+ } catch (e) {
2238
+ lastResponse = encoder.encode(
2239
+ JSON.stringify({ error: e.message })
2240
+ );
2241
+ }
2242
+ }
2243
+ delete stream.hostContent;
2244
+ delete stream.hostModified;
2245
+ },
2246
+ read(stream, buffer, offset, length, position) {
2247
+ const content = stream.hostContent;
2248
+ if (!content) return 0;
2249
+ const size = content.length;
2250
+ if (position >= size) return 0;
2251
+ const bytesToRead = Math.min(length, size - position);
2252
+ buffer.set(content.subarray(position, position + bytesToRead), offset);
2253
+ return bytesToRead;
2254
+ },
2255
+ write(stream, buffer, offset, length, position) {
2256
+ let content = stream.hostContent || new Uint8Array(0);
2257
+ const newSize = Math.max(content.length, position + length);
2258
+ if (newSize > content.length) {
2259
+ const newContent = new Uint8Array(newSize);
2260
+ newContent.set(content);
2261
+ content = newContent;
2262
+ stream.hostContent = content;
2263
+ }
2264
+ content.set(buffer.subarray(offset, offset + length), position);
2265
+ stream.hostModified = true;
2266
+ return length;
2267
+ },
2268
+ llseek(stream, offset, whence) {
2269
+ let position = offset;
2270
+ if (whence === 1)
2271
+ position += stream.position;
2272
+ else if (whence === 2) {
2273
+ const content = stream.hostContent;
2274
+ position += content ? content.length : 0;
2275
+ }
2276
+ if (position < 0) throw new FS.ErrnoError(28);
2277
+ return position;
2278
+ }
2279
+ }
2280
+ };
2281
+ return HTTPFS;
2282
+ }
2283
+ function generateHttpBridgeCode() {
2284
+ return `
2285
+ # HTTP bridge: jb_http module
2286
+ # Write request JSON to /_jb_http/request (custom FS triggers HTTP via SharedArrayBuffer)
2287
+ # Then read response JSON from same path.
2288
+
1914
2289
  class _JbHttpResponse:
1915
2290
  """HTTP response object similar to requests.Response"""
1916
2291
  def __init__(self, data):
@@ -1936,20 +2311,23 @@ class _JbHttpResponse:
1936
2311
  raise Exception(f"HTTP {self.status_code}: {self.reason}")
1937
2312
 
1938
2313
  class _JbHttp:
1939
- """HTTP client that bridges to just-bash's secureFetch"""
2314
+ """HTTP client that bridges to just-bash's secureFetch via custom FS"""
2315
+ def _do_request(self, method, url, headers=None, body=None):
2316
+ import json as _json
2317
+ req = _json.dumps({'url': url, 'method': method, 'headers': headers, 'body': body})
2318
+ # Write request to HTTPFS \u2014 close triggers the HTTP call synchronously
2319
+ with _orig_open('/_jb_http/request', 'w') as f:
2320
+ f.write(req)
2321
+ # Read response (cached by HTTPFS from the HTTP call above)
2322
+ with _orig_open('/_jb_http/request', 'r') as f:
2323
+ return _json.loads(f.read())
2324
+
1940
2325
  def request(self, method, url, headers=None, data=None, json_data=None):
1941
- # Import fresh each time to ensure we use the current bridge
1942
- # (important when worker is reused with different SharedArrayBuffer)
1943
- import _jb_http_bridge
1944
2326
  if json_data is not None:
1945
2327
  data = json.dumps(json_data)
1946
2328
  headers = headers or {}
1947
2329
  headers['Content-Type'] = 'application/json'
1948
- # Serialize headers to JSON to avoid PyProxy issues when passing to JS
1949
- headers_json = json.dumps(headers) if headers else None
1950
- result_json = _jb_http_bridge.request(url, method, headers_json, data)
1951
- result = json.loads(result_json)
1952
- # Check for errors from the bridge (network not configured, URL not allowed, etc.)
2330
+ result = self._do_request(method, url, headers, data)
1953
2331
  if 'error' in result and result.get('status') is None:
1954
2332
  raise Exception(result['error'])
1955
2333
  return _JbHttpResponse(result)
@@ -1972,7 +2350,6 @@ class _JbHttp:
1972
2350
  def patch(self, url, headers=None, data=None, json=None, **kwargs):
1973
2351
  return self.request('PATCH', url, headers=headers, data=data, json_data=json, **kwargs)
1974
2352
 
1975
- # Register jb_http as an importable module
1976
2353
  import types
1977
2354
  jb_http = types.ModuleType('jb_http')
1978
2355
  jb_http._client = _JbHttp()
@@ -1985,456 +2362,123 @@ jb_http.patch = jb_http._client.patch
1985
2362
  jb_http.request = jb_http._client.request
1986
2363
  jb_http.Response = _JbHttpResponse
1987
2364
  sys.modules['jb_http'] = jb_http
1988
-
1989
- # ============================================================
1990
- # SANDBOX SECURITY SETUP
1991
- # ============================================================
1992
- # Only apply sandbox restrictions once per Pyodide instance
1993
- if not hasattr(builtins, '_jb_sandbox_initialized'):
1994
- builtins._jb_sandbox_initialized = True
1995
-
1996
- # ------------------------------------------------------------
1997
- # 1. Block dangerous module imports (js, pyodide, pyodide_js, pyodide.ffi)
1998
- # These allow sandbox escape via JavaScript execution
1999
- # ------------------------------------------------------------
2000
- _BLOCKED_MODULES = frozenset({'js', 'pyodide', 'pyodide_js', 'pyodide.ffi'})
2001
- _BLOCKED_PREFIXES = ('js.', 'pyodide.', 'pyodide_js.')
2002
-
2003
- # Remove pre-loaded dangerous modules from sys.modules
2004
- for _blocked_mod in list(sys.modules.keys()):
2005
- if _blocked_mod in _BLOCKED_MODULES or any(_blocked_mod.startswith(p) for p in _BLOCKED_PREFIXES):
2006
- del sys.modules[_blocked_mod]
2007
-
2008
- # Create a secure callable wrapper that hides introspection attributes
2009
- # This prevents access to __closure__, __kwdefaults__, __globals__, etc.
2010
- def _make_secure_import(orig_import, blocked, prefixes):
2011
- """Create import function wrapped to block introspection."""
2012
- def _inner(name, globals=None, locals=None, fromlist=(), level=0):
2013
- if name in blocked or any(name.startswith(p) for p in prefixes):
2014
- raise ImportError(f"Module '{name}' is blocked in this sandbox")
2015
- return orig_import(name, globals, locals, fromlist, level)
2016
-
2017
- class _SecureImport:
2018
- """Wrapper that hides function internals from introspection."""
2019
- __slots__ = ()
2020
- def __call__(self, name, globals=None, locals=None, fromlist=(), level=0):
2021
- return _inner(name, globals, locals, fromlist, level)
2022
- def __getattribute__(self, name):
2023
- if name in ('__call__', '__class__'):
2024
- return object.__getattribute__(self, name)
2025
- raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
2026
- def __repr__(self):
2027
- return '<built-in function __import__>'
2028
- return _SecureImport()
2029
-
2030
- builtins.__import__ = _make_secure_import(builtins.__import__, _BLOCKED_MODULES, _BLOCKED_PREFIXES)
2031
- del _BLOCKED_MODULES, _BLOCKED_PREFIXES, _make_secure_import
2032
-
2033
- # ------------------------------------------------------------
2034
- # 2. Path redirection helper
2035
- # ------------------------------------------------------------
2036
- def _should_redirect(path):
2037
- """Check if a path should be redirected to /host."""
2038
- return (isinstance(path, str) and
2039
- path.startswith('/') and
2040
- not path.startswith('/lib') and
2041
- not path.startswith('/proc') and
2042
- not path.startswith('/host'))
2043
-
2044
- # ------------------------------------------------------------
2045
- # 3. Secure wrapper factory for file operations
2046
- # ------------------------------------------------------------
2047
- # This creates callable wrappers that hide __closure__, __globals__, etc.
2048
- def _make_secure_wrapper(func, name):
2049
- """Wrap a function to block introspection attributes."""
2050
- class _SecureWrapper:
2051
- __slots__ = ()
2052
- def __call__(self, *args, **kwargs):
2053
- return func(*args, **kwargs)
2054
- def __getattribute__(self, attr):
2055
- if attr in ('__call__', '__class__'):
2056
- return object.__getattribute__(self, attr)
2057
- raise AttributeError(f"'{type(self).__name__}' object has no attribute '{attr}'")
2058
- def __repr__(self):
2059
- return f'<built-in function {name}>'
2060
- return _SecureWrapper()
2061
-
2062
- # ------------------------------------------------------------
2063
- # 4. Redirect file operations to /host (with secure wrappers)
2064
- # ------------------------------------------------------------
2065
- # builtins.open
2066
- _orig_open = builtins.open
2067
- def _redir_open(path, mode='r', *args, **kwargs):
2068
- if _should_redirect(path):
2069
- path = '/host' + path
2070
- return _orig_open(path, mode, *args, **kwargs)
2071
- builtins.open = _make_secure_wrapper(_redir_open, 'open')
2072
-
2073
- # os.listdir
2074
- _orig_listdir = os.listdir
2075
- def _redir_listdir(path='.'):
2076
- if _should_redirect(path):
2077
- path = '/host' + path
2078
- return _orig_listdir(path)
2079
- os.listdir = _make_secure_wrapper(_redir_listdir, 'listdir')
2080
-
2081
- # os.path.exists
2082
- _orig_exists = os.path.exists
2083
- def _redir_exists(path):
2084
- if _should_redirect(path):
2085
- path = '/host' + path
2086
- return _orig_exists(path)
2087
- os.path.exists = _make_secure_wrapper(_redir_exists, 'exists')
2088
-
2089
- # os.path.isfile
2090
- _orig_isfile = os.path.isfile
2091
- def _redir_isfile(path):
2092
- if _should_redirect(path):
2093
- path = '/host' + path
2094
- return _orig_isfile(path)
2095
- os.path.isfile = _make_secure_wrapper(_redir_isfile, 'isfile')
2096
-
2097
- # os.path.isdir
2098
- _orig_isdir = os.path.isdir
2099
- def _redir_isdir(path):
2100
- if _should_redirect(path):
2101
- path = '/host' + path
2102
- return _orig_isdir(path)
2103
- os.path.isdir = _make_secure_wrapper(_redir_isdir, 'isdir')
2104
-
2105
- # os.stat
2106
- _orig_stat = os.stat
2107
- def _redir_stat(path, *args, **kwargs):
2108
- if _should_redirect(path):
2109
- path = '/host' + path
2110
- return _orig_stat(path, *args, **kwargs)
2111
- os.stat = _make_secure_wrapper(_redir_stat, 'stat')
2112
-
2113
- # os.mkdir
2114
- _orig_mkdir = os.mkdir
2115
- def _redir_mkdir(path, *args, **kwargs):
2116
- if _should_redirect(path):
2117
- path = '/host' + path
2118
- return _orig_mkdir(path, *args, **kwargs)
2119
- os.mkdir = _make_secure_wrapper(_redir_mkdir, 'mkdir')
2120
-
2121
- # os.makedirs
2122
- _orig_makedirs = os.makedirs
2123
- def _redir_makedirs(path, *args, **kwargs):
2124
- if _should_redirect(path):
2125
- path = '/host' + path
2126
- return _orig_makedirs(path, *args, **kwargs)
2127
- os.makedirs = _make_secure_wrapper(_redir_makedirs, 'makedirs')
2128
-
2129
- # os.remove
2130
- _orig_remove = os.remove
2131
- def _redir_remove(path, *args, **kwargs):
2132
- if _should_redirect(path):
2133
- path = '/host' + path
2134
- return _orig_remove(path, *args, **kwargs)
2135
- os.remove = _make_secure_wrapper(_redir_remove, 'remove')
2136
-
2137
- # os.rmdir
2138
- _orig_rmdir = os.rmdir
2139
- def _redir_rmdir(path, *args, **kwargs):
2140
- if _should_redirect(path):
2141
- path = '/host' + path
2142
- return _orig_rmdir(path, *args, **kwargs)
2143
- os.rmdir = _make_secure_wrapper(_redir_rmdir, 'rmdir')
2144
-
2145
- # os.getcwd - strip /host prefix
2146
- _orig_getcwd = os.getcwd
2147
- def _redir_getcwd():
2148
- cwd = _orig_getcwd()
2149
- if cwd.startswith('/host'):
2150
- return cwd[5:] # Strip '/host' prefix
2151
- return cwd
2152
- os.getcwd = _make_secure_wrapper(_redir_getcwd, 'getcwd')
2153
-
2154
- # os.chdir
2155
- _orig_chdir = os.chdir
2156
- def _redir_chdir(path):
2157
- if _should_redirect(path):
2158
- path = '/host' + path
2159
- return _orig_chdir(path)
2160
- os.chdir = _make_secure_wrapper(_redir_chdir, 'chdir')
2161
-
2162
- # ------------------------------------------------------------
2163
- # 5. Additional file operations (glob, walk, scandir, io.open)
2164
- # ------------------------------------------------------------
2165
- import glob as _glob_module
2166
-
2167
- _orig_glob = _glob_module.glob
2168
- def _redir_glob(pathname, *args, **kwargs):
2169
- if _should_redirect(pathname):
2170
- pathname = '/host' + pathname
2171
- return _orig_glob(pathname, *args, **kwargs)
2172
- _glob_module.glob = _make_secure_wrapper(_redir_glob, 'glob')
2173
-
2174
- _orig_iglob = _glob_module.iglob
2175
- def _redir_iglob(pathname, *args, **kwargs):
2176
- if _should_redirect(pathname):
2177
- pathname = '/host' + pathname
2178
- return _orig_iglob(pathname, *args, **kwargs)
2179
- _glob_module.iglob = _make_secure_wrapper(_redir_iglob, 'iglob')
2180
-
2181
- # os.walk (generator - needs special handling)
2182
- _orig_walk = os.walk
2183
- def _redir_walk(top, *args, **kwargs):
2184
- redirected = False
2185
- if _should_redirect(top):
2186
- top = '/host' + top
2187
- redirected = True
2188
- for dirpath, dirnames, filenames in _orig_walk(top, *args, **kwargs):
2189
- if redirected and dirpath.startswith('/host'):
2190
- dirpath = dirpath[5:] if len(dirpath) > 5 else '/'
2191
- yield dirpath, dirnames, filenames
2192
- os.walk = _make_secure_wrapper(_redir_walk, 'walk')
2193
-
2194
- # os.scandir
2195
- _orig_scandir = os.scandir
2196
- def _redir_scandir(path='.'):
2197
- if _should_redirect(path):
2198
- path = '/host' + path
2199
- return _orig_scandir(path)
2200
- os.scandir = _make_secure_wrapper(_redir_scandir, 'scandir')
2201
-
2202
- # io.open (same secure wrapper as builtins.open)
2203
- import io as _io_module
2204
- _io_module.open = builtins.open
2205
-
2206
- # ------------------------------------------------------------
2207
- # 6. shutil file operations
2208
- # ------------------------------------------------------------
2209
- import shutil as _shutil_module
2210
-
2211
- # shutil.copy(src, dst)
2212
- _orig_shutil_copy = _shutil_module.copy
2213
- def _redir_shutil_copy(src, dst, *args, **kwargs):
2214
- if _should_redirect(src):
2215
- src = '/host' + src
2216
- if _should_redirect(dst):
2217
- dst = '/host' + dst
2218
- return _orig_shutil_copy(src, dst, *args, **kwargs)
2219
- _shutil_module.copy = _make_secure_wrapper(_redir_shutil_copy, 'copy')
2220
-
2221
- # shutil.copy2(src, dst)
2222
- _orig_shutil_copy2 = _shutil_module.copy2
2223
- def _redir_shutil_copy2(src, dst, *args, **kwargs):
2224
- if _should_redirect(src):
2225
- src = '/host' + src
2226
- if _should_redirect(dst):
2227
- dst = '/host' + dst
2228
- return _orig_shutil_copy2(src, dst, *args, **kwargs)
2229
- _shutil_module.copy2 = _make_secure_wrapper(_redir_shutil_copy2, 'copy2')
2230
-
2231
- # shutil.copyfile(src, dst)
2232
- _orig_shutil_copyfile = _shutil_module.copyfile
2233
- def _redir_shutil_copyfile(src, dst, *args, **kwargs):
2234
- if _should_redirect(src):
2235
- src = '/host' + src
2236
- if _should_redirect(dst):
2237
- dst = '/host' + dst
2238
- return _orig_shutil_copyfile(src, dst, *args, **kwargs)
2239
- _shutil_module.copyfile = _make_secure_wrapper(_redir_shutil_copyfile, 'copyfile')
2240
-
2241
- # shutil.copytree(src, dst)
2242
- _orig_shutil_copytree = _shutil_module.copytree
2243
- def _redir_shutil_copytree(src, dst, *args, **kwargs):
2244
- if _should_redirect(src):
2245
- src = '/host' + src
2246
- if _should_redirect(dst):
2247
- dst = '/host' + dst
2248
- return _orig_shutil_copytree(src, dst, *args, **kwargs)
2249
- _shutil_module.copytree = _make_secure_wrapper(_redir_shutil_copytree, 'copytree')
2250
-
2251
- # shutil.move(src, dst)
2252
- _orig_shutil_move = _shutil_module.move
2253
- def _redir_shutil_move(src, dst, *args, **kwargs):
2254
- if _should_redirect(src):
2255
- src = '/host' + src
2256
- if _should_redirect(dst):
2257
- dst = '/host' + dst
2258
- return _orig_shutil_move(src, dst, *args, **kwargs)
2259
- _shutil_module.move = _make_secure_wrapper(_redir_shutil_move, 'move')
2260
-
2261
- # shutil.rmtree(path)
2262
- _orig_shutil_rmtree = _shutil_module.rmtree
2263
- def _redir_shutil_rmtree(path, *args, **kwargs):
2264
- if _should_redirect(path):
2265
- path = '/host' + path
2266
- return _orig_shutil_rmtree(path, *args, **kwargs)
2267
- _shutil_module.rmtree = _make_secure_wrapper(_redir_shutil_rmtree, 'rmtree')
2268
-
2269
- # ------------------------------------------------------------
2270
- # 7. pathlib.Path - redirect path resolution
2271
- # ------------------------------------------------------------
2272
- from pathlib import Path, PurePosixPath
2273
-
2274
- def _redirect_path(p):
2275
- """Convert a Path to redirect /absolute paths to /host."""
2276
- s = str(p)
2277
- if _should_redirect(s):
2278
- return Path('/host' + s)
2279
- return p
2280
-
2281
- # Helper to create method wrappers for Path
2282
- def _wrap_path_method(orig_method, name):
2283
- def wrapper(self, *args, **kwargs):
2284
- redirected = _redirect_path(self)
2285
- return getattr(redirected, '_orig_' + name)(*args, **kwargs)
2286
- return wrapper
2287
-
2288
- # Store original methods with _orig_ prefix, then replace with redirecting versions
2289
- # Path.stat()
2290
- Path._orig_stat = Path.stat
2291
- def _path_stat(self, *args, **kwargs):
2292
- return _redirect_path(self)._orig_stat(*args, **kwargs)
2293
- Path.stat = _path_stat
2294
-
2295
- # Path.exists()
2296
- Path._orig_exists = Path.exists
2297
- def _path_exists(self):
2298
- return _redirect_path(self)._orig_exists()
2299
- Path.exists = _path_exists
2300
-
2301
- # Path.is_file()
2302
- Path._orig_is_file = Path.is_file
2303
- def _path_is_file(self):
2304
- return _redirect_path(self)._orig_is_file()
2305
- Path.is_file = _path_is_file
2306
-
2307
- # Path.is_dir()
2308
- Path._orig_is_dir = Path.is_dir
2309
- def _path_is_dir(self):
2310
- return _redirect_path(self)._orig_is_dir()
2311
- Path.is_dir = _path_is_dir
2312
-
2313
- # Path.open()
2314
- Path._orig_open = Path.open
2315
- def _path_open(self, *args, **kwargs):
2316
- return _redirect_path(self)._orig_open(*args, **kwargs)
2317
- Path.open = _path_open
2318
-
2319
- # Path.read_text()
2320
- Path._orig_read_text = Path.read_text
2321
- def _path_read_text(self, *args, **kwargs):
2322
- return _redirect_path(self)._orig_read_text(*args, **kwargs)
2323
- Path.read_text = _path_read_text
2324
-
2325
- # Path.read_bytes()
2326
- Path._orig_read_bytes = Path.read_bytes
2327
- def _path_read_bytes(self):
2328
- return _redirect_path(self)._orig_read_bytes()
2329
- Path.read_bytes = _path_read_bytes
2330
-
2331
- # Path.write_text()
2332
- Path._orig_write_text = Path.write_text
2333
- def _path_write_text(self, *args, **kwargs):
2334
- return _redirect_path(self)._orig_write_text(*args, **kwargs)
2335
- Path.write_text = _path_write_text
2336
-
2337
- # Path.write_bytes()
2338
- Path._orig_write_bytes = Path.write_bytes
2339
- def _path_write_bytes(self, data):
2340
- return _redirect_path(self)._orig_write_bytes(data)
2341
- Path.write_bytes = _path_write_bytes
2342
-
2343
- # Path.mkdir()
2344
- Path._orig_mkdir = Path.mkdir
2345
- def _path_mkdir(self, *args, **kwargs):
2346
- return _redirect_path(self)._orig_mkdir(*args, **kwargs)
2347
- Path.mkdir = _path_mkdir
2348
-
2349
- # Path.rmdir()
2350
- Path._orig_rmdir = Path.rmdir
2351
- def _path_rmdir(self):
2352
- return _redirect_path(self)._orig_rmdir()
2353
- Path.rmdir = _path_rmdir
2354
-
2355
- # Path.unlink()
2356
- Path._orig_unlink = Path.unlink
2357
- def _path_unlink(self, *args, **kwargs):
2358
- return _redirect_path(self)._orig_unlink(*args, **kwargs)
2359
- Path.unlink = _path_unlink
2360
-
2361
- # Path.iterdir()
2362
- Path._orig_iterdir = Path.iterdir
2363
- def _path_iterdir(self):
2364
- redirected = _redirect_path(self)
2365
- for p in redirected._orig_iterdir():
2366
- # Strip /host prefix from results
2367
- s = str(p)
2368
- if s.startswith('/host'):
2369
- yield Path(s[5:])
2370
- else:
2371
- yield p
2372
- Path.iterdir = _path_iterdir
2373
-
2374
- # Path.glob()
2375
- Path._orig_glob = Path.glob
2376
- def _path_glob(self, pattern):
2377
- redirected = _redirect_path(self)
2378
- for p in redirected._orig_glob(pattern):
2379
- s = str(p)
2380
- if s.startswith('/host'):
2381
- yield Path(s[5:])
2382
- else:
2383
- yield p
2384
- Path.glob = _path_glob
2385
-
2386
- # Path.rglob()
2387
- Path._orig_rglob = Path.rglob
2388
- def _path_rglob(self, pattern):
2389
- redirected = _redirect_path(self)
2390
- for p in redirected._orig_rglob(pattern):
2391
- s = str(p)
2392
- if s.startswith('/host'):
2393
- yield Path(s[5:])
2394
- else:
2395
- yield p
2396
- Path.rglob = _path_rglob
2397
-
2398
- # Set cwd to host mount
2399
- os.chdir('/host' + ${JSON.stringify(input.cwd)})
2365
+ `;
2366
+ }
2367
+ var cachedStdlibZip = new Uint8Array(readFileSync(stdlibZipPath));
2368
+ async function runPython(input) {
2369
+ const backend = new SyncFsBackend(input.sharedBuffer);
2370
+ const createPythonModule = require2(`${cpythonDir}/python.cjs`);
2371
+ let moduleReady = false;
2372
+ const pendingStdout = [];
2373
+ const pendingStderr = [];
2374
+ let Module;
2375
+ try {
2376
+ Module = await createPythonModule({
2377
+ noInitialRun: true,
2378
+ preRun: [
2379
+ (mod) => {
2380
+ mod.FS.mkdirTree("/lib");
2381
+ mod.FS.writeFile("/lib/python313.zip", cachedStdlibZip);
2382
+ mod.ENV.PYTHONHOME = "/";
2383
+ mod.ENV.PYTHONPATH = "/lib/python313.zip";
2384
+ }
2385
+ ],
2386
+ print: (text) => {
2387
+ if (moduleReady) {
2388
+ backend.writeStdout(`${text}
2389
+ `);
2390
+ } else {
2391
+ pendingStdout.push(`${text}
2392
+ `);
2393
+ }
2394
+ },
2395
+ printErr: (text) => {
2396
+ if (typeof text === "string" && (text.includes("Could not find platform") || text.includes("LLVM Profile Error"))) {
2397
+ return;
2398
+ }
2399
+ if (moduleReady) {
2400
+ backend.writeStderr(`${text}
2400
2401
  `);
2402
+ } else {
2403
+ pendingStderr.push(`${text}
2404
+ `);
2405
+ }
2406
+ }
2407
+ });
2401
2408
  } catch (e) {
2402
2409
  return {
2403
2410
  success: false,
2404
- error: `Failed to set up environment: ${e.message}`
2411
+ error: `Failed to load CPython: ${e.message}`
2405
2412
  };
2406
2413
  }
2414
+ activateDefense();
2415
+ moduleReady = true;
2416
+ for (const text of pendingStdout) backend.writeStdout(text);
2417
+ for (const text of pendingStderr) backend.writeStderr(text);
2418
+ const HOSTFS = createHOSTFS(backend, Module.FS, Module.PATH);
2407
2419
  try {
2408
- const wrappedCode = `
2420
+ Module.FS.mkdir("/host");
2421
+ Module.FS.mount(HOSTFS, { root: "/" }, "/host");
2422
+ } catch (e) {
2423
+ return {
2424
+ success: false,
2425
+ error: `Failed to mount HOSTFS: ${e.message}`
2426
+ };
2427
+ }
2428
+ const HTTPFS = createHTTPFS(backend, Module.FS);
2429
+ try {
2430
+ Module.FS.mkdir("/_jb_http");
2431
+ Module.FS.mount(HTTPFS, { root: "/" }, "/_jb_http");
2432
+ } catch (e) {
2433
+ return {
2434
+ success: false,
2435
+ error: `Failed to mount HTTPFS: ${e.message}`
2436
+ };
2437
+ }
2438
+ const setupCode = generateSetupCode(input);
2439
+ const httpBridgeCode = generateHttpBridgeCode();
2440
+ const wrappedCode = `
2409
2441
  import sys
2410
2442
  _jb_exit_code = 0
2411
2443
  try:
2444
+ ${setupCode.split("\n").map((line) => ` ${line}`).join("\n")}
2445
+ ${httpBridgeCode.split("\n").map((line) => ` ${line}`).join("\n")}
2412
2446
  ${input.pythonCode.split("\n").map((line) => ` ${line}`).join("\n")}
2413
2447
  except SystemExit as e:
2414
2448
  _jb_exit_code = e.code if isinstance(e.code, int) else (1 if e.code else 0)
2449
+ except Exception as e:
2450
+ import traceback
2451
+ traceback.print_exc()
2452
+ _jb_exit_code = 1
2453
+ sys.exit(_jb_exit_code)
2415
2454
  `;
2416
- await pyodide.runPythonAsync(wrappedCode);
2417
- const exitCode = pyodide.globals.get("_jb_exit_code");
2455
+ try {
2456
+ Module.FS.mkdir("/tmp");
2457
+ } catch (_e) {
2458
+ }
2459
+ const encoder = new TextEncoder();
2460
+ const scriptPath = "/tmp/_jb_script.py";
2461
+ const scriptData = encoder.encode(wrappedCode);
2462
+ Module.FS.writeFile(scriptPath, scriptData);
2463
+ try {
2464
+ const ret = Module.callMain([scriptPath]);
2465
+ const exitCode = (typeof ret === "number" ? ret : 0) || process.exitCode || 0;
2418
2466
  backend.exit(exitCode);
2419
2467
  return { success: true };
2420
2468
  } catch (e) {
2421
2469
  const error = e;
2422
- backend.writeStderr(`${error.message}
2423
- `);
2424
- backend.exit(1);
2470
+ const exitCode = error.status ?? process.exitCode ?? 1;
2471
+ backend.exit(exitCode);
2425
2472
  return { success: true };
2426
2473
  }
2427
2474
  }
2428
2475
  var defense = null;
2429
- async function initializeWithDefense() {
2430
- await getPyodide();
2476
+ function activateDefense() {
2477
+ if (defense) return;
2431
2478
  defense = new WorkerDefenseInDepth({
2432
2479
  excludeViolationTypes: [
2433
- "proxy",
2434
- "setImmediate",
2435
- // 3. SharedArrayBuffer/Atomics: Used by sync-fs-backend.ts for synchronous
2436
- // filesystem communication between Pyodide's WASM thread and the main thread.
2437
- // Without this, Pyodide cannot perform synchronous file I/O operations.
2480
+ // SharedArrayBuffer/Atomics: Used by sync-fs-backend.ts for synchronous
2481
+ // filesystem communication between the WASM thread and the main thread.
2438
2482
  "shared_array_buffer",
2439
2483
  "atomics"
2440
2484
  ],
@@ -2443,9 +2487,15 @@ async function initializeWithDefense() {
2443
2487
  }
2444
2488
  });
2445
2489
  }
2490
+ process.on("uncaughtException", (e) => {
2491
+ parentPort?.postMessage({
2492
+ success: false,
2493
+ error: `Worker uncaught exception: ${e.message}`
2494
+ });
2495
+ });
2446
2496
  if (parentPort) {
2447
2497
  if (workerData) {
2448
- initializeWithDefense().then(() => runPython(workerData)).then((result) => {
2498
+ runPython(workerData).then((result) => {
2449
2499
  result.defenseStats = defense?.getStats();
2450
2500
  parentPort?.postMessage(result);
2451
2501
  }).catch((e) => {
@@ -2456,20 +2506,4 @@ if (parentPort) {
2456
2506
  });
2457
2507
  });
2458
2508
  }
2459
- parentPort.on("message", async (input) => {
2460
- try {
2461
- if (!defense) {
2462
- await initializeWithDefense();
2463
- }
2464
- const result = await runPython(input);
2465
- result.defenseStats = defense?.getStats();
2466
- parentPort?.postMessage(result);
2467
- } catch (e) {
2468
- parentPort?.postMessage({
2469
- success: false,
2470
- error: e.message,
2471
- defenseStats: defense?.getStats()
2472
- });
2473
- }
2474
- });
2475
2509
  }