ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

HarmonyOS 7 ArkWeb:文档H5跳转URL边界归一与重定向拦截【鸿蒙心迹】

HarmonyOS 7 ArkWeb:文档H5跳转URL边界归一与重定向拦截【鸿蒙心迹】 一个应用把使用说明放进 ArkWeb通常是为了保持内容可更新。但当文档里的链接越来越多Web 的用途很容易悄悄从“阅读本应用文档”变成“任意网址浏览器”。尤其是站内重定向、用户名式 URL、相似域名和自定义 scheme 混在一起时仅根据字符串中是否出现公司域名判断跳转既不可靠也不便于排查。这篇用一个刻意缩小边界的 DemoDocRouteFence研究导航策略只阅读https://docs.example.org上固定结构的演示文章和帮助页其他跳转由应用自行拒绝。本文里的域名属于示例域名不是已部署并且经过域名所有权验证的服务所有八条测试均为本地输入向量。我们核对的是 URL 策略和 ArkWeb 回调的接线方法而不是宣称浏览器、网络或真机已经跑通。一、真正的风险不在网页能否打开而在导航资格如何改变文档阅读器经常在同一个页面里包含目录链接、帮助链接、第三方下载按钮和内容站的跳转脚本。应用初次装载的src是可信地址并不意味着之后每一次跳转都处于同一个可信边界。比如https://docs.example.org.evil.test/article/108里包含完整的站内域名字符串可它的实际主机根本不是docs.example.org。如果开发者采用raw.includes(docs.example.org)就等于把对方域名当成自己的。另一个常见误区是“只检查 scheme 就够了”。https代表传输协议并不声明这个页面有权获得应用信任。https://userdocs.example.org/article/108在 URL 语法里还引入了用户信息https://docs.example.org:8443/article/108使用了非预期端口带查询参数、fragment 的链接也可能扩大应用路由约定。我们不把这些全部描述成浏览器漏洞它们是业务策略没有明确写出来的边界。Demo 的任务编号固定为WEB-1010-17页面分别叫DocShellPage与LinkAuditPage主要策略对象为NavPolicy。主页展示的当前地址是https://docs.example.org/article/108对应文章“多设备文档排版”。规则修订记作policyEpoch4。总共八个固定样例预期三条允许、五条拒绝其中一条模拟了重定向到伪装域名的拒绝。因此redirectBlocked1是五条拒绝中的子集不应被重复加进拒绝总量。产品层面更值得先定下“不做什么”。这个文档壳不承担通用浏览器能力不需要任意 HTTP 地址、不允许javascript:也不承诺把被拦截的外链自动交给系统浏览器。所有跳转在经过策略检查前都不得被当成站内文档。若未来确实需要外部帮助站点应先做产品评审并建立另一套外跳规则而不是把域名通配符加宽。二、策略表先于页面实现免得靠截图反推业务此次固定的允许范围很窄协议必须为https:hostname 必须恰好等于docs.example.org端口必须为空URL 不含用户名、密码、查询或片段路径只接收/article/加三位数字或完全等于/help。/article/108、/article/109、/help三个样本预期允许其余五个被拒绝。这种策略不会自动适用于开放的新闻站点但对单应用说明文档足够直白。样例中的五个拒绝项也有意采用不同理由。http://docs.example.org/article/108是协议错误https://docs.example.org.evil.test/article/108是主机越界https://userdocs.example.org/article/108是凭据结构不允许https://docs.example.org:8443/article/108是端口越界javascript:alert(1)是非 HTTPS scheme。我们把第二项标记为一次模拟重定向目标体现“即使第一跳已经受信重定向目标也要重新判断”。这里应区分 URL 解析结果和原始字符串。比较 host 时使用规范化解析字段而不是把字符串切成斜线数组比较路径时需要完整匹配不能使用startsWith(/article/)就收下所有后缀。对嵌入式 Web 而言输出两种东西也要分开一是业务判定ALLOW/BLOCK_*二是 ArkWeb 的布尔拦截结果。业务允许对应回调返回 false业务拒绝对应 true。把这两个布尔含义颠倒日志会看起来正确导航却完全相反。还要考虑缺失或不合法的 URL。没有足够信息时按拒绝处理不要在 catch 分支默认放行。一个合理的策略模块应该有纯函数入口能够脱离 UI 运行固定输入向量。Web 控件的回调负责调用它而不负责临时推断域名含义。这样的设计也为未来策略变更留下回归测试位置。三、URL 规范化必须借助解析器不能写成包含关系在 ArkTS 侧可以使用kit.ArkTS的url.URL.parseURL。官方 URL 模块会提供protocol、hostname、port、username、password、pathname等字段。代码前要解决的问题是输入可能合法但不符合业务策略也可能压根无法解析两者都不应该被自动当成站内导航。// entry/src/main/ets/model/NavPolicy.ets import { url } from kit.ArkTS; export type NavDecision ALLOW | BLOCK_PARSE | BLOCK_SCHEME | BLOCK_HOST | BLOCK_CREDENTIALS | BLOCK_PORT | BLOCK_ROUTE; export class NavPolicy { classify(raw: string): NavDecision { try { const target url.URL.parseURL(raw); if (target.protocol ! https:) return BLOCK_SCHEME; if (target.hostname ! docs.example.org) return BLOCK_HOST; if (target.username || target.password) return BLOCK_CREDENTIALS; if (target.port) return BLOCK_PORT; if (target.search || target.hash) return BLOCK_ROUTE; const path: string target.pathname; const article /^\/article\/[0-9]{3}$/.test(path); return article || path /help ? ALLOW : BLOCK_ROUTE; } catch (_) { return BLOCK_PARSE; } } shouldBlock(raw: string): boolean { return this.classify(raw) ! ALLOW; } }这段代码特别没有处理“被允许的所有资源请求”。它判断的是顶层 URL 业务导航不是 CSS、字体、图片以及脚本资源的通用许可表。把导航白名单直接放到资源请求回调里会把合法站内网页依赖的 CDN 静态资源一起挡掉反过来把静态资源域名加入导航白名单又会放大可打开页面的范围。二者需要不同的数据模型。当前策略对带参数的文章链接也采取拒绝这并非所有产品必需。若以后允许?langzh应该把允许的键集合、值类型、重复键处理和规范化顺序设计成另一个严格步骤更新策略版本和测试。先给宽松规则再在代码里到处做特殊判断是文档壳最难维护的路径。正则表达式匹配只解决路径形态没有解决网络安全的全部问题。它不检查 DNS 解析和证书信任也无法证明服务器的最终内容是否可信TLS 及重定向仍由系统网络栈参与处理。我们把NavPolicy称为应用层“导航资格门禁”不把它包装成万能的 URL 安全系统。四、两个拦截回调承担不同的入口不把它们视为同义词华为 ArkWeb 文档明确区分onLoadIntercept和onOverrideUrlLoading前者涉及程序主动loadUrl等加载流程后者在其它即将导航的阶段被调用并非所有加载路径都能触发两者。两处都接入同一策略是为了降低漏检风险而不是认为两个回调一定会依次触发。初始src由可信常量指定如果它变成服务端配置值还要在构建 Web 组件前单独验一遍。下面代码的职责只有“把 URL 交给策略再把拒绝转换为 ArkWeb 布尔返回值”。它没有实现网络访问和测试数据来源也不会在回调里随意调用loadUrl形成递归重定向。要注意onLoadIntercept的event.data是 WebResourceRequest 相关事件数据onOverrideUrlLoading的参数则是 WebResourceRequest。代码日志以DocRouteFence为固定标识。// entry/src/main/ets/pages/DocShellPage.ets — 组件中的关键部分 import { webview } from kit.ArkWeb; import { NavPolicy } from ../model/NavPolicy; Entry Component struct DocShellPage { private controller: webview.WebviewController new webview.WebviewController(); private policy: NavPolicy new NavPolicy(); build() { Column() { Web({ src: https://docs.example.org/article/108, controller: this.controller }) .onLoadIntercept((event) { const target event.data.getRequestUrl(); const blocked this.policy.shouldBlock(target); console.info(DocRouteFence load ${blocked ? BLOCK : ALLOW}); return blocked; // true中止本次导航 }) .onOverrideUrlLoading((request) { const blocked this.policy.shouldBlock(request.getRequestUrl()); console.info(DocRouteFence override ${blocked ? BLOCK : ALLOW}); return blocked; }) }.width(100%).height(100%); } }这段接口接线需要用目标 API 版本的 DevEco Studio 编译核对事件签名。文中的示意图不是 IDE 运行证据不能因图片里的语法高亮和日志就宣称回调已经触发。官方还指出onLoadIntercept返回 true 表示取消这次导航、false 表示继续onOverrideUrlLoading的触发时机不同。两个事件的覆盖边界不应由一张成功截图来推断。图二刻意保留左工程目录、中央代码、右模拟器和底部日志四个区域帮助对应文件职责DocShellPage.ets是页面装配NavPolicy.ets是纯规则LinkAuditPage.ets是诊断呈现。右侧显示WEB-1010-17、policyEpoch4、允许3、拦截5、重定向拦截1、NAV_GUARDED。这些都是预设模型数据不是一次已成功加载的网页。五、用固定样本把边界撞出来比只写一句“加白名单”可靠八条输入不是随机搜索来的案例而是作为可重放的协议用例保存。这个做法的价值在于将来某位同事把路径放宽到/article/*或者为了一个帮助链接加入子域名通配符测试立刻指出原有假设发生变化。我们既关心错误的禁止也关心错误的允许只统计拦截数量会漏掉合法帮助页被误杀。对这类纯数据规则Node.js 能在开发机器上运行一组等价黄金向量。以下代码属于测试工具示例它不调用 ArkWeb也不能代替ArkTS目标环境的 URL 解析一致性测试。真实工程可以把八条 JSON 向量放到test/fixtures/nav-cases.json再由 ArkTS 侧用同一组输入确认一致性。// test/nav-cases.mjs本地黄金输入非真机网络测试consthostdocs.example.org;functionallowed(raw){try{constunewURL(raw);returnu.protocolhttps:u.hostnamehostu.port!u.username!u.password!u.search!u.hash(/^\/article\/[0-9]{3}$/.test(u.pathname)||u.pathname/help);}catch{returnfalse;}}constvectors[[https://docs.example.org/article/108,true],[https://docs.example.org/article/109,true],[https://docs.example.org/help,true],[http://docs.example.org/article/108,false],[https://docs.example.org.evil.test/article/108,false],[https://userdocs.example.org/article/108,false],[https://docs.example.org:8443/article/108,false],[javascript:alert(1),false]];letpass0,allow0;for(const[target,expected]ofvectors){constactualallowed(target);if(actual!expected)throwError(Mismatch${target});if(actual)allow;pass;}console.log(WEB-1010-17 cases${pass}allow${allow}block${pass-allow});测试结果与运行状态必须分开写。这里纯函数的预期是cases8 / allow3 / block5。而真实网络访问仍为NOT_RUN初始https://docs.example.org/article/108仅展示为方案中的模拟文档。将“八条本地规则全部符合预期”写成“HTTPS跳转已经在真实设备安全运行”既不准确也不利于后续验收。主界面提供一个可对照的状态面板固定文档“多设备文档排版”、版本4和NAV_GUARDED。这一页不是浏览器成功截图更像是把导航策略以产品可理解的方式呈现允许查看什么、拒绝什么以及为什么尚未进入网络集成阶段。查看拦截诊断进入的也是应用自己的审计页不会因为查看日志再触发远端文档的重载。六、一次重定向不能继承第一跳的许可策略更新也要有代次八条样例里伪装域名docs.example.org.evil.test被标成一条“模拟重定向的目标”。这个设定用于强调第一跳允许不意味着下一跳可以跳过检查。在真实 ArkWeb 中是否会产生某个回调、是哪一个回调、如何处理多级响应要按实际网络、目标 API 版本和服务端状态验证。本文只给出重定向目标同样交给规则判断的要求不编造已经覆盖所有重定向链的结论。另外一种更隐蔽的乱序发生在策略配置自己更新时。譬如用户已打开旧配置policyEpoch3管理员刚把白名单升级为版本4旧页面又发来一条延迟导航事件。业务层不能只看一个allowed布尔值还要记录它依据的策略版本。当前 Demo 将策略版本固定为4正式实现应在事件评价和最终消费之间带上版本旧版本结果不得直接提升为“当前可信”。这不是让 ArkWeb 支持一个新的“取消旧请求”API。应用层可以阻止自己用过期判断做新动作但不能保证系统里已经开始的所有网络请求被同步回滚。因此对于从文档页进入下载、身份认证、支付等敏感流程强约束应放到真正执行动作的业务服务端而不能仅依靠 Web 控件里的一个拦截回调。审核日志同样应当最小化。记录reason、policyEpoch和经过脱敏的 path 通常已经足够不要把 URL 中的 token、用户名、手机号和完整查询参数原样写进 HiLog。本文示例全部没有真实用户凭据故意用user作为语法测试。生产中要把敏感值过滤策略置于日志适配器内而不是依赖各个调用方主动记得删除。七、诊断页要能回答“为何拒绝”但不应制造成功幻觉在LinkAuditPage中最应该出现的是决策表而不只是“拦截成功”的勾。第一行到第三行ALLOW第四行BLOCK_SCHEME第五行BLOCK_HOST第六行BLOCK_CREDENTIALS第七行BLOCK_PORT第八行BLOCK_SCHEME。redirectBlocked1对应第五行的模拟链路仍计入五个拒绝内。日志里的文档当前地址与策略版本也要固定否则图文很难核对。诊断页还区分三个不同状态NAV_GUARDED表示本地规则准备好不表示 Web 页面已经渲染networkNOT_RUN表示没有真实网络加载证据policyEpoch4是规则修订号不是HarmonyOS API版本。这种状态分层能防止产品、测试和作者对同一个绿色标签作出不同解释。若未来接上真实Web应加入主frame进入次数、回调触发路径和服务器响应证据并且单独建立设备结果字段。实际验收可以准备三组设备侧观察。第一组在已授权测试域名打开/article/108记录onControllerAttached、onPageBegin、onPageEnd的顺序和页面是否可读第二组让测试站点返回跳到未授权主机的重定向核对onLoadIntercept或onOverrideUrlLoading的实际覆盖第三组测试about:blank、页面内iframe、非标准端口、断网和证书异常确认产品面对未知结果时保持关闭。任何一组缺少证据都不能据此声称完成系统级安全验收。要额外区分onInterceptRequest。它用于拦截请求并返回资源响应数据不是本文的主流程。错误地在里面直接替换HTML可能遮盖真正的重定向来源甚至把跨域资源问题变成错误的业务判断。本文选择只做导航资格判断是为了让工程问题保持聚焦真正的内容安全策略、CSP和网络证书校验另成独立工作项。八、工程交付时留下什么比一张通过界面更重要把这类Demo交给同事时我会要求同时保留NavPolicy.ets、八条黄金向量、策略版本、拒绝原因以及一张明示NOT_RUN的集成待办。规则不应散落在页面各种回调中只有这样回归检查才能在没有真机时先完成纯逻辑部分在有设备和测试站点后再补上真实页面与网络链路证据。这一方案的边界也明确当前代码验证 URL 形态和路由范围没有验证真实站点归属未处理所有重定向与子资源无法代替后端授权不讨论支付或下载。Web回调的精确参数仍以所用 DevEco Studio SDK 的.d.ts和真机行为为最终依据。若编译出现签名调整应先依据官方文档修正适配器而不是为了凑一张“已运行”截图删除关键检查。从工程取舍看最值得保留的不是那五条拒绝规则而是把导航许可写成纯函数把 Web 事件只当成输入通道把设备网络验证和本地规则验证分开。这样文档H5扩大了内容范围也不必同步扩大应用信任边界。下一步先拿到可控 HTTPS 测试域名与服务器重定向夹具再把NOT_RUN一项项替换为可追溯的设备证据才是真正意义上的闭环。还有一个经常被低估的验收维度用户按返回键。Web自身的历史记录和ArkUI页面路由栈不是同一套结构。在文档阅读器里用户按返回时应用必须先决定是回退Web历史页还是关闭整个原生容器。无论采用哪种交互方案不能因为回退动作“不像一个新跳转”就假设它必然在白名单内。对于返回到旧页面的情况要记录当前可见URL和策略版本必要时退到一个原生安全页而不是盲目重放历史字符串。这个问题需要真机测试实际触发事件本文不擅自宣称backward()在所有设备上都重新经过相同拦截链。另外错误页与登录页不能成为白名单后门。发生网络异常时有些团队会把错误原因转成file://、resource://或自定义HTML网页再塞进原有Web组件。它们的安全约束与HTTPS不同若产品确实需要离线错误页应当独立约定加载来源、允许资源和退出行为不宜直接给NavPolicy加上一条“允许所有本地协议”。一个原生错误卡片往往更容易说明当前内容不可用也更容易避免离线页意外继承其它Web权限。从测试设计看除了八条正向黄金向量还应该建立扩展回归池大小写混合的主机、尾点域名、编码斜杠、编码的点号、过长URL、无效百分号、Unicode域名、重复查询键和片段路由。这里不预设各类编码的最终解析结果而是记录原始输入、解析后的关键字段、最终判定三联数据。只要开发中修改了SDK版本、URL解析模块或政策修订号就重新执行若某个样本在不同环境出现不同归一化结果宁可暂时拒绝并升级规则也不要安静地放行。日志和告警也需要限流。恶意网页可能反复触发被拦截的导航若每次把完整URL、堆栈和时间戳同步刷新到ArkUI状态诊断系统本身就可能带来卡顿。可以按reason policyEpoch聚合计数仅保留有限长度的最近事件并设置最大缓存条数。这样既能看到“BLOCK_HOST在一分钟内出现了多少次”又不会使一个本应轻量的文档组件变成无界日志容器。聚合统计用于排查不自动等价于攻击证据。九、参考与边界华为 ArkWeb 白屏排查与生命周期FAQ更新时间2026-06-26https://developer.huawei.com/consumer/cn/doc/doccenter-dev-faq/faqs-arkweb-174华为“如何解析URL信息”更新时间2026-06-26https://developer.huawei.com/consumer/cn/doc/doccenter-dev-faq/faqs-arkts-159华为 ArkWeb 折叠屏分栏及导航拦截示例更新时间2026-06-26https://developer.huawei.com/consumer/cn/doc/doccenter-dev-faq/faqs-arkweb-194以上链接用于标明接口来源及能力边界。本文八条URL输入、计数、日志和文中所有生成图片均是设计演示素材不构成真实设备运行、安全测试、实际网络访问或者发布审核通过的证明。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进