From ba68fc8326fb1cbb9fc16d946a5729aa86a5c96b Mon Sep 17 00:00:00 2001 From: key Date: Wed, 29 Jul 2026 01:13:18 +0800 Subject: [PATCH] release: harden Android network security --- CHANGELOG.md | 2 +- README.md | 2 +- docs/MOBILE_APP_DEVICE_ACCEPTANCE.md | 4 +- docs/MOBILE_APP_PACKAGING.md | 11 ++-- .../帮道8月1日Android内测Go-No-Go-20260728.md | 10 +-- ...Android安装包测试版任务计划-20260727.docx | Bin 59300 -> 59693 bytes ...1日Android安装包测试版任务计划-20260727.md | 13 ++-- mobile-uni/AndroidManifest.xml | 10 +++ .../res/xml/network_security_config.xml | 8 +++ mobile-uni/package-lock.json | 4 +- mobile-uni/package.json | 4 +- .../scripts/sync-app-native-resources.mjs | 24 +++++++ mobile-uni/scripts/verify-android-apk.ps1 | 32 +++++++++- mobile-uni/scripts/verify-app-assets.mjs | 60 +++++++++++++++++- mobile-uni/src/manifest.json | 5 +- mobile-uni/tests/app-packaging.test.mjs | 38 +++++++++++ 16 files changed, 198 insertions(+), 29 deletions(-) create mode 100644 mobile-uni/AndroidManifest.xml create mode 100644 mobile-uni/nativeResources/android/res/xml/network_security_config.xml create mode 100644 mobile-uni/scripts/sync-app-native-resources.mjs diff --git a/CHANGELOG.md b/CHANGELOG.md index 30b870da..d8a75dfe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ## Unreleased -- **2026-07-28 至 2026-07-29,8 月 1 日 Android 受控内测候选包与法务链路,当前总体 No-Go**:补齐登录页协议默认不勾选与法务地址失败关闭、App-Plus 原生隐私配置生成/校验、APK 包名/版本/API/签名/哈希/包内资源一致性/敏感值门禁、DCloud 云证书自定义基座和独立测试包流程;《服务协议》《隐私政策》静态页已部署到 `/legal/`,发布前整站备份为 `/opt/wygj/backups/www-20260728224710`。客户端法务校验已移除 App-Plus 不保证支持的浏览器 `URL` 构造器,测试验证码也不再以源码常量进入安装包。当前独立 APK `com.yincheng.wygj 0.1.6 (106)` 的 `targetSdkVersion=35`,SHA-256 为 `A5F026B70F11327355597B9FEA3625C4168700297E6D7847F10B079CEB860914`;vivo V2443A(Android 15)已完成覆盖安装和冷启动,本机 `Medium_Phone` Android 15/API 35 虚拟机已完成最新包干净安装和冷启动,真实显示原生隐私弹窗,四项敏感权限保持未授权且未见崩溃。移动端 191 项测试、类型检查、H5/App 构建、管理端构建、P0 后端 39 项定向测试及 `ruoyi-aihr` 696 项全量测试通过;跨平台源码契约测试已统一换行符后复跑全绿。本次评估时工作区尚未提交,生产后端未包含本批 P0 回退语义,用户隐私选择、权限反例、认证态核心业务、正式内容和完整发布预检仍未完成,故 Go/No-Go 为 **No-Go**;本次除法务静态页外未发布后端、H5 业务资源或数据库。 +- **2026-07-28 至 2026-07-29,8 月 1 日 Android 受控内测候选包与法务链路,当前总体 No-Go**:补齐登录页协议默认不勾选与法务地址失败关闭、App-Plus 原生隐私配置生成/校验、APK 包名/版本/API/签名/哈希/包内资源一致性/敏感值门禁、DCloud 云证书自定义基座和独立测试包流程;《服务协议》《隐私政策》静态页已部署到 `/legal/`,发布前整站备份为 `/opt/wygj/backups/www-20260728224710`。客户端法务校验已移除 App-Plus 不保证支持的浏览器 `URL` 构造器,测试验证码也不再以源码常量进入安装包。当前独立 APK `com.yincheng.wygj 0.1.8 (108)` 的 `targetSdkVersion=35`,SHA-256 为 `E252140645F3AAD14B3594FD78E76BDF1A2122F7DC70EBD21CC5DF53223C7878`;新增 Android 原生网络安全配置并经 APK 反编译确认 `usesCleartextTraffic=false`、`networkSecurityConfig` 引用和系统证书信任配置均已进入安装包。本机 `Medium_Phone` Android 15/API 35 虚拟机已完成最新包干净安装和冷启动,真实显示原生隐私弹窗,四项敏感权限保持未授权且未见崩溃;`0.1.6` 已在 vivo V2443A(Android 15)完成覆盖安装和冷启动。移动端 193 项测试、类型检查和 App 构建预检通过;前一冻结批次的 H5/管理端构建及 `ruoyi-aihr` 696 项全量测试通过。生产后端仍未包含本批 P0 回退语义,用户隐私选择、权限反例、认证态核心业务、正式内容和完整发布预检仍未完成,故 Go/No-Go 为 **No-Go**;本次除法务静态页外未发布后端、H5 业务资源或数据库。 - **2026-07-26,阿里云百炼 ASR 生产热修复**:`POST /api/ai/asr` 新增 `dashscope/qianwen + qwen3-asr-flash` 的 `/compatible-mode/v1/chat/completions` + Base64 `input_audio` 分支,原 SiliconFlow `/audio/transcriptions` multipart 路径继续兼容,ASR 超时放宽至 30 秒。定向 Maven 测试 10 项通过;后端 JAR(SHA-256 `46c99cff75d21f8536a71c3c058b6437e99dbf9ff4bae3542b44471f3f34ad84`)已发布,生产 ASR 已切换至 `dashscope/qwen3-asr-flash`,旧 `siliconflow/TeleAI/TeleSpeechASR` 已停用。生产接口获得正确转写与受控 `ossId`,Android HBuilderX 标准基座“按住说话”请求成功,线上日志无 403/ASR 异常。回滚目录为 `/opt/wygj/backups/asr-hotfix-20260726210122`;本次未发布静态资源、未变更 schema,也不代表完整包一致性或原生 App 全项验收。 - **2026-07-26,移动端编译链升级与 uni-ui 解析修复,未发布**:本机 HBuilderX GUI 已升级为 `5.22.2026071707`,CLI 为 `5.22.2026072503-alpha`;`mobile-uni` 的 App、App-Plus、H5 与 Vite 编译包统一到对应的 `3.0.0-alpha-5020220260725001` 批次,消除 5.15/5.21 混用产生的两套 `uni-cli-shared` 状态隔离,使登录页 ``、`` 等组件重新由 `pages.json` easycom 规则正常解析。H5 与 App 均以 `Compiler version: 5.22(vue3)` 构建通过;浏览器和 vivo V2443A(Android 15)真机均确认手机号、验证码标签及输入框正常。HBuilder 5.22 标准基座 `versionName=15.22` 已自动更新并成功运行,测试手机号配合固定验证码登录成功,可进入员工首页并切换“问”页,未出现编译器/SDK 版本不匹配弹窗或组件解析告警。升级后仍须重建并安装 5.22 自定义运行基座完成原生权限全清单;`verify:app-release` 当前因缺少含正式法务 HTTPS 链接的 `androidPrivacy.json` 按预期阻断。本项未发布生产 H5,也未云打包 APK/IPA,手机上的 `com.yincheng.wygj` 正式包未被覆盖。 - **2026-07-26,全网 AI 本地库修复,未发布**:补齐 `reset-dev-db.sh` 遗漏的全网 AI 基表、数据库密钥和千问字段三项正式迁移,并增加迁移顺序及幂等回归,避免重置本地库后再次缺失 `aihr_web_search_provider`;当前本地库为 137 张表,管理端代理下的提供方列表接口验证通过。 diff --git a/README.md b/README.md index 6177be3d..feb8575d 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ - 品牌:管理端与员工端统一显示“帮道”,继续使用现有红色品牌图标。 - 基础链路:租户列表、验证码、`000000 / admin / admin123` 加密登录已验证。 - 管理端:除 AI 面试、训练、案例、SOP 和资料处理外,已包含大喇叭发布、单文件提炼与多岗位解读、五渠道直通反馈收件箱、内容/任务运营、问题榜运营、知识空间、全网 AI 配置和组织权限;员工个人训练、主管复盘、团队难题、成长证据与训练录音仅在认证 APP 按项目范围访问。 -- 用户侧:`mobile-uni/` 承载员工、候选人和主管三端;“问”已改为数字师傅 Agent,文字、ASR 转写和图片/视频统一先做意图规划,再按服务端权限调用知识、训练、待办、记忆、媒体或全网工具,寒暄和不完整表达不再机械落入 RAG。现有能力还包括手机号登录、30 分钟短期多轮、原文件/视频受控交付、大喇叭公司文件摘要/下载/多岗位解读/追问、五渠道直通反馈、“记一下”确认卡、三级字号、训练/题目反馈、问题榜与积分、岗位考试、案例、候选面试/资料、主管复盘/派发及学员模式。Agent 代码已随 2026-07-25 完整包部署;2026-07-29 当前候选包为 `0.1.6 (106)`,已通过 API 35、法务链接、包内资源一致性和敏感值门禁,并在 Android 15 真机完成覆盖安装、在本机 Android 15/API 35 虚拟机完成干净安装和冷启动。最新包已真实显示原生隐私弹窗,四项敏感权限保持未授权且未见崩溃;用户亲自执行的隐私选择、权限正反例、认证态核心业务全回归、生产依赖匹配和发布签认仍未完成,当前 Go/No-Go 仍为 No-Go。旧 `mobile/` 只用于历史回溯。 +- 用户侧:`mobile-uni/` 承载员工、候选人和主管三端;“问”已改为数字师傅 Agent,文字、ASR 转写和图片/视频统一先做意图规划,再按服务端权限调用知识、训练、待办、记忆、媒体或全网工具,寒暄和不完整表达不再机械落入 RAG。现有能力还包括手机号登录、30 分钟短期多轮、原文件/视频受控交付、大喇叭公司文件摘要/下载/多岗位解读/追问、五渠道直通反馈、“记一下”确认卡、三级字号、训练/题目反馈、问题榜与积分、岗位考试、案例、候选面试/资料、主管复盘/派发及学员模式。Agent 代码已随 2026-07-25 完整包部署;2026-07-29 当前候选包为 `0.1.8 (108)`,已通过 API 35、法务链接、包内资源一致性、敏感值、禁止明文 HTTP 和网络安全资源门禁,并在本机 Android 15/API 35 虚拟机完成干净安装和冷启动。最新包已真实显示原生隐私弹窗,四项敏感权限保持未授权且未见崩溃;`0.1.6` 已在 Android 15 真机完成覆盖安装。用户亲自执行的隐私选择、权限正反例、认证态核心业务全回归、生产依赖匹配和发布签认仍未完成,当前 Go/No-Go 仍为 No-Go。旧 `mobile/` 只用于历史回溯。 - 英雄路径:AI 面试(真模型出题/评分)、三角色对练(真 LLM 客户回复+评分,ASR/TTS 配置后支持语音)、案例沉淀(真实语音上传+ASR 转写+模型整理)均为真模型优先、未配置或失败回退本地兜底;生产短音频 ASR 当前为阿里云百炼 `qwen3-asr-flash`,SiliconFlow audio 接口保留兼容。SOP 知识库已接中文关键词 + MySQL Fulltext + Qdrant 混合召回 + rerank 重排,支持 txt/md/PDF/Word/Excel/PPT/图片/视频上传解析入库(图片走 vision OCR,视频走 ffmpeg 抽音轨转写+关键帧描述),资料处理中心支持 ZIP 异步展开后逐文件入队;模型配置页可查看向量库维度/点数并一键重建。 - 资料处理:已新增解析任务状态页,按 `aihr_knowledge_attach` 和 `aihr_knowledge_fragment` 展示等待解析、解析中、已完成、失败、片段数和向量化状态;任务列表按当前筛选结果前端分页(10/20/50 条,筛选或刷新回到第一页)。页面只保留“批量导入”,通过异步队列处理多文件或 ZIP,不提供浏览器目录选择和服务端目录导入入口。后端目录导入接口仅保留为运维/调试能力。 - 组织人员:`aihr_org_snapshot` 本地 reset 保留演示 seed;生产通过 `POST /api/aihr/org/sync` 同步外部组织。2026-07-25 记录的生产快照为 3417 人、2938 人在职且均有姓名、3392 个手机号映射、175 名在职主管和 20 名项目经理;姓名通过稳定 `employee_id` 定向补齐,未执行全量覆盖。当前上游 `employee_number` 与既有主体不一致且手机号大量脱敏,修复契约前禁止强制全量同步;人数属于时点事实,发布或验收前应重新查询。 diff --git a/docs/MOBILE_APP_DEVICE_ACCEPTANCE.md b/docs/MOBILE_APP_DEVICE_ACCEPTANCE.md index 723ed515..53b27c5e 100644 --- a/docs/MOBILE_APP_DEVICE_ACCEPTANCE.md +++ b/docs/MOBILE_APP_DEVICE_ACCEPTANCE.md @@ -24,6 +24,8 @@ 同日,本机 Android Studio `Medium_Phone` 虚拟机完成 Android 15/API 35 启动,物理分辨率 `1080×2400`、密度 `420`。在虚拟机此前未安装本应用的前提下,`0.1.6 (106)` 首次安装成功并从系统 Launcher 冷启动,`am start -W` 记录总耗时约 `6.4s`;最新包真实显示原生《服务协议和隐私政策》弹窗,四项相机、录音、图片和视频权限均为 `granted=false`,crash 缓冲区为空。截图和 UI 层级保存在忽略目录 `output/aug1-rc/android-emulator/`;验收未代替用户点击同意或拒绝。虚拟机证据用于补充干净安装和 API 35 权限基线,不能替代真实设备的媒体、WebRTC、弱网和系统浏览器交互。 +同日,候选包继续升级为 `0.1.8 (108)`,大小 `33,921,705` 字节,SHA-256 为 `E252140645F3AAD14B3594FD78E76BDF1A2122F7DC70EBD21CC5DF53223C7878`。新增的项目根原生清单与 `network_security_config.xml` 已进入实际云打包输入,193 项移动端测试、类型检查、App 构建及联网法务预检通过;APK 反编译确认包内资源与当前编译产物一致、`usesCleartextTraffic=false`、`networkSecurityConfig` 引用、系统证书信任配置、签名和敏感值扫描均通过。虚拟机卸载旧实例后全新安装,Launcher 冷启动总耗时 `2.904s`,最新原生隐私弹窗真实显示,四项敏感权限均为 `granted=false`,crash 缓冲区为空。仍未代替用户点击拒绝或同意。 + ## 已完成的 Android 基座证据 - [x] 自定义基座包名、版本、API 35、单签名、v2 签名和 SHA-256 可重复校验。 @@ -33,7 +35,7 @@ - [x] 独立 DCloud 云证书内测 APK 的包内法务配置、API 35、单签名、v2 签名和 SHA-256 可重复校验。 - [x] 清除本 App 测试数据后首次启动先显示原生隐私弹窗,服务协议与隐私政策链接均可在系统浏览器打开。 - [x] `0.1.6 (106)` 当前候选包完成同签名覆盖安装、冷启动和启动权限复核,包内资源与当前 App-Plus 编译产物一致。 -- [x] `0.1.6 (106)` 在本机 Android 15/API 35 虚拟机完成干净安装和冷启动,最新包原生隐私弹窗真实显示,四项敏感权限保持未授权且未见崩溃。 +- [x] `0.1.8 (108)` 在本机 Android 15/API 35 虚拟机完成卸载重装和冷启动,最新包原生隐私弹窗真实显示,四项敏感权限保持未授权、网络安全配置已进入 APK 且未见崩溃。 ## Android(最低 Android 10 + 一台当前主流机型) diff --git a/docs/MOBILE_APP_PACKAGING.md b/docs/MOBILE_APP_PACKAGING.md index 06896160..33b68940 100644 --- a/docs/MOBILE_APP_PACKAGING.md +++ b/docs/MOBILE_APP_PACKAGING.md @@ -42,13 +42,14 @@ npm --prefix mobile-uni run build:app npm --prefix mobile-uni run verify:app-release ``` -`configure:app-privacy` 会同时校验 HTTPS、公网域名、两个地址互不相同,并从模板生成被 Git 忽略的 `mobile-uni/src/androidPrivacy.json`;`build:app` 再将其复制到 `dist/build/app/androidPrivacy.json`。这两个位置都纳入门禁,避免 npm/Vite CLI 项目把配置误写到工程根目录而产生假绿。`verify:app-release` 还会确认: +`configure:app-privacy` 会同时校验 HTTPS、公网域名、两个地址互不相同,并从模板生成被 Git 忽略的 `mobile-uni/src/androidPrivacy.json`;`build:app` 再将其复制到 `dist/build/app/androidPrivacy.json`,并把项目根的 `AndroidManifest.xml` 与 `nativeResources/android/res/xml/network_security_config.xml` 同步到云打包输入目录。源码和编译产物都纳入门禁,避免 npm/Vite CLI 项目把原生配置留在源码但未进入安装包而产生假绿。`verify:app-release` 还会确认: - 首次与二次隐私弹窗使用同一组地址; - 原生弹窗地址与本次 App 编译使用的 `VITE_APP_TERMS_URL`、`VITE_APP_PRIVACY_URL` 完全一致; - 两个页面可通过公网 HTTPS 访问并包含可识别的协议正文; - `VITE_DEV_SMS_HINT_ENABLED` 与 `VITE_DEV_SMS_HINT_VALUE` 均未进入发布环境; - 编译产物不含手机号、固定测试验证码、私钥或常见云端/API 令牌格式。 +- Android 清单与网络安全配置均禁止明文 HTTP,并只信任系统证书。 登录页默认不勾选协议。协议地址缺失或不合规时,页面允许查看但禁止发送验证码和登录;测试验证码快捷提示只有开发环境同时显式设置 `VITE_DEV_SMS_HINT_ENABLED=true` 和本机 `VITE_DEV_SMS_HINT_VALUE` 才显示。源码不再内置固定验证码值。 @@ -64,23 +65,23 @@ HBuilderX 5.22 当前实测中,自定义调试基座不支持安心打包, ```powershell & '.\mobile-uni\scripts\verify-android-apk.ps1' ` - -ApkPath '.\mobile-uni\dist\debug\bangdao-0.1.6-dcloud-test.apk' ` + -ApkPath '.\mobile-uni\dist\debug\bangdao-0.1.8-dcloud-test.apk' ` -BuildKind dcloud-test ``` -APK 门禁还会比较包内与当前 `dist/build/app/app-service.js` 的 SHA-256,避免用同版本号的旧包冒充当前源码,并在反编译结果中复查敏感文件、手机号、固定测试验证码、私钥和常见令牌格式。 +APK 门禁还会比较包内与当前 `dist/build/app/app-service.js` 的 SHA-256,避免用同版本号的旧包冒充当前源码,并在反编译结果中复查敏感文件、手机号、固定测试验证码、私钥和常见令牌格式;`dcloud-test` 与 `release` 模式都拒绝 `usesCleartextTraffic=true`。 完成代码评审并提交冻结后,从仓库根目录执行统一 RC 证据脚本。默认拒绝脏工作区;`-AllowDirtyRehearsal -SkipBuild` 只能用于验证脚本和 APK,生成的证据会明确标记为不可发布: ```powershell .\scripts\prepare-aug1-rc.ps1 ` - -ApkPath '.\mobile-uni\dist\debug\bangdao-0.1.6-dcloud-test.apk' ` + -ApkPath '.\mobile-uni\dist\debug\bangdao-0.1.8-dcloud-test.apk' ` -BuildKind dcloud-test ``` 脚本会复跑移动端测试/类型检查/H5/App 构建、管理端构建、后端定向测试和打包,验证 APK 后在被 Git 忽略的 `output/aug1-rc/` 生成标准文件名、哈希及 JSON/Markdown 证据。它不执行生产同步、数据库迁移、服务重启或用户隐私选择。 -2026-07-28 最终自定义基座大小为 `21,158,193` 字节,SHA-256 为 `82D3352FF96A86F5DB4AC0C71DCB0ED6CEC98F2A785F6EAED2FE8D4FC0C5F704`。2026-07-29 当前独立内测候选包为 `bangdao-0.1.6-dcloud-test.apk`:包名 `com.yincheng.wygj`、版本 `0.1.6 (106)`、`targetSdkVersion=35`,大小 `33,921,279` 字节,SHA-256 为 `A5F026B70F11327355597B9FEA3625C4168700297E6D7847F10B079CEB860914`,签名证书 SHA-256 为 `AB06ECC0DE544DE73C385F3F0690B22966EBC12887FB8BEE1830CDBEFDEBEF5D`。APK 反编译门禁已确认包内业务资源等于当前 App-Plus 编译产物,法务配置和敏感值扫描通过;同签名覆盖安装和冷启动成功,启动后敏感权限保持未授权且未见 `FATAL EXCEPTION`。前一候选包的干净启动已显示原生 template 隐私弹窗,两个链接均通过系统浏览器打开线上正文。该包使用 DCloud 云端证书,且 DCloud 运行壳仍声明允许明文流量,只能作为 8 月 1 日受控内测包;企业正式 release 门禁仍会拒绝明文流量和非企业签名。 +2026-07-28 最终自定义基座大小为 `21,158,193` 字节,SHA-256 为 `82D3352FF96A86F5DB4AC0C71DCB0ED6CEC98F2A785F6EAED2FE8D4FC0C5F704`。2026-07-29 当前独立内测候选包为 `bangdao-0.1.8-dcloud-test.apk`:包名 `com.yincheng.wygj`、版本 `0.1.8 (108)`、`targetSdkVersion=35`,大小 `33,921,705` 字节,SHA-256 为 `E252140645F3AAD14B3594FD78E76BDF1A2122F7DC70EBD21CC5DF53223C7878`,签名证书 SHA-256 为 `AB06ECC0DE544DE73C385F3F0690B22966EBC12887FB8BEE1830CDBEFDEBEF5D`。APK 反编译门禁已确认包内业务资源等于当前 App-Plus 编译产物,法务配置、敏感值扫描、`usesCleartextTraffic=false`、`networkSecurityConfig` 引用和系统证书信任配置均通过;本机 Android 15/API 35 虚拟机完成卸载重装、冷启动和权限基线复核,总启动耗时约 `2.9s`,四项敏感权限保持未授权且 crash 缓冲区为空。该包仍使用 DCloud 云端测试证书,只能作为 8 月 1 日受控内测包;企业正式 release 还必须使用签认后的企业证书摘要。 企业正式签名 APK 还必须用发布负责人保管的证书摘要执行 release 模式核验;该模式会额外拒绝 `debuggable=true`、明文 HTTP 和签名摘要不一致: diff --git a/docs/帮道8月1日Android内测Go-No-Go-20260728.md b/docs/帮道8月1日Android内测Go-No-Go-20260728.md index 34c165d7..c7976ce4 100644 --- a/docs/帮道8月1日Android内测Go-No-Go-20260728.md +++ b/docs/帮道8月1日Android内测Go-No-Go-20260728.md @@ -14,17 +14,17 @@ ## 已通过 -| 检查项 | 2026-07-28 结果 | +| 检查项 | 当前结果 | |---|---| | 仓库同步 | 本地 `main` 与 `origin/main` 均为 `9ae5f750`,前后差异 `0/0` | -| 移动端自动化 | 191 项单元测试通过;TypeScript 类型检查通过 | +| 移动端自动化 | 193 项单元测试通过;TypeScript 类型检查通过 | | 构建 | `mobile-uni` H5、App-Plus、管理端生产构建通过;后端 `ruoyi-admin.jar` 重建通过 | | P0 后端语义 | `AihrSopSeedServiceTest` 39 项通过;`ruoyi-aihr` 696 项全量测试通过,0 失败、0 错误 | | App 资源门禁 | 正式法务 URL、原生隐私配置、API 35、图标和启动图检查通过 | -| 独立 APK | `com.yincheng.wygj`,`0.1.6 (106)`,`targetSdkVersion=35`,v2 签名;SHA-256 `A5F026B70F11327355597B9FEA3625C4168700297E6D7847F10B079CEB860914` | +| 独立 APK | `com.yincheng.wygj`,`0.1.8 (108)`,`targetSdkVersion=35`,v2 签名,明文 HTTP 关闭且网络安全资源已进入 APK;SHA-256 `E252140645F3AAD14B3594FD78E76BDF1A2122F7DC70EBD21CC5DF53223C7878` | | APK 内容门禁 | 包内业务资源等于当前 App-Plus 编译产物;法务链接、手机号、固定测试验证码、私钥和常见令牌扫描通过 | | 真机安装启动 | vivo V2443A(Android 15)完成 `0.1.6` 覆盖安装和冷启动;相机、录音、媒体权限保持未授权,未见 `FATAL EXCEPTION` | -| 虚拟机干净安装 | 本机 `Medium_Phone` Android 15/API 35 完成 `0.1.6` 首次安装和冷启动;最新包真实显示原生隐私弹窗,四项敏感权限未授权,crash 缓冲区为空 | +| 虚拟机干净安装 | 本机 `Medium_Phone` Android 15/API 35 完成 `0.1.8` 卸载重装和冷启动;最新包真实显示原生隐私弹窗,四项敏感权限未授权,crash 缓冲区为空 | | 首次隐私弹窗 | 最新包已在虚拟机干净启动显示原生弹窗;前一候选包的两个 HTTPS 链接已在 vivo 系统浏览器打开,仍需用户亲自执行拒绝/同意反例 | | 法务静态页 | `https://peilian.njzhmj.top/legal/terms.html` 与 `https://peilian.njzhmj.top/legal/privacy.html` 已部署并可访问;发布前站点备份为 `/opt/wygj/backups/www-20260728224710` | @@ -35,7 +35,7 @@ 3. **原生人工反例未完成。** 覆盖安装和冷启动已通过;仍缺用户亲自执行的同意/拒绝、拒绝后退出、同意后登录、短信失败与重新登录、麦克风允许/拒绝、媒体取消、前后台切换和弱网验证。 4. **核心业务真机闭环未完成。** 仍缺认证账号下完整“查/练/问”、五个直通渠道、五个正式对练场景、ASR/TTS、附件上传下载和越权反例。 5. **内容与业务验收不足。** 当前仅收到 1 份生活顾问访谈,距离计划中的 24 份访谈、30 道人工审核政策题、5 个正式场景及五渠道真实处理人配置均无完成证据。 -6. **发布与回滚未签认。** 尚无四个发布单元的版本矩阵、负责人签字、完整预检结果和回滚演练记录。当前 APK 使用 DCloud 测试证书且运行壳允许明文流量,只能用于受控内测;企业签名和公司法务正式签认仍是正式发布门槛。 +6. **发布与回滚未签认。** 本地已有 APK/后端最小版本矩阵、构建证据和回滚边界,但尚无负责人签字、完整预检通过和回滚演练记录。当前 APK 已关闭明文流量但仍使用 DCloud 测试证书,只能用于受控内测;企业签名和公司法务正式签认仍是正式发布门槛。 ## 转为 Go 的最短路径 diff --git a/docs/帮道8月1日Android安装包测试版任务计划-20260727.docx b/docs/帮道8月1日Android安装包测试版任务计划-20260727.docx index 1a853401258231161369ac6d3298e58b22ace530..1cef18dc2f4ca9ca0a5750d6f62fae7ed96b7073 100644 GIT binary patch delta 20747 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 Y0LQ=5>HR>?%yJi9mPkGS0ig!XaKoH<`G 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 ef70EiQP_1}^6D4Yc|fhRsFQUu+7C 文档版本:v1.9 +> 文档版本:v2.0 > > 制定日期:2026-07-27 > @@ -86,16 +86,16 @@ ### 3.2 RC 自动化门禁快照(2026-07-29) -本节记录当前工作区自动化和测试签名自定义基座结果,不代表企业签名 RC APK 或完整真机验收完成。 +本节记录当前工作区自动化、测试签名自定义基座和独立内测 APK 结果,不代表企业签名 RC APK 或完整真机验收完成。 | 门禁 | 结果 | 说明 | |---|---|---| -| `mobile-uni` 单元测试 | 通过 | 191/191;测试过程有本机 WebSocket 端口占用告警,但无失败 | +| `mobile-uni` 单元测试 | 通过 | 193/193;测试过程有本机 WebSocket 端口占用告警,但无失败 | | `mobile-uni` TypeScript 检查 | 通过 | `npm --prefix mobile-uni run typecheck` | | H5 资源构建 | 通过 | 仅证明 H5 资源可编译 | | App-Plus 资源构建 | 通过 | 仅生成 HBuilderX 出包资源,不等同于 APK | -| App 原生配置、图标与启动图校验 | 通过 | 固定包名、语义版本、API 35、双 ABI、启动权限策略、Camera/Record 模块、22 个图标和 14 个启动图 | -| 自定义基座 APK 结构与签名校验 | 通过 | `verify:android-apk` 已核对包名、版本、`targetSdkVersion`、单签名、v2 签名和 SHA-256 | +| App 原生配置、图标与启动图校验 | 通过 | 固定包名、语义版本、API 35、双 ABI、启动权限策略、Camera/Record 模块、禁止明文 HTTP、网络安全资源、22 个图标和 14 个启动图 | +| 独立内测 APK 结构与签名校验 | 通过 | `verify-android-apk.ps1 -BuildKind dcloud-test` 已核对包名、版本、`targetSdkVersion`、网络安全配置、包内业务资源、单签名、v2 签名和 SHA-256 | | `390×844` 未登录首屏与入口 | 通过 | 今日主操作、练、问、问制度快捷项、大喇叭和直通车登录门禁均在首屏可见;练、问已实际点击可达,无控制台错误 | | 登录后核心业务路径 | 未执行 | 当前没有可用的本地 APP 登录主体和后端会话;不得用伪造手机号或未认证预览冒充通过 | | 管理端生产构建 | 通过 | 存在大 chunk 告警,不阻断本轮构建 | @@ -122,7 +122,8 @@ - 2026-07-29 当前候选包升级为 `0.1.6 (106)`,位于 `mobile-uni/dist/debug/bangdao-0.1.6-dcloud-test.apk`,大小 `33,921,279` 字节,SHA-256 为 `A5F026B70F11327355597B9FEA3625C4168700297E6D7847F10B079CEB860914`。登录页法务校验已移除 App-Plus 不保证支持的浏览器 `URL` 构造器,测试验证码不再以源码常量进入包内;APK 反编译门禁已确认包内业务资源等于当前编译产物,并通过法务链接、签名和敏感值扫描。 - `0.1.6 (106)` 已在同一设备完成保留数据的覆盖安装和 Launcher 冷启动;相机、录音和媒体权限保持未授权,未见 `FATAL EXCEPTION`。覆盖安装保留了前一版本的隐私状态,因此最新包的拒绝/同意反例仍须由用户亲自执行。 - 本机 Android Studio `Medium_Phone` 虚拟机已启动为 Android 15/API 35(`1080×2400`、密度 `420`)。在此前未安装本应用的虚拟机上,`0.1.6 (106)` 首次安装和 Launcher 冷启动成功,最新包真实显示原生隐私弹窗,四项相机、录音、图片和视频权限均未授予,crash 缓冲区为空。虚拟机只补充干净安装和 API 35 权限基线,不替代真机媒体、WebRTC、弱网与系统浏览器验收;拒绝/同意仍由用户亲自执行。 -- 自定义基座和独立内测 APK 均使用 DCloud 云端测试证书,不是企业正式签名 APK;独立内测包的 DCloud 运行壳仍声明允许明文流量,因此只可用于 8 月 1 日受控内测。公司法务签认、企业签名、用户实际同意/拒绝、登录后权限反例仍是正式发布阻断。 +- `0.1.8 (108)` 进一步把 Android 原生清单和 `network_security_config.xml` 纳入 App 构建链及 APK 反编译门禁。独立内测 APK 大小 `33,921,705` 字节,SHA-256 为 `E252140645F3AAD14B3594FD78E76BDF1A2122F7DC70EBD21CC5DF53223C7878`;APK 反编译确认 `usesCleartextTraffic=false`、`networkSecurityConfig` 引用和系统证书信任配置均已进入安装包,本机虚拟机卸载旧实例后全新安装并在 `2.904s` 内冷启动,最新隐私弹窗真实显示,四项敏感权限未授予且 crash 缓冲区为空。 +- 自定义基座和独立内测 APK 均使用 DCloud 云端测试证书,不是企业正式签名 APK;当前 `0.1.8` 已关闭明文流量并内置网络安全配置,但仍只可用于 8 月 1 日受控内测。公司法务签认、企业签名、用户实际同意/拒绝、登录后权限反例仍是正式发布阻断。 ## 4. P0 工作分解 diff --git a/mobile-uni/AndroidManifest.xml b/mobile-uni/AndroidManifest.xml new file mode 100644 index 00000000..e5f3eb3d --- /dev/null +++ b/mobile-uni/AndroidManifest.xml @@ -0,0 +1,10 @@ + + + + diff --git a/mobile-uni/nativeResources/android/res/xml/network_security_config.xml b/mobile-uni/nativeResources/android/res/xml/network_security_config.xml new file mode 100644 index 00000000..683208ff --- /dev/null +++ b/mobile-uni/nativeResources/android/res/xml/network_security_config.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/mobile-uni/package-lock.json b/mobile-uni/package-lock.json index 69529b69..00b500d4 100644 --- a/mobile-uni/package-lock.json +++ b/mobile-uni/package-lock.json @@ -1,12 +1,12 @@ { "name": "aihr-mobile-uni", - "version": "0.1.6", + "version": "0.1.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "aihr-mobile-uni", - "version": "0.1.6", + "version": "0.1.8", "dependencies": { "@dcloudio/uni-app": "3.0.0-alpha-5020220260725001", "@dcloudio/uni-app-plus": "3.0.0-alpha-5020220260725001", diff --git a/mobile-uni/package.json b/mobile-uni/package.json index 7009a6ba..4bf01be0 100644 --- a/mobile-uni/package.json +++ b/mobile-uni/package.json @@ -1,14 +1,14 @@ { "$schema": "https://json.schemastore.org/package", "name": "aihr-mobile-uni", - "version": "0.1.6", + "version": "0.1.8", "private": true, "type": "module", "scripts": { "dev": "uni -p h5", "dev:h5": "uni -p h5", "build:h5": "uni build -p h5", - "build:app": "uni build -p app", + "build:app": "uni build -p app && node scripts/sync-app-native-resources.mjs", "generate:app-assets": "sh scripts/generate-app-assets.sh", "configure:app-privacy": "node scripts/configure-android-privacy.mjs", "verify:app-assets": "node scripts/verify-app-assets.mjs", diff --git a/mobile-uni/scripts/sync-app-native-resources.mjs b/mobile-uni/scripts/sync-app-native-resources.mjs new file mode 100644 index 00000000..9a604169 --- /dev/null +++ b/mobile-uni/scripts/sync-app-native-resources.mjs @@ -0,0 +1,24 @@ +import { cpSync, existsSync, mkdirSync } from 'node:fs'; +import { dirname, resolve } from 'node:path'; + +const projectRoot = resolve(import.meta.dirname, '..'); +const mappings = [ + [ + resolve(projectRoot, 'AndroidManifest.xml'), + resolve(projectRoot, 'dist/build/app/AndroidManifest.xml') + ], + [ + resolve(projectRoot, 'nativeResources/android/res/xml/network_security_config.xml'), + resolve(projectRoot, 'dist/build/app/nativeResources/android/res/xml/network_security_config.xml') + ] +]; + +for (const [source, destination] of mappings) { + if (!existsSync(source)) { + throw new Error(`Missing required native App resource: ${source}`); + } + mkdirSync(dirname(destination), { recursive: true }); + cpSync(source, destination); +} + +console.log('Android native manifest and network security resources synchronized.'); diff --git a/mobile-uni/scripts/verify-android-apk.ps1 b/mobile-uni/scripts/verify-android-apk.ps1 index b7adfd43..349f902a 100644 --- a/mobile-uni/scripts/verify-android-apk.ps1 +++ b/mobile-uni/scripts/verify-android-apk.ps1 @@ -205,19 +205,45 @@ try { $application = $decodedManifest.manifest.application $debuggable = $application.GetAttribute('debuggable', $androidNamespace) -eq 'true' $usesCleartextTraffic = $application.GetAttribute('usesCleartextTraffic', $androidNamespace) -eq 'true' + $networkSecurityConfig = $application.GetAttribute('networkSecurityConfig', $androidNamespace) if ($BuildKind -eq 'custom-base' -and -not $debuggable) { throw 'Custom base APK must remain debuggable.' } if ($BuildKind -eq 'dcloud-test' -and $debuggable) { throw 'DCloud internal test APK must not be debuggable.' } + if ($BuildKind -in @('dcloud-test', 'release') -and $usesCleartextTraffic) { + throw "$BuildKind APK must not allow cleartext HTTP traffic." + } + if ($BuildKind -in @('dcloud-test', 'release')) { + if ($networkSecurityConfig -ne '@xml/network_security_config') { + throw "$BuildKind APK must reference @xml/network_security_config." + } + $decodedNetworkSecurityPath = Join-Path $decodePath 'res\xml\network_security_config.xml' + if (-not (Test-Path -LiteralPath $decodedNetworkSecurityPath)) { + throw "$BuildKind APK is missing res/xml/network_security_config.xml." + } + $decodedNetworkSecurity = Get-Content -Raw -Encoding UTF8 -LiteralPath $decodedNetworkSecurityPath + if ($decodedNetworkSecurity -notmatch 'cleartextTrafficPermitted=\"false\"') { + throw "$BuildKind APK network security config must reject cleartext traffic." + } + if ($decodedNetworkSecurity -match 'cleartextTrafficPermitted=\"true\"') { + throw "$BuildKind APK network security config must not contain a cleartext exception." + } + if ($decodedNetworkSecurity -notmatch '') { + throw "$BuildKind APK network security config must trust system certificates only." + } + if ($decodedNetworkSecurity -match ' failures.push(message); const compiledAppRoot = resolve(projectRoot, 'dist/build/app'); +const sourceAndroidManifestPath = resolve(projectRoot, 'AndroidManifest.xml'); +const compiledAndroidManifestPath = resolve(compiledAppRoot, 'AndroidManifest.xml'); +const sourceNetworkSecurityPath = resolve( + projectRoot, + 'nativeResources/android/res/xml/network_security_config.xml' +); +const compiledNetworkSecurityPath = resolve( + compiledAppRoot, + 'nativeResources/android/res/xml/network_security_config.xml' +); const releaseTextExtensions = new Set(['.css', '.html', '.js', '.json', '.txt', '.xml']); const releaseSensitiveFilePattern = /(^|[\\/])(?:\.env(?:\.|$)|id_rsa$)|\.(?:jks|keystore|p12|pfx|pem)$/i; const releaseSensitiveContentPatterns = [ @@ -126,7 +136,7 @@ if (app?.ssl?.untrustedca !== 'refuse') { fail('app-plus.ssl.untrustedca must remain refuse'); } if (!/^\d+\.\d+\.\d+$/.test(String(manifest.versionName || ''))) { - fail('versionName must use semantic numeric form such as 0.1.6'); + fail('versionName must use semantic numeric form such as 0.1.8'); } if (!/^[1-9]\d*$/.test(String(manifest.versionCode || ''))) { fail('versionCode must be a positive integer'); @@ -137,6 +147,9 @@ if (android?.packagename !== 'com.yincheng.wygj') { if (android?.targetSdkVersion !== 35) { fail('Android targetSdkVersion must be 35 for the August 1 test package'); } +if (android?.usesCleartextTraffic !== false) { + fail('Android manifest configuration must explicitly disable cleartext traffic'); +} const expectedAndroidAbis = ['arm64-v8a', 'armeabi-v7a']; const actualAndroidAbis = Array.isArray(android?.abiFilters) ? [...android.abiFilters].sort() @@ -162,6 +175,46 @@ for (const permission of ['android.permission.RECORD_AUDIO', 'android.permission } } +const verifyAndroidNetworkSecurity = (manifestPath, networkSecurityPath, label) => { + if (!existsSync(manifestPath)) { + fail(`${label} is missing AndroidManifest.xml`); + return; + } + if (!existsSync(networkSecurityPath)) { + fail(`${label} is missing network_security_config.xml`); + return; + } + const androidManifest = readFileSync(manifestPath, 'utf8'); + const networkSecurity = readFileSync(networkSecurityPath, 'utf8'); + if (!/android:usesCleartextTraffic\s*=\s*["']false["']/.test(androidManifest)) { + fail(`${label} AndroidManifest.xml must disable cleartext traffic`); + } + if (!/android:networkSecurityConfig\s*=\s*["']@xml\/network_security_config["']/.test(androidManifest)) { + fail(`${label} AndroidManifest.xml must reference network_security_config`); + } + if (!/cleartextTrafficPermitted\s*=\s*["']false["']/.test(networkSecurity)) { + fail(`${label} network security config must reject cleartext traffic`); + } + if (/cleartextTrafficPermitted\s*=\s*["']true["']/.test(networkSecurity)) { + fail(`${label} network security config must not contain a cleartext exception`); + } + if (!//.test(networkSecurity)) { + fail(`${label} network security config must trust system certificates only`); + } + if (/ assert.equal(android?.packagename, 'com.yincheng.wygj'); assert.equal(android?.targetSdkVersion, 35, '不得回退到 DCloud 默认的 targetSdkVersion 28 兼容模式'); + assert.equal(android?.usesCleartextTraffic, false, '发布配置不得允许明文 HTTP'); +}); + +test('Android 原生网络安全配置拒绝明文流量且只信任系统证书', async () => { + const androidManifest = await readFile( + new URL('../AndroidManifest.xml', import.meta.url), + 'utf8' + ); + const networkSecurity = await readFile( + new URL('../nativeResources/android/res/xml/network_security_config.xml', import.meta.url), + 'utf8' + ); + + assert.match(androidManifest, /android:usesCleartextTraffic="false"/); + assert.match(androidManifest, /android:networkSecurityConfig="@xml\/network_security_config"/); + assert.match(networkSecurity, /cleartextTrafficPermitted="false"/); + assert.match(networkSecurity, //); + assert.doesNotMatch(networkSecurity, /cleartextTrafficPermitted="true"/); + assert.doesNotMatch(networkSecurity, / { + const pkg = await readJson('../package.json'); + const syncScript = await readFile( + new URL('../scripts/sync-app-native-resources.mjs', import.meta.url), + 'utf8' + ); + + assert.match(pkg.scripts?.['build:app'] || '', /sync-app-native-resources\.mjs/); + assert.match(syncScript, /dist\/build\/app\/AndroidManifest\.xml/); + assert.match(syncScript, /dist\/build\/app\/nativeResources\/android\/res\/xml\/network_security_config\.xml/); }); test('Android 启动阶段不主动索取设备信息或外部存储权限', async () => { @@ -146,6 +178,12 @@ test('APK 门禁区分自定义调试基座与内置业务资源的测试包', a assert.match(verifier, /dist\\build\\app-plus\\app-service\.js/); assert.match(verifier, /fixed test SMS code/); assert.match(verifier, /'mobile number'\s*=/); + assert.match(verifier, /dcloud-test.*release.*usesCleartextTraffic/s); + assert.match(verifier, /res\\xml\\network_security_config\.xml/); + assert.match(verifier, /network security config must trust system certificates only/); + assert.match(verifier, /must not contain a cleartext exception/); + assert.match(verifier, /must not trust user or bundled certificates/); + assert.match(verifier, /must not contain debug trust overrides/); }); test('8 月 1 日 RC 脚本默认拒绝脏工作区且记录完整构建证据', async () => {