统一移动壳外链协议来源

让移动壳导航测试按共享 HostBridge 外链协议清单反查

增加移动壳配置门禁防止 WebView 外链协议在 shell 层重新硬编码

补充共享决策记录说明 WebView 外链协议必须复用共享归一化逻辑
This commit is contained in:
2026-06-19 14:51:21 +08:00
parent e1a8aa4ef2
commit e0f53f2994
3 changed files with 25 additions and 9 deletions
@@ -1265,7 +1265,9 @@ for (const snippet of [
}
for (const snippet of [
'normalizeHostBridgeExternalUrl',
'resolveMobileShellWebViewUrl',
'return normalizeHostBridgeExternalUrl(rawUrl)',
'shouldOpenInMobileShellWebView(rawUrl, allowedOrigin)',
'new URL(rawUrl, allowedOrigin).toString()',
]) {
@@ -1274,6 +1276,17 @@ for (const snippet of [
}
}
if (
navigationSource.includes('javascript:') ||
navigationSource.includes('mailto:') ||
navigationSource.includes('tel:+') ||
navigationSource.includes("protocol === '")
) {
throw new Error(
'mobile shell WebView external protocol policy must use shared HostBridge normalizer',
);
}
if (shellAppSource.includes('127.0.0.1:3000')) {
throw new Error(
'mobile shell ShellApp must not hard-code localhost as the default H5 URL',
+11 -9
View File
@@ -1,5 +1,6 @@
import { describe, expect, test } from 'vitest';
import { HOST_BRIDGE_EXTERNAL_URL_PROTOCOLS } from '../../../../packages/shared/src/contracts/hostBridge';
import {
resolveMobileShellExternalUrl,
resolveMobileShellWebViewUrl,
@@ -52,15 +53,16 @@ describe('shouldOpenInMobileShellWebView', () => {
});
test('只有允许协议能交给系统外部应用打开', () => {
expect(resolveMobileShellExternalUrl(' https://example.com/path ')).toBe(
'https://example.com/path',
);
expect(resolveMobileShellExternalUrl('mailto:hi@example.com')).toBe(
'mailto:hi@example.com',
);
expect(resolveMobileShellExternalUrl('tel:+12345678')).toBe(
'tel:+12345678',
);
for (const protocol of HOST_BRIDGE_EXTERNAL_URL_PROTOCOLS) {
const url =
protocol === 'mailto:'
? 'mailto:hi@example.com'
: protocol === 'tel:'
? 'tel:+12345678'
: `${protocol}//example.com/path`;
expect(resolveMobileShellExternalUrl(` ${url} `)).toBe(url);
}
expect(resolveMobileShellExternalUrl('javascript:alert(1)')).toBeNull();
expect(resolveMobileShellExternalUrl('file:///etc/passwd')).toBeNull();
expect(resolveMobileShellExternalUrl('/relative/path')).toBeNull();
@@ -24,6 +24,7 @@
- 2026-06-18 外链接入:H5 新增 `openHostExternalUrl()` facade`native_app` 下会把外链归一化为允许协议的绝对 URL 后请求 `app.openExternalUrl`;ICP备案号和 RPG 资产调试原图入口已优先走宿主系统浏览器,普通浏览器和小程序保留原 `<a>` 行为,宿主不可用或拒绝时回退浏览器外链。
- 2026-06-18 外链协议白名单门禁:`packages/shared/src/contracts/hostBridge.ts``HOST_BRIDGE_EXTERNAL_URL_PROTOCOLS``app.openExternalUrl` 唯一协议来源,当前只允许 `http:``https:``mailto:``tel:`;Expo 直接复用共享归一化逻辑,Tauri Rust 侧必须用 URL parser 镜像同一清单,根级 `npm run check:native-shells` 会拒绝共享契约与桌面壳协议清单漂移。
- 2026-06-18 移动壳 WebView 导航收紧:Expo WebView 自身拦截外域导航时复用 HostBridge 外链协议白名单,只把 `http:``https:``mailto:``tel:` 交给 `Linking.openURL``javascript:``file:`、相对异常路径等危险目标直接阻断,避免离开同源主站后仍保留完整 HostBridge。
- 2026-06-19 移动壳 WebView 外链协议共源:`apps/mobile-shell/src/shell/navigation.ts` 的 WebView 外链离壳判断必须调用共享 `normalizeHostBridgeExternalUrl`,不得在 shell 层另写协议判断;`apps/mobile-shell/scripts/check-config.mjs` 会拒绝重新硬编码 `mailto:` / `tel:` / `javascript:` 等协议分支,`navigation.test.ts``HOST_BRIDGE_EXTERNAL_URL_PROTOCOLS` 反查当前允许协议。
- 2026-06-19 移动壳系统分享 URL 边界:Expo `share.open` 调用 React Native 系统分享面板前,只允许把 `url``href``path``targetPath``work` 归一为 `https://app.genarrative.world` 同源公开 URL;外域、协议相对 URL、`javascript:` 等危险目标必须返回 `invalid_request`,且显式非法 payload 不得回退到之前缓存的 `share.setTarget` 目标。分享实现复用移动壳入口 URL 的生产主站 origin,配置检查会拒绝重新声明同值 origin 或移除协议相对 URL 拦截。
- 2026-06-19 桌面壳系统分享 URL 边界:Tauri `share.open` 写入系统剪贴板前同样只允许把 `url``href``path``targetPath``work` 归一为 `https://app.genarrative.world` 同源公开 URL;外域、协议相对 URL、`javascript:` 等危险目标必须返回 `invalid_request`,且显式非法 payload 不得回退到之前缓存的 `share.setTarget` 目标。桌面壳配置检查会拒绝移除同源分享 URL 归一和协议相对 URL 拦截。
- 2026-06-18 能力声明收紧:`packages/shared/src/contracts/hostBridge.ts` 提供 HostBridge method / capability 白名单,H5 的 `getHostRuntime()` 会解析并过滤 `hostCapabilities``openHostShare``writeHostClipboardText``requestHostHapticsImpact``setHostAppTitle``exportHostTextFile` 等 native 能力只在宿主声明对应 capability 后调用。发布分享弹窗只有声明 `share.open` 时才显示“系统分享”,避免旧壳或裁剪壳露出不可用入口。