鸿蒙HarmonyOS AI 应用安全红线实战 —— API Key 管理、网络安全、工具安全、发布扫描

发布时间:2026/7/17 22:46:34
鸿蒙HarmonyOS AI 应用安全红线实战 —— API Key 管理、网络安全、工具安全、发布扫描 一、前言AI 应用安全的五条红线做 AI Agent 应用有五条安全红线绝不能碰Key 不落盘API Key 只在进程内存永不写入文件/数据库/preferences/rawfile/日志。Key 不入 StateAgentState 持久化时Key 绝不被序列化。HTTPS only所有网络请求走 HTTPS禁止明文 HTTP禁止重定向到非 HTTPS。工具最小权限工具默认只读危险操作需用户确认ToolContext 不暴露 Key。发布前扫描HAP 包发布前必须通过密钥扫描确认无泄漏。这五条红线看似简单但在实际工程中每一条都有自己的陷阱。ArkAgent 用 RuntimeCredentials BearerTokenProvider UrlUtil scan-hap-secrets.sh security-baseline.md 构建了完整的安全防护体系。二、API Key 生命周期2.1 RuntimeCredentials内存专用持有器ArkAgent 示例工程的RuntimeCredentialsentry/src/main/ets/session/RuntimeCredentials.ets是 Key 管理的最佳实践参考/** * Keys live only in process memory. They are never written to filesDir, * preferences, rawfile, or logs. Call clearAll() on Ability destroy and * when the page tears down. */ export class RuntimeCredentials { private static instance?: RuntimeCredentials private zhipuApiKey: string private deepseekApiKey: string private zhipuModel: string glm-5.2 // 模型 ID 非敏感 private deepseekModel: string deepseek-v4-flash static shared(): RuntimeCredentials { ... } /** Does not log, persist, or echo the value. */ setApiKey(provider: string, key: string): void { const trimmed key.trim() if (provider deepseek) { this.deepseekApiKey trimmed } else { this.zhipuApiKey trimmed } } /** Callers must not log, persist, or put this into AgentState / UI snapshots. */ getApiKey(provider: string): string { ... } /** Only returns 已输入仅内存 or 未输入 — never the key content. */ keyStatusLabel(provider: string): string { ... } /** Zero out keys. Call on Ability destroy. */ clearAll(): void { this.zhipuApiKey this.deepseekApiKey } }2.2 Key 存储位置红线位置能不能存 Key原因rawfile/❌打入 HAP可被解包提取preferences❌明文存储在设备 filesDirfilesDir 文件❌设备备份可被提取AgentState❌State 会被持久化到文件日志hilog❌日志可被收集错误信息❌可能显示给用户或记入日志Git 提交❌版本控制泄漏进程内存✅唯一允许的位置2.3 页面集成中的密钥流转// 用户输入 Key 后立即清空 UI 缓冲 private applyRuntimeKey(): void { SessionCoordinator.shared().setRuntimeApiKey(this.apiKeyInput) this.apiKeyInput // ← 立即清空 this.refreshKeyStatus() } // 页面销毁清零 aboutToDisappear(): void { this.apiKeyInput SessionCoordinator.shared().clearSecrets() }Key 输入后立即清空 UI 输入框缓冲不让 Key 在State里多留一秒。2.4 ⚠️ 踩坑一rawfile 打包 Key 进 HAP症状阶段 9 验收发现 local key 可能进入 HAP——rawfile 会打包进应用资源HAP 安装包里直接包含 Key 文件。根因如果把provider_keys.local.json放在entry/src/main/resources/rawfile/下HAP 打包时会把它打入资源包。任何人解包 HAP 就能提取 Key。修复禁止在 main rawfile 存放任何 key 文件Key 只通过运行时密码输入内存发布前执行scan-hap-secrets.sh扫描 HAP 包阶段 9 报告踩坑表记录local key 进入 HAPrawfile 会打包进资源 → 禁止 main rawfile Key运行时内存输入 secret scan。2.4 ⚠️ 踩坑二错误信息拼接 Authorization 泄漏症状网络请求失败时错误信息里拼接了完整的请求头含Authorization: Bearer xxx日志收集系统记录了这个错误Key 泄漏。根因错误处理代码直接把请求的 cause 信息透传给上层没有做脱敏。HTTP 库的错误消息可能包含完整的请求 URL 和 Header。修复所有可能包含 Key 的错误信息都做Bearer ***替换// HarmonyHttpTransport 和 OpenAICompatibleClient 的错误处理 const sanitized message.replace(/Bearer\s[A-Za-z0-9._\-]/gi, Bearer ***)Eval 模块更进一步用EvalSanitize.redactString做多模式脱敏Bearer ***、sk-***、data:***;base64,***、签名 query、Basic ***。三、密钥注入BearerTokenProvider3.1 SPI 接口export interface BearerTokenProvider { getToken(): Promisestring } export class StaticBearerTokenProvider implements BearerTokenProvider { private readonly token: string constructor(token: string) { this.token token } async getToken(): Promisestring { return this.token } }3.2 注入点// OpenAICompatibleClient.buildHttpRequest const token await this.config.tokenProvider.getToken() if (token.trim().length 0) { throw ArkAgentError.config(missing_token, Bearer token is empty) } headers.set(Authorization, Bearer ${token})Core 不持久化 token——Token 通过 ProviderConfig 的 tokenProvider 注入只在网络调用时取出使用不进入 ModelConfig、不进入 AgentState、不进入日志。四、网络安全4.1 HTTPS only// UrlUtil export function ensureHttps(url: string): Resultvoid, ArkAgentError { if (!url.startsWith(https://)) { return Result.failure(ArkAgentError.security(https_required, Only HTTPS endpoints are allowed)) } return Result.success(undefined as void) }每次请求/stream 前都调UrlUtil.ensureHttps校验——HarmonyHttpTransport 的validateRequest里做了这道门。4.2 禁止重定向security-baseline.md 明确禁止自动跟随非 HTTPS 或跨主域重定向。HarmonyHttpTransport 没有任何followRedirect选项——系统 http 模块默认不跟随且 security-baseline 禁止开启。4.3 Proxy 硬失败private ensureProxyNotConfigured(): void { if (this.config.proxyUrl ! undefined this.config.proxyUrl.trim().length 0) { throw ArkAgentError.config(proxy_unsupported, HTTP proxy is not supported by HarmonyHttpTransport; leave proxyUrl empty) } }鸿蒙ohos.net.http不支持应用层 HTTP 代理。设置 proxyUrl 是硬性配置错误不静默忽略。4.4 错误信息脱敏// HarmonyHttpTransport.mapTransportError // Never include authorization or bodies in cause. const sanitized message.replace(/Bearer\s[A-Za-z0-9._\-]/gi, Bearer ***) return ArkAgentError.transport(http_transport_error, HTTP transport failure, ErrorRetryability.conditional, sanitized)所有可能包含 Key 的错误信息都做了Bearer ***替换。OpenAICompatibleClient 的asError方法也做了同样的脱敏。五、HeaderPolicy敏感头脱敏5.1 RESERVED headersstatic readonly RESERVED: string[] [ authorization, content-type, accept, host, content-length, transfer-encoding, connection, proxy-authorization ]RESERVED headers 不可被 custom headers 覆盖。mergeCustomHeaders撞到 RESERVED 名返回reserved_header错误——不静默丢弃显式报错。5.2 SENSITIVE 脱敏static readonly SENSITIVE_NAME_HINTS: string[] [ authorization, api-key, apikey, x-api-key, token, secret, password, cookie, set-cookie ] static redactHeadersForLog(headers: HttpHeaders): JsonObject { // 匹配 SENSITIVE_NAME_HINTS 的 header → 值替换为 *** }所有日志输出的 header 都经redactHeadersForLog脱敏。六、工具安全6.1 最小权限security-baseline.mdTool 默认最小权限读、写、删除、支付、发送、系统设置分级。危险 Tool 在 beforeToolCall 要求用户确认。6.2 ToolContext 不暴露 KeyToolContext 不暴露 API Key 和任意 Service Locator。ToolContext 只有sessionId只读 state、batchCallId、signal、services有限白名单 ToolServiceRegistry。没有 Key、没有任意 Service Locator。6.3 路径遍历防护// safeSessionFileName // 拒绝空、..、/、\\、控制字符 // 非安全字符替换为 _State 文件路径用safeSessionFileName校验防止../../etc/passwd作为 sessionId 读取任意文件。七、scan-hap-secrets.sh发布前扫描7.1 四类扫描# scripts/scan-hap-secrets.sh # Fails if any of the following appear in main resources or packaged HAP: # provider_keys.local.json... Authorization/Bearer headers with non-redacted material... # placeholder/sentinel key strings... # Does NOT print secret values. Hits are reported by path pattern name only.扫描类型检查内容main rawfile禁provider_keys.local.json 禁getRawFileContentSync(...provider_keys...)HAP 文件名禁止含密钥文件名placeholder/sentinel禁REPLACE_WITH_ZHIPU_API_KEY/REPLACE_WITH_DEEPSEEK_API_KEY/arkagent-test-sentinel-key-do-not-ship非脱敏 Bearer禁Bearer [A-Za-z0-9._\-]容忍Bearer ***7.2 验收证据阶段 9 报告scan-hap-secrets.sh OK clean主 HAP 无 local key 文件 / placeholder / 非脱敏 Bearer。八、EvalSanitize评测脱敏Eval 模块的EvalSanitize.redactString做了多模式脱敏// Bearer *** Authorization Header // sk-*** API Key 前缀 sk- // data:***;base64,*** Base64 数据 // 签名 querySignature|X-Amz-Signature|token|access_token|Expires // Basic *** Basic Auth所有 Trace/Recording/Report 出口都经此脱敏——保证评测产物不泄漏敏感信息。九、风险登记册ArkAgent 的risk-register.md记录了 12 条风险ID风险严重度状态R-002签名凭据进入版本控制严重开放R-003ArkTS 递归 JSON 类型中已缓解R-004HarmonyOS HTTP 流被缓冲高开放SPI 逃生通道R-005Provider 兼容协议漂移中已缓解90 天复验R-006任意 JS 无安全沙箱严重1.0 不实现ADR-0012R-007暂停恢复重复 Tool 副作用严重已缓解配对 checkpointR-009reasoning/用户数据进入遥测高已缓解脱敏R-010Sub-agent 资源费用失控高开放预算限制R-012多模态大对象内存压力高开放大小限制URI关键设计风险登记册不假装风险已解决——R-004HTTP 缓冲至今保持开放状态用 Transport SPI 留逃生通道。这种诚实面对未知的工程态度是安全的基础。十、security-baseline.md 发布门禁6 条发布安全门禁secret 扫描无高风险危险 Tool 确认测试路径穿越测试脱敏测试依赖许可证清单在线 Provider 测试无请求内容泄漏十一、最佳实践清单密钥管理✅ Key 只在运行时内存输入密码框不写入任何持久化存储。✅ Key 输入后立即清空 UI 缓冲。✅ 页面 aboutToDisappear / Ability onDestroy 调用 clearAll()。✅ Key 通过 BearerTokenProvider 注入不进入 ModelConfig/AgentState。❌ 不把 Key 写入 rawfile/preferences/filesDir/Git。网络安全✅ 所有请求 HTTPS onlyUrlUtil.ensureHttps。✅ 禁止重定向到非 HTTPS。✅ Proxy 硬失败不静默忽略。✅ 错误信息 Bearer *** 脱敏。工具安全✅ 工具默认最小权限。✅ 危险工具标 ToolRiskLevel.dangerous 审批。✅ ToolContext 不暴露 Key/任意 Service Locator。✅ 路径参数用 safeSessionFileName 校验。发布安全✅ 发布前跑 scan-hap-secrets.sh。✅ 日志 header 经 redactHeadersForLog 脱敏。✅ Eval 产物经 EvalSanitize 脱敏。✅ RESERVED headers 不可被 custom headers 覆盖。十二、常见错误对照表错误做法问题正确做法Key 写入 rawfileHAP 可被解包提取运行时内存输入Key 留在 UI State页面销毁前暴露应用后立即清空AgentState 存 Key持久化泄漏密钥State 只存可序列化非敏感数据日志输出 Authorization日志收集泄漏Bearer *** 脱敏错误信息拼接请求体泄漏 prompt/Header只显示错误码和简短说明HTTP 明文传输中间人攻击HTTPS only自动跟随重定向钓鱼重定向禁止重定向proxyUrl 静默忽略行为不可预测硬性 config error工具能访问 KeyKey 泄漏风险ToolContext 不暴露 Key危险工具自动执行不可逆操作ToolRiskLevel.dangerous 审批sessionId 不校验路径路径遍历读任意文件safeSessionFileName发布不扫密钥HAP 内残留 Keyscan-hap-secrets.sh十三、验证清单密钥Key 输入后输入框立即清空退出页面后 Key 不保留AgentState 持久化文件中无 API Key错误信息不含 Key / Authorization日志不含 Key / Authorization网络所有请求 HTTPSHTTP URL 被拒绝无重定向proxyUrl 被拒绝工具ToolContext 无 Key危险工具触发审批路径参数防遍历发布scan-hap-secrets.sh 无命中HAP 无 placeholder/sentinelHAP 无非脱敏 Bearer设备 filesDir 无 Key 文件十四、构建验证# 构建后扫描 NODE_HOME/Applications/DevEco-Studio.app/Contents/tools/node \ DEVECO_SDK_HOME/Applications/DevEco-Studio.app/Contents/sdk \ /Applications/DevEco-Studio.app/Contents/tools/hvigor/bin/hvigorw assembleHap --no-daemon # 密钥扫描 bash scripts/scan-hap-secrets.shscan-hap-secrets.sh: OK clean十五、写在最后Key 只进内存不落盘页面销毁就清零。 BearerTokenProvider 注入State 日志永不存。 HTTPS only 不重定向proxy 硬失败不留情。 错误信息不拼体Bearer 星号来脱敏。 RESERVED 头不可盖敏感头日志打星号。 工具最小权限走危险操作要审批。 ToolContext 不露 KeyService 白名单来把关。 sessionId 防遍历safeSessionFileName 保平安。 发布之前扫密钥scan-hap-secrets 不能少。 风险登记不假装开放风险留通道。本文是 ArkAgent 鸿蒙教程系列的第十四篇也是最后一篇。从架构全景到快速接入从流式输出到 JSON 类型安全从工具调用到状态持久化从控制安全到记忆子 Agent从技能规划到评估框架从测试策略到安全红线——十四篇文章构成了鸿蒙 AI Agent SDK 开发的完整知识体系。如果你正在鸿蒙上做 AI 应用可以把这套架构和设计决策直接作为你的工程基线。