feat(sandbox): 不可信字节在沙箱子进程里解析(撤销权限、超时与内存强杀);种子、文件预览、字幕解析迁入

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-10-03 01:44:31 +08:00
1 parent 1a76d2d700
commit eeec91e892
21 files changed
+632 -178

No files matched your search

+51
View File
@@ -0,0 +1,51 @@
import { buildFakeTorrent } from "@umt/testkit";
import { zipSync } from "fflate";
import { describe, expect, it } from "vitest";
import { runSandbox, SandboxError } 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("正常返回;子进程里读文件被拒绝", async () => {
expect(await runSandbox("self.test", { mode: "ok" })).toEqual({ ok: true });
await expect(runSandbox("self.test", { mode: "io" })).rejects.toMatchObject({ kind: "job", message: /NotCapable/ });
});
it("卡死:超时后强杀,不拖住调用方", async () => {
const t0 = Date.now();
const e = await runSandbox("self.test", { mode: "hang" }, { timeoutMs: 800 }).catch((x) => x);
expect(e).toBeInstanceOf(SandboxError);
expect(e.kind).toBe("timeout");
expect(Date.now() - t0).toBeLessThan(5000);
});
it("吃内存:超过上限强杀", async () => {
const e = await runSandbox("self.test", { mode: "memory" }, { memoryMb: 128, timeoutMs: 20_000 }).catch((x) => x);
expect(e).toBeInstanceOf(SandboxError);
expect(e.kind).toBe("memory");
});
it("任务自己抛错:kind = job,带原因", async () => {
await expect(runSandbox("self.test", { mode: "throw" })).rejects.toMatchObject({
kind: "job",
message: /任务自己抛的错/,
});
});
it("字幕:zip 解压、语言与季集;种子:文件清单", async () => {
const zip = zipSync({ "a/Show.S01E03.chs.srt": enc("1\n00:00:01,000 --> 00:00:02,000\n你好\n") });
const r = await runSandbox("subtitle.parse", { files: [{ name: "x.zip", base64: b64encode(zip) }] });
expect(r.items[0]).toMatchObject({
name: "a/Show.S01E03.chs.srt",
archive: "x.zip",
language: "zh-Hans",
episode: 3,
});
const t = buildFakeTorrent({ name: "Show.S01E01.mkv" });
const f = await runSandbox("torrent.parse", { base64: b64encode(t.bytes) });
expect(f.files.map((x) => x.path)).toEqual(["Show.S01E01.mkv"]);
expect(f.hash).toBe(t.infoHash);
});
});