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 @@