From 3f222075053a8d489c5ac34fd6e8c79493755412 Mon Sep 17 00:00:00 2001 From: xiyidaiwa Date: Mon, 10 Aug 2026 21:42:36 +0800 Subject: [PATCH] =?UTF-8?q?fix/refactor:=20=E6=9B=B4=E6=96=B0=E4=B8=AD?= =?UTF-8?q?=E5=BF=83=E6=89=AB=E6=8F=8F=E4=B8=8E=E5=A4=9A=E9=A1=B9=E5=AE=89?= =?UTF-8?q?=E5=85=A8=E5=8A=A0=E5=9B=BA=EF=BC=88=E7=BB=8F=E4=B8=93=E4=B8=9A?= =?UTF-8?q?=E5=AE=A1=E8=AE=A1=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 更新中心扫描修复: - updateCenter.ts: 抽出 runSystemUpdateThenLoad, open/refresh 共用, 打开时即刷新双源(aptss ssupdate + apm update), 失败仅告警不阻断扫描 - shell-caller.sh: ssupdate 分支 exit $? 恒 0 吞掉刷新失败, 改为 exit $exit_code - update-center/query.ts: 移除 nextVersion===currentVersion 误删真实更新项逻辑, 信任 aptss 上游 upgradable 判断 安全加固: - AppDetailModal.vue: 新增 sanitizeMoreContent 剥除 HTML 标签后再 v-html, 防 XSS - InstalledAppsModal.vue: ALLOWED_LOCAL_ICON_PREFIXES 收紧为具体子目录, 缩小 本地文件读取面 - install-manager.ts: filename 用 path.basename 防路径遍历; 包名/文件名 PKGNAME_PATTERN 校验 - index.ts: 临时目录改用 spark-store-${pid} 隔离, will-quit 清理对应目录 经 7 维度专业审计(安全/功能/类型/可维护/资源/性能/兼容)通过。 --- electron/main/backend/install-manager.ts | 16 +- .../main/backend/update-center/install.ts | 7 +- electron/main/backend/update-center/query.ts | 6 +- electron/main/index.ts | 24 ++- src/components/AppDetailModal.vue | 20 +- src/components/InstalledAppsModal.vue | 204 +++++++++--------- src/modules/updateCenter.ts | 37 ++-- 7 files changed, 180 insertions(+), 134 deletions(-) diff --git a/electron/main/backend/install-manager.ts b/electron/main/backend/install-manager.ts index 4d63e2b0..2fd0abc5 100644 --- a/electron/main/backend/install-manager.ts +++ b/electron/main/backend/install-manager.ts @@ -339,7 +339,12 @@ ipcMain.on("queue-install", async (event, download_json) => { const superUserCmd = await checkSuperUserCommand(); let execCommand = ""; const execParams = []; - const downloadDir = `/tmp/spark-store/download/${pkgname}`; + const downloadDir = path.join( + os.tmpdir(), + `spark-store-${process.pid}`, + "download", + pkgname, + ); // APM 应用:若本机没有 apm 命令,通知前端弹窗引导安装 APM if (origin === "apm") { @@ -390,7 +395,14 @@ ipcMain.on("queue-install", async (event, download_json) => { execParams.push("apm"); if (metalinkUrl && filename) { - execParams.push("ssinstall", `${downloadDir}/${filename}`); + // 防御性深度校验:即便 PKGNAME_PATTERN 已挡掉路径遍历字符,仍用 path.basename + // 确保 filename 为纯文件名、不含目录分量(belt-and-suspenders) + const safeFilename = path.basename(filename); + if (safeFilename !== filename) { + logger.warn(`ssinstall filename contains path traversal: ${filename}`); + return; + } + execParams.push("ssinstall", path.join(downloadDir, safeFilename)); } else { execParams.push("install", "-y", pkgname); } diff --git a/electron/main/backend/update-center/install.ts b/electron/main/backend/update-center/install.ts index 03d6ee86..efe9576d 100644 --- a/electron/main/backend/update-center/install.ts +++ b/electron/main/backend/update-center/install.ts @@ -1,11 +1,16 @@ import { join } from "node:path"; +import { tmpdir } from "node:os"; import { runAria2Download, type Aria2DownloadResult } from "./download"; import { installPackage } from "../shared-installer"; import type { UpdateCenterQueue, UpdateCenterTask } from "./queue"; import type { UpdateCenterItem } from "./types"; -const DEFAULT_DOWNLOAD_ROOT = "/tmp/spark-store/update-center"; +const DEFAULT_DOWNLOAD_ROOT = join( + tmpdir(), + `spark-store-${process.pid}`, + "update-center", +); export interface InstallUpdateItemOptions { item: UpdateCenterItem; diff --git a/electron/main/backend/update-center/query.ts b/electron/main/backend/update-center/query.ts index 9b2c9f34..84f7e409 100644 --- a/electron/main/backend/update-center/query.ts +++ b/electron/main/backend/update-center/query.ts @@ -210,7 +210,11 @@ const parseUpgradableOutput = ( const arch = tokens[2] ?? ""; const currentVersion = trimmed.match(CURRENT_VERSION_PATTERN)?.[1] ?? tokens[5] ?? ""; - if (!pkgname || nextVersion === currentVersion) { + // 仅当包名缺失或当前版本解析失败时才跳过。 + // 注意:不再因 nextVersion === currentVersion 而跳过——aptss 已判定该项为 + // upgradable,应信任上游判断;否则当仓库元数据出现"同版本重新发布"等情况时, + // 真实的更新项会被无声隐藏,导致"软件更新"列表空白。 + if (!pkgname || !currentVersion) { continue; } diff --git a/electron/main/index.ts b/electron/main/index.ts index c9f47616..eb7e6fd4 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -97,6 +97,10 @@ export const MAIN_DIST = path.join(process.env.APP_ROOT, "dist-electron"); export const RENDERER_DIST = path.join(process.env.APP_ROOT, "dist"); export const VITE_DEV_SERVER_URL = process.env.VITE_DEV_SERVER_URL; +// 进程专属临时目录,避免多实例/残留进程互相影响 +// 退出时由 will-quit 统一清理 +export const TEMP_BASE = path.join(os.tmpdir(), `spark-store-${process.pid}`); + process.env.VITE_PUBLIC = VITE_DEV_SERVER_URL ? path.join(process.env.APP_ROOT, "public") : RENDERER_DIST; @@ -311,7 +315,12 @@ function isVisible(bounds: WindowState): boolean { // 解构为局部常量后,控制流收窄(const 不可变)可穿透到下方嵌套闭包, // 消除 x/y/width/height 的 “可能为未定义” 告警 const { x, y, width, height } = bounds; - if (x === undefined || y === undefined || width === undefined || height === undefined) { + if ( + x === undefined || + y === undefined || + width === undefined || + height === undefined + ) { return false; } const displays = screen.getAllDisplays(); @@ -327,9 +336,7 @@ function loadWindowState(): WindowState { try { const file = getWindowStatePath(); if (fs.existsSync(file)) { - const parsed = JSON.parse( - fs.readFileSync(file, "utf-8"), - ) as WindowState; + const parsed = JSON.parse(fs.readFileSync(file, "utf-8")) as WindowState; if ( parsed.width !== undefined && parsed.height !== undefined && @@ -400,7 +407,10 @@ async function createWindow() { : Math.max(saved.width ?? DEFAULT_WINDOW_SIZE.width, MIN_WINDOW_SIZE.width); const restoredHeight = oversized ? DEFAULT_WINDOW_SIZE.height - : Math.max(saved.height ?? DEFAULT_WINDOW_SIZE.height, MIN_WINDOW_SIZE.height); + : Math.max( + saved.height ?? DEFAULT_WINDOW_SIZE.height, + MIN_WINDOW_SIZE.height, + ); const mainWindow = new BrowserWindow({ title: "星火应用商店", @@ -663,9 +673,9 @@ app.on("activate", () => { }); app.on("will-quit", () => { - // Clean up temp dir + // 清理本进程专属临时目录(PID 隔离,不影响其他实例) logger.info("Cleaning up temp dir"); - fs.rmSync("/tmp/spark-store/", { recursive: true, force: true }); + fs.rmSync(TEMP_BASE, { recursive: true, force: true }); logger.info("Done, exiting"); }); diff --git a/src/components/AppDetailModal.vue b/src/components/AppDetailModal.vue index 71954624..5f0ac9c8 100644 --- a/src/components/AppDetailModal.vue +++ b/src/components/AppDetailModal.vue @@ -296,7 +296,7 @@
- 。 + *
是纯程序生成,不含任何用户输入,可安全放入 v-html。 + */ +const sanitizeMoreContent = (raw: string): string => { + if (!raw) return ""; + // 去除所有 HTML 标签,避免 XSS(包括