拒绝桌面壳默认下载落盘
桌面壳 WebView 下载事件默认拒绝网页触发的落盘动作 桌面壳配置检查守卫下载拒绝策略 宿主壳方案和共享决策记录桌面保存边界
This commit is contained in:
@@ -529,6 +529,10 @@ const requiredMainSnippets = [
|
||||
'open_desktop_external_navigation',
|
||||
'.on_navigation(move |url|',
|
||||
'.on_new_window(move |url, _features|',
|
||||
'DownloadEvent',
|
||||
'should_allow_desktop_webview_download',
|
||||
'DownloadEvent::Requested { .. } => false',
|
||||
'.on_download(|_webview, event| should_allow_desktop_webview_download(&event))',
|
||||
'NewWindowResponse::Deny',
|
||||
];
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ use std::sync::Mutex;
|
||||
use std::time::Duration;
|
||||
use tauri::menu::{Menu, MenuItem};
|
||||
use tauri::tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent};
|
||||
use tauri::webview::NewWindowResponse;
|
||||
use tauri::webview::{DownloadEvent, NewWindowResponse};
|
||||
use tauri::DragDropEvent;
|
||||
use tauri::Manager;
|
||||
use tauri::Theme;
|
||||
@@ -326,6 +326,14 @@ fn open_desktop_external_navigation(app: &tauri::AppHandle, url: &Url) {
|
||||
let _ = app.opener().open_url(external_url, None::<&str>);
|
||||
}
|
||||
|
||||
fn should_allow_desktop_webview_download(event: &DownloadEvent<'_>) -> bool {
|
||||
match event {
|
||||
DownloadEvent::Requested { .. } => false,
|
||||
DownloadEvent::Finished { .. } => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_native_page_url(raw_url: &str) -> Option<Url> {
|
||||
let url = raw_url.trim();
|
||||
if url.is_empty() || url.chars().any(char::is_control) {
|
||||
@@ -1636,6 +1644,7 @@ fn main() {
|
||||
open_desktop_external_navigation(&new_window_app_handle, &url);
|
||||
NewWindowResponse::Deny
|
||||
})
|
||||
.on_download(|_webview, event| should_allow_desktop_webview_download(&event))
|
||||
.build()?;
|
||||
register_desktop_window_close_events(&window, tray_registered);
|
||||
register_desktop_lifecycle_events(&window);
|
||||
@@ -2025,6 +2034,27 @@ mod tests {
|
||||
assert_eq!(desktop_external_navigation_url(&file_url), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn desktop_webview_downloads_are_blocked_by_default() {
|
||||
let mut destination = std::env::temp_dir().join("genarrative-webview-download.txt");
|
||||
let url =
|
||||
Url::parse("https://app.genarrative.world/download.txt").expect("download url");
|
||||
let requested = DownloadEvent::Requested {
|
||||
url: url.clone(),
|
||||
destination: &mut destination,
|
||||
};
|
||||
|
||||
assert!(!should_allow_desktop_webview_download(&requested));
|
||||
|
||||
let finished = DownloadEvent::Finished {
|
||||
url,
|
||||
path: None,
|
||||
success: false,
|
||||
};
|
||||
|
||||
assert!(should_allow_desktop_webview_download(&finished));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_page_url_normalization_allows_same_origin_routes() {
|
||||
assert_eq!(
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
- 2026-06-18 桌面壳安装包身份:Tauri 桌面壳的产品名固定为 `Genarrative`,应用 identifier 固定为 `world.genarrative.desktop`,Tauri 配置、`apps/desktop-shell/package.json` 与 Cargo package 版本统一为 `0.1.0`;Release 主窗口只加载打包的 `index.html` 和根 `dist` H5 资产,dev URL 只指向本机 Vite 调试入口。桌面壳 CSP 保持 `script-src 'self'`,不得加入 `unsafe-eval`、`tauri:` 或 `file:`,也不得在没有真实端点、签名密钥和发布流程前配置 updater;检查脚本会拒绝包身份、版本、CSP 或 updater 约束漂移。
|
||||
- 2026-06-18 桌面壳运行时平台 query:Tauri 静态配置中的 `hostPlatform=unknown` 只作为跨平台构建模板值;Rust `setup` 手动创建主窗口前必须把入口 URL 改写为当前 `macos` / `windows` / `linux`,保证 H5 首屏 query 与 `host.getRuntime` 回读的平台一致。第二实例参数、外部 deep link 或 H5 自报值不得覆盖该字段;桌面壳测试和配置检查会拒绝绕过该归一流程。
|
||||
- 2026-06-18 桌面壳顶层导航边界:Tauri 主 WebView 只允许打包资产 URL 和 `https://app.genarrative.world` 同源 H5 route 留在主窗口;外域 `http:` / `https:`、`mailto:`、`tel:` 导航与 `window.open` 请求交给系统 opener 后拒绝 WebView 留壳;`javascript:`、`file:` 等危险协议直接拒绝。该规则不进入 HostBridge capability,不开放 opener JS guest API,配置检查和 cargo test 覆盖导航策略。
|
||||
- 2026-06-18 桌面壳默认下载边界:Tauri 主 WebView 的下载事件默认拒绝网页自动下载和 `<a download>` 落盘,桌面文件保存只能通过 `file.exportText`、`file.exportImage`、`file.exportAudio` 等已声明 HostBridge method 进入 Rust 侧系统保存对话框,并继续执行 MIME、大小、文件名清洗和用户确认。该规则不进入 HostBridge capability,配置检查和 cargo test 覆盖下载拒绝策略。
|
||||
- 2026-06-18 桌面壳 Tauri 命令白名单:桌面壳源码、Tauri build manifest、主窗口 capability 和本地自动生成权限目录都只能暴露 `host_bridge_request` 一个受控 command;所有桌面能力继续在 Rust 内部按 HostBridge method 白名单分发,不新增可被 H5 直接 `invoke` 的 Tauri command,也不授予插件 JS guest API。检查脚本会拒绝多余 command、权限列表顺序漂移和残留的自动生成权限文件。
|
||||
- 2026-06-18 桌面壳 CSP 分层:Tauri release `csp` 不得包含 `http://127.0.0.1:*`、`ws://127.0.0.1:*` 或其它本机调试源,本机 Vite、HMR WebSocket 和开发 frame 只允许出现在 `devCsp`。桌面壳配置检查会同时拒绝 release CSP 混入本机调试源、dev CSP 缺失本机开发源,以及 release / dev CSP 加入 `unsafe-eval`、`tauri:` 或 `file:`。
|
||||
- 2026-06-18 壳生产代码禁用临时替身:Expo 与 Tauri 壳的生产源码和配置不得出现 mock / fake / placeholder / stub / TODO / FIXME 以及对应中文脚手架词;测试文件仍可使用 mock。两端壳配置检查会扫描生产入口、配置和壳实现,防止把临时替身、占位文案或伪实现带进可分发壳。
|
||||
|
||||
@@ -179,6 +179,7 @@ Tauri 壳同样只负责桌面宿主能力,不承接玩法业务。
|
||||
- Tauri capabilities 只授予主窗口所需命令;默认不开放文件系统、shell、全局剪贴板或任意插件能力。
|
||||
- 桌面支付首期走现有 H5 / 二维码 / 外部浏览器路径,不在 Rust 侧保存支付凭据。
|
||||
- 文件导出、作品卡保存、图片拖拽导入、系统托盘、自动更新等桌面能力按后续需求逐项开放;其中系统托盘属于桌面壳自有能力,不作为 HostBridge method 暴露给 H5。
|
||||
- 主 WebView 默认拒绝网页自动下载或 `<a download>` 触发的落盘动作;用户保存文本、图片、音频等内容必须走已声明的 `file.exportText`、`file.exportImage`、`file.exportAudio` HostBridge method,由 Rust 侧执行 MIME、大小、文件名清洗和系统保存对话框确认。
|
||||
|
||||
桌面 release 和 dev 模式:
|
||||
|
||||
@@ -233,6 +234,7 @@ GameBridge 禁止:
|
||||
- 登录和支付能力在真实 SDK、渠道流程、后端契约和失败回退全部落地前不得进入 Expo / Tauri capabilities,也不得写进入口 URL `hostCapabilities`;两端配置检查会拒绝 `auth.requestLogin` 和 `payment.request` 的伪声明。
|
||||
- 宿主壳不得把长期 token、支付密钥或用户敏感资料回传给 H5。
|
||||
- Tauri 禁止把 shell / fs 等高危插件作为默认能力暴露给主 WebView。
|
||||
- Tauri 主 WebView 禁止默认下载落盘;桌面文件保存只能通过受控 HostBridge 导出能力进入系统保存对话框。
|
||||
- RN WebView 禁止打开任意 URL 后仍保留完整 HostBridge;跳外链只允许 `http:`、`https:`、`mailto:`、`tel:`,并使用系统浏览器或降级能力,危险协议直接阻断。
|
||||
- AI sandbox iframe 必须使用独立 CSP、`sandbox` 属性和单独 GameBridge allowlist。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user