Firecrawl API Key SecretRef 在配置状态已解析的情况下仍返回 401 Unauthorized
使用 1Password SecretRefs 配置 Firecrawl webFetch.apiKey 时,尽管网关配置显示密钥已解析且已掩码,但请求仍然失败并返回 401 Unauthorized
🔍 症状
主要错误表现
当 web_fetch 请求通过 Firecrawl 插件路由,且 API 密钥使用 1Password SecretRef 时,尽管网关显示密钥已正确解析,但操作仍会因认证错误而失败。
shell
终端 1:验证配置解析状态
$ openclaw config get plugins.entries.firecrawl.config.webFetch.apiKey –verbose
sourceConfig: “op://openclaw/Firecrawl API key/credential” resolved: “••••••••••••••••” resolvedAt: “2026-04-15T10:23:41Z” status: “resolved”
shell
终端 2:执行 web_fetch 请求
$ openclaw tools web-fetch “https://example.com”
Error: Firecrawl API error (401): Unauthorized: Invalid token at FirecrawlProvider.fetch (firecrawl-provider.ts:147) at WebFetchTool.execute (web-fetch-tool.ts:89) at Gateway.handleToolCall (gateway.ts:204)
配置检查差异
运行时配置视图显示该字段为 resolved 和 masked,这表明物化层接受了 SecretRef。然而,Firecrawl 请求路径似乎接收到以下之一:
- 未解析的原始 `op://...` 字符串
- 过时的或不正确的配置快照
- 已解析但未传播到提供程序实例的值
诊断 CLI 命令
shell
验证插件注册和运行时状态
openclaw plugins list –verbose | grep -A5 firecrawl
检查提供程序实际使用的 API 密钥
openclaw debug provider firecrawl –show-config
启用密钥解析的跟踪日志
OPENCLAW_LOG_LEVEL=trace openclaw gateway start 2>&1 | grep -i “firecrawl|secretref|apiKey”
环境上下文
- OpenClaw 版本: 2026.4.11
- 操作系统: Ubuntu(Linux 内核 5.15+)
- 运行时模式: 网关本地模式
- 密钥提供商: 1Password CLI(`op://` 方案)
- 网关配置: JSON 配置文件
🧠 根因分析
架构故障点
该 bug 源于网关配置解析层与 Firecrawl 提供程序请求执行层之间的配置快照隔离失败。
┌─────────────────────────────────────────────────────────────────────┐ │ GATEWAY PROCESS │ ├─────────────────────────────────────────────────────────────────────┤ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │ │ Config Loader │───▶│ Secret Resolver │───▶│ Runtime Config │ │ │ │ (JSON/YAML) │ │ (1Password CLI) │ │ Snapshot │ │ │ └─────────────────┘ └─────────────────┘ └────────┬────────┘ │ │ │ │ │ │ snapshot │ │ │ copied │ │ ▼ │ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │ │ WebFetchTool │───▶│ FirecrawlProv │───▶│ Provider Init │ │ │ │ (web-fetch) │ │ Instance │ │ (ctor/snapshot)│ │ │ └─────────────────┘ └─────────────────┘ └────────┬────────┘ │ │ │ │ │ │ uses │ │ ▼ │ │ ┌─────────────────┐ │ │ │ Firecrawl REST │ │ │ │ API Call │ │ │ └─────────────────┘ │ └─────────────────────────────────────────────────────────────────────┘
故障序列
- 配置加载阶段: 网关加载包含 `plugins.entries.firecrawl.config.webFetch.apiKey: "op://openclaw/Firecrawl API key/credential"` 的 JSON 配置。
- 密钥解析阶段:
SecretResolver服务处理 1Password SecretRef 并检索明文令牌。运行时配置视图正确显示为已解析和已掩码。 - 提供程序初始化阶段(BUG): 当
FirecrawlProvider被实例化时,它接收到的是过时的配置快照,而不是解析后的配置对象。提供程序的构造函数捕获了:// firecrawl-provider.ts - 构造函数(有 bug) constructor(config: FirecrawlConfig) { // 在初始化时捕获配置快照 this.apiKey = config.webFetch.apiKey; this.baseUrl = config.webFetch.baseUrl; // ... } - 请求执行阶段: 当调用
web_fetch时,提供程序使用this.apiKey,其中仍包含原始的 SecretRef 字符串"op://openclaw/Firecrawl API key/credential",因为快照是在密钥解析完成之前拍摄的。 - Firecrawl API 拒绝: Firecrawl API 接收到字面量
op://...字符串作为 API 密钥,导致401 Unauthorized错误。
代码路径分析
根本原因表现在 src/plugins/firecrawl/firecrawl-provider.ts 中的初始化顺序:
// 有问题的初始化顺序
class FirecrawlProvider {
private apiKey: string;
private baseUrl: string;
// 在网关启动期间调用,接收预解析配置
constructor(config: FirecrawlConfig) {
this.apiKey = config.webFetch.apiKey; // ← 接收 "op://..." 字符串
this.baseUrl = config.webFetch.baseUrl;
}
// 此方法在请求时调用,但使用的是捕获的值
async fetch(url: string): Promise<FetchResult> {
const headers = {
'Authorization': `Bearer ${this.apiKey}`, // ← 发送未解析的引用
'Content-Type': 'application/json'
};
// ...
}
}与正常工作明文路径的对比
使用明文配置时,不需要密钥解析:
{
"plugins": {
"entries": {
"firecrawl": {
"config": {
"webFetch": {
"apiKey": "fc-actual-token-plaintext" // ← 直接赋值
}
}
}
}
}
}提供程序直接接收并存储实际令牌,绕过有问题的快照机制。
相关架构模式
此故障与 SecretRef 运行时物化 bug #28359 具有相同的特征,其中在不同生命周期阶段拍摄的配置快照包含不一致的解析状态。Firecrawl 提供程序的构造函数在初始化时捕获状态,但密钥解析对于此特定配置路径发生在初始化之后。
🛠️ 逐步修复
选项 1:延迟解析(推荐)
修改 Firecrawl 提供程序,在请求时而非构造时执行密钥解析。
文件: src/plugins/firecrawl/firecrawl-provider.ts
typescript // 修复前(有 bug) class FirecrawlProvider { private apiKey: string;
constructor(config: FirecrawlConfig) { this.apiKey = config.webFetch.apiKey; // 在初始化时捕获 }
async fetch(url: string): PromiseBearer ${this.apiKey} }
});
}
}
// 修复后(已修复) import { SecretResolver } from ‘@openclaw/core/secrets’;
class FirecrawlProvider { private config: FirecrawlConfig; private secretResolver: SecretResolver;
constructor(config: FirecrawlConfig, secretResolver: SecretResolver) { this.config = config; this.secretResolver = secretResolver; // 注入解析器 }
async fetch(url: string): Promise
const response = await fetch(this.apiEndpoint, {
headers: { 'Authorization': `Bearer ${resolvedApiKey}` }
});
} }
文件: src/plugins/firecrawl/plugin-registration.ts
typescript // 修复前 export function registerFirecrawlPlugin(container: PluginContainer) { container.registerSingleton(FirecrawlProvider, (config) => new FirecrawlProvider(config) ); }
// 修复后 export function registerFirecrawlPlugin(container: PluginContainer) { container.registerSingleton(FirecrawlProvider, (config, secretResolver) => new FirecrawlProvider(config, secretResolver) ); }
选项 2:解析后刷新提供程序
在网关生命周期中密钥解析完成后,强制刷新提供程序。
文件: src/gateway/boot.ts
typescript // 添加到网关初始化序列 async function initializeGateway(config: GatewayConfig) { // 阶段 1:加载和解析密钥 const resolvedConfig = await configResolver.resolveWithSecrets(config);
// 阶段 2:使用解析后的配置初始化提供程序 await providerRegistry.initialize(resolvedConfig.plugins);
// 阶段 3(修复):刷新所有提供程序实例以确保使用解析后的值 await providerRegistry.refreshAll();
// 阶段 4:启动网关 await gateway.start(); }
选项 3:环境变量变通方案(紧急缓解)
如果无法进行代码级修复,请使用环境变量注入 API 密钥:
步骤 1: 在您的环境中设置 API 密钥:
shell export FIRECRAWL_API_KEY=“your-actual-api-key-from-1password”
步骤 2: 更新配置以引用环境变量:
json { “plugins”: { “entries”: { “firecrawl”: { “enabled”: true, “config”: { “webFetch”: { “apiKey”: “${FIRECRAWL_API_KEY}”, “baseUrl”: “https://api.firecrawl.dev” } } } } } }
步骤 3: 验证环境变量可访问:
shell echo $FIRECRAWL_API_KEY
应输出:your-actual-api-key-from-1password
如果直接使用 1Password CLI:
export FIRECRAWL_API_KEY=$(op read “op://openclaw/Firecrawl API key/credential”)
步骤 4:验证修复有效
应用任何修复后,执行:
shell openclaw tools web-fetch “https://example.com”
预期输出应为获取的 HTML 内容,而非 401 错误。
🧪 验证
修复前诊断
在尝试修复之前确认 bug 存在:
shell
1. 验证 SecretRef 已配置
openclaw config get plugins.entries.firecrawl.config.webFetch.apiKey
预期输出:
op://openclaw/Firecrawl API key/credential
2. 检查 1Password CLI 是否已认证
op vault list
预期输出:
Vaults in personal:
├── openclaw
└── …
3. 确认密钥存在且可访问
op item get “Firecrawl API key” –vault openclaw
预期:包含 credential 字段的项目详情
4. 测试 web_fetch(修复前应失败)
openclaw tools web-fetch “https://httpbin.org/headers" –provider firecrawl
修复前预期结果:
Error: Firecrawl API error (401): Unauthorized: Invalid token
修复后预期结果:
{“headers”: {“Host”: “httpbin.org”, …}}
修复后验证步骤
步骤 1:验证密钥解析
shell openclaw config get plugins.entries.firecrawl.config.webFetch.apiKey –verbose
预期:
{
“sourceConfig”: “op://openclaw/Firecrawl API key/credential”,
“resolved”: “••••••••••••••••”,
“status”: “resolved”,
“resolvedAt”: “2026-04-15T10:23:41Z”
}
步骤 2:验证提供程序初始化
shell openclaw debug provider firecrawl –show-config
预期:apiKey 字段应显示 “••••••••••••••••"(掩码)
不应是原始 “op://…” 字符串
步骤 3:验证请求执行
shell
使用简单端点测试,该端点回显 Authorization 头
openclaw tools web-fetch “https://httpbin.org/headers" –provider firecrawl
预期:应返回包含 Host 头的 JSON
不应出现 401 错误
步骤 4:验证 Firecrawl API 交互(调试模式)
shell OPENCLAW_LOG_LEVEL=debug openclaw tools web-fetch “https://example.com” –provider firecrawl 2>&1 | grep -E “(firecrawl|Firecrawl|Authorization|Authorization: Bearer)”
预期:应显示正在发送的已解析 Bearer 令牌
Authorization 头中不应显示 “op://…”
步骤 5:自动化测试套件
如果可用,运行 Firecrawl 插件测试套件:
shell openclaw test –plugin firecrawl –secretref-mode
预期:所有测试通过,包括 SecretRef 场景
退出码验证
shell
成功情况
openclaw tools web-fetch “https://example.com” –provider firecrawl echo “Exit code: $?” # 应为 0
失败情况(修复前)
openclaw tools web-fetch “https://example.com” –provider firecrawl echo “Exit code: $?” # 应为非零(通常为 1)
⚠️ 常见陷阱
环境特定陷阱
- 1Password CLI 未认证:
在使用 SecretRef 之前,确保 1Password CLI 已登录:# 检查认证状态 op account list如果未认证,请登录:
op signin
验证对保管库的访问:
op vault list
- 保管库访问权限:
1Password 账户必须能够访问 SecretRef 中引用的保管库:# 验证保管库权限 op account get确保保管库 “openclaw” 可访问
op vault get openclaw
- 服务账户 vs 个人账户:
如果以服务方式运行网关,确保服务账户具有 1Password 访问权限,而不仅仅是您的个人会话:# 对于 systemd 服务,通过环境或 systemd-run 设置 1Password 会话 # 避免依赖个人 1Password 会话令牌
配置陷阱
- 配置中的双重解析:
如果您已将FIRECRAWL_API_KEY设置为环境变量,同时也在配置中引用它,确保配置使用${FIRECRAWL_API_KEY}语法,而非$FIRECRAWL_API_KEY:# 错误 - 将按字面量传递 "apiKey": "$FIRECRAWL_API_KEY"正确 - 将被解析
“apiKey”: “${FIRECRAWL_API_KEY}"
- SecretRef 中的空白字符:
1Password SecretRef 不能有前导/尾随空白字符:# 错误 - 可能导致解析失败 "apiKey": "op://openclaw/Firecrawl API key/credential "正确
“apiKey”: “op://openclaw/Firecrawl API key/credential”
- 项目名称中的特殊字符:
如果 1Password 项目名称包含特殊字符,请对其进行 URL 编码:# 对于名为 "Firecrawl API (Dev & Prod)" 的项目 "apiKey": "op://openclaw/Firecrawl%20API%20(Dev%20%26%20Prod)/credential"
运行时陷阱
- 需要重启网关:
对 SecretRef 的更改需要重启网关才能生效,即使网关热重载其他配置更改:# 更改 SecretRef 后必须执行 openclaw gateway restart以下操作不够:
openclaw config reload # 仅重载非密钥配置
- 提供程序单例行为:
由于单例提供程序模式,在密钥解析之前缓存的提供程序实例将继续使用过时值,直到网关完全重启:# 验证没有过时实例 openclaw debug provider firecrawl --status应显示:“Initialized: true”, “Config Snapshot: fresh”
- Docker 卷挂载注意事项:
如果在 Docker 中运行,确保正确挂载 1Password socket/凭证:# 错误 - 凭证在容器中不可用 docker run openclaw:latest正确 - 挂载 1Password socket
docker run -v openclaw_config:/app/config -e OP_SESSION=… openclaw:latest
macOS 特定问题
- Keychain 访问提示:
1Password CLI 在非交互式运行时可能会提示 Keychain 访问。在 CI/CD 环境中使用op signin --account配合服务账户。 - SSH Agent 转发:
如果 1Password 凭证通过 SSH 转发,请确保OP_SESSION环境变量也被转发。
Windows 特定问题
- 路径分隔符:
SecretRef 路径在 Windows 上也使用正斜杠。不要转换为反斜杠。 - WSL2 环境变量:
如果在 WSL2 中运行 OpenClaw 而 1Password 在 Windows 上,请设置 WSL2 中的 1Password CLI 并在 WSL2 中单独进行身份验证,与 Windows 主机分开。
🔗 相关错误
主要相关问题
- #28359 - SecretRef 运行时物化不一致
历史问题,描述了 SecretRef 值如何在不同的解析状态被捕获在配置快照中。Firecrawl bug 是这一更广泛模式的具体表现。
症状相关的错误
401 Unauthorized: Invalid token
通用认证失败。在此上下文中,由字面量op://...字符串作为 Bearer 令牌发送引起。SecretRef resolution failed: Item not found
表示 SecretRef 中指定的 1Password 项目或保管库不存在或不可访问。不同于 Firecrawl bug——此处解析本身失败。Plugin initialization failed: Config snapshot mismatch
内部 OpenClaw 错误,表示插件在初始化阶段接收到不一致的配置。如果 Firecrawl 提供程序初始化顺序被改变,可能会在网关启动期间出现。Gateway config validation error: Invalid SecretRef format
如果 SecretRef 不符合op://vault/item/field模式,则为模式验证失败。
错误码参考
| 错误码 | 类别 | 描述 |
|---|---|---|
SECRET_REF_001 | 解析 | SecretRef 格式验证失败 |
SECRET_REF_002 | 解析 | 1Password CLI 未认证 |
SECRET_REF_003 | 解析 | 目标项目或保管库不可访问 |
SECRET_REF_004 | 物化 | 解析后的值未传播到消费者 |
PLUGIN_AUTH_401 | 认证 | 插件收到无效凭证 |
PLUGIN_INIT_001 | 初始化 | 提供程序使用过时的配置快照初始化 |
跨插件注意事项
相同的快照隔离模式影响其他接受以下内容的插件:
- 通过 SecretRef 接收 API 密钥或令牌
- 在网关启动期间初始化提供程序
- 使用单例提供程序实例
已知可能受影响的插件:
plugins.entries.anthropic.config.apiKeyplugins.entries.google.config.credentialsplugins.entries.aws.config.accessKeyIdplugins.entries.azure.config.subscriptionKey
通过测试这些插件各自的 SecretRef 配置来验证它们是否遇到相同问题。