mirror of
https://gitee.com/spark-store-project/spark-store
synced 2026-09-20 21:50:11 +08:00
fix: 安全与健壮性排查修复(IPC 类型校验/AbortSignal/空名去重/窗口 await)
阻断项: - install-manager: uninstall-installed / remove-installed / check-installed 由 any 改为 unknown + parseAppPayload 严格解析(PKGNAME_PATTERN 校验 pkgname,origin 仅限 spark|apm),消除命令注入风险;parseAppPayload/PKGNAME_PATTERN 提升至模块顶部复用并避免 TDZ - App.vue openDetail: 参数由 Record<string,unknown> 收紧为 App | OpenDetailInput,新增 pkgname 运行时校验,避免访问 undefined 属性 改进项: - App.vue homeLinks 去重:跳过空名称避免污染 seenNames,并对 links 数组与 HomeLink 必需字段做校验 - App.vue fetchWithRetry: 新增 AbortSignal 参数,递归链路传递,组件卸载时 rootAbortController.abort() 取消未完成任务 - index.ts showAndFocusMainWindow: 改为 async 并 await createWindow,调用点用 void 修饰避免 floating promise vue-tsc --noEmit 通过。
This commit is contained in:
@@ -12,6 +12,26 @@ import { findExecutable, SUPER_USER_COMMAND_CANDIDATES } from "./superuser";
|
||||
|
||||
const logger = pino({ name: "install-manager" });
|
||||
|
||||
// 包名白名单:仅允许合法包名字符,杜绝命令注入(spawn 用 shell:false 仍须校验)。
|
||||
const PKGNAME_PATTERN = /^[a-zA-Z0-9._+-]+$/;
|
||||
|
||||
// 解析并校验应用类 IPC 的 payload(可能是旧版字符串或对象)。
|
||||
// 返回规范化后的 { pkgname, origin },pkgname 非法时返回 null。
|
||||
const parseAppPayload = (
|
||||
payload: unknown,
|
||||
): { pkgname: string; origin: "spark" | "apm" } | null => {
|
||||
if (typeof payload === "string") {
|
||||
if (!PKGNAME_PATTERN.test(payload)) return null;
|
||||
return { pkgname: payload, origin: "spark" };
|
||||
}
|
||||
if (typeof payload !== "object" || payload === null) return null;
|
||||
const p = payload as Record<string, unknown>;
|
||||
const pkgname = typeof p.pkgname === "string" ? p.pkgname : "";
|
||||
if (!PKGNAME_PATTERN.test(pkgname)) return null;
|
||||
const origin: "spark" | "apm" = p.origin === "apm" ? "apm" : "spark";
|
||||
return { pkgname, origin };
|
||||
};
|
||||
|
||||
const getStoreFilterFromArgv = (): "spark" | "apm" | "both" => {
|
||||
const argv = process.argv;
|
||||
const noApm = argv.includes("--no-apm");
|
||||
@@ -815,15 +835,13 @@ async function runInstallPhase(task: InstallTask) {
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
ipcMain.handle("check-installed", async (_event, payload: any) => {
|
||||
const pkgname = typeof payload === "string" ? payload : payload.pkgname;
|
||||
const origin = typeof payload === "string" ? "spark" : payload.origin;
|
||||
|
||||
if (!pkgname) {
|
||||
logger.warn("check-installed missing pkgname");
|
||||
ipcMain.handle("check-installed", async (_event, payload: unknown) => {
|
||||
const parsed = parseAppPayload(payload);
|
||||
if (!parsed) {
|
||||
logger.warn("check-installed invalid payload");
|
||||
return false;
|
||||
}
|
||||
const { pkgname, origin } = parsed;
|
||||
|
||||
logger.info(`检查应用是否已安装: ${pkgname} (来源: ${origin})`);
|
||||
|
||||
@@ -890,13 +908,12 @@ ipcMain.handle("check-installed", async (_event, payload: any) => {
|
||||
|
||||
ipcMain.on("remove-installed", async (_event, payload) => {
|
||||
const webContents = _event.sender;
|
||||
const pkgname = typeof payload === "string" ? payload : payload.pkgname;
|
||||
const origin = typeof payload === "string" ? "spark" : payload.origin;
|
||||
|
||||
if (!pkgname) {
|
||||
logger.warn("remove-installed missing pkgname");
|
||||
const parsed = parseAppPayload(payload);
|
||||
if (!parsed) {
|
||||
logger.warn("remove-installed invalid payload");
|
||||
return;
|
||||
}
|
||||
const { pkgname, origin } = parsed;
|
||||
logger.info(`卸载已安装应用: ${pkgname} (来源: ${origin})`);
|
||||
|
||||
let execCommand = "";
|
||||
@@ -1243,14 +1260,18 @@ ipcMain.handle("show-apm-install-dialog", async (event) => {
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
ipcMain.handle("uninstall-installed", async (_event, payload: any) => {
|
||||
const pkgname = typeof payload === "string" ? payload : payload.pkgname;
|
||||
const origin = typeof payload === "string" ? "spark" : payload.origin;
|
||||
|
||||
if (!pkgname) {
|
||||
logger.warn("uninstall-installed missing pkgname");
|
||||
return { success: false, message: "missing pkgname" };
|
||||
}
|
||||
ipcMain.handle(
|
||||
"uninstall-installed",
|
||||
async (
|
||||
_event,
|
||||
payload: unknown,
|
||||
): Promise<{ success: boolean; message?: string }> => {
|
||||
const parsed = parseAppPayload(payload);
|
||||
if (!parsed) {
|
||||
logger.warn("uninstall-installed invalid payload");
|
||||
return { success: false, message: "invalid payload" };
|
||||
}
|
||||
const { pkgname, origin } = parsed;
|
||||
|
||||
const superUserCmd = await checkSuperUserCommand();
|
||||
const execCommand = superUserCmd || SHELL_CALLER_PATH;
|
||||
@@ -1287,9 +1308,6 @@ interface LaunchAppPayload {
|
||||
origin?: "spark" | "apm";
|
||||
}
|
||||
|
||||
// 合法包名字符(Debian 包名规范 + Spark 应用包名常见字符),
|
||||
// 用于拦截包含特殊字符的非法输入,避免命令注入。
|
||||
const PKGNAME_PATTERN = /^[a-zA-Z0-9._+-]+$/;
|
||||
|
||||
ipcMain.handle(
|
||||
"launch-app",
|
||||
|
||||
Reference in New Issue
Block a user