From 3f1c6d02f6ddd06bf2e9adff70c697c9459ac100 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 8 Oct 2026 18:08:28 +0800 Subject: [PATCH] =?UTF-8?q?fix(sandbox):=20=E5=AD=90=E8=BF=9B=E7=A8=8B?= =?UTF-8?q?=E7=8E=AF=E5=A2=83=E4=BF=9D=E7=95=99=20PATH,=E7=BC=96=E8=AF=91?= =?UTF-8?q?=E4=BA=A7=E7=89=A9=E9=87=8C=E6=B2=99=E7=AE=B1=E4=BB=BB=E5=8A=A1?= =?UTF-8?q?=E6=81=A2=E5=A4=8D;=E7=BC=96=E8=AF=91=E8=87=AA=E6=A3=80?= =?UTF-8?q?=E5=8A=A0=E6=B2=99=E7=AE=B1=E5=90=AF=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 清空环境后编译产物在入口按 PATH 解析 --allow-run 失败直接退出,Bangumi 热门、字幕验证码 / 解压等沙箱任务一直报错。 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei --- apps/server/src/sandbox/client.ts | 11 ++++++++++- apps/server/test/sandbox/sandbox.test.ts | 8 +++++++- scripts/check-compiled-binary.sh | 12 ++++++++++-- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/apps/server/src/sandbox/client.ts b/apps/server/src/sandbox/client.ts index 37c72d3b..9a06ea42 100644 --- a/apps/server/src/sandbox/client.ts +++ b/apps/server/src/sandbox/client.ts @@ -51,6 +51,15 @@ function command(memoryMb: number): { cmd: string; args: string[] } { 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 { + return { PATH: Deno.env.get("PATH") || "/usr/local/bin:/usr/bin:/bin" }; +} + /** 子进程实际内存(KB);读不到返回 null */ async function rssKb(pid: number): Promise { const s = await readProcText(`/proc/${pid}/status`); @@ -75,7 +84,7 @@ export async function runSandbox( stdin: "piped", stdout: "piped", stderr: "piped", - env: {}, + env: sandboxEnv(), clearEnv: true, }).spawn(); } catch (e) { diff --git a/apps/server/test/sandbox/sandbox.test.ts b/apps/server/test/sandbox/sandbox.test.ts index ff37d4b7..370845d5 100644 --- a/apps/server/test/sandbox/sandbox.test.ts +++ b/apps/server/test/sandbox/sandbox.test.ts @@ -1,13 +1,19 @@ import { buildFakeTorrent, RAR5_SAMPLE_BASE64, SEVENZIP_SUBTITLE_BASE64, ZIMUKU_CAPTCHA } from "@umt/testkit"; import { zipSync } from "fflate"; 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"; // 沙箱(docs/adr/ADR-005-sandbox.md):子进程撤销全部权限;卡死超时强杀;内存超限强杀;任务错误原样带回 const enc = (s: string) => new TextEncoder().encode(s); describe("沙箱子进程", () => { + it("子进程环境只带 PATH(编译产物入口要按 PATH 解析 --allow-run 的命令名,没有会直接退出)", () => { + const env = sandboxEnv(); + expect(Object.keys(env)).toEqual(["PATH"]); + expect(env.PATH).toBeTruthy(); + }); + it("正常返回;子进程里读文件被拒绝", async () => { expect(await runSandbox("self.test", { mode: "ok" })).toEqual({ ok: true }); await expect(runSandbox("self.test", { mode: "io" })).rejects.toMatchObject({ kind: "job", message: /NotCapable/ }); diff --git a/scripts/check-compiled-binary.sh b/scripts/check-compiled-binary.sh index bf9ad7b1..07d6651b 100755 --- a/scripts/check-compiled-binary.sh +++ b/scripts/check-compiled-binary.sh @@ -10,8 +10,16 @@ case "$bin" in /*) ;; *) bin="$PWD/$bin" ;; esac out=$(cd /tmp && env -i PATH="$PATH" "$bin" --role preflight 2>&1) code=$? if [ "$code" -eq 2 ] && printf '%s' "$out" | grep -q '配置无效'; then - echo "check-compiled-binary: ok(模块图加载完整)" - exit 0 + # 沙箱子进程:与 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 + fi + echo "check-compiled-binary: 沙箱子进程起不来:" >&2 + printf '%s\n' "$sb" >&2 + exit 1 fi echo "check-compiled-binary: 编译产物自检失败(退出码 $code):" >&2 printf '%s\n' "$out" >&2