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

+4 -2
View File
@@ -9,6 +9,8 @@
* - `app/modules/indexer/parser/mtorrent.py`(api/member/profile)→ `healthCheck`
* 详情 `/api/torrent/detail` 为本项目扩展(MP spider 无详情调用,字段结构参考 MovieClaw mteam.py,data-ref)。
*/
import { inspectTorrentIsolated } from "../../isolation.ts";
import { redactUrl } from "../../rss/redact.ts";
import { buildHttpContext, type SiteAdapterDeps } from "../deps.ts";
import { pageFromBbcode } from "../detail-text.ts";
@@ -26,7 +28,7 @@ import {
type SiteRelease,
type SiteReleaseDetail,
} from "../types.ts";
import { extractDoubanId, inspectTorrent, parseSiteTime } from "../util.ts";
import { extractDoubanId, parseSiteTime } from "../util.ts";
import { mteamPost } from "./client.ts";
import { type MteamDefinition, mteamApiBase } from "./definition.ts";
import { type MteamTorrentJson, mteamSearchParams, parseMteamResult } from "./spider.ts";
@@ -192,7 +194,7 @@ export function createMteamAdapter(input: MteamDefinition, deps: SiteAdapterDeps
);
}
const bytes = await readBody(http, res);
const info = inspectTorrent(bytes);
const info = await inspectTorrentIsolated(bytes);
if (!info) throw new SiteError(def.id, "parse_error", `下载 ${id}:返回内容不是 .torrent`);
return {
kind: "torrent",
@@ -11,6 +11,8 @@
* 本项目调整:登录页不再"返回空列表",而是抛 `auth_expired`(REQ §4.7.1 不展示空列表冒充无内容);
* 3xx 不自动跟随(避免把 cookie 带到站外,并把跳转 login 识别为失效);凭证经 SecretResolver 注入。
*/
import { inspectTorrentIsolated } from "../../isolation.ts";
import { redactText } from "../../rss/redact.ts";
import { buildHttpContext, type SiteAdapterDeps } from "../deps.ts";
import { readBody, readText, siteFetch } from "../http.ts";
@@ -25,7 +27,6 @@ import {
type SitePage,
type SiteReleaseDetail,
} from "../types.ts";
import { inspectTorrent } from "../util.ts";
import { parseDetailPage } from "./detail.ts";
import { filesFromFileListHtml } from "./detail-page.ts";
import { spiderTorrentToRelease } from "./map.ts";
@@ -288,7 +289,7 @@ export function createSpiderSiteAdapter(input: IndexerDefinition, deps: SiteAdap
});
}
const bytes = await readBody(http, res);
const info = inspectTorrent(bytes);
const info = await inspectTorrentIsolated(bytes);
if (!info) {
// 站点在 cookie 失效时常返回 200 + 登录页 HTML
const $ = loadHtml(new TextDecoder().decode(bytes.subarray(0, 200_000)));
@@ -11,10 +11,11 @@
* - `temporal.format_timestamp` → `formatTimestampTz`;`urllib.parse.quote` → `pyQuote`
* - 各 spider `__process_response` 的错误分支 → `apiRequest`(HTTP 401/403 → auth_invalid)+ `businessError`
*/
import { inspectTorrentIsolated } from "../../isolation.ts";
import { redactText, redactUrl } from "../../rss/redact.ts";
import { readBody, readText, siteFetch } from "../http.ts";
import { type DownloadTarget, SiteError, type SiteErrorKind } from "../types.ts";
import { inspectTorrent } from "../util.ts";
import type { SpiderContext } from "./types.ts";
/** 下载链接中需要脱敏的额外参数名(HDDolby downhash 等效 passkey) */
@@ -353,7 +354,7 @@ export async function downloadTorrentFile(
});
}
const bytes = await readBody(ctx.http, res);
const info = inspectTorrent(bytes);
const info = await inspectTorrentIsolated(bytes);
if (!info) throw new SiteError(ctx.def.id, "parse_error", `下载 ${safe}:返回内容不是 .torrent`);
return { kind: "torrent", bytes, infoHash: info.infoHash, name: info.name, fileName: null, verifiedAt };
}
@@ -3,7 +3,7 @@
* 简介里的 IMDb / 豆瓣 / TMDB / bgm.tv 链接取作识别证据;页面没有文件清单的站(蜜柑、AniBT)下载种子读文件列表。
*/
import { load } from "cheerio";
import { parseTorrent } from "../../qbittorrent/torrent.ts";
import { parseTorrentIsolated } from "../../isolation.ts";
import { emptyDetailPage, extractTmdb, MAX_FILES, MAX_SCREENSHOTS, screenshotUrl } from "../detail-text.ts";
import { blockText, imdbFromText } from "../spider/detail-page.ts";
import { SiteError, type SiteReleaseDetail } from "../types.ts";
@@ -59,7 +59,7 @@ export async function specializedDetail(
try {
const t = await spider.resolveDownload(ctx, siteReleaseId, enclosure);
if (t.kind === "torrent")
files = parseTorrent(t.bytes).files.map((f) => ({ name: f.path, sizeBytes: f.length || null }));
files = (await parseTorrentIsolated(t.bytes)).files.map((f) => ({ name: f.path, sizeBytes: f.length || null }));
} catch {
// 种子取不到只是没有文件清单,不影响详情
}