mirror of
https://gitee.com/spark-store-project/spark-store
synced 2026-09-20 13:40:11 +08:00
fix/refactor: 更新中心扫描与多项安全加固(经专业审计)
更新中心扫描修复:
- 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 维度专业审计(安全/功能/类型/可维护/资源/性能/兼容)通过。
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+17
-7
@@ -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");
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user