收口HostBridge方法白名单门禁
移动壳配置检查拒绝共享契约外HostBridge method分发 桌面壳配置检查校验Rust method白名单与共享契约一致 更新原生壳方案和共享决策记录
This commit is contained in:
@@ -333,6 +333,17 @@ function extractStringArrayExport(source, exportName) {
|
||||
return entries;
|
||||
}
|
||||
|
||||
function extractRustStringArrayConst(source, constName) {
|
||||
const match = source.match(
|
||||
new RegExp(`const ${constName}[^=]*= \\[([\\s\\S]*?)\\];`),
|
||||
);
|
||||
if (!match) {
|
||||
throw new Error(`unable to read Rust const ${constName}`);
|
||||
}
|
||||
|
||||
return [...match[1].matchAll(/"([^"]+)"/g)].map((entry) => entry[1]);
|
||||
}
|
||||
|
||||
function extractDesktopCapabilities(source) {
|
||||
const match = source.match(/fn capabilities\(\)[^{]*\{[\s\S]*?vec!\[([\s\S]*?)\]\s*\}/);
|
||||
if (!match) {
|
||||
@@ -441,8 +452,14 @@ const sharedCapabilities = extractStringArrayExport(
|
||||
sharedContractSource,
|
||||
'HOST_BRIDGE_CAPABILITIES',
|
||||
);
|
||||
const sharedMethods = extractStringArrayExport(
|
||||
sharedContractSource,
|
||||
'HOST_BRIDGE_METHODS',
|
||||
);
|
||||
const desktopMethods = extractRustStringArrayConst(main, 'HOST_BRIDGE_METHODS');
|
||||
const desktopCapabilities = extractDesktopCapabilities(main);
|
||||
const sdkBackedCapabilities = ['auth.requestLogin', 'payment.request'];
|
||||
assertSameList(desktopMethods, sharedMethods, 'desktop shell HostBridge method whitelist');
|
||||
const unknownDesktopCapabilities = desktopCapabilities.filter(
|
||||
(capability) => !sharedCapabilities.includes(capability),
|
||||
);
|
||||
|
||||
@@ -133,6 +133,17 @@ function extractStringConstExport(source, exportName) {
|
||||
return match[1];
|
||||
}
|
||||
|
||||
function extractMobileBridgeHandledMethods(source) {
|
||||
const match = source.match(
|
||||
/async function handleRequest\(request: HostBridgeRequest\)[^{]*\{[\s\S]*?switch \(request\.method\) \{([\s\S]*?)\n \}/,
|
||||
);
|
||||
if (!match) {
|
||||
throw new Error('unable to read mobile shell HostBridge handler methods');
|
||||
}
|
||||
|
||||
return [...match[1].matchAll(/case '([^']+)':/g)].map((entry) => entry[1]);
|
||||
}
|
||||
|
||||
function assertNoBlockedMobileChannelDependencies(packageJson, packageLabel) {
|
||||
const dependencySections = [
|
||||
'dependencies',
|
||||
@@ -260,6 +271,11 @@ const sharedCapabilities = extractStringArrayExport(
|
||||
sharedContractSource,
|
||||
'HOST_BRIDGE_CAPABILITIES',
|
||||
);
|
||||
const sharedMethods = extractStringArrayExport(
|
||||
sharedContractSource,
|
||||
'HOST_BRIDGE_METHODS',
|
||||
);
|
||||
const handledMobileMethods = extractMobileBridgeHandledMethods(bridgeSource);
|
||||
const mobileCapabilities = extractStringArrayExport(
|
||||
bridgeSource,
|
||||
'MOBILE_HOST_CAPABILITIES',
|
||||
@@ -271,6 +287,15 @@ const iosMobileCapabilities = extractStringArrayExport(
|
||||
const mobileCapabilitySet = new Set(mobileCapabilities);
|
||||
const iosMobileCapabilitySet = new Set(iosMobileCapabilities);
|
||||
const sdkBackedCapabilities = ['auth.requestLogin', 'payment.request'];
|
||||
const unknownHandledMobileMethods = handledMobileMethods.filter(
|
||||
(method) => !sharedMethods.includes(method),
|
||||
);
|
||||
if (unknownHandledMobileMethods.length > 0) {
|
||||
throw new Error(
|
||||
`mobile shell handles unknown HostBridge methods: ${unknownHandledMobileMethods.join(', ')}`,
|
||||
);
|
||||
}
|
||||
|
||||
const unknownMobileCapabilities = mobileCapabilities.filter(
|
||||
(capability) => !sharedCapabilities.includes(capability),
|
||||
);
|
||||
|
||||
@@ -69,6 +69,7 @@
|
||||
- 2026-06-18 桌面壳 Tauri 命令白名单:桌面壳源码、Tauri build manifest、主窗口 capability 和本地自动生成权限目录都只能暴露 `host_bridge_request` 一个受控 command;所有桌面能力继续在 Rust 内部按 HostBridge method 白名单分发,不新增可被 H5 直接 `invoke` 的 Tauri command,也不授予插件 JS guest API。检查脚本会拒绝多余 command、权限列表顺序漂移和残留的自动生成权限文件。
|
||||
- 2026-06-18 HostBridge request id replay:Expo 和 Tauri 壳都必须按 request id 回放首次完成结果;同 id 进行中的请求共享同一执行结果,已完成请求直接回放缓存响应,避免系统分享、外链、剪贴板、文件选择 / 保存、本地通知、窗口导航等宿主副作用被重复触发。两端配置检查和测试会锁住 replay 结构。
|
||||
- 2026-06-18 HostBridge request envelope 校验:共享契约提供 `isHostBridgeMethod` 与 `normalizeHostBridgeRequestId`,Expo 壳直接复用,Tauri 壳镜像同一白名单和 id 规则;空 id、控制字符 id、超长 id 和未知 method 都必须在 replay / 能力分发前返回 `invalid_request`,已知但当前壳未实现的登录 / 支付等 method 才返回 `unsupported_method`。
|
||||
- 2026-06-18 HostBridge method 白名单跨壳门禁:`packages/shared/src/contracts/hostBridge.ts` 的 `HOST_BRIDGE_METHODS` 是唯一协议来源;Expo 壳 HostBridge 分发不得处理共享契约外 method,Tauri 壳 Rust `HOST_BRIDGE_METHODS` 必须与共享契约逐项一致。新增宿主 method 必须先更新共享契约,再落两端壳实现或明确 unsupported。
|
||||
- 2026-06-18 桌面壳 CSP 分层:Tauri release `csp` 不得包含 `http://127.0.0.1:*`、`ws://127.0.0.1:*` 或其它本机调试源,本机 Vite、HMR WebSocket 和开发 frame 只允许出现在 `devCsp`。桌面壳配置检查会同时拒绝 release CSP 混入本机调试源、dev CSP 缺失本机开发源,以及 release / dev CSP 加入 `unsafe-eval`、`tauri:` 或 `file:`。
|
||||
- 2026-06-18 壳生产代码禁用临时替身:Expo 与 Tauri 壳的生产源码和配置不得出现 mock / fake / placeholder / stub / TODO / FIXME 以及对应中文脚手架词;测试文件仍可使用 mock。两端壳配置检查会扫描生产入口、配置和壳实现,根级 `npm run check:native-shells` 也会统一扫描两端壳生产源码,防止把临时替身、占位文案或伪实现带进可分发壳。
|
||||
- 2026-06-18 移动壳启动页与 adaptive icon:Expo 移动壳启动页和 Android adaptive icon 复用现有真实品牌图标 `apps/mobile-shell/assets/icon.png`,背景色固定为 H5 壳根背景 `#fffdf9`。该 PNG 是 1024x1024 RGBA 透明前景品牌资产,不新增占位图;配置检查会校验图标尺寸、透明像素、splash 和 adaptive icon 指向,避免后续换成非品牌或占位素材。
|
||||
@@ -2400,6 +2401,13 @@
|
||||
- 影响范围:`packages/shared/src/contracts/hostBridge.ts`、`apps/mobile-shell/src/mobileHostBridge.ts`、`apps/desktop-shell/src-tauri/src/main.rs`、两端配置检查、Expo / Tauri HostBridge 方案文档。
|
||||
- 验证方式:`npm run check:native-shells`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
|
||||
|
||||
## 2026-06-18 HostBridge method 白名单跨壳门禁
|
||||
|
||||
- 背景:Expo 壳直接引用 TypeScript 共享契约,Tauri 壳必须在 Rust 中镜像 HostBridge method 白名单;如果只靠人工同步,后续新增 method 时容易出现 H5 已发送、Expo 已处理、Tauri 仍按未知 method 拒绝,或 Tauri 额外接受共享契约外 method 的漂移。
|
||||
- 决策:`HOST_BRIDGE_METHODS` 以 `packages/shared/src/contracts/hostBridge.ts` 为唯一协议来源。移动壳配置检查解析 `handleRequest` 的 method case,拒绝共享契约外 method;桌面壳配置检查解析 Rust `HOST_BRIDGE_METHODS`,要求与共享契约逐项一致。新增宿主 method 必须先更新共享契约,再在 Expo / Tauri 中实现或明确返回 `unsupported_method`。
|
||||
- 影响范围:`apps/mobile-shell/scripts/check-config.mjs`、`apps/desktop-shell/scripts/check-config.mjs`、Expo / Tauri HostBridge 方案文档。
|
||||
- 验证方式:`npm run check:native-shells`、`npm run mobile-shell:typecheck`、`npm run desktop-shell:typecheck`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
|
||||
|
||||
## 2026-06-18 移动壳渠道 SDK 依赖收口
|
||||
|
||||
- 背景:Expo 移动壳运行时依赖可能从根安装树解析;如果只检查 `apps/mobile-shell/package.json`,根 H5 包仍可能直接引入 Expo Updates、Sentry、Firebase Analytics、PostHog、Amplitude、Segment、CodePush 等移动端发布通道、崩溃上报或 analytics SDK,让壳边界绕过真实渠道契约。
|
||||
|
||||
@@ -352,6 +352,8 @@ GameBridge 禁止:
|
||||
|
||||
2026-06-18 追加:HostBridge request envelope 校验收紧。共享契约提供 `isHostBridgeMethod` 和 `normalizeHostBridgeRequestId`;Expo 壳直接复用,Tauri 壳镜像同一 method 白名单和 request id 规则。空 id、控制字符 id、超长 id 和未知 method 都在进入 replay / 能力分发前返回 `invalid_request`,已知但当前壳未实现的登录 / 支付等 method 才返回 `unsupported_method`。
|
||||
|
||||
2026-06-18 追加:HostBridge method 白名单进入跨壳门禁。`packages/shared/src/contracts/hostBridge.ts` 的 `HOST_BRIDGE_METHODS` 是唯一协议来源;Expo 壳的 HostBridge 分发 case 不得处理共享契约外 method,Tauri 壳 Rust `HOST_BRIDGE_METHODS` 必须与共享契约逐项一致。两端配置检查会在 `npm run check:native-shells` 中拒绝 method 白名单漂移,新增宿主能力必须先更新共享契约,再落壳实现。
|
||||
|
||||
2026-06-18 追加:桌面壳 release CSP 与 dev CSP 分离。Release `csp` 不再包含 `http://127.0.0.1:*` 或 `ws://127.0.0.1:*`,只允许打包资产、自身脚本、生产 HTTPS / WSS API、图片、媒体和 sandbox frame 所需来源;本地 Vite、HMR WebSocket 和开发 frame 只写入 Tauri `devCsp`。`apps/desktop-shell/scripts/check-config.mjs` 会拒绝 release CSP 混入本机调试源,也会校验 dev CSP 仍保留本机开发源。
|
||||
|
||||
2026-06-18 追加:桌面壳 release 构建烟测进入统一验收。`npm run check:native-shells` 会在 H5 HostBridge、Expo 壳和 Tauri 单测通过后执行 `npm run desktop-shell:build -- --no-bundle`,确认根 `dist` H5 资产、Tauri release 入口、受控命令白名单、图标和 Rust release 编译可以共同产出桌面二进制;该烟测不生成平台安装包,避免把 Linux 本机缺少的系统打包器误判为 HostBridge 回归。
|
||||
|
||||
Reference in New Issue
Block a user