收口移动壳H5入口来源

移动壳启动URL仅允许生产主站和本机开发入口

补充移动壳URL和deep link测试,防止外域H5获得HostBridge

更新原生壳方案和共享决策记录
This commit is contained in:
2026-06-18 12:50:16 +08:00
parent 6cf1038ff3
commit e64b73e9b4
6 changed files with 52 additions and 13 deletions
@@ -74,11 +74,11 @@ describe('buildMobileShellUrlFromDeepLink', () => {
expect(url.pathname).toBe('/');
});
test('基准 H5 URL 配置非法时回退到默认启动地址', () => {
test('基准 H5 URL 配置非法或外域时回退到默认启动地址', () => {
const url = new URL(
buildMobileShellUrlFromDeepLink(
'genarrative://open/works/detail?work=PZ-1',
'file:///tmp/index.html',
'https://example.com/app',
options,
),
);
+17 -6
View File
@@ -13,7 +13,7 @@ describe('buildMobileShellUrl', () => {
test('为 H5 附加原生移动壳上下文', () => {
const url = new URL(
buildMobileShellUrl('https://app.test/works/detail?work=PZ-1', {
buildMobileShellUrl('https://app.genarrative.world/works/detail?work=PZ-1', {
platform: 'ios',
hostVersion: '0.1.0',
capabilities: ['host.getRuntime', 'share.open'],
@@ -34,14 +34,14 @@ describe('buildMobileShellUrl', () => {
test('支持按平台注入不同能力清单', () => {
const iosUrl = new URL(
buildMobileShellUrl('https://app.test/', {
buildMobileShellUrl('https://app.genarrative.world/', {
platform: 'ios',
hostVersion: '0.1.0',
capabilities: ['host.getRuntime', 'app.setBadgeCount'],
}),
);
const androidUrl = new URL(
buildMobileShellUrl('https://app.test/', {
buildMobileShellUrl('https://app.genarrative.world/', {
platform: 'android',
hostVersion: '0.1.0',
capabilities: ['host.getRuntime'],
@@ -56,13 +56,24 @@ describe('buildMobileShellUrl', () => {
);
});
test('移动壳基准 URL 只接受 http 和 https', () => {
expect(resolveMobileShellBaseWebUrl('https://app.test/path')).toBe(
'https://app.test/path',
test('移动壳基准 URL 只接受生产主站和本机开发入口', () => {
expect(
resolveMobileShellBaseWebUrl('https://app.genarrative.world/path'),
).toBe(
'https://app.genarrative.world/path',
);
expect(resolveMobileShellBaseWebUrl(' http://127.0.0.1:3000/ ')).toBe(
'http://127.0.0.1:3000/',
);
expect(resolveMobileShellBaseWebUrl('http://localhost:3000/')).toBe(
'http://localhost:3000/',
);
expect(resolveMobileShellBaseWebUrl('https://example.com/path')).toBe(
DEFAULT_MOBILE_SHELL_WEB_URL,
);
expect(resolveMobileShellBaseWebUrl('http://192.168.1.2:3000/')).toBe(
DEFAULT_MOBILE_SHELL_WEB_URL,
);
expect(resolveMobileShellBaseWebUrl('javascript:alert(1)')).toBe(
DEFAULT_MOBILE_SHELL_WEB_URL,
);
+16 -1
View File
@@ -10,6 +10,21 @@ export type MobileShellUrlOptions = {
};
export const DEFAULT_MOBILE_SHELL_WEB_URL = 'https://app.genarrative.world/';
const ALLOWED_PRODUCTION_WEB_ORIGIN = 'https://app.genarrative.world';
const LOCAL_DEVELOPMENT_WEB_HOSTS = new Set([
'127.0.0.1',
'localhost',
'[::1]',
]);
function isAllowedMobileShellBaseUrl(url: URL) {
if (url.origin === ALLOWED_PRODUCTION_WEB_ORIGIN) {
return true;
}
return url.protocol === 'http:' &&
LOCAL_DEVELOPMENT_WEB_HOSTS.has(url.hostname);
}
export function resolveMobileShellBaseWebUrl(rawUrl: unknown) {
if (typeof rawUrl !== 'string') {
@@ -23,7 +38,7 @@ export function resolveMobileShellBaseWebUrl(rawUrl: unknown) {
try {
const url = new URL(value);
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
if (!isAllowedMobileShellBaseUrl(url)) {
return DEFAULT_MOBILE_SHELL_WEB_URL;
}