From a330ce5bbb82a44d3f158b33992cd6f0a9f80579 Mon Sep 17 00:00:00 2001 From: kdletters Date: Sat, 20 Jun 2026 15:18:35 +0800 Subject: [PATCH] =?UTF-8?q?=E9=94=81=E5=AE=9A=E6=A1=8C=E9=9D=A2=E5=A3=B3?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E8=B0=83=E7=94=A8=E9=A1=BA=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 桌面壳配置检查新增导出文件 payload 校验早于系统保存对话框的顺序门禁 桌面壳配置检查新增本地通知 payload 校验早于通知权限访问的顺序门禁 共享决策记录补充桌面壳系统能力调用顺序约定 --- apps/desktop-shell/scripts/check-config.mjs | 42 +++++++++++++++++++ .../shared-memory/decision-log.md | 8 ++++ 2 files changed, 50 insertions(+) diff --git a/apps/desktop-shell/scripts/check-config.mjs b/apps/desktop-shell/scripts/check-config.mjs index b8bcc3e8d..845913123 100644 --- a/apps/desktop-shell/scripts/check-config.mjs +++ b/apps/desktop-shell/scripts/check-config.mjs @@ -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', diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 0c0d24e98..9e97b0904 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -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` 等生成文件,若纳入扫描会让门禁被缓存内容污染。