[Firecrawl APIキーのSecretRefが解決済み構成でも401エラーを返す] - Firecrawl API Key SecretRef Returns 401 Unauthorized Despite Resolved Config Status
1PasswordのSecretRefをFirecrawlのwebFetch.apiKeyに使用すると、ゲートウェイ構成でシークレットが解決済みでマスクされているにもかかわらず、401 Unauthorizedエラーでリクエストが失敗します。
🔍 症状
主なエラーの発生状況
web_fetchリクエストがFirecrawlプラグイン経由でルーティングされ、APIキーに1PasswordのSecretRefを使用している場合、ゲートウェイがシークレットが正常に解決されたことを示しているにもかかわらず、認証エラーで失敗します。
shell
Terminal 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
Terminal 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
- OS: Ubuntu (Linux kernel 5.15+)
- ランタイムモード: ゲートウェイローカルモード
- シークレットプロバイダー: 1Password CLI (`op://`スキーム)
- ゲートウェイ設定: JSON設定ファイル
🧠 原因
アーキテクチャ上の障害ポイント
このバグは、ゲートウェイの設定解決レイヤーと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を処理し、プレーンテキストトークンを取得します。ランタイム設定ビューには解決済みおよびマスク済みとして正しく表示されます。 - プロバイダー初期化フェーズ(バグ):
FirecrawlProviderがインスタンス化されるとき、シークレット解決後の設定オブジェクトではなく、古い設定スナップショットを受け取ります。プロバイダーのコンストラクターがキャプチャする内容:// firecrawl-provider.ts - Constructor (buggy) constructor(config: FirecrawlConfig) { // Captures config snapshot at init time 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はAPIキーとしてリテラルな
op://...文字列を受け取り、401 Unauthorizedが発生します。
コードパスの分析
根本原因は以下のsrc/plugins/firecrawl/firecrawl-provider.ts内の初期化順序で発生します:
// Problematic initialization order
class FirecrawlProvider {
private apiKey: string;
private baseUrl: string;
// Called during gateway startup, receives pre-resolution config
constructor(config: FirecrawlConfig) {
this.apiKey = config.webFetch.apiKey; // ← Receives "op://..." string
this.baseUrl = config.webFetch.baseUrl;
}
// This method is called at request time, but uses the captured value
async fetch(url: string): Promise<FetchResult> {
const headers = {
'Authorization': `Bearer ${this.apiKey}`, // ← Sends unresolved ref
'Content-Type': 'application/json'
};
// ...
}
}プレーンテキストパスの動作との比較
プレーンテキスト設定を使用する場合、シークレット解決は不要です:
{
"plugins": {
"entries": {
"firecrawl": {
"config": {
"webFetch": {
"apiKey": "fc-actual-token-plaintext" // ← Direct assignment
}
}
}
}
}
}プロバイダーは壊れたスナップショットメカニズムをバイパスして、実際のトークンを直接受け取ります。
関連するアーキテクチャパターン
この障害は、異なるライフサイクルフェーズで取得された設定スナップショットが一貫しない解決状態を含む可能性がある、SecretRefランタイムマテリアライゼーションバグ#28359と特性を共有しています。Firecrawlプロバイダーのコンストラクターは初期化時に状態をキャプチャしますが、この特定の設定パスではシークレット解決が初期化の後に発生します。
🛠️ 解決手順
オプション1: 遅延解決(推奨)
Firecrawlプロバイダーを、構築時ではなくリクエスト時にシークレット解決を実行するように修正します。
ファイル: src/plugins/firecrawl/firecrawl-provider.ts
typescript // BEFORE (buggy) class FirecrawlProvider { private apiKey: string;
constructor(config: FirecrawlConfig) { this.apiKey = config.webFetch.apiKey; // Captures at init }
async fetch(url: string): PromiseBearer ${this.apiKey} }
});
}
}
// AFTER (fixed) 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; // Inject resolver }
async fetch(url: string): Promise
const response = await fetch(this.apiEndpoint, {
headers: { 'Authorization': `Bearer ${resolvedApiKey}` }
});
} }
ファイル: src/plugins/firecrawl/plugin-registration.ts
typescript // BEFORE export function registerFirecrawlPlugin(container: PluginContainer) { container.registerSingleton(FirecrawlProvider, (config) => new FirecrawlProvider(config) ); }
// AFTER export function registerFirecrawlPlugin(container: PluginContainer) { container.registerSingleton(FirecrawlProvider, (config, secretResolver) => new FirecrawlProvider(config, secretResolver) ); }
オプション2: 解決後プロバイダーリフレッシュ
ゲートライライフサイクルでシークレット解決が完了した後、プロバイダーリフレッシュを強制します。
ファイル: src/gateway/boot.ts
typescript // Add to gateway initialization sequence async function initializeGateway(config: GatewayConfig) { // Phase 1: Load and resolve secrets const resolvedConfig = await configResolver.resolveWithSecrets(config);
// Phase 2: Initialize providers with resolved config await providerRegistry.initialize(resolvedConfig.plugins);
// Phase 3 (FIX): Refresh all provider instances to ensure they use resolved values await providerRegistry.refreshAll();
// Phase 4: Start gateway 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”
出力は401エラーではなく、取得したHTMLコンテンツである必要があります。
🧪 検証
修正前の診断
修復を試みる前に、バグが存在することを確認します:
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
期待される出力: 資格情報フィールドを含むアイテム詳細
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[Test=“true” enabled=“true”] plugin=[Test=“true” enabled=“true”] suiteを実行します:
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がサインインしていることを確認します:# Check authentication status op account listIf not authenticated, sign in:
op signin
Verify access to the vault:
op vault list
- ボールトアクセスの権限:
1PasswordアカウントはSecretRefで参照されているボールトにアクセスできる必要があります:# Verify vault permissions op account getEnsure the vault “openclaw” is accessible
op vault get openclaw
- サービスアカウントと個人アカウント:
ゲートウェイをサービスとして実行している場合、サービスが1Passwordにアクセスできることを確認してください。個人のセッションだけに依存しないでください:# For systemd service, set up 1Password session via environment or systemd-run # Avoid relying on personal 1Password session tokens
設定の落とし穴
- 設定内の二重解決:
FIRECRAWL_API_KEYを環境変数として設定し、設定でも参照している場合、設定では$FIRECRAWL_API_KEYではなく${FIRECRAWL_API_KEY}構文を使用してください:# Wrong - will be passed literally "apiKey": "$FIRECRAWL_API_KEY"Correct - will be resolved
“apiKey”: “${FIRECRAWL_API_KEY}"
- SecretRef内の空白:
1Password SecretRefには先頭または末尾の空白を含めないでください:# Wrong - may cause resolution failure "apiKey": "op://openclaw/Firecrawl API key/credential "Correct
“apiKey”: “op://openclaw/Firecrawl API key/credential”
- アイテム名内の特殊文字:
1Passwordアイテム名に特殊文字が含まれている場合は、URLエンコードしてください:# For item named "Firecrawl API (Dev & Prod)" "apiKey": "op://openclaw/Firecrawl%20API%20(Dev%20%26%20Prod)/credential"
ランタイムの落とし穴
- ゲートウェイの再起動が必要:
SecretRefへの変更は、ゲートウェイが他の設定変更をホットリロードする場合でも、ゲートウェイの再起動が必要です:# Required after changing SecretRef openclaw gateway restartNot sufficient:
openclaw config reload # Only reloads non-secret config
- プロバイダーシングルトンの動作:
シングルトンプロバイダーパターンのため、シークレット解決前にキャッシュされたプロバイダーインスタンスは、ゲートウェイが完全に再起動するまで古い値の使用を続けます:# Verify no stale instances openclaw debug provider firecrawl --statusShould show: “Initialized: true”, “Config Snapshot: fresh”
- Dockerボリュームマウントの考慮事項:
Dockerで実行している場合、1Passwordソケット/資格情報が適切にマウントされていることを確認してください:# Wrong - credentials not available in container docker run openclaw:latestCorrect - mount 1Password socket
docker run -v openclaw_config:/app/config -e OP_SESSION=… openclaw:latest
macOS固有の問題
- キーチェインアクセスのプロンプト:
1Password CLIは非対話的に実行する場合にキーチェインアクセスを要求する可能性があります。CI/CD環境ではサービスアカウントでop signin --accountを使用してください。 - SSHエージェント転送:
1Password資格情報がSSH経由で転送される場合、OP_SESSION環境変数も転送されていることを確認してください。
Windows固有の問題
- パス区切り文字:
SecretRefパスはWindowsでもスラッシュ前方を使用します。バックスラッシュに変換しないでください。 - WSL2環境変数:
WSL2でOpenClawを実行し、Windows 1Passwordを使用している場合、WSL2内に1Password CLIをセットアップし、Windowsホストとは別の場所で認証してください。
🔗 関連するエラー
主な関連問題
- #28359 - SecretRefランタイムマテリアライゼーショ的不整合
SecretRef値が異なる解決状態で設定スナップショットにキャプチャされる可能性がある方法を説明する過去のイシューです。Firecrawlバグはこのより広範なパターンの特定の現れです。
症状として関連するエラー
401 Unauthorized: Invalid token
汎用的な認証失敗です。この文脈では、リテラルop://...文字列がBearerトークンとして送信されることで発生しています。SecretRef resolution failed: Item not found
SecretRefで指定された1Passwordアイテムまたはボールトが存在しないかアクセスできないことを示します。Firecrawlバグとは異なり、ここでは解決自体が失敗しています。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
Firecrawl[Test=“true” enabled=“true”] plugin=[Test=“true” enabled=“true”] suiteを実行して、同じイシューをテストしてください。