- 回滚/备份:空 bak 改名 .bad 跳过;升级刷新 bak 补上单数 marker 补丁;统一原子写 - 补丁命中:optional 命中不符记软失败;#42 插入型替换加探针防重复插入 - Windows:进程枚举改用 | 分隔 + UTF-8,修复中文路径换包不杀旧进程;换包失败拉起原版 - 安全:asar 解包防越界;红队路径校验 realpath 防空 symlink;更新仅 https;报告回放去换行注入 - 提示词钩子:停用后 rewriteFn 返回原装配文本并清理 assemble guard;缓存签名纳入红队/策略/override - 红队:listEngagements 默认不算统计;listVulns/报告支持 evidenceLimit - 子代理名额占位即时生效并回退;--apply 有失败项退出码 1 - 清理:删除 lib/mnemon-settings.js 及 13 处死代码/未使用 import;移除 test 目录与 test 脚本
86 lines
2.9 KiB
JavaScript
86 lines
2.9 KiB
JavaScript
// #197:把「启动前回收残留 web server」的钩子挂到宿主 bin.js 顶部。
|
|
// 必须在 Cordis 激活之前跑:webserver 插件绑端口失败在 0.2.x 是 fatal,等插件层再处理就晚了。
|
|
// 注入形态与 hide-console 一致:宿主 lib 下放一个 sidecar + bin.js 顶部加一行 import。
|
|
// 与 hide-console 的区别:这里跨平台生效(detached 重启在 macOS/Linux 同样会留下孤儿)。
|
|
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { isAsarSealedPath } from "./desktop.js";
|
|
import { looksLikeSourceCliRoot, resolveDshBinRoot, writeHostFileAtomic } from "./hide-console.js";
|
|
|
|
const MARK = "dsh-purge-web-preflight";
|
|
const IMPORT_LINE = `import "./${MARK}.js"; // [dsh-purge] web preflight`;
|
|
const ENTRY = fileURLToPath(new URL("./web-preflight-entry.js", import.meta.url));
|
|
|
|
export function installBinPreflight(aiBase) {
|
|
const root = resolveDshBinRoot(aiBase);
|
|
if (!root) return "missing_bin";
|
|
// 源码部署的 apps/cli/lib 是 tsdown 产物:只准读、不准改(#41)。
|
|
if (looksLikeSourceCliRoot(root)) return "skipped_source_cli";
|
|
const binJs = path.join(root, "lib", "bin.js");
|
|
const side = path.join(root, "lib", `${MARK}.js`);
|
|
if (isAsarSealedPath(root) || isAsarSealedPath(binJs)) return "sealed_asar";
|
|
|
|
let source;
|
|
try {
|
|
source = fs.readFileSync(ENTRY, "utf8");
|
|
writeHostFileAtomic(fs, side, source);
|
|
} catch (e) {
|
|
return `error:${e && e.message ? e.message : e}`;
|
|
}
|
|
|
|
let text;
|
|
try {
|
|
text = fs.readFileSync(binJs, "utf8");
|
|
} catch (e) {
|
|
return `error:${e && e.message ? e.message : e}`;
|
|
}
|
|
if (text.includes(IMPORT_LINE)) return "already_injected";
|
|
text = text.replace(/^import "\.\/dsh-purge-web-preflight\.js";.*\r?\n/gm, "");
|
|
if (/^#!/.test(text)) {
|
|
const nl = text.indexOf("\n");
|
|
text =
|
|
nl >= 0
|
|
? text.slice(0, nl + 1) + `${IMPORT_LINE}\n` + text.slice(nl + 1)
|
|
: `${text}\n${IMPORT_LINE}\n`;
|
|
} else {
|
|
text = `${IMPORT_LINE}\n${text}`;
|
|
}
|
|
try {
|
|
writeHostFileAtomic(fs, binJs, text);
|
|
} catch (e) {
|
|
return `error:${e && e.message ? e.message : e}`;
|
|
}
|
|
return "injected";
|
|
}
|
|
|
|
export function revertBinPreflight(aiBase) {
|
|
const root = resolveDshBinRoot(aiBase);
|
|
if (!root) return "missing_bin";
|
|
const binJs = path.join(root, "lib", "bin.js");
|
|
const side = path.join(root, "lib", `${MARK}.js`);
|
|
let changed = false;
|
|
if (!looksLikeSourceCliRoot(root)) {
|
|
try {
|
|
const text = fs.readFileSync(binJs, "utf8");
|
|
const next = text.replace(/^import "\.\/dsh-purge-web-preflight\.js";.*\r?\n/gm, "");
|
|
if (next !== text) {
|
|
writeHostFileAtomic(fs, binJs, next);
|
|
changed = true;
|
|
}
|
|
} catch (e) {
|
|
return `error:${e && e.message ? e.message : e}`;
|
|
}
|
|
}
|
|
try {
|
|
if (fs.existsSync(side)) {
|
|
fs.unlinkSync(side);
|
|
changed = true;
|
|
}
|
|
} catch {
|
|
/* 重启后再删 */
|
|
}
|
|
return changed ? "reverted" : "absent";
|
|
}
|