fix(sandbox): 子进程环境保留 PATH,编译产物里沙箱任务恢复;编译自检加沙箱启动
清空环境后编译产物在入口按 PATH 解析 --allow-run 失败直接退出,Bangumi 热门、字幕验证码 / 解压等沙箱任务一直报错。 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
1 parent
0d796d92ef
commit
3f1c6d02f6
3 files changed
+26
-3
No files matched your search
@@ -51,6 +51,15 @@ function command(memoryMb: number): { cmd: string; args: string[] } {
|
|||||||
return { cmd: exe, args: ["--role", "sandbox"] };
|
return { cmd: exe, args: ["--role", "sandbox"] };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 子进程环境:清空,只留 PATH。编译产物启动时要按 PATH 解析 --allow-run 里的命令名(ffprobe、cat…),
|
||||||
|
* 没有 PATH 会在进程入口报「no path to search」直接退出,所有沙箱任务都失败(2026-10 Bangumi 热门、字幕验证码即此)。
|
||||||
|
* 子进程随后撤销全部权限,PATH 不放大能力。
|
||||||
|
*/
|
||||||
|
export function sandboxEnv(): Record<string, string> {
|
||||||
|
return { PATH: Deno.env.get("PATH") || "/usr/local/bin:/usr/bin:/bin" };
|
||||||
|
}
|
||||||
|
|
||||||
/** 子进程实际内存(KB);读不到返回 null */
|
/** 子进程实际内存(KB);读不到返回 null */
|
||||||
async function rssKb(pid: number): Promise<number | null> {
|
async function rssKb(pid: number): Promise<number | null> {
|
||||||
const s = await readProcText(`/proc/${pid}/status`);
|
const s = await readProcText(`/proc/${pid}/status`);
|
||||||
@@ -75,7 +84,7 @@ export async function runSandbox<J extends SandboxJobName>(
|
|||||||
stdin: "piped",
|
stdin: "piped",
|
||||||
stdout: "piped",
|
stdout: "piped",
|
||||||
stderr: "piped",
|
stderr: "piped",
|
||||||
env: {},
|
env: sandboxEnv(),
|
||||||
clearEnv: true,
|
clearEnv: true,
|
||||||
}).spawn();
|
}).spawn();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -1,13 +1,19 @@
|
|||||||
import { buildFakeTorrent, RAR5_SAMPLE_BASE64, SEVENZIP_SUBTITLE_BASE64, ZIMUKU_CAPTCHA } from "@umt/testkit";
|
import { buildFakeTorrent, RAR5_SAMPLE_BASE64, SEVENZIP_SUBTITLE_BASE64, ZIMUKU_CAPTCHA } from "@umt/testkit";
|
||||||
import { zipSync } from "fflate";
|
import { zipSync } from "fflate";
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import { runSandbox, SandboxError } from "../../src/sandbox/client.ts";
|
import { runSandbox, SandboxError, sandboxEnv } from "../../src/sandbox/client.ts";
|
||||||
import { b64encode } from "../../src/sandbox/jobs.ts";
|
import { b64encode } from "../../src/sandbox/jobs.ts";
|
||||||
|
|
||||||
// 沙箱(docs/adr/ADR-005-sandbox.md):子进程撤销全部权限;卡死超时强杀;内存超限强杀;任务错误原样带回
|
// 沙箱(docs/adr/ADR-005-sandbox.md):子进程撤销全部权限;卡死超时强杀;内存超限强杀;任务错误原样带回
|
||||||
const enc = (s: string) => new TextEncoder().encode(s);
|
const enc = (s: string) => new TextEncoder().encode(s);
|
||||||
|
|
||||||
describe("沙箱子进程", () => {
|
describe("沙箱子进程", () => {
|
||||||
|
it("子进程环境只带 PATH(编译产物入口要按 PATH 解析 --allow-run 的命令名,没有会直接退出)", () => {
|
||||||
|
const env = sandboxEnv();
|
||||||
|
expect(Object.keys(env)).toEqual(["PATH"]);
|
||||||
|
expect(env.PATH).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
it("正常返回;子进程里读文件被拒绝", async () => {
|
it("正常返回;子进程里读文件被拒绝", async () => {
|
||||||
expect(await runSandbox("self.test", { mode: "ok" })).toEqual({ ok: true });
|
expect(await runSandbox("self.test", { mode: "ok" })).toEqual({ ok: true });
|
||||||
await expect(runSandbox("self.test", { mode: "io" })).rejects.toMatchObject({ kind: "job", message: /NotCapable/ });
|
await expect(runSandbox("self.test", { mode: "io" })).rejects.toMatchObject({ kind: "job", message: /NotCapable/ });
|
||||||
|
|||||||
@@ -10,8 +10,16 @@ case "$bin" in /*) ;; *) bin="$PWD/$bin" ;; esac
|
|||||||
out=$(cd /tmp && env -i PATH="$PATH" "$bin" --role preflight 2>&1)
|
out=$(cd /tmp && env -i PATH="$PATH" "$bin" --role preflight 2>&1)
|
||||||
code=$?
|
code=$?
|
||||||
if [ "$code" -eq 2 ] && printf '%s' "$out" | grep -q '配置无效'; then
|
if [ "$code" -eq 2 ] && printf '%s' "$out" | grep -q '配置无效'; then
|
||||||
echo "check-compiled-binary: ok(模块图加载完整)"
|
# 沙箱子进程:与 sandbox/client.ts 同样的环境(只有 PATH)拉起 `--role sandbox`,请求一个不存在的任务,
|
||||||
|
# 应回一行 {"ok":false,...};入口就崩(如 --allow-run 解析失败)会没有这行
|
||||||
|
sb=$(cd /tmp && printf '{"job":"__check__","input":null}' | env -i PATH="$PATH" "$bin" --role sandbox 2>&1)
|
||||||
|
if printf '%s' "$sb" | grep -q '"ok":false'; then
|
||||||
|
echo "check-compiled-binary: ok(模块图加载完整,沙箱子进程能启动)"
|
||||||
exit 0
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "check-compiled-binary: 沙箱子进程起不来:" >&2
|
||||||
|
printf '%s\n' "$sb" >&2
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo "check-compiled-binary: 编译产物自检失败(退出码 $code):" >&2
|
echo "check-compiled-binary: 编译产物自检失败(退出码 $code):" >&2
|
||||||
printf '%s\n' "$out" >&2
|
printf '%s\n' "$out" >&2
|
||||||
|
|||||||
Reference in new issue
Block a user