导航闸门怎么接、引擎里改不了的安全属性怎么办、JS 门面的静默降级怎么防——这篇文章用一个 Cordova / HarmonyOS 安全基线插件,把这三个问题一次讲透。

仓库地址:https://atomgit.com/lqjmac/cordova-plugin-harmony-webguard
版本地址:https://atomgit.com/CPF-Cordova
三方库框架:https://atomgit.com/oh-tpc

0. 环境与实测设备

项版本/型号
DevEco Studio26.0.0.851(HarmonyOS SDK 7.0.0 / API 26)
Cordovav14.0.2
hcordova1.0.7(CPF-Cordova 鸿蒙社区提供)
Cordova OHOS 引擎@cordova-ohos/ohos 14.0.2
cordova-plugin-harmony-webguard1.0.0(本仓库)
Node.js20.19.5(审计 CLI 与 node:test)
测试node:test 19 项:URL 策略、审计规则命中、config.xml 通配边界、空工程 fail-closed、CLI 退出码、Markdown 输出
实测环境HarmonyOS 2in1 模拟器(API 26)

1. 页面能不能加载,容器说了算——这个问题是怎么找上我的

起因其实很简单。给一个 Cordova 鸿蒙应用做安全自查,我的第一反应跟大多数人一样朴素:Cordova 应用的页面不就是自己 www/ 里那点 HTML 吗,加载什么自己控制,能有什么风险?直到我在测试页的链接里塞了一个 javascript:alert(1),又把启动页临时换成 file:// 路径——两个都加载成功了。这时我才意识到,Cordova 应用跑在 ArkWeb 里,WebView 对任何导航来者不拒:window.location 改一下、业务代码拼一个 URL、甚至被注入一段脚本发起的跳转,容器都不会过问。

往深处查才发现一个更根本的问题:Cordova OHOS 引擎把 Web 组件的安全属性——mixedMode、databaseAccess、setPathAllowingUniversalAccess、客户端证书的默认 confirm——全部硬编码在引擎源码 MainPage.ets 里。我原以为写个插件运行时改掉就行,实际上这些属性在 Web 组件创建期就固定了,插件 API 根本够不着。

所以这个题目的边界反而清晰了:能改的,运行时拦;改不了的,构建期审。具体拆成三件事:

  • 导航闸门怎么接(onPageStart + 策略引擎 + JS 预检查,这是核心);
  • 引擎里改不了的安全属性怎么办(构建期扫描 MainPage.ets / module.json5 / config.xml,退出码卡 CI);
  • JS 门面怎么防静默降级(mirror、isNative()、策略校验)。

下面是干完之后的完整记录,包括我踩的每一个坑。

2. 效果展示

按老规矩,先给效果再讲原理。示例应用叫 WebGuardDemo,页面顶部把定位写在标题下:WebGuard · Cordova / HarmonyOS WebView 安全基线,右上角一枚 ArkWeb 徽章。摘要一句话:"Cordova 应用跑在 ArkWeb 里,页面能不能加载由容器决定。这个页面演示三件事:导航前判定 URL、核对引擎配置红线、读构建期审计快照。"下面三个统计数字——3 高危、5 中危、4/8 用例通过——直接告诉你这个演示工程自己就没过审计。

第一个 Tab 是 URL 判定。四个策略开关加上一份实时判定记录:

在这里插入图片描述

开关区是策略的可视化:仅 HTTPS(明文 HTTP 直接拒绝)和禁止 IP 直连(避免绕过域名白名单)默认开启;允许 file://(只用于受控本地页)和允许混合内容(开启会拉低整页安全等级)默认关闭。下面的判定记录是这台模拟器上真实跑出来的——checked 8 · blocked 4,八次导航判定拦下四次:javascript:alert(1) 和 file:///data/storage/el2/base/secret.html 被 BLOCKED_SCHEME 拦下(协议在禁止列表),https://evil.example/phish 被判 HOST_NOT_ALLOWED(不在白名单),明文 http://app.example.com/home 也被拒——同一个主机,换个协议就过不去。页面底部一行小字交代了基线:WebGuard 1.0.0 · Cordova 14.0.2 · HarmonyOS API 26。

第二个 Tab 是配置红线,展示的是运行时改不了、只能构建期审的那部分:

在这里插入图片描述

卡片标题"引擎里的配置红线",右上角标注构建期扫描 · MainPage.ets。说明文字把逻辑讲透了:"这些是 Cordova OHOS 引擎默认值里真实存在的配置。Web 组件属性在创建期固定,插件运行时改不了,所以统一放到构建期审计,用 --fail-on high 卡在 CI。"四条命中逐条列出:WG-WEB-001 高危——混合内容全放行(MainPage.ets:641),建议改为 MixedMode.None;WG-WEB-002 高危——setPathAllowingUniversalAccess 允许 file:// 访问应用沙箱(:502),建议删除该调用;WG-WEB-003 高危——客户端证书请求被默认确认(:232),金融类页面必须走双向认证白名单;WG-WEB-004 中危——Web SQL/IndexedDB 常开(:642)。每条都精确到文件与行号——这是静态扫描,不是泛泛的安全建议。

3. 闸门与红线:运行时和构建期的分工

写代码之前得先把原理捋好。

整个插件的设计哲学就是标题那句话,链路分成两条:

运行时(插件做得到)
  JS 侧:WebGuard.checkUrl(url)            ← 导航前预检查
     │
     ▼
  ArkWeb onPageStart(每次主框架导航广播)
     │  WebGuardPlugin:取当前 URL → 策略引擎同步判定
     ├─ ALLOW  → 放行
     └─ 拒绝   → 立即 stop(),code 记入判定记录

构建期(插件做不到,审计 CLI 补位)
  node lib/cli/webguard-audit.js --root . --fail-on high
     ├─ MainPage.ets     → WG-WEB-001~012(引擎源码里的安全属性)
     ├─ module.json5     → WG-PRM-001(高敏权限声明)
     └─ config.xml       → WG-CFG-001/002(allow-intent / allow-navigation)
          └─ findings 达到阈值 → 退出码 2,CI 失败

先说运行时这条。策略判定本身是纯同步的字符串操作——解析 scheme/host、查禁止列表、查白名单、拦 IP 与 Punycode——没有任何网络和 IO,单次判定微秒级,挂在每次导航的 onPageStart 上毫无压力。策略引擎写成纯函数还有一个收益:同一份规则既能用在原生 onPageStart,也能用在 JS 侧的预检查(类注释原话),页面在改 location 之前就能知道这次跳转会不会被拦。

再说构建期这条,它是整个设计的诚实之处。mixedMode(MixedMode.All) 这类属性写在引擎源码里,Web 组件创建期就固定了——插件在运行时假装能改它,是很多"安全插件"的通病。WebGuard 的做法是明确在能力边界表里写"不能":不能修改创建期属性、不能关闭引擎已调用的 setPathAllowingUniversalAccess、不能自动修复引擎源码——改不了的,交给审计 CLI 扫源码出报告,用退出码逼着构建管线去改。

顺手记一笔性能账:运行时闸门是纯字符串判定,微秒级,不碰网络;审计 CLI 扫的是几个文本文件加十几条正则规则,秒级完成。真正值钱的成本在设计上——把"能改"和"不能改"分清楚,比多写十条规则都重要。

各条路线对照一下:

防护方式时机鸿蒙 Cordova 工程的现状
config.xml 白名单配置期Cordova 内建机制,但引擎实现各异、粒度粗
各应用手写 onPageStart 拦截运行时逻辑散落在业务代码里,没有统一策略
引擎 MainPage 硬编码属性引擎期安全属性固定值,运行时不可改
本库运行时闸门运行时统一策略引擎:协议 / 白名单 / IP / Punycode / 长度
本库构建期审计构建期12 条源码规则 + 权限 + config.xml,退出码卡 CI

能力边界照抄 README 那张表的一半(“不能"的那一半):修改 Web 组件创建期的 mixedMode、databaseAccess——不能;关闭引擎已调用的 setPathAllowingUniversalAccess——不能;修改客户端证书默认 confirm 分支——不能;自动修复引擎源码——不能。了解了这个边界,你就知道这个插件不承诺"一键安全”,它承诺的是把安全状态变成可见、可测、可卡 CI 的东西。

4. 工程怎么搭:一个纯策略内核,三副面孔

先看整体结构。这个插件分三层:JS 门面给业务,ArkTS 策略引擎给原生,审计 CLI 给 CI:

cordova-plugin-harmony-webguard/
├─ plugin.xml
├─ lib/
│  ├─ webguard.js               # JS 门面 + 本地 mirror + isNative()
│  └─ cli/webguard-audit.js     # 构建期审计 CLI(fail-closed)
├─ ohos/
│  └─ src/main/ets/
│     ├─ WebGuardPolicy.ets     # 纯策略引擎(无 Cordova 依赖)
│     └─ WebGuardPlugin.ets     # CordovaPlugin:onPageStart 闸门 + checkUrl
├─ test/                        # node:test 19 项 + fixture 工程
├─ example/                     # www + 宿主补丁 + setup.ps1 一键搭建
├─ docs/                        # 配置红线文章与截图
├─ plugin.xml / package.json
└─ README.OpenHarmony.md / README.OpenHarmony_CN.md

在这里插入图片描述

这次设计最关键的决定有两个:

策略引擎是纯的,插件壳是薄的。 WebGuardPolicy.ets 不 import 任何 Cordova 类型——它只认识 URL 字符串和策略配置,checkUrl 进、CheckResult 出。类注释里写明这样做的收益:“同一份规则既能用在 onPageStart 导航拦截,也能用在 JS 侧的预检查”,而且它能被 node:test 直接测(第 6 节的 19 条回归测试里大半打在策略层)。WebGuardPlugin.ets 则只干两件事(类注释原文):“把策略引擎暴露给 JS(checkUrl / configure / getStats);在 onPageStart 生命周期里做最后一道 URL 闸门。”

审计宁可误杀不可漏杀。 退出码表里有一条刻意的设计:退出码 3 表示没有扫描到目标文件,默认失败,--allow-empty 才放行。README 把理由写得很重:"–root 指错、忘了 platform add harmonyos、引擎升级后 MainPage.ets 换了位置,都不能让 CI 变绿。"审计工具最危险的失败模式不是报错,是扫了个寂寞还显示通过。

还有一件事必须单独讲:Cordova OHOS 的 ArkTS 插件不是自动扫描注册的,宿主要有三步接线——把插件实例放进 PluginEntry 数组传给 MainPage、把插件源码复制到 cordova/src/main/ets/components/WebGuard/、在 cordova/Index.ets 追加导出:

// harmonyos/entry/src/main/ets/pages/Index.ets
import {
  MainPage, PluginEntry, WebGuardPlugin,
  pageBackPress, pageHideEvent, pageShowEvent
} from '@cordova-ohos/ohos/Index';

@Entry
@Component
struct Index {
  private plugs: PluginEntry[] = [
    { pluginName: 'WebGuard', pluginObject: new WebGuardPlugin() }
  ];

  build() {
    RelativeContainer() {
      MainPage({ cordovaPlugs: this.plugs });
    }.height('100%').width('100%');
  }
}

这套步骤被 example/scripts/setup.ps1 自动化了:建工程、加平台、复制 www/、复制插件源码、打 Index.ets 补丁,五步一条命令。对使用方来说,JS 侧的 API 是这样的:

await WebGuard.install({
  allowedHosts: ['www.example.com', 'app.example.com', '*.trusted.example'],
  httpsOnly: true,
  blockIpHost: true,
  allowFileAccess: false,
});

const result = await WebGuard.checkUrl('https://app.example.com/home');
// { allowed: true, code: 'ALLOW', host: 'app.example.com', rule: 'app.example.com' }

const blocked = await WebGuard.checkUrl('javascript:alert(1)');
// { allowed: false, code: 'BLOCKED_SCHEME', reason: '协议 javascript: 在禁止列表中' }

await WebGuard.navigate('https://evil.example'); // 先判定,拒绝则不跳转

5. 把代码讲透

5.1 策略引擎:code 是契约,reason 是给人看的

WebGuardPolicy.ets 的第一个类型定义就是这份契约:

// ohos/src/main/ets/WebGuardPolicy.ets(节选)
export interface CheckResult {
  allowed: boolean;
  code: string;      // 'ALLOW' / 'BLOCKED_SCHEME' / 'HTTP_NOT_ALLOWED'
  reason: string;    // / 'HOST_NOT_ALLOWED' …稳定机器可读枚举
  host: string;
  scheme: string;
  rule: string;
}

接口注释把用法规定得很死:“code 是稳定的机器可读枚举,方便在 CI 和 UI 里做断言,不要用 reason 做判断。”——reason 是中文提示文案,文案会改;code 不会。判定链的顺序就是风险优先级:先查 scheme 禁止列表(BLOCKED_SCHEME),再查 HTTPS(HTTP_NOT_ALLOWED),再查域名白名单(HOST_NOT_ALLOWED),全过才是 ALLOW。示例页判定记录里那四种结论,正好是这条链的四级采样。

默认策略(DEFAULT_POLICY)里藏着整个插件最重要的一条注释:

allowedHosts: ['www.example.com', 'app.example.com', '*.trusted.example'],
// www.example.com 是 Cordova OHOS 把 rawfile/www 映射出来的本地虚拟主机,
// 不是公网域名;不放行它,应用自己的首页会被 onPageStart 拦掉。

白名单必须包含 Cordova 的本地虚拟主机——它长得像个公网域名,其实是自家 rawfile/www 的映射。漏掉它,插件一装上首页就白屏(坑一)。*.trusted.example 这种限定在具体域名下的通配是允许的,审计 CLI 对 config.xml 的通配判定也遵守同一条边界。

5.2 onPageStart 闸门:最后一道,不是唯一一道

WebGuardPlugin 在 initialize 里先干一件与策略无关但越早越好的事:

// ohos/src/main/ets/WebGuardPlugin.ets(节选)
initialize(cordovaInterface: CordovaInterface, cordovaWebView: CordovaWebView): void {
  this.controller = cordovaWebView.getWebviewController();
  // 生产包一律关闭 Web 调试;这行不依赖任何页面,越早执行越好。
  try {
    webview.WebviewController.setWebDebuggingAccess(false);
  } catch (error) { /* … */ }
}

然后是核心闸门。onPageStart 注释把它的定位讲得很准:“Cordova 每次主框架开始导航都会广播 onPageStart。这里拿当前 URL 做一次同步判定;不允许就立刻 stop()。这是’最后一道闸’,不是唯一一道:JS 侧在改 location 之前也应先 checkUrl。”——最后一道闸的意思是它兜底,不是唯一的意思是它不做全部:被注入的脚本发起的导航它拦得住,但业务代码里明知会被拦还要硬跳的失败体验,得靠 JS 侧预检查提前避免。插件还维护一份 GuardStats(checked / blocked / lastCode / lastUrl),getStats 暴露给 JS——示例页判定记录顶部那行"checked 8 · blocked 4"的数据源就是它。

5.3 JS 门面:mirror、isNative 与静默降级的防线

浏览器里直接打开演示页时没有 Cordova 环境,lib/webguard.js 会退回一个本地 mirror——用同一套纯 JS 策略实现回答 checkUrl,让演示页在浏览器里也能跑。这本是便利,却藏着一个危险:如果真机上因为加载问题静默退回了 mirror,所有"原生策略"都成了假象。所以门面做了三层防护:

// 加载路径:require / cordova.require 双路径,拿不到 exec 不再静默 mirror
const result = await WebGuard.checkUrl('https://app.example.com/home');
if (!await WebGuard.isNative()) {
  // 当前在用本地 mirror——排查"改了 native 没生效"先看这个
}

README 里 isNative() 的描述写得很直白:"false 表示在用本地 mirror(浏览器预览或 exec 不可用),排查’改了 native 没生效’时先看这个。"这个函数是坑三的直接产物。另外 install / configure 会先校验策略:未知字段忽略(向前兼容),类型、条数(≤200)或长度(≤253)不合法时返回 rejected Promise——脏配置在 JS 侧就被拒掉,不会带进 native(ArkTS 侧还有一层上限兜底,双保险)。

5.4 构建期审计:12 条源码规则与 fail-closed

lib/cli/webguard-audit.js 扫三类目标。引擎源码 MainPage.ets 上有 12 条 WG-WEB-* 规则,从高到低:001 混合内容全放行、002 setPathAllowingUniversalAccess(注明了触发条件——默认 https 虚拟主机下不触发,改用 file:// 启动页才生效)、003 客户端证书默认 confirm、012 Web 调试开启;清单 config.xml 上是 WG-CFG-*:allow-intent 指向明文 http 或通配主机、allow-navigation 放开任意站点(*、https://*/*、*.com)都判高危,而 https://*.trusted.example/* 这类限定具体域名的通配不误报,tel:* / mailto:* 也不在列;权限 module.json5 上是 WG-PRM-001 高敏权限声明。每条命中带文件路径与行号——示例页"配置红线"Tab 里那些 MainPage.ets:641 就是这么来的。

退出码是这条链路的牙齿:

退出码含义
0通过(无达到阈值的 finding)
1参数或路径错误
2命中 --fail-on 阈值
3没有扫描到目标文件(默认失败,--allow-empty 才放行)

还有一条补丁规则:只找到部分文件(比如 MainPage.ets 缺失)时,报告会明确警告哪些 WG-WEB-* 规则没有执行,不会静默当成干净。审计的公信力就在这些细节里。

5.5 一键搭建:setup.ps1 与 audit:demo

示例工程把"从零到能跑"的全过程脚本化了:powershell scripts/setup.ps1 依次完成 hcordova create 建工程、platform add harmonyos、复制 www/、复制插件源码与 config.xml、给 Index.ets 和 cordova/Index.ets 打补丁。而 npm run audit:demo 扫描仓库内置的 test/fixtures/sample——一个带敏感权限与通配 intent 的故意"脏"工程,干净克隆即可复现 6 条高危(退出码 2 是预期)。演示不需要一个真实工程,fixture 就是可复现的证据——这个思路和供应链审计篇的演练样本一脉相承。

6. 我踩过的坑(每一条都有真实修复记录)

这一节可能是全文最值钱的部分。以下坑全部来自 CHANGELOG 1.0.0 修订记录里的真实修复。

坑一:插件一装上,首页白屏。 默认白名单漏了 www.example.com——它是 Cordova OHOS 把 rawfile/www 映射出来的本地虚拟主机,长得像公网域名,实际是自家首页。它不在白名单里,首页第一次导航就被自家闸门拦掉。修复:默认策略内置它,并在代码注释里专门解释(见 5.1)。教训:安全策略上线前,先用自家首页过一遍闸门。

坑二:审计扫了个寂寞,CI 还是绿的。 最初的审计器 fail-open——--root 指错、忘了 platform add harmonyos、引擎升级换目录,扫到 0 个目标文件就输出"通过"。修复:fail-closed——扫不到任何目标文件时报告 empty、passed=false,CLI 返回退出码 3,需要 --allow-empty 才显式放行;部分文件缺失时还要在报告里警告哪些规则没执行。审计工具最危险的失败不是报错,是没干活却报了平安。

坑三:真机上策略"没生效",其实跑的是 mirror。 示例页以普通 <script> 加载 webguard.js 时,require("cordova/exec") 不可用,门面静默退回本地 mirror——JS 层看一切正常,原生闸门根本没参与。修复:加载改为 require / cordova.require 双路径,新增 WebGuard.isNative() 区分 native 与 mirror。降级可以,静默降级不行——必须给调用方一个"你现在在哪条腿上"的查询。

坑四:config.xml 的通配,误报与漏报两头堵。 一开始 *、https://*/*、*.com、app.*.com 这类放开任意站点的写法没全覆盖;反过来又要保证 https://*.trusted.example/*(限定具体域名的通配)不误报、tel:* / mailto:* 不在列。修复:通配判定按"是否放开到任意主机"划线,并配了边界单测。

坑五:Punycode 只拦了顶级域,子域标签绕过了。 www.xn--... 这种子域标签形式的同形异义域名校验没覆盖。修复:Punycode 拦截下沉到子域标签粒度,JS 门面与 ArkTS 策略两侧同步改——策略改一处,两侧必须同时动,这也是策略引擎抽成纯函数的另一个理由:对照起来容易。

坑六:修复建议写得想当然。 WG-WEB-002(setPathAllowingUniversalAccess)最初的建议是"删除该调用",但默认 https 虚拟主机模式下这个调用并不触发,只有改用 file:// 启动页才生效——按原建议改,多数工程白改。修复:建议里注明触发条件。审计规则的修复建议必须写清楚适用条件,不然就是噪音。

坑七:脏配置一路带进 native。 install / configure 最初不校验,allowedHosts 传个非数组也能过。修复:JS 侧校验类型、条数(≤200)、长度(≤253),不合法返回 rejected Promise;ArkTS 侧保留上限兜底。配置是策略的输入,输入不干净,策略输出的一切都是自欺。

7. 现在怎么用:从一条命令到 CI 卡点

经过上面的整理,这个工程是"运行时插件 + 构建期审计"双形态交付(插件 ID 见 plugin.xml,版本 1.0.0,Apache-2.0)。安装:

# 本地插件
hcordova plugin add ../cordova-plugin-harmony-webguard

# 或从 AtomGit
hcordova plugin add https://atomgit.com/lqjmac/cordova-plugin-harmony-webguard.git

安装后确认 harmonyos/entry/src/main/resources/rawfile/config.xml 出现 WebGuard 的 feature 声明,再按第 4 节完成三步注册(或直接跑 example/scripts/setup.ps1 一键搭建)。运行时闸门即装即用;构建期审计进 CI:

# 扫当前 Cordova 工程(含 harmonyos/),高危命中即失败
node lib/cli/webguard-audit.js --root . --format md --fail-on high

# 输出 JSON 归档
node lib/cli/webguard-audit.js --root . --format json --out build/security/webguard.json

# 干净克隆复现:扫内置 fixture,6 条高危,退出码 2 是预期
npm run audit:demo

完整构建示例应用:cd harmonyos && ohpm install --all && hvigorw assembleHap --mode module -p product=default -p buildMode=debug --no-daemon,再 hdc install(注意工程路径不要含中文,hvigor 会拒绝)。更完整的规则表、退出码语义和接线说明在仓库的文档里:

  • README.OpenHarmony_CN.md:中文,能力边界、注册接线、规则表与退出码语义;
  • README.OpenHarmony.md:英文,内容与中文版一一对应;
  • docs/Cordova鸿蒙WebView安全配置红线.md:配置红线的专题文章与截图;
  • test/:node:test 19 项回归 + fixture 工程。

当前版本在 Cordova 14.0.2 / hcordova 1.0.7 / Cordova OHOS 引擎 @cordova-ohos/ohos 14.0.2、DevEco Studio 26.0.0.851(HarmonyOS SDK API 26)环境下验证:npm test 19 项全绿——覆盖 URL 策略(HTTPS、白名单、*. 子域、IP、Punycode、凭据、长度、参数校验)、审计规则命中、config.xml 通配边界、空工程 fail-closed、CLI 退出码与 Markdown 输出;示例 WebGuardDemo 在 2in1 模拟器实测:URL 判定 checked 8 · blocked 4(javascript:、file://、明文 http、站外域名四种拦截全部命中),配置红线 Tab 读出构建期审计快照 3 高危 5 中危,每条精确到引擎源码行号。能改的运行时拦、改不了的构建期审——这两句话就是整个插件的安全模型,也是它对自己能力边界最诚实的表达。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐