校验移动壳HostBridge消息来源

移动壳 onMessage 按页面 URL 拦截非同源 HostBridge 消息

移动壳导航测试覆盖同源消息入口和异常来源丢弃

移动壳检查脚本要求保留消息来源校验

宿主壳方案和共享决策记录移动消息入口边界
This commit is contained in:
2026-06-18 09:23:53 +08:00
parent 7fabb5ed4c
commit a1afd33004
6 changed files with 49 additions and 0 deletions

View File

@@ -21,6 +21,7 @@ import { buildMobileShellUrlFromDeepLink } from './src/mobileShellDeepLink';
import { lifecyclePayloadFromAppState } from './src/mobileShellLifecycle'; import { lifecyclePayloadFromAppState } from './src/mobileShellLifecycle';
import { import {
resolveMobileShellExternalUrl, resolveMobileShellExternalUrl,
shouldAcceptMobileShellHostBridgeMessage,
shouldOpenInMobileShellWebView, shouldOpenInMobileShellWebView,
} from './src/mobileShellNavigation'; } from './src/mobileShellNavigation';
import { subscribeMobileNetworkStatus } from './src/mobileShellNetwork'; import { subscribeMobileNetworkStatus } from './src/mobileShellNetwork';
@@ -149,6 +150,15 @@ export default function App() {
}, []); }, []);
const handleMessage = (event: WebViewMessageEvent) => { const handleMessage = (event: WebViewMessageEvent) => {
if (
!shouldAcceptMobileShellHostBridgeMessage(
event.nativeEvent.url,
allowedWebOrigin,
)
) {
return;
}
void handleMobileHostBridgeMessage(event.nativeEvent.data, (response) => { void handleMobileHostBridgeMessage(event.nativeEvent.data, (response) => {
webViewRef.current?.injectJavaScript( webViewRef.current?.injectJavaScript(
buildHostBridgeMessageScript(response), buildHostBridgeMessageScript(response),

View File

@@ -284,6 +284,7 @@ for (const snippet of [
"Linking.addEventListener('url'", "Linking.addEventListener('url'",
'buildMobileShellUrlFromDeepLink', 'buildMobileShellUrlFromDeepLink',
'configureMobileHostBridgeNavigation', 'configureMobileHostBridgeNavigation',
'shouldAcceptMobileShellHostBridgeMessage',
'webViewRef.current?.reload()', 'webViewRef.current?.reload()',
'AppState.addEventListener', 'AppState.addEventListener',
'app.lifecycle', 'app.lifecycle',

View File

@@ -3,6 +3,7 @@ import { describe, expect, test } from 'vitest';
import { import {
resolveMobileShellExternalUrl, resolveMobileShellExternalUrl,
resolveMobileShellWebViewUrl, resolveMobileShellWebViewUrl,
shouldAcceptMobileShellHostBridgeMessage,
shouldOpenInMobileShellWebView, shouldOpenInMobileShellWebView,
} from './mobileShellNavigation'; } from './mobileShellNavigation';
@@ -84,4 +85,30 @@ describe('shouldOpenInMobileShellWebView', () => {
resolveMobileShellWebViewUrl('about:blank', allowedOrigin), resolveMobileShellWebViewUrl('about:blank', allowedOrigin),
).toBeNull(); ).toBeNull();
}); });
test('HostBridge 消息只接受同源主站页面', () => {
const allowedOrigin = 'https://app.genarrative.world';
expect(
shouldAcceptMobileShellHostBridgeMessage(
'https://app.genarrative.world/creation/puzzle',
allowedOrigin,
),
).toBe(true);
expect(
shouldAcceptMobileShellHostBridgeMessage('about:blank', allowedOrigin),
).toBe(false);
expect(
shouldAcceptMobileShellHostBridgeMessage(
'https://example.com/evil',
allowedOrigin,
),
).toBe(false);
expect(
shouldAcceptMobileShellHostBridgeMessage(
'javascript:alert(1)',
allowedOrigin,
),
).toBe(false);
});
}); });

View File

@@ -32,6 +32,14 @@ export function resolveMobileShellExternalUrl(rawUrl: string) {
return normalizeHostBridgeExternalUrl(rawUrl); return normalizeHostBridgeExternalUrl(rawUrl);
} }
export function shouldAcceptMobileShellHostBridgeMessage(
rawUrl: string,
allowedOrigin: string,
) {
return rawUrl !== 'about:blank' &&
shouldOpenInMobileShellWebView(rawUrl, allowedOrigin);
}
export function resolveMobileShellWebViewUrl( export function resolveMobileShellWebViewUrl(
rawUrl: string, rawUrl: string,
allowedOrigin: string, allowedOrigin: string,

View File

@@ -60,6 +60,7 @@
- 2026-06-18 移动壳麦克风权限禁用Expo 移动壳的 `expo-image-picker` 插件必须保持 `microphonePermission=false`Android 包配置必须通过 `android.blockedPermissions` 显式移除 `android.permission.RECORD_AUDIO`,且不得在 `android.permissions` 中重新声明;移动壳现阶段没有录音、后台音频采集或远程语音 SDK音频导入只走系统文档选择器。配置检查会拒绝麦克风权限拦截缺失或被反向加入。 - 2026-06-18 移动壳麦克风权限禁用Expo 移动壳的 `expo-image-picker` 插件必须保持 `microphonePermission=false`Android 包配置必须通过 `android.blockedPermissions` 显式移除 `android.permission.RECORD_AUDIO`,且不得在 `android.permissions` 中重新声明;移动壳现阶段没有录音、后台音频采集或远程语音 SDK音频导入只走系统文档选择器。配置检查会拒绝麦克风权限拦截缺失或被反向加入。
- 2026-06-18 移动壳 Android 自动备份关闭Expo 移动壳必须保持 `android.allowBackup=false`,避免 WebView cookie、localStorage、缓存文件和宿主文件导入导出中间态进入 Google Drive 自动备份 / 恢复链路;正式业务事实仍以后端账号、作品、钱包和草稿状态为准。配置检查会拒绝恢复 Android 默认允许备份的包配置。 - 2026-06-18 移动壳 Android 自动备份关闭Expo 移动壳必须保持 `android.allowBackup=false`,避免 WebView cookie、localStorage、缓存文件和宿主文件导入导出中间态进入 Google Drive 自动备份 / 恢复链路;正式业务事实仍以后端账号、作品、钱包和草稿状态为准。配置检查会拒绝恢复 Android 默认允许备份的包配置。
- 2026-06-18 移动壳 WebView 安全开关Expo 移动壳 WebView 必须显式禁用 JS 自动开窗、多窗口、文件访问、file URL 跨源访问、HTTPS 混合内容、第三方 Cookie、共享 Cookie 和 WebView 远程调试;同源主站页面才能留在带 HostBridge 的 WebView 内,外链只通过受控协议离开容器交给系统。配置检查和移动壳导航测试会拒绝这些边界被放宽。 - 2026-06-18 移动壳 WebView 安全开关Expo 移动壳 WebView 必须显式禁用 JS 自动开窗、多窗口、文件访问、file URL 跨源访问、HTTPS 混合内容、第三方 Cookie、共享 Cookie 和 WebView 远程调试;同源主站页面才能留在带 HostBridge 的 WebView 内,外链只通过受控协议离开容器交给系统。配置检查和移动壳导航测试会拒绝这些边界被放宽。
- 2026-06-18 移动壳 HostBridge 消息来源校验Expo 移动壳 `onMessage` 必须根据 `event.nativeEvent.url` 校验消息来源,只有同源主站页面能进入 `handleMobileHostBridgeMessage``about:blank`、外域、协议降级和危险协议页面消息直接丢弃,不返回宿主能力错误细节。该规则与 WebView 导航留壳规则共用同源判断,配置检查和移动壳导航测试会拒绝移除。
- 影响范围:`src/services/host-bridge/`、未来 `apps/mobile-shell/`、未来 `apps/desktop-shell/`、移动端支付 / 分享 / 深链 / 推送、桌面端系统能力、AI H5 sandbox 的 GameBridge 边界。 - 影响范围:`src/services/host-bridge/`、未来 `apps/mobile-shell/`、未来 `apps/desktop-shell/`、移动端支付 / 分享 / 深链 / 推送、桌面端系统能力、AI H5 sandbox 的 GameBridge 边界。
- 验证方式普通浏览器、小程序、Expo 壳、Tauri 壳都能返回正确 `getHostRuntime()`;未支持能力能回退 H5固定玩法在各宿主中读取同一作品数据和运行态 snapshotAI sandbox 无法直接调用 HostBridgeTauri release 不允许任意远端页面调用桌面命令。 - 验证方式普通浏览器、小程序、Expo 壳、Tauri 壳都能返回正确 `getHostRuntime()`;未支持能力能回退 H5固定玩法在各宿主中读取同一作品数据和运行态 snapshotAI sandbox 无法直接调用 HostBridgeTauri release 不允许任意远端页面调用桌面命令。
- 关联文档:`docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md``docs/【前端架构】宿主壳能力统一协议-2026-06-17.md` - 关联文档:`docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md``docs/【前端架构】宿主壳能力统一协议-2026-06-17.md`

View File

@@ -323,6 +323,8 @@ GameBridge 禁止:
2026-06-18 追加:移动壳 WebView 原生安全开关显式收紧。`react-native-webview` 只加载同源主站入口,保留 JS 和 DOM storage 以运行现有 H5但禁用 JS 自动开窗、多窗口、文件访问、file URL 跨源访问、HTTPS 页面加载 HTTP 混合内容、第三方 Cookie、共享 Cookie 和 WebView 远程调试;外链继续只允许 `http:``https:``mailto:``tel:` 离开 WebView 交给系统。`apps/mobile-shell/scripts/check-config.mjs``mobileShellNavigation.test.ts` 会覆盖这些壳边界,避免后续为单个页面调试把完整 HostBridge 暴露给外域页面。 2026-06-18 追加:移动壳 WebView 原生安全开关显式收紧。`react-native-webview` 只加载同源主站入口,保留 JS 和 DOM storage 以运行现有 H5但禁用 JS 自动开窗、多窗口、文件访问、file URL 跨源访问、HTTPS 页面加载 HTTP 混合内容、第三方 Cookie、共享 Cookie 和 WebView 远程调试;外链继续只允许 `http:``https:``mailto:``tel:` 离开 WebView 交给系统。`apps/mobile-shell/scripts/check-config.mjs``mobileShellNavigation.test.ts` 会覆盖这些壳边界,避免后续为单个页面调试把完整 HostBridge 暴露给外域页面。
2026-06-18 追加:移动壳 HostBridge 消息入口增加来源校验。`onMessage` 不只依赖导航拦截和 `originWhitelist`,还会读取 `event.nativeEvent.url`,只有同源主站页面才能进入 `handleMobileHostBridgeMessage``about:blank`、外域 URL、协议降级或危险协议页面发来的消息全部丢弃不返回 HostBridge 错误细节。该校验与 `navigation.openNativePage` 共用同源规则,防止历史中间页或异常页面在带完整 HostBridge 的 WebView 中发起宿主能力请求。
### Phase 4宿主能力扩展 ### Phase 4宿主能力扩展
- 移动端接入系统分享、推送、原生登录和渠道支付。 - 移动端接入系统分享、推送、原生登录和渠道支付。