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.
- package/dist/Bash.d.ts +1 -1
- package/dist/bin/chunks/python3-YJ7YGEW7.js +16 -0
- package/dist/bin/chunks/worker.js +589 -555
- package/dist/bin/just-bash.js +2 -2
- package/dist/bin/shell/chunks/python3-YJ7YGEW7.js +16 -0
- package/dist/bin/shell/shell.js +41 -41
- package/dist/bundle/browser.js +115 -115
- package/dist/bundle/chunks/python3-6Y4Z63NZ.js +15 -0
- package/dist/bundle/chunks/worker.js +589 -555
- package/dist/bundle/index.cjs +709 -707
- package/dist/bundle/index.js +32 -32
- 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() {
|
|
@@ -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
|
-
|
|
1533
|
-
var pyodideLoading = null;
|
|
1538
|
+
import { readFileSync } from "node:fs";
|
|
1534
1539
|
var require2 = createRequire(import.meta.url);
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
if (
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
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
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
2417
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2430
|
-
|
|
2476
|
+
function activateDefense() {
|
|
2477
|
+
if (defense) return;
|
|
2431
2478
|
defense = new WorkerDefenseInDepth({
|
|
2432
2479
|
excludeViolationTypes: [
|
|
2433
|
-
|
|
2434
|
-
|
|
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
|
-
|
|
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
|
}
|