锁定桌面壳系统调用顺序

桌面壳配置检查新增导出文件 payload 校验早于系统保存对话框的顺序门禁

桌面壳配置检查新增本地通知 payload 校验早于通知权限访问的顺序门禁

共享决策记录补充桌面壳系统能力调用顺序约定
This commit is contained in:
2026-06-20 15:18:35 +08:00
parent 8a0285382c
commit a330ce5bbb
2 changed files with 50 additions and 0 deletions
@@ -1040,6 +1040,14 @@ function extractFunctionBody(source, functionName) {
throw new Error(`unable to read Rust function body ${functionName}`);
}
function assertSnippetOrder(source, firstSnippet, secondSnippet, label) {
const firstIndex = source.indexOf(firstSnippet);
const secondIndex = source.indexOf(secondSnippet);
if (firstIndex < 0 || secondIndex < 0 || firstIndex > secondIndex) {
throw new Error(`${label} must call ${firstSnippet} before ${secondSnippet}`);
}
}
function extractDesktopDialogFilter(source, ownerName, filterLabel) {
const ownerBody = extractFunctionBody(source, ownerName);
const match = ownerBody.match(
@@ -1102,6 +1110,19 @@ function assertDesktopDialogBoundary(method, functionName, filterLabel, expected
}
}
function assertDesktopFileExportPayloadOrder(method, functionName, payloadFunction) {
const fileFunctionBody = extractFunctionBody(
desktopHostBridgeFilesSource,
functionName,
);
assertSnippetOrder(
fileFunctionBody,
payloadFunction,
'.dialog()',
`desktop shell ${method} export boundary`,
);
}
function assertNoRawHostContextUrl(urlValue, label) {
const rawUrl = String(urlValue ?? '');
const blockedQueryKeys = [
@@ -2555,6 +2576,12 @@ if (
'desktop shell notification HostBridge method must delegate to notifications module',
);
}
assertSnippetOrder(
extractFunctionBody(desktopHostBridgeNotificationsSource, 'show_desktop_local_notification'),
'local_notification_payload(request)',
'app.notification()',
'desktop shell notification boundary',
);
for (const snippet of [
'tauri_plugin_notification::{NotificationExt, PermissionState}',
'HOST_BRIDGE_LOCAL_NOTIFICATION_DELIVERED_TO_SYSTEM_ACTION',
@@ -2687,6 +2714,21 @@ assertDesktopDialogBoundary(
['mp3', 'm4a', 'wav', 'ogg', 'webm'],
'save',
);
assertDesktopFileExportPayloadOrder(
'file.exportText',
'export_desktop_host_bridge_text_file',
'export_text_payload(request)',
);
assertDesktopFileExportPayloadOrder(
'file.exportImage',
'export_desktop_host_bridge_image_file',
'export_image_payload(request)',
);
assertDesktopFileExportPayloadOrder(
'file.exportAudio',
'export_desktop_host_bridge_audio_file',
'export_audio_payload(request)',
);
for (const snippet of [
'.dialog()',
'blocking_save_file',
@@ -16,6 +16,14 @@
---
## 2026-06-20 桌面壳系统能力调用顺序门禁
- 背景:Tauri 桌面壳文件导出和本地通知已经在运行时代码中先校验 HostBridge payload,再打开系统保存对话框、读取通知权限或请求通知权限;如果后续重构把系统能力调用提前,非法请求会触达原生系统边界。
- 决策:桌面壳单端配置检查必须逐函数反查 `file.exportText``file.exportImage``file.exportAudio` 先调用对应 payload helper 再进入 `.dialog()`,并反查 `notification.showLocal` 先调用 `local_notification_payload(request)` 再进入 `app.notification()`。文件导入仍以用户主动选择文件后的本地 payload 读取和大小 / MIME 校验为准。
- 影响范围:`apps/desktop-shell/scripts/check-config.mjs``apps/desktop-shell/src-tauri/src/host_bridge/files.rs``apps/desktop-shell/src-tauri/src/host_bridge/notifications.rs`
- 验证方式:`node apps/desktop-shell/scripts/check-config.mjs``npm run check:native-shells``npm run check:encoding``git diff --check`
- 关联文档:`docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md``docs/【前端架构】宿主壳能力统一协议-2026-06-17.md`
## 2026-06-20 原生壳替身词扫描排除构建产物
- 背景:桌面壳单端配置检查会递归扫描生产源码和配置中的替身词;本地或 CI 运行 Tauri / Cargo 后,`apps/desktop-shell/src-tauri/target/` 会包含依赖 `.d` 等生成文件,若纳入扫描会让门禁被缓存内容污染。