mirror of
https://gitee.com/spark-store-project/spark-store
synced 2026-09-20 21:50:11 +08:00
fix(security): PR 审查整改 - setWindowOpenHandler 域名白名单 / openWebsite 协议校验 / 批量云端安装并发控制
- electron/main/index.ts: setWindowOpenHandler 增加 ALLOWED_EXTERNAL_HOSTS 域名后缀白名单,仅可信 https 域名可 shell.openExternal(阻断项2) - AppDetailModal.vue: openWebsite 增加 http/https 协议校验 + noopener,noreferrer(改进项5) - useAccountSync.ts: installCloudItems 改为分批(BATCH_SIZE=3) + Promise.allSettled 错误收集(改进项2) - 审查误报/未改动项已贴代码实证:阻断1 v-html 已用 textContent 转义;改进3 resolveCloudInstallCandidate 已有降级匹配链;改进4 单窗口无 HMR 不重复注册;改进1 暂停/恢复主进程无 handler 维持 TODO
This commit is contained in:
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
spark-store (5.2.1.5-test) UNRELEASED; urgency=medium
|
spark-store (5.2.1.6-test) UNRELEASED; urgency=medium
|
||||||
|
|
||||||
* Initial release. (Closes: #nnnn) <nnnn is the bug number of your ITP>
|
* Initial release. (Closes: #nnnn) <nnnn is the bug number of your ITP>
|
||||||
|
|
||||||
|
|||||||
+19
-2
@@ -473,9 +473,26 @@ async function createWindow() {
|
|||||||
logger.info("Renderer process is ready.");
|
logger.info("Renderer process is ready.");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Make all links open with the browser, not with the application
|
// 仅允许可信域名的 https 链接通过浏览器打开,避免钓鱼/恶意站。
|
||||||
|
// 协议前缀 + 域名后缀白名单双重校验;非法/无效 URL 一律拒绝。
|
||||||
|
const ALLOWED_EXTERNAL_HOSTS = [
|
||||||
|
"spark-app.store",
|
||||||
|
"gitee.com",
|
||||||
|
"bbs.spark-app.store",
|
||||||
|
"spark-app.cn",
|
||||||
|
];
|
||||||
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
|
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
|
||||||
if (url.startsWith("https:")) shell.openExternal(url);
|
try {
|
||||||
|
const parsed = new URL(url);
|
||||||
|
if (
|
||||||
|
parsed.protocol === "https:" &&
|
||||||
|
ALLOWED_EXTERNAL_HOSTS.some((h) => parsed.hostname === h || parsed.hostname.endsWith(`.${h}`))
|
||||||
|
) {
|
||||||
|
shell.openExternal(url);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// 无效 URL,拒绝打开
|
||||||
|
}
|
||||||
return { action: "deny" };
|
return { action: "deny" };
|
||||||
});
|
});
|
||||||
// win.webContents.on('will-navigate', (event, url) => { }) #344
|
// win.webContents.on('will-navigate', (event, url) => { }) #344
|
||||||
|
|||||||
@@ -644,8 +644,15 @@ const closeMetaModal = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const openWebsite = (url: string) => {
|
const openWebsite = (url: string) => {
|
||||||
if (url) {
|
if (!url) return;
|
||||||
window.open(url, "_blank");
|
try {
|
||||||
|
const parsed = new URL(url);
|
||||||
|
// 仅允许 http/https 协议,杜绝 javascript:/data: 等危险协议
|
||||||
|
if (parsed.protocol === "https:" || parsed.protocol === "http:") {
|
||||||
|
window.open(url, "_blank", "noopener,noreferrer");
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// 无效 URL,不打开
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -319,11 +319,24 @@ const openRestoreFromAccount = async (): Promise<void> => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const installCloudItems = (items: SyncedAppListItem[]): void => {
|
// 批量云端安装:分批触发(每批 3 个),收集每个安装的结果并反馈失败项。
|
||||||
for (const item of items) {
|
// onDetailInstall 内部本身已串行排队,这里仅限制"同时发起"的并发,避免一次注入大量任务。
|
||||||
const app = resolveCloudInstallCandidate(item, apps.value);
|
const installCloudItems = async (items: SyncedAppListItem[]): Promise<void> => {
|
||||||
if (!app) continue;
|
const BATCH_SIZE = 3;
|
||||||
void onDetailInstall(app);
|
let failedCount = 0;
|
||||||
|
for (let i = 0; i < items.length; i += BATCH_SIZE) {
|
||||||
|
const batch = items.slice(i, i + BATCH_SIZE);
|
||||||
|
const results = await Promise.allSettled(
|
||||||
|
batch.map(async (item) => {
|
||||||
|
const app = resolveCloudInstallCandidate(item, apps.value);
|
||||||
|
if (!app) return;
|
||||||
|
await onDetailInstall(app);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
failedCount += results.filter((r) => r.status === "rejected").length;
|
||||||
|
}
|
||||||
|
if (failedCount > 0) {
|
||||||
|
console.error(`批量云端安装中有 ${failedCount} 项失败`);
|
||||||
}
|
}
|
||||||
showRestoreModal.value = false;
|
showRestoreModal.value = false;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user