鸿蒙 PC Markdown 编辑器内部隐私与安全评审
仓库地址:https://gitcode.com/VON-/codex_md_oh
代码基线:G3-09 设备收口fc7de5a,G3-10 内部安全评审941a1dc。
Markdown 编辑器为什么是高敏感本地应用
Markdown 文件往往包含设计文档、会议记录、源码片段、账号说明、未发布文章和本地图片。编辑器没有云服务,不代表安全问题自动消失:恶意 Markdown 可以尝试脚本执行,图片和链接可以诱导越权文件读取,拖放 URI 可能指向工作区外,导出可能把危险活动内容分发出去,分享缓存可能留下敏感副本。
OhMarkdown 的安全目标不是“绝对没有风险”,而是建立清晰的信任边界:用户文档默认不外发;ArkWeb 不直接访问任意文件;不可信渲染结果必须净化;文件和资源操作不越过系统授权;写入失败保留缓冲与旧版本;系统分享只在明确点击时发生;日志不记录正文和敏感路径。
G3-10 本轮完成单轮内部评审。它覆盖代码、配置、测试和 npm 已知漏洞,但不是独立第三方渗透测试,也不声称穷尽每条攻击路径。报告将“内部基线通过”和“最终 Beta 安全退出”分开,后者仍需要真机 PDF/分享权限复核、分享缓存策略和独立人员审查。
资产、入口和受信组件
主要资产包括:CodeMirror 当前正文、磁盘 Markdown、文件格式元数据、恢复记录、保存前备份、工作区根授权、图片资源、搜索结果、导出产物和分享缓存。入口包括 Markdown/HTML、公式、Mermaid、代码围栏、文件名、链接、拖放 URI、剪贴板图片、Bridge JSON 和系统选择器 URI。
受信组件并不是“所有原生代码”。ArkUI 工作台有调用文件和系统 Ability 的权力,必须对来自 Web 的 payload 二次校验;ArkWeb 只负责编辑和渲染,不应因为页面来自 HAP 就获得任意文件权。Core File Kit 的授权 URI 是能力边界,应用仍要限制类型、大小、目录和相对路径。
系统打印、分享和选择器是外部能力。应用可以准备净化文档、申请最小权限和处理失败,却不能假设每台模拟器都有 PDF 服务或 Markdown 接收方。缺失系统目标应成为可恢复错误,不应降级到网络上传。
权限最小化
最终模块只声明两个权限:打印和前台剪贴板读取。
"requestPermissions": [ { "name": "ohos.permission.PRINT", "reason": "$string:print_permission_reason", "usedScene": { "abilities": ["EntryAbility"], "when": "inuse" } }, { "name": "ohos.permission.READ_PASTEBOARD", "reason": "$string:pasteboard_permission_reason", "usedScene": { "abilities": ["EntryAbility"], "when": "inuse" } } ]项目没有 INTERNET、定位、相机、麦克风或通讯录权限。READ_PASTEBOARD 是 G3-09 中文剪切粘贴设备故障后补齐,使用场景限定前台 EntryAbility;应用不轮询剪贴板历史,不在后台分析内容。中英文用途说明明确“粘贴文本和文件”,不使用模糊的“正常运行所需”。
最小权限还意味着功能缺席时不偷偷绕过。模拟器没有分享目标时,应用显示 No matching ability,而不是把文件上传到服务器;没有 PDF 服务时停在系统打印能力矩阵,不引入远程转换。
网络边界由权限和 CSP 双重建立
没有 INTERNET 权限是原生层边界,connect-src 'none'是 Web 内容边界。编辑器源页面 CSP 如下:
<metahttp-equiv="Content-Security-Policy"content="default-src 'none'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src data: blob: resource: file:; font-src data:; connect-src 'none'; object-src 'none'; base-uri 'none'; form-action 'none'">生产包是单 HTML,脚本、样式、KaTeX 字体、Mermaid 和 CodeMirror 全部进入 HAP;自动化断言没有外部 script/link 子资源。unsafe-inline是单文件打包的当前成本,但脚本来源仍是受控 HAP,Markdown 内容先经过解析和 DOMPurify,不能变成新 script 节点。
图片允许 blob、resource 和 file scheme,是本地预览表示需要;ArkWeb 本身仍设置fileAccess(false),本地资源必须由 AssetService 读取后分块交付。CSP 不是文件授权,二者共同限制才有意义。
ArkWeb 不是系统文件代理
工作台创建 Web 组件时显式关闭文件、DOM 存储和定位:
Web({src:$rawfile('editor/index.html'),controller:this.editorController}).javaScriptAccess(true).domStorageAccess(false).imageAccess(true).fileAccess(false).geolocationAccess(false).javaScriptProxy({object:this.editorBridge,name:'ohMarkdownBridge',methodList:['onReady','onState','onChange','onSnapshot','onAssetImport','onAssetRead','onCommand']})JavaScript 必须开启,因为 CodeMirror 和渲染管线运行在 ArkWeb;安全策略不是关掉 JavaScript,而是固定页面来源、固定 Proxy、限制文件与网络、净化不可信内容。七个 Bridge 方法都是事件或受限请求,没有execute、readFile(path)或通用对象反射。
图片与链接 payload 分别有 14 MiB 和 8 MiB 总上限。原生层再次验证 requestId、sessionId、MIME、Base64、长度、路径段和当前文档会话。Web 页面即使构造伪造 JSON,也不能直接选择任意目标路径。
原生向 Web 动态传参的注入检查
WorkspaceShell 多处使用runJavaScript,这是混合架构最需要审查的表面。内部评审逐项检查文件名、正文、相对路径、错误信息、搜索词、替换词和快捷键 JSON,全部使用JSON.stringify形成合法字符串字面量;偏移与尺寸经过数值解析和范围校验。
例如图片插入:
awaitthis.editorController.runJavaScript(`window.OhMarkdownEditor?.insertNativeDroppedAsset(`+`${JSON.stringify(asset.relativePath)},`+`${JSON.stringify(asset.storedName)}, null, null)`);文件名即使包含引号、换行或);alert(1);//,也只成为字符串内容。请求命令由固定分支和白名单处理,不将菜单文案或用户查询反向解析为代码。
长期风险是 WorkspaceShell 很大,新路径容易忘记编码。评审将超大组件记为 P2 可维护性风险,G4 应按真实领域拆分 Bridge 调用封装,但 Beta 退出前不做无行为收益的大规模重构。
普通 Markdown 的第一层净化
markdown-it 把 Markdown 转为 HTML 后立即进入 DOMPurify:
functionsanitizeMarkdown(content:string):string{constunsafeHtml=markdownRenderer.render(content);returnDOMPurify.sanitize(unsafeHtml,{USE_PROFILES:{html:true},FORBID_TAGS:['style','iframe','object','embed','form'],FORBID_ATTR:['style']});}原始 HTML 默认不作为可信能力。脚本、事件属性和危险 URL 由 DOMPurify 处理,style、iframe、object、embed 和 form 进一步禁止。预览外部链接点击统一 preventDefault,只把#anchor或无 scheme 的相对目标交给 LinkService;HTTP 链接当前不会从预览直接打开。
恶意测试包含 script、标题闭合、javascript URL、远程图片、危险属性和 HTML 表单。预览 script 节点必须为零,页面标志不能被执行。安全测试和正常 GFM、公式、图片共同跑,避免修安全时把普通渲染完全破坏。
KaTeX、Mermaid 和代码的第二层净化
专业渲染器不信任库输出。KaTeX 使用trust:false、throwOnError:true、maxExpand 1000 和 maxSize 20,输出再以 HTML/MathML/SVG profile 净化。Highlight.js 只处理已知语言,结果只允许 span 与 class。
Mermaid 面更复杂:strict securityLevel、htmlLabels false、最大 24 图、每图 50,000 字符、最多 500 边;禁止%%{...}%%配置指令;render 结果再次禁止 script、foreignObject、iframe、object、embed、a、href 和 xlink:href。这样用户图表不能覆盖应用安全配置,也不能用 SVG 链接建立第二条导航通道。
单个公式或图表错误被替换为可点击的错误按钮,只影响当前节点。局部错误“settled”后导出仍可继续,不会因为攻击者提供复杂语法把整个输出锁死 30 秒。
本地图片与路径约束
AssetService 只接受 PNG、JPEG、GIF 和 WebP,单项最大 10 MiB。文件名去除控制字符和桌面保留字符,目标只能进入assets或“文档名.assets”。读取请求必须正好两段相对路径,不接受..、反斜线、查询串或片段。
Reference 模式并非“任意路径引用”。源必须位于已授权父目录下,且第一段是受管资源目录;工作区外图片在设备中明确拒绝。Copy/Move 先写临时文件、确认字节和 fsync,再替换目标;Move 只有落盘成功后才删除源;任何失败都不插入 Markdown 链接。
Web 本地预览只接收原生验证后的 MIME、长度和 Base64,分块协议绑定 requestId、sessionId 与 relativePath。切换标签后迟到的图片不会进入错误会话。
工作区搜索和链接不会越界
SearchService 上限为 5000 文档、2000 目录、单目录 2000 条、单文档 4 MiB和 500 结果,使用 lstat 跳过符号链接。单篇匹配在 TaskPool 执行,取消通过代际和 taskpool.cancel 双重实现。本轮 1000 文件模拟器压力发现/扫描 1000、跳过 0。
LinkService 拒绝绝对路径、外部 scheme、反斜线、查询参数、NUL 和工作区外归一化结果;读取文档使用 NOFOLLOW。中文路径逐段编码,标题锚点来自离线文档。预览不直接导航 URL,而是把 href 和当前正文交给原生解析,目标通过后才打开授权内 Markdown。
“本地优先”不意味着用户写的../../secret.md应被信任。应用必须把工作区根作为显式边界,错误要解释“outside workspace”而不是默默跳转。
保存与恢复安全
DocumentService 读取上限 20 MiB,按 64 KiB 解码 UTF-8,要求最终字节数与读取前 stat 一致;无效 UTF-8 和读取期间变化会失败。保存比较实际写入字节与预期 UTF-8 字节,随后 truncate 和 fsync。
用户 URI 不一定支持同目录 rename,所以应用先把旧正文和格式写入沙箱 PendingSaveBackup。RecoveryService 使用.new、.bak、fsync 与 rename 维护恢复记录;启动时修复中断事务。保存失败不会把编辑缓冲标成已保存,也不会清掉备份。
ohosTest 覆盖 BOM/CRLF 字节一致、混合换行、目标不可用时旧版本恢复和写入后指纹变化。安全评审将数据完整性与脚本安全视为同等级底线,因为对编辑器而言,静默覆盖就是最高影响问题之一。
导出需要重新净化
导出不是直接复用 Markdown 字符串,也不是盲取 preview.innerHTML。应用等待 KaTeX、Mermaid、Highlight 和本地图片最终代际;克隆预览;把受管图片转 Data URL;移除内部 data 属性;再次 DOMPurify;用静态 HTML 外壳和严格 CSP 封装。
导出 CSP 使用script-src 'none'、connect-src 'none'、img-src data:和font-src data:。HTML 离开应用后仍不依赖脚本执行或 CDN。标题单独 HTML 转义,文件名单独清理,不能把两个边界混为一谈。
PNG 捕获校验宽高和 16000 px 系统上限,PixelMap 与 ImagePacker 在 finally 释放;错误恢复原视图。PDF 交给系统打印适配器,应用不引入远程转换。模拟器打印预览已通过但没有 PDF 服务,所以文章不把预览写成文件成功。
分享权限与缓存残留
分享从当前 CodeMirror 缓冲生成 cacheDir Markdown,不直接分享可能落后的磁盘 URI。Want 使用text/markdown、stream URI 和FLAG_AUTH_READ_URI_PERMISSION,不给写权限,也不指定固定第三方 bundle。系统无接收方时返回失败,正文和脏标记保持。
当前发现一个 P2:不同文件名会形成多个share-*.md,依赖系统缓存清理或同名覆盖。文件仍在应用沙箱,只有用户明确分享的单 URI 获得临时读权,所以不是已验证的跨应用泄露;但最小留存原则要求 G4 前增加有界过期清理。
不能在 startAbility 返回后立即删除,因为目标可能稍后读取。正确方案要在真机测量分享目标生命周期,再选择例如下一次启动清理超过 24 小时的受控前缀文件。为了追求“立即无残留”而让分享偶发失败,同样不可靠。
日志与隐私
生产日志记录读取字节、字符数、耗时和 UDMF record type,不记录正文、文件名或完整路径。Ability 生命周期和窗口加载错误不包含用户文档。项目没有遥测 SDK,也没有网络发送点。
性能测试使用合成“唯一压力命中”和 report 序号。内部试用和封闭测试的数据字典规定不收集正文、文件名、路径、链接或图片。以后若增加崩溃附件,必须让用户查看、关闭和清除,不能因为“本地工具需要排错”默认上传。
npm 已知漏洞审计
本轮分别执行生产依赖和全部依赖审计。运行时 143 个依赖、总计 221 个依赖,info、low、moderate、high、critical 均为 0。检查以 2026-07-21 当前 lockfile 与 npm 官方审计接口为准。
npm audit 的零不是永久证明。它不覆盖未披露漏洞、恶意包维护者或 HarmonyOS SDK;版本升级也会改变结论。DOMPurify、Mermaid、KaTeX、CodeMirror 和 markdown-it 每次升级都必须重跑恶意语料、CSP、离线单 HTML 和导出净化。
供应链的另一个边界是运行时不从 npm/CDN加载。所有依赖在构建期进入单 HTML,应用安装后不执行包管理器,也不动态下载插件。这大幅缩小运行时依赖变更面。
应用内部安全证据
下图是最终 HAP 的设置与导出面板。它展示 HTML、PDF、PNG、Markdown 分享、自动保存和图片资源规则都位于同一原生工作台;这些入口分别受到文件选择器、打印、ImagePacker、只读分享 URI 和受管资源目录约束。
截图证明应用入口和本地化,不单独证明脚本零执行或路径不越界。安全结论来自代码边界、恶意自动化、ohosTest、设备失败路径和 npm 审计共同组成。系统打印和分享图也保留在测试报告中,明确记录环境能力缺席。
评审发现与严重度
本轮未发现 P0/P1。P2 一是分享缓存过期清理;P2 二是 WorkspaceShell 与 Web main 文件较大,安全规则分布广,未来改动遗漏 JSON 编码或状态清理的概率上升。第二项是可维护性风险,不是已验证漏洞。
还有环境项:PDF 文件、成功分享目标和独立 HTML/PNG查看未闭环;真机系统权限策略未复验。这些不把现有代码判成漏洞,但阻止导出能力与本地隐私记分升到 4。
若后续出现正文外发、路径越权、危险脚本执行、保存覆盖或恢复丢失,必须立即升级 P0/P1,停止扩展并撤销当前基线通过结论。
内部通过不等于最终发布通过
内部评审完成了 G3-10 一个独立小阶段,可以继续竞品和真机工作。但 G3 Beta 退出仍要求 50-100 人封闭测试、核心任务领先目标、崩溃恢复指标和完整系统导出。安全方面建议在对外 Beta 前由独立人员复核高影响路径,而不是只依赖实现者自审。
真机重点包括:剪贴板权限撤销、分享目标读取期限、PDF 服务、DocumentSave 用户 URI、只读文件、空间不足、系统字体、物理触控板和多输入法。每项保留 HAP 哈希与原始结果。
对产品优势的意义
OhMarkdown 想形成的优势不是“功能最多”,而是文件可靠、本地优先和鸿蒙 PC 原生任务连续。安全边界让这些优势可成立:图片自动管理但不越权;链接补全但不离开工作区;Mermaid 可渲染但不执行活动内容;分享当前正文但不暴露原目录;导出复杂内容但不携带脚本。
当前内容安全和离线完整保持 3 分,有模拟器与自动化证据;没有真机和独立竞品任务前不能升 4。宣传也不能使用“绝对安全”或“全面领先”。更合适的表达是“默认离线、不申请网络权限、受限 Bridge、危险内容净化”,并提供可追溯报告。
结论
鸿蒙 PC Markdown 编辑器的内部安全评审必须同时覆盖权限、网络、ArkWeb、Bridge、渲染、文件、恢复、导出、分享、日志和依赖。OhMarkdown 当前没有 INTERNET,编辑器 CSP 禁止连接,ArkWeb 禁止任意文件访问,Proxy 只有七项白名单,动态参数 JSON 编码,不可信 HTML/MathML/SVG 多层净化,路径与文件大小有界,保存失败保留恢复事实。
两轮 npm 审计为零已知漏洞,Playwright 44/44、ohosTest 10/10 和设备失败路径共同通过。本轮内部基线未发现 P0/P1,同时公开记录分享缓存与超大组件两个 P2。安全结论因此既不是“什么都没问题”,也不是“环境不全所以无法前进”,而是一份明确说明已经证明什么、还必须证明什么的 Beta 工程事实。