SecretRef de API Key de Firecrawl Retorna 401 Unauthorized A Pesar del Estado de Configuración Resuelto
Al usar SecretRefs de 1Password para webFetch.apiKey de Firecrawl, las solicitudes fallan con 401 Unauthorized aunque la configuración del gateway muestra el secreto como resuelto y enmascarado.
🔍 Síntomas
Manifestación principal del error
Cuando las solicitudes web_fetch se enrutan a través del plugin Firecrawl con un SecretRef de 1Password para la clave API, la operación falla con un error de autenticación a pesar de que la puerta de enlace indica que el secreto está correctamente resuelto.
shell
Terminal 1: Verificar el estado de resolución de la configuración
$ 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: Ejecutar solicitud 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)
Discrepancia en la inspección de configuración
La vista de configuración en tiempo de ejecución muestra el campo como resolved y masked, lo que sugiere que la capa de materialización aceptó el SecretRef. Sin embargo, la ruta de solicitud de Firecrawl parece recibir cualquiera de los siguientes:
- La cadena sin resolver `op://...`
- Una instantánea de configuración obsoleta o incorrecta
- Un valor que se resolvió pero no se propagó a la instancia del proveedor
Comandos CLI de diagnóstico
shell
Verificar el registro del plugin y el estado en tiempo de ejecución
openclaw plugins list –verbose | grep -A5 firecrawl
Verificar la clave API real que está utilizando el proveedor
openclaw debug provider firecrawl –show-config
Habilitar el registro de seguimiento para la resolución de secretos
OPENCLAW_LOG_LEVEL=trace openclaw gateway start 2>&1 | grep -i “firecrawl|secretref|apiKey”
Contexto del entorno
- Versión de OpenClaw: 2026.4.11
- SO: Ubuntu (kernel de Linux 5.15+)
- Modo de ejecución: Modo local de puerta de enlace
- Proveedor de secretos: CLI de 1Password (`op://` esquema)
- Configuración de puerta de enlace: Archivo de configuración JSON
🧠 Causa raíz
Punto de fallo arquitectónico
El error se origina a partir de un fallo de aislamiento de instantánea de configuración entre la capa de resolución de configuración de la puerta de enlace y la capa de ejecución de solicitudes del proveedor Firecrawl.
┌─────────────────────────────────────────────────────────────────────┐ │ PROCESO DE PUERTA DE ENLACE │ ├─────────────────────────────────────────────────────────────────────┤ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │ │ Cargador de │───▶│ Resolvedor de │───▶│ Instantánea de │ │ │ │ Configuración │ │ Secretos │ │ Configuración │ │ │ │ (JSON/YAML) │ │ (CLI de 1Pwd) │ │ en Runtime │ │ │ └─────────────────┘ └─────────────────┘ └────────┬────────┘ │ │ │ │ │ │ instantánea│ │ │ copiada │ │ ▼ │ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │ │ WebFetchTool │───▶│ Instancia de │───▶│ Inicialización │ │ │ │ (web-fetch) │ │ FirecrawlProv │ │ del Proveedor │ │ │ └─────────────────┘ └─────────────────┘ └────────┬────────┘ │ │ │ │ │ │ usa │ │ ▼ │ │ ┌─────────────────┐ │ │ │ Llamada REST │ │ │ │ API de Firecrawl│ │ │ └─────────────────┘ │ └─────────────────────────────────────────────────────────────────────┘
Secuencia de fallo
- Fase de carga de configuración: La puerta de enlace carga la configuración JSON que contiene `plugins.entries.firecrawl.config.webFetch.apiKey: "op://openclaw/Firecrawl API key/credential"`.
- Fase de resolución de secretos: El servicio
SecretResolverprocesa el SecretRef de 1Password y recupera el token en texto plano. La vista de configuración en tiempo de ejecución muestra correctamente esto como resuelto y enmascarado. - Fase de inicialización del proveedor (ERROR): Cuando se instancia
FirecrawlProvider, recibe una instantánea de configuración obsoleta en lugar del objeto de configuración post-resolución. El constructor del proveedor captura:// firecrawl-provider.ts - Constructor (con error) constructor(config: FirecrawlConfig) { // Captura instantánea de configuración al momento de inicialización this.apiKey = config.webFetch.apiKey; this.baseUrl = config.webFetch.baseUrl; // ... } - Fase de ejecución de solicitud: Cuando se invoca
web_fetch, el proveedor usathis.apiKey, que todavía contiene la cadena SecretRef sin resolver"op://openclaw/Firecrawl API key/credential"porque la instantánea se tomó antes de que la resolución del secreto se completara. - Rechazo de API de Firecrawl: La API de Firecrawl recibe la cadena literal
op://...como la clave API, resultando en401 Unauthorized.
Análisis de ruta de código
La causa raíz se manifiesta en el orden de inicialización dentro de src/plugins/firecrawl/firecrawl-provider.ts:
// Orden de inicialización problemático
class FirecrawlProvider {
private apiKey: string;
private baseUrl: string;
// Llamado durante el inicio de la puerta de enlace, recibe configuración pre-resolución
constructor(config: FirecrawlConfig) {
this.apiKey = config.webFetch.apiKey; // ← Recibe cadena "op://..."
this.baseUrl = config.webFetch.baseUrl;
}
// Este método se llama en tiempo de solicitud, pero usa el valor capturado
async fetch(url: string): Promise<FetchResult> {
const headers = {
'Authorization': `Bearer ${this.apiKey}`, // ← Envía ref sin resolver
'Content-Type': 'application/json'
};
// ...
}
}Contraste con la ruta de texto plano que funciona
Cuando se usa configuración de texto plano, no se requiere resolución de secretos:
{
"plugins": {
"entries": {
"firecrawl": {
"config": {
"webFetch": {
"apiKey": "fc-actual-token-plaintext" // ← Asignación directa
}
}
}
}
}
}El proveedor recibe y almacena el token real directamente, evitando el mecanismo roto de instantánea.
Patrón arquitectónico relacionado
Este fallo comparte características con el error de materialización en tiempo de ejecución de SecretRef #28359, donde las instantáneas de configuración tomadas en diferentes fases del ciclo de vida contienen estados de resolución inconsistentes. El constructor del proveedor Firecrawl captura el estado al momento de inicialización, pero la resolución del secreto ocurre después de la inicialización para esta ruta de configuración específica.
🛠️ Solución paso a paso
Opción 1: Resolución diferida (Recomendada)
Modificar el proveedor Firecrawl para realizar la resolución de secretos en tiempo de solicitud en lugar de tiempo de construcción.
Archivo: src/plugins/firecrawl/firecrawl-provider.ts
typescript // ANTES (con error) class FirecrawlProvider { private apiKey: string;
constructor(config: FirecrawlConfig) { this.apiKey = config.webFetch.apiKey; // Captura en init }
async fetch(url: string): PromiseBearer ${this.apiKey} }
});
}
}
// DESPUÉS (corregido) 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; // Inyectar resolvedor }
async fetch(url: string): Promise
const response = await fetch(this.apiEndpoint, {
headers: { 'Authorization': `Bearer ${resolvedApiKey}` }
});
} }
Archivo: src/plugins/firecrawl/plugin-registration.ts
typescript // ANTES export function registerFirecrawlPlugin(container: PluginContainer) { container.registerSingleton(FirecrawlProvider, (config) => new FirecrawlProvider(config) ); }
// DESPUÉS export function registerFirecrawlPlugin(container: PluginContainer) { container.registerSingleton(FirecrawlProvider, (config, secretResolver) => new FirecrawlProvider(config, secretResolver) ); }
Opción 2: Actualización del proveedor post-resolución
Forzar una actualización del proveedor después de que la resolución de secretos se complete en el ciclo de vida de la puerta de enlace.
Archivo: src/gateway/boot.ts
typescript // Agregar a la secuencia de inicialización de la puerta de enlace async function initializeGateway(config: GatewayConfig) { // Fase 1: Cargar y resolver secretos const resolvedConfig = await configResolver.resolveWithSecrets(config);
// Fase 2: Inicializar proveedores con configuración resuelta await providerRegistry.initialize(resolvedConfig.plugins);
// Fase 3 (CORRECCIÓN): Actualizar todas las instancias de proveedor para asegurar que usen valores resueltos await providerRegistry.refreshAll();
// Fase 4: Iniciar puerta de enlace await gateway.start(); }
Opción 3: Solución alternativa con variable de entorno (Mitigación inmediata)
Si las correcciones a nivel de código no están disponibles, usar la inyección de variable de entorno para la clave API:
Paso 1: Establecer la clave API en tu entorno:
shell export FIRECRAWL_API_KEY=“tu-clave-api-real-de-1password”
Paso 2: Actualizar la configuración para referenciar la variable de entorno:
json { “plugins”: { “entries”: { “firecrawl”: { “enabled”: true, “config”: { “webFetch”: { “apiKey”: “${FIRECRAWL_API_KEY}”, “baseUrl”: “https://api.firecrawl.dev” } } } } } }
Paso 3: Verificar que la variable de entorno sea accesible:
shell echo $FIRECRAWL_API_KEY
Debería mostrar: tu-clave-api-real-de-1password
Si usas CLI de 1Password directamente:
export FIRECRAWL_API_KEY=$(op read “op://openclaw/Firecrawl API key/credential”)
Paso 4: Verificar que la corrección funcione
Después de aplicar cualquier corrección, ejecutar:
shell openclaw tools web-fetch “https://example.com”
La salida esperada debe ser el contenido HTML obtenido, no un error 401.
🧪 Verificación
Diagnóstico pre-corrección
Confirmar que el error existe antes de intentar la remediación:
shell
1. Verificar que el SecretRef esté configurado
openclaw config get plugins.entries.firecrawl.config.webFetch.apiKey
Salida esperada:
op://openclaw/Firecrawl API key/credential
2. Verificar que el CLI de 1Password esté autenticado
op vault list
Salida esperada:
Vaults in personal:
├── openclaw
└── …
3. Confirmar que el secreto existe y es accesible
op item get “Firecrawl API key” –vault openclaw
Esperado: Detalles del item con campo de credencial
4. Probar web_fetch (debería fallar antes de la corrección)
openclaw tools web-fetch “https://httpbin.org/headers" –provider firecrawl
Esperado (antes de la corrección):
Error: Firecrawl API error (401): Unauthorized: Invalid token
Esperado (después de la corrección):
{“headers”: {“Host”: “httpbin.org”, …}}
Pasos de verificación post-corrección
Paso 1: Verificar resolución de secretos
shell openclaw config get plugins.entries.firecrawl.config.webFetch.apiKey –verbose
Esperado:
{
“sourceConfig”: “op://openclaw/Firecrawl API key/credential”,
“resolved”: “••••••••••••••••”,
“status”: “resolved”,
“resolvedAt”: “2026-04-15T10:23:41Z”
}
Paso 2: Verificar inicialización del proveedor
shell openclaw debug provider firecrawl –show-config
Esperado: El campo apiKey debe mostrar “••••••••••••••••” (enmascarado)
NO debe mostrar la cadena sin resolver “op://…”
Paso 3: Verificar ejecución de solicitud
shell
Probar con un endpoint simple que devuelve el encabezado Authorization
openclaw tools web-fetch “https://httpbin.org/headers" –provider firecrawl
Esperado: Debe devolver JSON con el encabezado Host visible
No debe ocurrir error 401
Paso 4: Verificar interacción con API de Firecrawl (Modo depuración)
shell OPENCLAW_LOG_LEVEL=debug openclaw tools web-fetch “https://example.com” –provider firecrawl 2>&1 | grep -E “(firecrawl|Firecrawl|Authorization|Authorization: Bearer)”
Esperado: Debe mostrar el token Bearer resuelto que se envía
NO debe mostrar “op://…” en el encabezado Authorization
Paso 5: Suite de pruebas automatizadas
Ejecutar la suite de pruebas del plugin Firecrawl si está disponible:
shell openclaw test –plugin firecrawl –secretref-mode
Esperado: Todas las pruebas pasan incluyendo escenarios de SecretRef
Verificación de código de salida
shell
Caso de éxito
openclaw tools web-fetch “https://example.com” –provider firecrawl echo “Exit code: $?” # Debe ser 0
Caso de fallo (antes de la corrección)
openclaw tools web-fetch “https://example.com” –provider firecrawl echo “Exit code: $?” # Debe ser distinto de cero (típicamente 1)
⚠️ Errores comunes
Trampas específicas del entorno
- CLI de 1Password sin autenticación:
Antes de usar SecretRefs, asegurar que el CLI de 1Password esté conectado:# Verificar estado de autenticación op account listSi no está autenticado, conectarse:
op signin
Verificar acceso al vault:
op vault list
- Permisos de acceso al vault:
La cuenta de 1Password debe tener acceso al vault referenciado en el SecretRef:# Verificar permisos del vault op account getAsegurar que el vault “openclaw” sea accesible
op vault get openclaw
- Cuenta de servicio vs Cuenta personal:
Si se ejecuta la puerta de enlace como servicio, asegurar que la cuenta de servicio tenga acceso a 1Password, no solo tu sesión personal:# Para servicio systemd, configurar sesión de 1Password mediante entorno o systemd-run # Evitar depender de tokens de sesión personales de 1Password
Errores de configuración
- Doble-resolución en la configuración:
Si has establecidoFIRECRAWL_API_KEYcomo variable de entorno y también la referencias en la configuración, asegurar que la configuración use la sintaxis${FIRECRAWL_API_KEY}, no$FIRECRAWL_API_KEY:# Incorrecto - se pasará literalmente "apiKey": "$FIRECRAWL_API_KEY"Correcto - se resolverá
“apiKey”: “${FIRECRAWL_API_KEY}"
- Espacios en blanco en SecretRefs:
Los SecretRefs de 1Password no deben tener espacios en blanco al inicio o al final:# Incorrecto - puede causar fallo de resolución "apiKey": "op://openclaw/Firecrawl API key/credential "Correcto
“apiKey”: “op://openclaw/Firecrawl API key/credential”
- Caracteres especiales en nombres de items:
Si el nombre del item de 1Password contiene caracteres especiales, codificarlos en URL:# Para item nombrado "Firecrawl API (Dev & Prod)" "apiKey": "op://openclaw/Firecrawl%20API%20(Dev%20%26%20Prod)/credential"
Errores en tiempo de ejecución
- Se requiere reiniciar la puerta de enlace:
Los cambios en SecretRefs requieren un reinicio de la puerta de enlace para que surtan efecto, incluso si la puerta de enlace recarga otros cambios de configuración en caliente:# Requerido después de cambiar SecretRef openclaw gateway restartNo es suficiente:
openclaw config reload # Solo recarga configuración no secreta
- Comportamiento singleton del proveedor:
Debido al patrón de proveedor singleton, las instancias de proveedor en caché antes de la resolución de secretos continuará usando valores obsoletos hasta que la puerta de enlace se reinicie completamente:# Verificar que no haya instancias obsoletas openclaw debug provider firecrawl --statusDebe mostrar: “Initialized: true”, “Config Snapshot: fresh”
- Consideraciones de montaje de volúmenes Docker:
Si se ejecuta en Docker, asegurar que el socket/credenciales de 1Password estén correctamente montados:# Incorrecto - credenciales no disponibles en el contenedor docker run openclaw:latestCorrecto - montar socket de 1Password
docker run -v openclaw_config:/app/config -e OP_SESSION=… openclaw:latest
Problemas específicos de macOS
- Prompts de acceso a Keychain:
El CLI de 1Password puede solicitar acceso a Keychain al ejecutarse de forma no interactiva. Usarop signin --accountcon una cuenta de servicio para entornos de CI/CD. - Reenvío de agente SSH:
Si las credenciales de 1Password se reenvían vía SSH, asegurar que la variable de entornoOP_SESSIONtambién se reenvíe.
Problemas específicos de Windows
- Separadores de ruta:
Las rutas de SecretRef usan barras inclinadas hacia adelante incluso en Windows. No convertir a barras inclinadas hacia atrás. - Variables de entorno en WSL2:
Si se ejecuta OpenClaw en WSL2 con 1Password de Windows, configurar el CLI de 1Password en WSL2 y autenticarse allí por separado del host de Windows.
🔗 Errores relacionados
Problema principal relacionado
- #28359 - Inconsistencia de materialización en tiempo de ejecución de SecretRef
Problema histórico que describe cómo los valores de SecretRef pueden ser capturados en instantáneas de configuración en diferentes estados de resolución. El error de Firecrawl es una manifestación específica de este patrón más amplio.
Errores relacionados sintomáticamente
401 Unauthorized: Invalid token
Fallo de autenticación genérico. En este contexto, causado por la cadena literalop://...que se envía como el token Bearer.SecretRef resolution failed: Item not found
Indica que el item o vault de 1Password especificado en el SecretRef no existe o no es accesible. Diferente del error de Firecrawl—aquí la resolución misma falla.Plugin initialization failed: Config snapshot mismatch
Error interno de OpenClaw que indica que un plugin recibió configuración inconsistente en diferentes fases de inicialización. Puede aparecer durante el inicio de la puerta de enlace si se altera el orden de inicialización del proveedor Firecrawl.Gateway config validation error: Invalid SecretRef format
Fallo de validación de esquema si el SecretRef no se ajusta al patrónop://vault/item/field.
Referencia de códigos de error
| Código de error | Categoría | Descripción |
|---|---|---|
SECRET_REF_001 | Resolución | Validación de formato SecretRef fallida |
SECRET_REF_002 | Resolución | CLI de 1Password sin autenticación |
SECRET_REF_003 | Resolución | Item o vault objetivo no accesible |
SECRET_REF_004 | Materialización | Valor resuelto no propagado al consumidor |
PLUGIN_AUTH_401 | Autenticación | Plugin recibió credenciales inválidas |
PLUGIN_INIT_001 | Inicialización | Proveedor inicializado con instantánea de configuración obsoleta |
Consideraciones entre plugins
El mismo patrón de aislamiento de instantánea afecta a otros plugins que:
- Aceptan claves API o tokens vía SecretRef
- Inicializan proveedores durante el inicio de la puerta de enlace
- Usan instancias de proveedor singleton
Plugins potencialmente afectados conocidos:
plugins.entries.anthropic.config.apiKeyplugins.entries.google.config.credentialsplugins.entries.aws.config.accessKeyIdplugins.entries.azure.config.subscriptionKey
Verificar que estos plugins no estén experimentando el mismo problema probando sus respectivas configuraciones de SecretRef.