From a3751f18afac135290c6f004d9489692b569bc80 Mon Sep 17 00:00:00 2001 From: kdletters Date: Sat, 20 Jun 2026 22:06:01 +0800 Subject: [PATCH] =?UTF-8?q?=E9=94=81=E5=AE=9A=E5=8E=9F=E7=94=9F=E5=A3=B3?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E6=9D=A5=E6=BA=90=E8=BE=B9=E7=95=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 补充 HostBridge 伪造 origin 事件测试 新增原生壳消息 source 与 origin 门禁 --- scripts/check-native-shells.mjs | 43 +++++++++++++++++++ .../host-bridge/nativeAppHostBridge.test.ts | 25 +++++++++++ 2 files changed, 68 insertions(+) diff --git a/scripts/check-native-shells.mjs b/scripts/check-native-shells.mjs index fe473eec5..5a2de4c52 100644 --- a/scripts/check-native-shells.mjs +++ b/scripts/check-native-shells.mjs @@ -1873,6 +1873,46 @@ function assertH5NativeAppTransportTimeoutBoundaries() { } } +function assertH5NativeAppMessageSourceBoundaries() { + const nativeAppHostBridgeSource = fs.readFileSync( + 'src/services/host-bridge/nativeAppHostBridge.ts', + 'utf8', + ); + const nativeAppHostBridgeTestSource = fs.readFileSync( + 'src/services/host-bridge/nativeAppHostBridge.test.ts', + 'utf8', + ); + + const requiredSourceSnippets = [ + 'function isNativeInjectedMessageEvent(event: MessageEvent)', + 'event.source && event.source !== nativeWindow', + 'event.origin && event.origin !== nativeWindow.location.origin', + 'if (!isNativeInjectedMessageEvent(event))', + ]; + for (const snippet of requiredSourceSnippets) { + if (!nativeAppHostBridgeSource.includes(snippet)) { + throw new Error( + `H5 native app transport must verify injected message source and origin: ${snippet}`, + ); + } + } + + const requiredTestSnippets = [ + '忽略非当前窗口来源伪造的 HostBridge 回包', + 'source: channel.port1', + '忽略非当前页面来源伪造的宿主事件', + "origin: 'https://sandbox.genarrative.invalid'", + 'expect(listener).not.toHaveBeenCalled();', + ]; + for (const snippet of requiredTestSnippets) { + if (!nativeAppHostBridgeTestSource.includes(snippet)) { + throw new Error( + `H5 native app transport source boundary test must include ${snippet}`, + ); + } + } +} + function extractDocumentCapabilityList(source, marker) { return extractDocumentCapabilityListBefore(source, marker, '。'); } @@ -3039,6 +3079,9 @@ assertH5HostBridgePayloadBoundaries(); console.log('[check:native-shells] h5-native-app-transport-timeout-boundaries'); assertH5NativeAppTransportTimeoutBoundaries(); +console.log('[check:native-shells] h5-native-app-message-source-boundaries'); +assertH5NativeAppMessageSourceBoundaries(); + console.log('[check:native-shells] production-shell-dev-scaffold-scan'); assertNoProductionShellDevScaffoldTerms(); diff --git a/src/services/host-bridge/nativeAppHostBridge.test.ts b/src/services/host-bridge/nativeAppHostBridge.test.ts index 9308990a8..eba6e3acb 100644 --- a/src/services/host-bridge/nativeAppHostBridge.test.ts +++ b/src/services/host-bridge/nativeAppHostBridge.test.ts @@ -331,6 +331,31 @@ describe('nativeAppHostBridge', () => { channel.port2.close(); }); + test('忽略非当前页面来源伪造的宿主事件', () => { + window.ReactNativeWebView = { + postMessage: vi.fn(), + }; + const listener = vi.fn(); + subscribeNativeAppHostBridgeEvent('navigation.canGoBack', listener); + + window.dispatchEvent( + new MessageEvent('message', { + data: JSON.stringify({ + bridge: HOST_BRIDGE_PROTOCOL, + version: HOST_BRIDGE_VERSION, + event: 'navigation.canGoBack', + payload: { + canGoBack: true, + }, + }), + origin: 'https://sandbox.genarrative.invalid', + source: window, + }), + ); + + expect(listener).not.toHaveBeenCalled(); + }); + test('订阅 React Native WebView 注入的宿主事件', () => { window.ReactNativeWebView = { postMessage: vi.fn(),