just-bash 2.11.9 → 2.11.11
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/dist/Bash.d.ts +1 -1
- package/dist/bin/chunks/python3-YJ7YGEW7.js +16 -0
- package/dist/bin/chunks/worker.js +581 -714
- package/dist/bin/just-bash.js +1 -1
- package/dist/bin/shell/chunks/python3-YJ7YGEW7.js +16 -0
- package/dist/bin/shell/shell.js +1 -1
- package/dist/bundle/chunks/python3-6Y4Z63NZ.js +15 -0
- package/dist/bundle/chunks/worker.js +581 -714
- package/dist/bundle/index.cjs +709 -707
- package/dist/bundle/index.js +1 -1
- package/dist/commands/python3/protocol.d.ts +1 -1
- package/dist/commands/python3/python3.d.ts +4 -1
- package/dist/commands/python3/worker.d.ts +8 -4
- package/package.json +10 -9
- package/vendor/cpython-emscripten/python.cjs +2 -0
- package/vendor/cpython-emscripten/python.wasm +0 -0
- package/vendor/cpython-emscripten/python313.zip +0 -0
- package/dist/bin/chunks/python3-UK6DVXEU.js +0 -14
- package/dist/bin/shell/chunks/python3-UK6DVXEU.js +0 -14
- package/dist/bundle/chunks/python3-W2D6SNQF.js +0 -13
|
@@ -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() {
|
|
@@ -1536,21 +1535,60 @@ var SyncFsBackend = class {
|
|
|
1536
1535
|
};
|
|
1537
1536
|
|
|
1538
1537
|
// src/commands/python3/worker.ts
|
|
1539
|
-
|
|
1540
|
-
var pyodideLoading = null;
|
|
1538
|
+
import { readFileSync } from "node:fs";
|
|
1541
1539
|
var require2 = createRequire(import.meta.url);
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
if (
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
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
|
+
};
|
|
1549
1580
|
}
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
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";
|
|
1553
1590
|
}
|
|
1591
|
+
var stdlibZipPath = `${cpythonDir}/python313.zip`;
|
|
1554
1592
|
function createHOSTFS(backend, FS, PATH) {
|
|
1555
1593
|
const ERRNO_CODES = {
|
|
1556
1594
|
EPERM: 63,
|
|
@@ -1816,114 +1854,438 @@ function createHOSTFS(backend, FS, PATH) {
|
|
|
1816
1854
|
};
|
|
1817
1855
|
return HOSTFS;
|
|
1818
1856
|
}
|
|
1819
|
-
|
|
1820
|
-
const backend = new SyncFsBackend(input.sharedBuffer);
|
|
1821
|
-
let pyodide;
|
|
1822
|
-
try {
|
|
1823
|
-
pyodide = await getPyodide();
|
|
1824
|
-
} catch (e) {
|
|
1825
|
-
return {
|
|
1826
|
-
success: false,
|
|
1827
|
-
error: `Failed to load Pyodide: ${e.message}`
|
|
1828
|
-
};
|
|
1829
|
-
}
|
|
1830
|
-
pyodide.setStdout({ batched: () => {
|
|
1831
|
-
} });
|
|
1832
|
-
pyodide.setStderr({ batched: () => {
|
|
1833
|
-
} });
|
|
1834
|
-
try {
|
|
1835
|
-
pyodide.runPython(`
|
|
1836
|
-
import sys
|
|
1837
|
-
if hasattr(sys.stdout, 'flush'):
|
|
1838
|
-
sys.stdout.flush()
|
|
1839
|
-
if hasattr(sys.stderr, 'flush'):
|
|
1840
|
-
sys.stderr.flush()
|
|
1841
|
-
`);
|
|
1842
|
-
} catch (_e) {
|
|
1843
|
-
}
|
|
1844
|
-
pyodide.setStdout({
|
|
1845
|
-
batched: (text) => {
|
|
1846
|
-
backend.writeStdout(`${text}
|
|
1847
|
-
`);
|
|
1848
|
-
}
|
|
1849
|
-
});
|
|
1850
|
-
pyodide.setStderr({
|
|
1851
|
-
batched: (text) => {
|
|
1852
|
-
backend.writeStderr(`${text}
|
|
1853
|
-
`);
|
|
1854
|
-
}
|
|
1855
|
-
});
|
|
1856
|
-
const FS = pyodide.FS;
|
|
1857
|
-
const PATH = pyodide.PATH;
|
|
1858
|
-
const HOSTFS = createHOSTFS(backend, FS, PATH);
|
|
1859
|
-
try {
|
|
1860
|
-
try {
|
|
1861
|
-
pyodide.runPython(`import os; os.chdir('/')`);
|
|
1862
|
-
} catch (_e) {
|
|
1863
|
-
}
|
|
1864
|
-
try {
|
|
1865
|
-
FS.mkdir("/host");
|
|
1866
|
-
} catch (_e) {
|
|
1867
|
-
}
|
|
1868
|
-
try {
|
|
1869
|
-
FS.unmount("/host");
|
|
1870
|
-
} catch (_e) {
|
|
1871
|
-
}
|
|
1872
|
-
FS.mount(HOSTFS, { root: "/" }, "/host");
|
|
1873
|
-
} catch (e) {
|
|
1874
|
-
return {
|
|
1875
|
-
success: false,
|
|
1876
|
-
error: `Failed to mount HOSTFS: ${e.message}`
|
|
1877
|
-
};
|
|
1878
|
-
}
|
|
1879
|
-
try {
|
|
1880
|
-
pyodide.runPython(`
|
|
1881
|
-
import sys
|
|
1882
|
-
if '_jb_http_bridge' in sys.modules:
|
|
1883
|
-
del sys.modules['_jb_http_bridge']
|
|
1884
|
-
if 'jb_http' in sys.modules:
|
|
1885
|
-
del sys.modules['jb_http']
|
|
1886
|
-
`);
|
|
1887
|
-
} catch (_e) {
|
|
1888
|
-
}
|
|
1889
|
-
const bridgeRequestFn = (url, method, headersJson, body) => {
|
|
1890
|
-
try {
|
|
1891
|
-
const headers = headersJson ? JSON.parse(headersJson) : void 0;
|
|
1892
|
-
const result = backend.httpRequest(url, {
|
|
1893
|
-
method: method || "GET",
|
|
1894
|
-
headers,
|
|
1895
|
-
body: body || void 0
|
|
1896
|
-
});
|
|
1897
|
-
return JSON.stringify(result);
|
|
1898
|
-
} catch (e) {
|
|
1899
|
-
return JSON.stringify({ error: e.message });
|
|
1900
|
-
}
|
|
1901
|
-
};
|
|
1902
|
-
pyodide.registerJsModule("_jb_http_bridge", {
|
|
1903
|
-
request: bridgeRequestFn
|
|
1904
|
-
});
|
|
1905
|
-
pyodide.globals.set("_jb_http_req_fn", bridgeRequestFn);
|
|
1857
|
+
function generateSetupCode(input) {
|
|
1906
1858
|
const envSetup = Object.entries(input.env).map(([key, value]) => {
|
|
1907
1859
|
return `os.environ[${JSON.stringify(key)}] = ${JSON.stringify(value)}`;
|
|
1908
1860
|
}).join("\n");
|
|
1909
1861
|
const argv0 = input.scriptPath || "python3";
|
|
1910
1862
|
const argvList = [argv0, ...input.args].map((arg) => JSON.stringify(arg)).join(", ");
|
|
1911
|
-
|
|
1912
|
-
await pyodide.runPythonAsync(`
|
|
1863
|
+
return `
|
|
1913
1864
|
import os
|
|
1914
1865
|
import sys
|
|
1915
|
-
import builtins
|
|
1916
1866
|
import json
|
|
1917
1867
|
|
|
1918
1868
|
${envSetup}
|
|
1919
1869
|
|
|
1920
1870
|
sys.argv = [${argvList}]
|
|
1921
1871
|
|
|
1922
|
-
#
|
|
1923
|
-
|
|
1924
|
-
|
|
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.
|
|
1925
2288
|
|
|
1926
|
-
# Create jb_http module for HTTP requests
|
|
1927
2289
|
class _JbHttpResponse:
|
|
1928
2290
|
"""HTTP response object similar to requests.Response"""
|
|
1929
2291
|
def __init__(self, data):
|
|
@@ -1949,21 +2311,23 @@ class _JbHttpResponse:
|
|
|
1949
2311
|
raise Exception(f"HTTP {self.status_code}: {self.reason}")
|
|
1950
2312
|
|
|
1951
2313
|
class _JbHttp:
|
|
1952
|
-
"""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
|
+
|
|
1953
2325
|
def request(self, method, url, headers=None, data=None, json_data=None):
|
|
1954
|
-
# Uses _jb_http_req_fn captured above from sys.modules before scrub.
|
|
1955
|
-
# The bridge module itself is blocked from user import.
|
|
1956
|
-
if _jb_http_req_fn is None:
|
|
1957
|
-
raise Exception('HTTP bridge not available')
|
|
1958
2326
|
if json_data is not None:
|
|
1959
2327
|
data = json.dumps(json_data)
|
|
1960
2328
|
headers = headers or {}
|
|
1961
2329
|
headers['Content-Type'] = 'application/json'
|
|
1962
|
-
|
|
1963
|
-
headers_json = json.dumps(headers) if headers else None
|
|
1964
|
-
result_json = _jb_http_req_fn(url, method, headers_json, data)
|
|
1965
|
-
result = json.loads(result_json)
|
|
1966
|
-
# Check for errors from the bridge (network not configured, URL not allowed, etc.)
|
|
2330
|
+
result = self._do_request(method, url, headers, data)
|
|
1967
2331
|
if 'error' in result and result.get('status') is None:
|
|
1968
2332
|
raise Exception(result['error'])
|
|
1969
2333
|
return _JbHttpResponse(result)
|
|
@@ -1986,7 +2350,6 @@ class _JbHttp:
|
|
|
1986
2350
|
def patch(self, url, headers=None, data=None, json=None, **kwargs):
|
|
1987
2351
|
return self.request('PATCH', url, headers=headers, data=data, json_data=json, **kwargs)
|
|
1988
2352
|
|
|
1989
|
-
# Register jb_http as an importable module
|
|
1990
2353
|
import types
|
|
1991
2354
|
jb_http = types.ModuleType('jb_http')
|
|
1992
2355
|
jb_http._client = _JbHttp()
|
|
@@ -1999,609 +2362,123 @@ jb_http.patch = jb_http._client.patch
|
|
|
1999
2362
|
jb_http.request = jb_http._client.request
|
|
2000
2363
|
jb_http.Response = _JbHttpResponse
|
|
2001
2364
|
sys.modules['jb_http'] = jb_http
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
_e.__traceback__ = None
|
|
2042
|
-
raise
|
|
2043
|
-
def __getattribute__(self, name):
|
|
2044
|
-
if name in ('__call__', '__class__'):
|
|
2045
|
-
return object.__getattribute__(self, name)
|
|
2046
|
-
raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
|
|
2047
|
-
def __repr__(self):
|
|
2048
|
-
return '<built-in function __import__>'
|
|
2049
|
-
return _SecureImport()
|
|
2050
|
-
|
|
2051
|
-
builtins.__import__ = _make_secure_import(builtins.__import__, _BLOCKED_MODULES, _BLOCKED_PREFIXES)
|
|
2052
|
-
|
|
2053
|
-
# ------------------------------------------------------------
|
|
2054
|
-
# 1b. Block imports via sys.meta_path finder
|
|
2055
|
-
# importlib.import_module routes through _bootstrap._find_and_load,
|
|
2056
|
-
# NOT through builtins.__import__. A meta_path finder blocks ALL
|
|
2057
|
-
# import paths at the most fundamental level.
|
|
2058
|
-
# ------------------------------------------------------------
|
|
2059
|
-
_blocked_set = frozenset(_BLOCKED_MODULES)
|
|
2060
|
-
_blocked_pfx = tuple(_BLOCKED_PREFIXES)
|
|
2061
|
-
|
|
2062
|
-
class _BlockingFinder:
|
|
2063
|
-
"""Meta-path finder that blocks imports of sandboxed modules."""
|
|
2064
|
-
__slots__ = ()
|
|
2065
|
-
def find_module(self, name, path=None):
|
|
2066
|
-
if name in _blocked_set or any(name.startswith(p) for p in _blocked_pfx):
|
|
2067
|
-
raise ImportError(f"Module '{name}' is blocked in this sandbox")
|
|
2068
|
-
return None
|
|
2069
|
-
def find_spec(self, name, path, target=None):
|
|
2070
|
-
if name in _blocked_set or any(name.startswith(p) for p in _blocked_pfx):
|
|
2071
|
-
raise ImportError(f"Module '{name}' is blocked in this sandbox")
|
|
2072
|
-
return None
|
|
2073
|
-
sys.meta_path.insert(0, _BlockingFinder())
|
|
2074
|
-
|
|
2075
|
-
# ------------------------------------------------------------
|
|
2076
|
-
# 1c. Patch importlib.import_module and importlib.util.find_spec
|
|
2077
|
-
# Belt-and-suspenders: also block at the importlib API level
|
|
2078
|
-
# in case sys.modules already contains the blocked module.
|
|
2079
|
-
# ------------------------------------------------------------
|
|
2080
|
-
import importlib
|
|
2081
|
-
import importlib.util
|
|
2082
|
-
|
|
2083
|
-
_orig_import_module = importlib.import_module
|
|
2084
|
-
def _secure_import_module_inner(name, package=None):
|
|
2085
|
-
if name in _blocked_set or any(name.startswith(p) for p in _blocked_pfx):
|
|
2086
|
-
raise ImportError(f"Module '{name}' is blocked in this sandbox")
|
|
2087
|
-
return _orig_import_module(name, package)
|
|
2088
|
-
|
|
2089
|
-
class _SecureImportModule:
|
|
2090
|
-
__slots__ = ()
|
|
2091
|
-
def __call__(self, name, package=None):
|
|
2092
|
-
try:
|
|
2093
|
-
return _secure_import_module_inner(name, package)
|
|
2094
|
-
except BaseException as _e:
|
|
2095
|
-
_e.__traceback__ = None
|
|
2096
|
-
raise
|
|
2097
|
-
def __getattribute__(self, name):
|
|
2098
|
-
if name in ('__call__', '__class__'):
|
|
2099
|
-
return object.__getattribute__(self, name)
|
|
2100
|
-
raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
|
|
2101
|
-
def __repr__(self):
|
|
2102
|
-
return '<function import_module>'
|
|
2103
|
-
importlib.import_module = _SecureImportModule()
|
|
2104
|
-
|
|
2105
|
-
_orig_find_spec = importlib.util.find_spec
|
|
2106
|
-
def _secure_find_spec_inner(name, package=None):
|
|
2107
|
-
if name in _blocked_set or any(name.startswith(p) for p in _blocked_pfx):
|
|
2108
|
-
raise ImportError(f"Module '{name}' is blocked in this sandbox")
|
|
2109
|
-
return _orig_find_spec(name, package)
|
|
2110
|
-
|
|
2111
|
-
class _SecureFindSpec:
|
|
2112
|
-
__slots__ = ()
|
|
2113
|
-
def __call__(self, name, package=None):
|
|
2114
|
-
try:
|
|
2115
|
-
return _secure_find_spec_inner(name, package)
|
|
2116
|
-
except BaseException as _e:
|
|
2117
|
-
_e.__traceback__ = None
|
|
2118
|
-
raise
|
|
2119
|
-
def __getattribute__(self, name):
|
|
2120
|
-
if name in ('__call__', '__class__'):
|
|
2121
|
-
return object.__getattribute__(self, name)
|
|
2122
|
-
raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
|
|
2123
|
-
def __repr__(self):
|
|
2124
|
-
return '<function find_spec>'
|
|
2125
|
-
importlib.util.find_spec = _SecureFindSpec()
|
|
2126
|
-
|
|
2127
|
-
# Note: We intentionally do NOT replace sys.modules with a custom dict
|
|
2128
|
-
# subclass. Replacing it breaks lazy imports (e.g., _strptime used by
|
|
2129
|
-
# datetime.strptime) because Pyodide's C-level import machinery
|
|
2130
|
-
# caches the original dict. Instead, we rely on:
|
|
2131
|
-
# 1. Per-run scrubbing of blocked modules from sys.modules
|
|
2132
|
-
# 2. meta_path finder blocking new imports
|
|
2133
|
-
# 3. builtins.__import__ hook blocking import statements
|
|
2134
|
-
|
|
2135
|
-
# ------------------------------------------------------------
|
|
2136
|
-
# 1cc. Patch importlib._bootstrap at the deepest level
|
|
2137
|
-
# _bootstrap.__import__ holds a reference to the ORIGINAL __import__
|
|
2138
|
-
# (not our wrapper). An attacker can use it directly to bypass all
|
|
2139
|
-
# our hooks. Patch it with our wrapper.
|
|
2140
|
-
# Also patch _bootstrap._find_and_load \u2014 this is the function that
|
|
2141
|
-
# the C-level import machinery calls. Even if an attacker steals
|
|
2142
|
-
# orig_import via closure introspection AND replaces sys.meta_path,
|
|
2143
|
-
# this blocks the import at the lowest Python-accessible level.
|
|
2144
|
-
# ------------------------------------------------------------
|
|
2145
|
-
_bootstrap_mod = sys.modules.get('importlib._bootstrap')
|
|
2146
|
-
if _bootstrap_mod:
|
|
2147
|
-
_bootstrap_mod.__import__ = builtins.__import__
|
|
2148
|
-
|
|
2149
|
-
_orig_find_and_load = _bootstrap_mod._find_and_load
|
|
2150
|
-
def _secure_find_and_load(name, import_):
|
|
2151
|
-
if name in _blocked_set or any(name.startswith(p) for p in _blocked_pfx):
|
|
2152
|
-
raise ImportError(f"Module '{name}' is blocked in this sandbox")
|
|
2153
|
-
return _orig_find_and_load(name, import_)
|
|
2154
|
-
_bootstrap_mod._find_and_load = _secure_find_and_load
|
|
2155
|
-
|
|
2156
|
-
# Also patch _find_and_load_unlocked (called internally)
|
|
2157
|
-
if hasattr(_bootstrap_mod, '_find_and_load_unlocked'):
|
|
2158
|
-
_orig_find_and_load_unlocked = _bootstrap_mod._find_and_load_unlocked
|
|
2159
|
-
def _secure_find_and_load_unlocked(name, import_):
|
|
2160
|
-
if name in _blocked_set or any(name.startswith(p) for p in _blocked_pfx):
|
|
2161
|
-
raise ImportError(f"Module '{name}' is blocked in this sandbox")
|
|
2162
|
-
return _orig_find_and_load_unlocked(name, import_)
|
|
2163
|
-
_bootstrap_mod._find_and_load_unlocked = _secure_find_and_load_unlocked
|
|
2164
|
-
|
|
2165
|
-
del _BLOCKED_MODULES, _BLOCKED_PREFIXES, _make_secure_import
|
|
2166
|
-
|
|
2167
|
-
# ------------------------------------------------------------
|
|
2168
|
-
# 1d. Block gc object-discovery functions
|
|
2169
|
-
# gc.get_objects() can find purged module objects still in memory
|
|
2170
|
-
# (held by Pyodide C internals), enabling sandbox escape.
|
|
2171
|
-
# ------------------------------------------------------------
|
|
2172
|
-
import gc as _gc_module
|
|
2173
|
-
_gc_module.get_objects = lambda: []
|
|
2174
|
-
_gc_module.get_referrers = lambda *args: []
|
|
2175
|
-
_gc_module.get_referents = lambda *args: []
|
|
2176
|
-
|
|
2177
|
-
# ------------------------------------------------------------
|
|
2178
|
-
# 1e. Neuter sys.settrace and sys.setprofile
|
|
2179
|
-
# These debugging APIs expose call frames via trace callbacks.
|
|
2180
|
-
# An attacker can use them to inspect closure variables in our
|
|
2181
|
-
# import hooks (e.g., orig_import in _inner), stealing the
|
|
2182
|
-
# original __import__ and bypassing all blocking.
|
|
2183
|
-
# ------------------------------------------------------------
|
|
2184
|
-
sys.settrace = lambda *args: None
|
|
2185
|
-
sys.setprofile = lambda *args: None
|
|
2186
|
-
|
|
2187
|
-
# ------------------------------------------------------------
|
|
2188
|
-
# 2. Path redirection helper
|
|
2189
|
-
# ------------------------------------------------------------
|
|
2190
|
-
def _should_redirect(path):
|
|
2191
|
-
"""Check if a path should be redirected to /host."""
|
|
2192
|
-
return (isinstance(path, str) and
|
|
2193
|
-
path.startswith('/') and
|
|
2194
|
-
not path.startswith('/lib') and
|
|
2195
|
-
not path.startswith('/proc') and
|
|
2196
|
-
not path.startswith('/host'))
|
|
2197
|
-
|
|
2198
|
-
# ------------------------------------------------------------
|
|
2199
|
-
# 3. Secure wrapper factory for file operations
|
|
2200
|
-
# ------------------------------------------------------------
|
|
2201
|
-
# This creates callable wrappers that hide __closure__, __globals__, etc.
|
|
2202
|
-
def _make_secure_wrapper(func, name):
|
|
2203
|
-
"""Wrap a function to block introspection attributes."""
|
|
2204
|
-
class _SecureWrapper:
|
|
2205
|
-
__slots__ = ()
|
|
2206
|
-
def __call__(self, *args, **kwargs):
|
|
2207
|
-
return func(*args, **kwargs)
|
|
2208
|
-
def __getattribute__(self, attr):
|
|
2209
|
-
if attr in ('__call__', '__class__'):
|
|
2210
|
-
return object.__getattribute__(self, attr)
|
|
2211
|
-
raise AttributeError(f"'{type(self).__name__}' object has no attribute '{attr}'")
|
|
2212
|
-
def __repr__(self):
|
|
2213
|
-
return f'<built-in function {name}>'
|
|
2214
|
-
return _SecureWrapper()
|
|
2215
|
-
|
|
2216
|
-
# ------------------------------------------------------------
|
|
2217
|
-
# 4. Redirect file operations to /host (with secure wrappers)
|
|
2218
|
-
# ------------------------------------------------------------
|
|
2219
|
-
# builtins.open
|
|
2220
|
-
_orig_open = builtins.open
|
|
2221
|
-
def _redir_open(path, mode='r', *args, **kwargs):
|
|
2222
|
-
if _should_redirect(path):
|
|
2223
|
-
path = '/host' + path
|
|
2224
|
-
return _orig_open(path, mode, *args, **kwargs)
|
|
2225
|
-
builtins.open = _make_secure_wrapper(_redir_open, 'open')
|
|
2226
|
-
|
|
2227
|
-
# os.listdir
|
|
2228
|
-
_orig_listdir = os.listdir
|
|
2229
|
-
def _redir_listdir(path='.'):
|
|
2230
|
-
if _should_redirect(path):
|
|
2231
|
-
path = '/host' + path
|
|
2232
|
-
return _orig_listdir(path)
|
|
2233
|
-
os.listdir = _make_secure_wrapper(_redir_listdir, 'listdir')
|
|
2234
|
-
|
|
2235
|
-
# os.path.exists
|
|
2236
|
-
_orig_exists = os.path.exists
|
|
2237
|
-
def _redir_exists(path):
|
|
2238
|
-
if _should_redirect(path):
|
|
2239
|
-
path = '/host' + path
|
|
2240
|
-
return _orig_exists(path)
|
|
2241
|
-
os.path.exists = _make_secure_wrapper(_redir_exists, 'exists')
|
|
2242
|
-
|
|
2243
|
-
# os.path.isfile
|
|
2244
|
-
_orig_isfile = os.path.isfile
|
|
2245
|
-
def _redir_isfile(path):
|
|
2246
|
-
if _should_redirect(path):
|
|
2247
|
-
path = '/host' + path
|
|
2248
|
-
return _orig_isfile(path)
|
|
2249
|
-
os.path.isfile = _make_secure_wrapper(_redir_isfile, 'isfile')
|
|
2250
|
-
|
|
2251
|
-
# os.path.isdir
|
|
2252
|
-
_orig_isdir = os.path.isdir
|
|
2253
|
-
def _redir_isdir(path):
|
|
2254
|
-
if _should_redirect(path):
|
|
2255
|
-
path = '/host' + path
|
|
2256
|
-
return _orig_isdir(path)
|
|
2257
|
-
os.path.isdir = _make_secure_wrapper(_redir_isdir, 'isdir')
|
|
2258
|
-
|
|
2259
|
-
# os.stat
|
|
2260
|
-
_orig_stat = os.stat
|
|
2261
|
-
def _redir_stat(path, *args, **kwargs):
|
|
2262
|
-
if _should_redirect(path):
|
|
2263
|
-
path = '/host' + path
|
|
2264
|
-
return _orig_stat(path, *args, **kwargs)
|
|
2265
|
-
os.stat = _make_secure_wrapper(_redir_stat, 'stat')
|
|
2266
|
-
|
|
2267
|
-
# os.mkdir
|
|
2268
|
-
_orig_mkdir = os.mkdir
|
|
2269
|
-
def _redir_mkdir(path, *args, **kwargs):
|
|
2270
|
-
if _should_redirect(path):
|
|
2271
|
-
path = '/host' + path
|
|
2272
|
-
return _orig_mkdir(path, *args, **kwargs)
|
|
2273
|
-
os.mkdir = _make_secure_wrapper(_redir_mkdir, 'mkdir')
|
|
2274
|
-
|
|
2275
|
-
# os.makedirs
|
|
2276
|
-
_orig_makedirs = os.makedirs
|
|
2277
|
-
def _redir_makedirs(path, *args, **kwargs):
|
|
2278
|
-
if _should_redirect(path):
|
|
2279
|
-
path = '/host' + path
|
|
2280
|
-
return _orig_makedirs(path, *args, **kwargs)
|
|
2281
|
-
os.makedirs = _make_secure_wrapper(_redir_makedirs, 'makedirs')
|
|
2282
|
-
|
|
2283
|
-
# os.remove
|
|
2284
|
-
_orig_remove = os.remove
|
|
2285
|
-
def _redir_remove(path, *args, **kwargs):
|
|
2286
|
-
if _should_redirect(path):
|
|
2287
|
-
path = '/host' + path
|
|
2288
|
-
return _orig_remove(path, *args, **kwargs)
|
|
2289
|
-
os.remove = _make_secure_wrapper(_redir_remove, 'remove')
|
|
2290
|
-
|
|
2291
|
-
# os.rmdir
|
|
2292
|
-
_orig_rmdir = os.rmdir
|
|
2293
|
-
def _redir_rmdir(path, *args, **kwargs):
|
|
2294
|
-
if _should_redirect(path):
|
|
2295
|
-
path = '/host' + path
|
|
2296
|
-
return _orig_rmdir(path, *args, **kwargs)
|
|
2297
|
-
os.rmdir = _make_secure_wrapper(_redir_rmdir, 'rmdir')
|
|
2298
|
-
|
|
2299
|
-
# os.getcwd - strip /host prefix
|
|
2300
|
-
_orig_getcwd = os.getcwd
|
|
2301
|
-
def _redir_getcwd():
|
|
2302
|
-
cwd = _orig_getcwd()
|
|
2303
|
-
if cwd.startswith('/host'):
|
|
2304
|
-
return cwd[5:] # Strip '/host' prefix
|
|
2305
|
-
return cwd
|
|
2306
|
-
os.getcwd = _make_secure_wrapper(_redir_getcwd, 'getcwd')
|
|
2307
|
-
|
|
2308
|
-
# os.chdir
|
|
2309
|
-
_orig_chdir = os.chdir
|
|
2310
|
-
def _redir_chdir(path):
|
|
2311
|
-
if _should_redirect(path):
|
|
2312
|
-
path = '/host' + path
|
|
2313
|
-
return _orig_chdir(path)
|
|
2314
|
-
os.chdir = _make_secure_wrapper(_redir_chdir, 'chdir')
|
|
2315
|
-
|
|
2316
|
-
# ------------------------------------------------------------
|
|
2317
|
-
# 5. Additional file operations (glob, walk, scandir, io.open)
|
|
2318
|
-
# ------------------------------------------------------------
|
|
2319
|
-
import glob as _glob_module
|
|
2320
|
-
|
|
2321
|
-
_orig_glob = _glob_module.glob
|
|
2322
|
-
def _redir_glob(pathname, *args, **kwargs):
|
|
2323
|
-
if _should_redirect(pathname):
|
|
2324
|
-
pathname = '/host' + pathname
|
|
2325
|
-
return _orig_glob(pathname, *args, **kwargs)
|
|
2326
|
-
_glob_module.glob = _make_secure_wrapper(_redir_glob, 'glob')
|
|
2327
|
-
|
|
2328
|
-
_orig_iglob = _glob_module.iglob
|
|
2329
|
-
def _redir_iglob(pathname, *args, **kwargs):
|
|
2330
|
-
if _should_redirect(pathname):
|
|
2331
|
-
pathname = '/host' + pathname
|
|
2332
|
-
return _orig_iglob(pathname, *args, **kwargs)
|
|
2333
|
-
_glob_module.iglob = _make_secure_wrapper(_redir_iglob, 'iglob')
|
|
2334
|
-
|
|
2335
|
-
# os.walk (generator - needs special handling)
|
|
2336
|
-
_orig_walk = os.walk
|
|
2337
|
-
def _redir_walk(top, *args, **kwargs):
|
|
2338
|
-
redirected = False
|
|
2339
|
-
if _should_redirect(top):
|
|
2340
|
-
top = '/host' + top
|
|
2341
|
-
redirected = True
|
|
2342
|
-
for dirpath, dirnames, filenames in _orig_walk(top, *args, **kwargs):
|
|
2343
|
-
if redirected and dirpath.startswith('/host'):
|
|
2344
|
-
dirpath = dirpath[5:] if len(dirpath) > 5 else '/'
|
|
2345
|
-
yield dirpath, dirnames, filenames
|
|
2346
|
-
os.walk = _make_secure_wrapper(_redir_walk, 'walk')
|
|
2347
|
-
|
|
2348
|
-
# os.scandir
|
|
2349
|
-
_orig_scandir = os.scandir
|
|
2350
|
-
def _redir_scandir(path='.'):
|
|
2351
|
-
if _should_redirect(path):
|
|
2352
|
-
path = '/host' + path
|
|
2353
|
-
return _orig_scandir(path)
|
|
2354
|
-
os.scandir = _make_secure_wrapper(_redir_scandir, 'scandir')
|
|
2355
|
-
|
|
2356
|
-
# io.open (same secure wrapper as builtins.open)
|
|
2357
|
-
import io as _io_module
|
|
2358
|
-
_io_module.open = builtins.open
|
|
2359
|
-
|
|
2360
|
-
# ------------------------------------------------------------
|
|
2361
|
-
# 6. shutil file operations
|
|
2362
|
-
# ------------------------------------------------------------
|
|
2363
|
-
import shutil as _shutil_module
|
|
2364
|
-
|
|
2365
|
-
# shutil.copy(src, dst)
|
|
2366
|
-
_orig_shutil_copy = _shutil_module.copy
|
|
2367
|
-
def _redir_shutil_copy(src, dst, *args, **kwargs):
|
|
2368
|
-
if _should_redirect(src):
|
|
2369
|
-
src = '/host' + src
|
|
2370
|
-
if _should_redirect(dst):
|
|
2371
|
-
dst = '/host' + dst
|
|
2372
|
-
return _orig_shutil_copy(src, dst, *args, **kwargs)
|
|
2373
|
-
_shutil_module.copy = _make_secure_wrapper(_redir_shutil_copy, 'copy')
|
|
2374
|
-
|
|
2375
|
-
# shutil.copy2(src, dst)
|
|
2376
|
-
_orig_shutil_copy2 = _shutil_module.copy2
|
|
2377
|
-
def _redir_shutil_copy2(src, dst, *args, **kwargs):
|
|
2378
|
-
if _should_redirect(src):
|
|
2379
|
-
src = '/host' + src
|
|
2380
|
-
if _should_redirect(dst):
|
|
2381
|
-
dst = '/host' + dst
|
|
2382
|
-
return _orig_shutil_copy2(src, dst, *args, **kwargs)
|
|
2383
|
-
_shutil_module.copy2 = _make_secure_wrapper(_redir_shutil_copy2, 'copy2')
|
|
2384
|
-
|
|
2385
|
-
# shutil.copyfile(src, dst)
|
|
2386
|
-
_orig_shutil_copyfile = _shutil_module.copyfile
|
|
2387
|
-
def _redir_shutil_copyfile(src, dst, *args, **kwargs):
|
|
2388
|
-
if _should_redirect(src):
|
|
2389
|
-
src = '/host' + src
|
|
2390
|
-
if _should_redirect(dst):
|
|
2391
|
-
dst = '/host' + dst
|
|
2392
|
-
return _orig_shutil_copyfile(src, dst, *args, **kwargs)
|
|
2393
|
-
_shutil_module.copyfile = _make_secure_wrapper(_redir_shutil_copyfile, 'copyfile')
|
|
2394
|
-
|
|
2395
|
-
# shutil.copytree(src, dst)
|
|
2396
|
-
_orig_shutil_copytree = _shutil_module.copytree
|
|
2397
|
-
def _redir_shutil_copytree(src, dst, *args, **kwargs):
|
|
2398
|
-
if _should_redirect(src):
|
|
2399
|
-
src = '/host' + src
|
|
2400
|
-
if _should_redirect(dst):
|
|
2401
|
-
dst = '/host' + dst
|
|
2402
|
-
return _orig_shutil_copytree(src, dst, *args, **kwargs)
|
|
2403
|
-
_shutil_module.copytree = _make_secure_wrapper(_redir_shutil_copytree, 'copytree')
|
|
2404
|
-
|
|
2405
|
-
# shutil.move(src, dst)
|
|
2406
|
-
_orig_shutil_move = _shutil_module.move
|
|
2407
|
-
def _redir_shutil_move(src, dst, *args, **kwargs):
|
|
2408
|
-
if _should_redirect(src):
|
|
2409
|
-
src = '/host' + src
|
|
2410
|
-
if _should_redirect(dst):
|
|
2411
|
-
dst = '/host' + dst
|
|
2412
|
-
return _orig_shutil_move(src, dst, *args, **kwargs)
|
|
2413
|
-
_shutil_module.move = _make_secure_wrapper(_redir_shutil_move, 'move')
|
|
2414
|
-
|
|
2415
|
-
# shutil.rmtree(path)
|
|
2416
|
-
_orig_shutil_rmtree = _shutil_module.rmtree
|
|
2417
|
-
def _redir_shutil_rmtree(path, *args, **kwargs):
|
|
2418
|
-
if _should_redirect(path):
|
|
2419
|
-
path = '/host' + path
|
|
2420
|
-
return _orig_shutil_rmtree(path, *args, **kwargs)
|
|
2421
|
-
_shutil_module.rmtree = _make_secure_wrapper(_redir_shutil_rmtree, 'rmtree')
|
|
2422
|
-
|
|
2423
|
-
# ------------------------------------------------------------
|
|
2424
|
-
# 7. pathlib.Path - redirect path resolution
|
|
2425
|
-
# ------------------------------------------------------------
|
|
2426
|
-
from pathlib import Path, PurePosixPath
|
|
2427
|
-
|
|
2428
|
-
def _redirect_path(p):
|
|
2429
|
-
"""Convert a Path to redirect /absolute paths to /host."""
|
|
2430
|
-
s = str(p)
|
|
2431
|
-
if _should_redirect(s):
|
|
2432
|
-
return Path('/host' + s)
|
|
2433
|
-
return p
|
|
2434
|
-
|
|
2435
|
-
# Helper to create method wrappers for Path
|
|
2436
|
-
def _wrap_path_method(orig_method, name):
|
|
2437
|
-
def wrapper(self, *args, **kwargs):
|
|
2438
|
-
redirected = _redirect_path(self)
|
|
2439
|
-
return getattr(redirected, '_orig_' + name)(*args, **kwargs)
|
|
2440
|
-
return wrapper
|
|
2441
|
-
|
|
2442
|
-
# Store original methods with _orig_ prefix, then replace with redirecting versions
|
|
2443
|
-
# Path.stat()
|
|
2444
|
-
Path._orig_stat = Path.stat
|
|
2445
|
-
def _path_stat(self, *args, **kwargs):
|
|
2446
|
-
return _redirect_path(self)._orig_stat(*args, **kwargs)
|
|
2447
|
-
Path.stat = _path_stat
|
|
2448
|
-
|
|
2449
|
-
# Path.exists()
|
|
2450
|
-
Path._orig_exists = Path.exists
|
|
2451
|
-
def _path_exists(self):
|
|
2452
|
-
return _redirect_path(self)._orig_exists()
|
|
2453
|
-
Path.exists = _path_exists
|
|
2454
|
-
|
|
2455
|
-
# Path.is_file()
|
|
2456
|
-
Path._orig_is_file = Path.is_file
|
|
2457
|
-
def _path_is_file(self):
|
|
2458
|
-
return _redirect_path(self)._orig_is_file()
|
|
2459
|
-
Path.is_file = _path_is_file
|
|
2460
|
-
|
|
2461
|
-
# Path.is_dir()
|
|
2462
|
-
Path._orig_is_dir = Path.is_dir
|
|
2463
|
-
def _path_is_dir(self):
|
|
2464
|
-
return _redirect_path(self)._orig_is_dir()
|
|
2465
|
-
Path.is_dir = _path_is_dir
|
|
2466
|
-
|
|
2467
|
-
# Path.open()
|
|
2468
|
-
Path._orig_open = Path.open
|
|
2469
|
-
def _path_open(self, *args, **kwargs):
|
|
2470
|
-
return _redirect_path(self)._orig_open(*args, **kwargs)
|
|
2471
|
-
Path.open = _path_open
|
|
2472
|
-
|
|
2473
|
-
# Path.read_text()
|
|
2474
|
-
Path._orig_read_text = Path.read_text
|
|
2475
|
-
def _path_read_text(self, *args, **kwargs):
|
|
2476
|
-
return _redirect_path(self)._orig_read_text(*args, **kwargs)
|
|
2477
|
-
Path.read_text = _path_read_text
|
|
2478
|
-
|
|
2479
|
-
# Path.read_bytes()
|
|
2480
|
-
Path._orig_read_bytes = Path.read_bytes
|
|
2481
|
-
def _path_read_bytes(self):
|
|
2482
|
-
return _redirect_path(self)._orig_read_bytes()
|
|
2483
|
-
Path.read_bytes = _path_read_bytes
|
|
2484
|
-
|
|
2485
|
-
# Path.write_text()
|
|
2486
|
-
Path._orig_write_text = Path.write_text
|
|
2487
|
-
def _path_write_text(self, *args, **kwargs):
|
|
2488
|
-
return _redirect_path(self)._orig_write_text(*args, **kwargs)
|
|
2489
|
-
Path.write_text = _path_write_text
|
|
2490
|
-
|
|
2491
|
-
# Path.write_bytes()
|
|
2492
|
-
Path._orig_write_bytes = Path.write_bytes
|
|
2493
|
-
def _path_write_bytes(self, data):
|
|
2494
|
-
return _redirect_path(self)._orig_write_bytes(data)
|
|
2495
|
-
Path.write_bytes = _path_write_bytes
|
|
2496
|
-
|
|
2497
|
-
# Path.mkdir()
|
|
2498
|
-
Path._orig_mkdir = Path.mkdir
|
|
2499
|
-
def _path_mkdir(self, *args, **kwargs):
|
|
2500
|
-
return _redirect_path(self)._orig_mkdir(*args, **kwargs)
|
|
2501
|
-
Path.mkdir = _path_mkdir
|
|
2502
|
-
|
|
2503
|
-
# Path.rmdir()
|
|
2504
|
-
Path._orig_rmdir = Path.rmdir
|
|
2505
|
-
def _path_rmdir(self):
|
|
2506
|
-
return _redirect_path(self)._orig_rmdir()
|
|
2507
|
-
Path.rmdir = _path_rmdir
|
|
2508
|
-
|
|
2509
|
-
# Path.unlink()
|
|
2510
|
-
Path._orig_unlink = Path.unlink
|
|
2511
|
-
def _path_unlink(self, *args, **kwargs):
|
|
2512
|
-
return _redirect_path(self)._orig_unlink(*args, **kwargs)
|
|
2513
|
-
Path.unlink = _path_unlink
|
|
2514
|
-
|
|
2515
|
-
# Path.iterdir()
|
|
2516
|
-
Path._orig_iterdir = Path.iterdir
|
|
2517
|
-
def _path_iterdir(self):
|
|
2518
|
-
redirected = _redirect_path(self)
|
|
2519
|
-
for p in redirected._orig_iterdir():
|
|
2520
|
-
# Strip /host prefix from results
|
|
2521
|
-
s = str(p)
|
|
2522
|
-
if s.startswith('/host'):
|
|
2523
|
-
yield Path(s[5:])
|
|
2524
|
-
else:
|
|
2525
|
-
yield p
|
|
2526
|
-
Path.iterdir = _path_iterdir
|
|
2527
|
-
|
|
2528
|
-
# Path.glob()
|
|
2529
|
-
Path._orig_glob = Path.glob
|
|
2530
|
-
def _path_glob(self, pattern):
|
|
2531
|
-
redirected = _redirect_path(self)
|
|
2532
|
-
for p in redirected._orig_glob(pattern):
|
|
2533
|
-
s = str(p)
|
|
2534
|
-
if s.startswith('/host'):
|
|
2535
|
-
yield Path(s[5:])
|
|
2536
|
-
else:
|
|
2537
|
-
yield p
|
|
2538
|
-
Path.glob = _path_glob
|
|
2539
|
-
|
|
2540
|
-
# Path.rglob()
|
|
2541
|
-
Path._orig_rglob = Path.rglob
|
|
2542
|
-
def _path_rglob(self, pattern):
|
|
2543
|
-
redirected = _redirect_path(self)
|
|
2544
|
-
for p in redirected._orig_rglob(pattern):
|
|
2545
|
-
s = str(p)
|
|
2546
|
-
if s.startswith('/host'):
|
|
2547
|
-
yield Path(s[5:])
|
|
2548
|
-
else:
|
|
2549
|
-
yield p
|
|
2550
|
-
Path.rglob = _path_rglob
|
|
2551
|
-
|
|
2552
|
-
_jb_init_sandbox()
|
|
2553
|
-
del _jb_init_sandbox
|
|
2554
|
-
|
|
2555
|
-
# Per-run: scrub blocked modules from sys.modules.
|
|
2556
|
-
# Pyodide's C-level JsFinder may re-insert 'js' etc. between runs.
|
|
2557
|
-
# This is hardcoded inline (not a callable on builtins) so attackers cannot neuter it.
|
|
2558
|
-
for _jb_k in list(sys.modules.keys()):
|
|
2559
|
-
if _jb_k in frozenset({'js', 'pyodide', 'pyodide_js', 'pyodide.ffi', '_pyodide', '_pyodide_core', 'ctypes', '_ctypes', '_jb_http_bridge'}) or \\
|
|
2560
|
-
any(_jb_k.startswith(_p) for _p in ('js.', 'pyodide.', 'pyodide_js.', '_pyodide.', '_pyodide_core.', 'ctypes.', '_ctypes.', '_jb_http_bridge.')):
|
|
2561
|
-
try: del sys.modules[_jb_k]
|
|
2562
|
-
except (KeyError, ImportError): pass
|
|
2563
|
-
del _jb_k
|
|
2564
|
-
|
|
2565
|
-
# Set cwd to host mount
|
|
2566
|
-
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}
|
|
2401
|
+
`);
|
|
2402
|
+
} else {
|
|
2403
|
+
pendingStderr.push(`${text}
|
|
2567
2404
|
`);
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
});
|
|
2568
2408
|
} catch (e) {
|
|
2569
2409
|
return {
|
|
2570
2410
|
success: false,
|
|
2571
|
-
error: `Failed to
|
|
2411
|
+
error: `Failed to load CPython: ${e.message}`
|
|
2572
2412
|
};
|
|
2573
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);
|
|
2574
2419
|
try {
|
|
2575
|
-
|
|
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 = `
|
|
2576
2441
|
import sys
|
|
2577
2442
|
_jb_exit_code = 0
|
|
2578
2443
|
try:
|
|
2444
|
+
${setupCode.split("\n").map((line) => ` ${line}`).join("\n")}
|
|
2445
|
+
${httpBridgeCode.split("\n").map((line) => ` ${line}`).join("\n")}
|
|
2579
2446
|
${input.pythonCode.split("\n").map((line) => ` ${line}`).join("\n")}
|
|
2580
2447
|
except SystemExit as e:
|
|
2581
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)
|
|
2582
2454
|
`;
|
|
2583
|
-
|
|
2584
|
-
|
|
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;
|
|
2585
2466
|
backend.exit(exitCode);
|
|
2586
2467
|
return { success: true };
|
|
2587
2468
|
} catch (e) {
|
|
2588
2469
|
const error = e;
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
backend.exit(1);
|
|
2470
|
+
const exitCode = error.status ?? process.exitCode ?? 1;
|
|
2471
|
+
backend.exit(exitCode);
|
|
2592
2472
|
return { success: true };
|
|
2593
2473
|
}
|
|
2594
2474
|
}
|
|
2595
2475
|
var defense = null;
|
|
2596
|
-
|
|
2597
|
-
|
|
2476
|
+
function activateDefense() {
|
|
2477
|
+
if (defense) return;
|
|
2598
2478
|
defense = new WorkerDefenseInDepth({
|
|
2599
2479
|
excludeViolationTypes: [
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
// 3. SharedArrayBuffer/Atomics: Used by sync-fs-backend.ts for synchronous
|
|
2603
|
-
// filesystem communication between Pyodide's WASM thread and the main thread.
|
|
2604
|
-
// 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.
|
|
2605
2482
|
"shared_array_buffer",
|
|
2606
2483
|
"atomics"
|
|
2607
2484
|
],
|
|
@@ -2610,9 +2487,15 @@ async function initializeWithDefense() {
|
|
|
2610
2487
|
}
|
|
2611
2488
|
});
|
|
2612
2489
|
}
|
|
2490
|
+
process.on("uncaughtException", (e) => {
|
|
2491
|
+
parentPort?.postMessage({
|
|
2492
|
+
success: false,
|
|
2493
|
+
error: `Worker uncaught exception: ${e.message}`
|
|
2494
|
+
});
|
|
2495
|
+
});
|
|
2613
2496
|
if (parentPort) {
|
|
2614
2497
|
if (workerData) {
|
|
2615
|
-
|
|
2498
|
+
runPython(workerData).then((result) => {
|
|
2616
2499
|
result.defenseStats = defense?.getStats();
|
|
2617
2500
|
parentPort?.postMessage(result);
|
|
2618
2501
|
}).catch((e) => {
|
|
@@ -2623,20 +2506,4 @@ if (parentPort) {
|
|
|
2623
2506
|
});
|
|
2624
2507
|
});
|
|
2625
2508
|
}
|
|
2626
|
-
parentPort.on("message", async (input) => {
|
|
2627
|
-
try {
|
|
2628
|
-
if (!defense) {
|
|
2629
|
-
await initializeWithDefense();
|
|
2630
|
-
}
|
|
2631
|
-
const result = await runPython(input);
|
|
2632
|
-
result.defenseStats = defense?.getStats();
|
|
2633
|
-
parentPort?.postMessage(result);
|
|
2634
|
-
} catch (e) {
|
|
2635
|
-
parentPort?.postMessage({
|
|
2636
|
-
success: false,
|
|
2637
|
-
error: e.message,
|
|
2638
|
-
defenseStats: defense?.getStats()
|
|
2639
|
-
});
|
|
2640
|
-
}
|
|
2641
|
-
});
|
|
2642
2509
|
}
|