一、前言:AI 应用安全的"五条红线"

做 AI Agent 应用,有五条安全红线绝不能碰

  1. Key 不落盘:API Key 只在进程内存,永不写入文件/数据库/preferences/rawfile/日志。

  2. Key 不入 State:AgentState 持久化时,Key 绝不被序列化。

  3. HTTPS only:所有网络请求走 HTTPS,禁止明文 HTTP,禁止重定向到非 HTTPS。

  4. 工具最小权限:工具默认只读,危险操作需用户确认,ToolContext 不暴露 Key。

  5. 发布前扫描: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

明文存储在设备 filesDir

filesDir 文件

设备备份可被提取

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 进入 HAP:rawfile 会打包进资源 → 禁止 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 ***


三、密钥注入:BearerTokenProvider

3.1 SPI 接口

export interface BearerTokenProvider {
  getToken(): Promise<string>
}

export class StaticBearerTokenProvider implements BearerTokenProvider {
  private readonly token: string
  constructor(token: string) { this.token = token }
  async getToken(): Promise<string> { 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): Result<void, 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 headers

static 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.md:

  • "Tool 默认最小权限;读、写、删除、支付、发送、系统设置分级。"

  • "危险 Tool 在 beforeToolCall 要求用户确认。"

6.2 ToolContext 不暴露 Key

"ToolContext 不暴露 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 数据)
// 签名 query(Signature|X-Amz-Signature|token|access_token|Expires)
// Basic *** (Basic Auth)

所有 Trace/Recording/Report 出口都经此脱敏——保证评测产物不泄漏敏感信息。


九、风险登记册

ArkAgent 的 risk-register.md 记录了 12 条风险:

ID

风险

严重度

状态

R-002

签名凭据进入版本控制

严重

开放

R-003

ArkTS 递归 JSON 类型

已缓解

R-004

HarmonyOS HTTP 流被缓冲

开放(SPI 逃生通道)

R-005

Provider 兼容协议漂移

已缓解(90 天复验)

R-006

任意 JS 无安全沙箱

严重

1.0 不实现(ADR-0012)

R-007

暂停恢复重复 Tool 副作用

严重

已缓解(配对 checkpoint)

R-009

reasoning/用户数据进入遥测

已缓解(脱敏)

R-010

Sub-agent 资源费用失控

开放(预算限制)

R-012

多模态大对象内存压力

开放(大小限制+URI)

关键设计:风险登记册不假装风险已解决——R-004(HTTP 缓冲)至今保持"开放"状态,用 Transport SPI 留逃生通道。这种"诚实面对未知"的工程态度是安全的基础。


十、security-baseline.md 发布门禁

6 条发布安全门禁:

  1. secret 扫描无高风险

  2. 危险 Tool 确认测试

  3. 路径穿越测试

  4. 脱敏测试

  5. 依赖许可证清单

  6. 在线 Provider 测试无请求内容泄漏


十一、最佳实践清单

密钥管理

  • ✅ Key 只在运行时内存输入(密码框),不写入任何持久化存储。

  • ✅ Key 输入后立即清空 UI 缓冲。

  • ✅ 页面 aboutToDisappear / Ability onDestroy 调用 clearAll()。

  • ✅ Key 通过 BearerTokenProvider 注入,不进入 ModelConfig/AgentState。

  • ❌ 不把 Key 写入 rawfile/preferences/filesDir/Git。

网络安全

  • ✅ 所有请求 HTTPS only(UrlUtil.ensureHttps)。

  • ✅ 禁止重定向到非 HTTPS。

  • ✅ Proxy 硬失败(不静默忽略)。

  • ✅ 错误信息 Bearer *** 脱敏。

工具安全

  • ✅ 工具默认最小权限。

  • ✅ 危险工具标 ToolRiskLevel.dangerous + 审批。

  • ✅ ToolContext 不暴露 Key/任意 Service Locator。

  • ✅ 路径参数用 safeSessionFileName 校验。

发布安全

  • ✅ 发布前跑 scan-hap-secrets.sh。

  • ✅ 日志 header 经 redactHeadersForLog 脱敏。

  • ✅ Eval 产物经 EvalSanitize 脱敏。

  • ✅ RESERVED headers 不可被 custom headers 覆盖。


十二、常见错误对照表

错误做法

问题

正确做法

Key 写入 rawfile

HAP 可被解包提取

运行时内存输入

Key 留在 UI @State

页面销毁前暴露

应用后立即清空

AgentState 存 Key

持久化泄漏密钥

State 只存可序列化非敏感数据

日志输出 Authorization

日志收集泄漏

Bearer *** 脱敏

错误信息拼接请求体

泄漏 prompt/Header

只显示错误码和简短说明

HTTP 明文传输

中间人攻击

HTTPS only

自动跟随重定向

钓鱼重定向

禁止重定向

proxyUrl 静默忽略

行为不可预测

硬性 config error

工具能访问 Key

Key 泄漏风险

ToolContext 不暴露 Key

危险工具自动执行

不可逆操作

ToolRiskLevel.dangerous + 审批

sessionId 不校验路径

路径遍历读任意文件

safeSessionFileName

发布不扫密钥

HAP 内残留 Key

scan-hap-secrets.sh


十三、验证清单

密钥

  • Key 输入后输入框立即清空

  • 退出页面后 Key 不保留

  • AgentState 持久化文件中无 API Key

  • 错误信息不含 Key / Authorization

  • 日志不含 Key / Authorization

网络

  • 所有请求 HTTPS

  • HTTP URL 被拒绝

  • 无重定向

  • proxyUrl 被拒绝

工具

  • ToolContext 无 Key

  • 危险工具触发审批

  • 路径参数防遍历

发布

  • scan-hap-secrets.sh 无命中

  • HAP 无 placeholder/sentinel

  • HAP 无非脱敏 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.sh
scan-hap-secrets.sh: OK clean

十五、写在最后

Key 只进内存不落盘,页面销毁就清零。 BearerTokenProvider 注入,State 日志永不存。 HTTPS only 不重定向,proxy 硬失败不留情。 错误信息不拼体,Bearer 星号来脱敏。 RESERVED 头不可盖,敏感头日志打星号。 工具最小权限走,危险操作要审批。 ToolContext 不露 Key,Service 白名单来把关。 sessionId 防遍历,safeSessionFileName 保平安。 发布之前扫密钥,scan-hap-secrets 不能少。 风险登记不假装,开放风险留通道。

本文是 ArkAgent 鸿蒙教程系列的第十四篇,也是最后一篇。从架构全景到快速接入,从流式输出到 JSON 类型安全,从工具调用到状态持久化,从控制安全到记忆子 Agent,从技能规划到评估框架,从测试策略到安全红线——十四篇文章构成了鸿蒙 AI Agent SDK 开发的完整知识体系。如果你正在鸿蒙上做 AI 应用,可以把这套架构和设计决策直接作为你的工程基线。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐