April 22, 2026 • Versión: v2026.4.14

[Bloqueo SSRF en transcripción de audio a red privada tras actualización] - SSRF Block on Private Network Audio Transcription After v2026.4.14 Upgrade

La transcripción de mensajes de voz a endpoints STT autoalojados en IPs privadas falla con SsrFBlockedError incluso cuando models.providers.*.request.allowPrivateNetwork está configurado, causado por dos bugs en cascada en la resolución de solicitudes del proveedor.

🔍 Síntomas

Manifestación principal del error

Las solicitudes de transcripción de audio a puntos de conexión compatibles con OpenAI en IPs privadas de LAN se bloquean con una violación SSRF, a pesar de la configuración explícita que permite el acceso a redes privadas.

[security] blocked URL fetch (url-fetch) target=http://192.168.x.x:5092/v1/audio/transcriptions reason=Blocked hostname or private/internal/special-use IP address
[media-understanding] audio: failed (0/1) reason=SsrFBlockedError

Operaciones afectadas

  • Transcripción de mensajes de voz mediante el modelo parakeet en puntos de conexión STT autoalojados
  • Cualquier invocación de la herramienta media.audio dirigida a direcciones IP privadas/internas
  • Puntos de conexión de API compatibles con OpenAI en direcciones LAN (por ejemplo, 192.168.x.x, 10.x.x.x, 172.16.x.x)

Configuración que debería funcionar

La siguiente configuración es el enfoque documentado para habilitar el acceso a redes privadas para proveedores de STT:

{
  "models": {
    "providers": {
      "openai": {
        "apiKey": "local",
        "baseUrl": "http://192.168.x.x:5092/v1",
        "request": {
          "allowPrivateNetwork": true
        }
      }
    }
  },
  "tools": {
    "media": {
      "audio": {
        "enabled": true,
        "models": [{ "provider": "openai", "model": "parakeet" }]
      }
    }
  }
}

Contexto de versión

VersiónComportamiento
v2026.4.12✅ Funciona correctamente
v2026.4.13✅ Funciona correctamente
v2026.4.14❌ Bloqueado por SSRF — regresión introducida

Salida del comando de diagnóstico

Cuando se habilita la depuración, puede aparecer lo siguiente en los registros:

# Enable debug logging to trace request resolution
DEBUG=openclaw:* node index.js

# Expected SSRF block in output:
[security] blocked URL fetch (url-fetch) target=http://192.168.x.x:5092/v1/audio/transcriptions reason=Blocked hostname or private/internal/special-use IP address

🧠 Causa raíz

Descripción general

Dos errores independientes en la versión v2026.4.14 forman una cascada que elimina silenciosamente la configuración allowPrivateNetwork de la configuración de solicitudes del proveedor. Ambos errores deben estar presentes para reproducir la falla, y ambos deben corregirse para restaurar el comportamiento correcto.


Error 1: resolveProviderExecutionContext Elimina allowPrivateNetwork de la Configuración del Proveedor

Archivo afectado: runner.entries-*.js (archivo dist)

Ruta del código:

La función resolveProviderExecutionContext construye el objeto request pasado a transcribeAudio a través de la siguiente cadena de fusión:

javascript request: mergeProviderRequestOverrides( sanitizeConfiguredProviderRequest(params.config?.request), sanitizeConfiguredProviderRequest(params.entry.request) )

Causa raíz:

La función fusiona solo:

  • params.config?.request — configuración de solicitud a nivel de herramienta (desde tools.media.audio.request)
  • params.entry.request — anulaciones de solicitud a nivel de entrada

Críticamente, la configuración a nivel de proveedor (models.providers.<id>.request) nunca se incluye en esta fusión. La función sanitizeConfiguredProviderRequest filtra explícitamente solo estos campos:

javascript // Fields preserved by sanitizeConfiguredProviderRequest const ALLOWED_REQUEST_FIELDS = [‘headers’, ‘auth’, ‘proxy’, ’tls’]; // Note: ‘allowPrivateNetwork’ is intentionally NOT in this list

Resultado: Incluso cuando un operador configura correctamente:

json “models”: { “providers”: { “openai”: { “request”: { “allowPrivateNetwork”: true } } } }

Este valor se descarta silenciosamente porque la configuración del proveedor nunca se consulta durante la construcción del objeto de solicitud para la transcripción de audio.


Error 2: resolveProviderRequestPolicyConfig Ignora allowPrivateNetwork en params.request

Archivo afectado: provider-request-config-*.js (archivo dist)

Ruta del código:

La función resolveProviderRequestPolicyConfig devuelve la política de seguridad resuelta:

javascript allowPrivateNetwork: params.allowPrivateNetwork ?? false

Causa raíz:

La función verifica solo params.allowPrivateNetwork — un parámetro directo que los llamadores deben pasar explícitamente. Sin embargo, todos los llamadores de transcripción de audio derivan su configuración de solicitud de resolveProviderHttpRequestConfig y la pasan como request: params.request.

Los llamadores (por ejemplo, transcribeOpenAiCompatibleAudio, transcribeDeepgramAudio) establecen:

javascript { request: resolveProviderHttpRequestConfig({ model: params.model, provider: params.provider, // allowPrivateNetwork IS present here from Bug 1 fix }) }

Pero resolveProviderRequestPolicyConfig recibe params.request?.allowPrivateNetwork y nunca lo verifica. El valor solo se lee del parámetro plano params.allowPrivateNetwork, que los llamadores de audio no establecen explícitamente.

Resultado: Incluso si el Error 1 se corrigiera y allowPrivateNetwork se colocara correctamente en params.request, el Error 2 lo descartaría silenciosamente durante la resolución de políticas.


Diagrama de Secuencia de Fallas

┌─────────────────────────────────────────────────────────────────────────┐ │ COMPORTAMIENTO CORRECTO (v2026.4.12/13) │ ├─────────────────────────────────────────────────────────────────────────┤ │ models.providers.openai.request.allowPrivateNetwork = true │ │ │ │ │ ▼ │ │ resolveProviderExecutionContext() merges provider config │ │ │ │ │ ▼ │ │ request.allowPrivateNetwork = true (propagated correctly) │ │ │ │ │ ▼ │ │ resolveProviderRequestPolicyConfig() reads from request object │ │ │ │ │ ▼ │ │ Audio transcription succeeds on private IP endpoint │ └─────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────┐ │ COMPORTAMIENTO ROTO (v2026.4.14) │ ├─────────────────────────────────────────────────────────────────────────┤ │ models.providers.openai.request.allowPrivateNetwork = true │ │ │ │ │ ┌───────────────────┴───────────────────┐ │ │ ▼ ▼ │ │ [BUG 1] resolveProviderExecutionContext() Provider config │ │ NEVER includes provider config → allowPrivateNetwork = undefined │ │ │ │ │ ▼ │ │ mergeProviderRequestOverrides() produces request without │ │ allowPrivateNetwork field │ │ │ │ │ ▼ │ │ [BUG 2] resolveProviderRequestPolicyConfig() │ │ only checks params.allowPrivateNetwork (not params.request) │ │ → allowPrivateNetwork = false (default) │ │ │ │ │ ▼ │ │ SSRF policy blocks private IP → SsrFBlockedError │ └─────────────────────────────────────────────────────────────────────────┘


Por qué Se Requieren Ambos Errores

¿Error 1 corregido?¿Error 2 corregido?Resultado
❌ No❌ NoBloqueo SSRF (estado actual roto)
✅ Sí❌ NoBloqueo SSRF (Error 2 todavía descarta el valor)
❌ No✅ SíSin cambio (Error 1 nunca propaga el valor)
✅ Sí✅ Sí✅ Comportamiento correcto restaurado

🛠️ Solución paso a paso

Opción 1: Hotfix Aplicado a Archivos Dist (Inmediato)

Este enfoque parcha los archivos de distribución compilados directamente. Adecuado para despliegues en contenedores o cuando reconstruir desde el código fuente no es inmediatamente posible.

Requisitos previos

  • Acceso al sistema de archivos del contenedor en ejecución o al entorno de despliegue
  • Los dos archivos dist afectados:
    • runner.entries-*.js
    • provider-request-config-*.js

Paso 1: Localizar los Archivos Afectados

# Find the dist files in your deployment
find /app -name "runner.entries-*.js" 2>/dev/null
find /app -name "provider-request-config-*.js" 2>/dev/null

# Typical container paths:
# /app/dist/api/worker/runner.entries-*.js
# /app/dist/api/providers/provider-request-config-*.js

Paso 2: Parchar runner.entries-*.js (Corrección del Error 1)

Antes (Con error): javascript request: mergeProviderRequestOverrides( sanitizeConfiguredProviderRequest(params.config?.request), sanitizeConfiguredProviderRequest(params.entry.request) )

Después (Corregido): javascript request: mergeModelProviderRequestOverrides( sanitizeConfiguredModelProviderRequest(providerConfig?.request), sanitizeConfiguredProviderRequest(params.config?.request), sanitizeConfiguredProviderRequest(params.entry.request) )

Paso 3: Parchar provider-request-config-*.js (Corrección del Error 2)

Antes (Con error): javascript allowPrivateNetwork: params.allowPrivateNetwork ?? false

Después (Corregido): javascript allowPrivateNetwork: params.allowPrivateNetwork ?? params.request?.allowPrivateNetwork ?? false

Paso 4: Reiniciar la Aplicación

# For Docker containers
docker-compose restart openclaw

# For Kubernetes
kubectl rollout restart deployment/openclaw

# For systemd
sudo systemctl restart openclaw

Opción 2: Workaround de Configuración (Sin Cambios en Código)

Si no puede modificar los archivos dist inmediatamente, puede evitar el error especificando allowPrivateNetwork en la configuración a nivel de herramienta en lugar del nivel de proveedor.

Cambio de Configuración

Antes (Nivel de proveedor — No funciona en v2026.4.14): json { “models”: { “providers”: { “openai”: { “baseUrl”: “http://192.168.x.x:5092/v1", “request”: { “allowPrivateNetwork”: true } } } }, “tools”: { “media”: { “audio”: { “models”: [{ “provider”: “openai”, “model”: “parakeet” }] } } } }

Después (Workaround a nivel de herramienta): json { “models”: { “providers”: { “openai”: { “baseUrl”: “http://192.168.x.x:5092/v1" } } }, “tools”: { “media”: { “audio”: { “request”: { “allowPrivateNetwork”: true }, “models”: [{ “provider”: “openai”, “model”: “parakeet” }] } } } }

Nota: Este workaround coloca allowPrivateNetwork en tools.media.audio.request, que se verifica a través de params.config?.request en la cadena de fusión existente. Sin embargo, esto debe aplicarse a cada configuración de herramienta que necesite acceso a la red privada.


Opción 3: Corrección Permanente mediante Modificación del Código Fuente (Recomendado)

Para una resolución a largo plazo, aplique las correcciones a los archivos TypeScript de origen antes de compilar.

Corrección del Error 1 — Archivo de Origen

Archivo: src/api/worker/runner/entries.ts (o equivalente)

Cambio: typescript // Before const request = mergeProviderRequestOverrides( sanitizeConfiguredProviderRequest(params.config?.request), sanitizeConfiguredProviderRequest(params.entry.request) );

// After const request = mergeModelProviderRequestOverrides( sanitizeConfiguredModelProviderRequest(providerConfig?.request), sanitizeConfiguredProviderRequest(params.config?.request), sanitizeConfiguredProviderRequest(params.entry.request) );

Corrección del Error 2 — Archivo de Origen

Archivo: src/api/providers/provider-request-config.ts (o equivalente)

Cambio: typescript // Before const allowPrivateNetwork = params.allowPrivateNetwork ?? false;

// After const allowPrivateNetwork = params.allowPrivateNetwork ?? params.request?.allowPrivateNetwork ?? false;

Recompilar y Desplegar

# Rebuild the application
npm run build

# Or with specific build command
pnpm build

# Redeploy
docker build -t openclaw:fixed .
docker push your-registry/openclaw:fixed
kubectl rollout restart deployment/openclaw

🧪 Verificación

Requisitos previos para la verificación

Asegúrese de tener:

  • Un punto de conexión STT autoalojado en una IP privada (por ejemplo, Parakeet en 192.168.1.100:5092)
  • Un archivo de audio de prueba para transcripción
  • Acceso a los registros de despliegue

Paso 1: Verificar que la Configuración Está Cargada

Verifique que su configuración de proveedor con allowPrivateNetwork esté correctamente reconocida:

# Check loaded configuration (if CLI exposes this)
openclaw config show --path "models.providers.openai.request"

# Expected output:
# { allowPrivateNetwork: true }

# Or in debug logs, look for:
# [config] loaded provider config: openai { ..., request: { allowPrivateNetwork: true } }

Paso 2: Habilitar el Registro de Depuración de Seguridad

# Set debug environment variable
export DEBUG=openclaw:security:*

# Or in docker-compose.yml:
# environment:
#   - DEBUG=openclaw:security:*

Paso 3: Ejecutar Transcripción de Prueba

# Create a test audio file (silence or short recording)
ffmpeg -f lavfi -i anullsrc=r=16000:cl=mono -t 1 -acodec pcm_s16le /tmp/test.wav

# Execute transcription via OpenClaw CLI or API
openclaw media transcribe \
  --provider openai \
  --model parakeet \
  --audio @/tmp/test.wav \
  --url http://192.168.1.100:5092/v1/audio/transcriptions

# Or via API
curl -X POST http://localhost:3000/api/media/transcribe \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "openai",
    "model": "parakeet",
    "audioUrl": "http://192.168.1.100:5092/v1/audio/transcriptions"
  }'

Paso 4: Verificar Éxito (Comportamiento Corregido)

Salida esperada (Éxito):

[media-understanding] audio: processing (1/1) provider=openai model=parakeet
[media-understanding] audio: completed (1/1) provider=openai model=parakeet duration=1.2s
# Transcription result should be returned without SSRF error

Verificación de Registro de Depuración (Corregido):

[security] resolving request policy for provider=openai
[security] allowPrivateNetwork=true (resolved from request config)
[security] URL fetch allowed: target=http://192.168.1.100:5092/v1/audio/transcriptions
# Should see allowPrivateNetwork=true in logs, not false

Paso 5: Verificar Estado de Falla (Línea base)

Si la corrección no está aplicada, verá:

[security] blocked URL fetch (url-fetch) target=http://192.168.1.100:5092/v1/audio/transcriptions reason=Blocked hostname or private/internal/special-use IP address
[media-understanding] audio: failed (0/1) reason=SsrFBlockedError
[security] allowPrivateNetwork=false (default fallback)
# Note: The second line shows the bug — should be true from config

Paso 6: Suite de Pruebas de Regresión

Cree un script de prueba para verificar ambos escenarios:

#!/bin/bash
# test-allow-private-network.sh

set -e

echo "=== Test 1: Provider-level allowPrivateNetwork ==="
openclaw config set models.providers.test.request.allowPrivateNetwork true
openclaw media transcribe \
  --provider test \
  --model test-model \
  --audio @/tmp/test.wav \
  --url http://192.168.1.100:5092/v1/audio/transcriptions

if [ $? -eq 0 ]; then
  echo "✅ Test 1 PASSED: Private network access granted"
else
  echo "❌ Test 1 FAILED: SSRF blocked the request"
  exit 1
fi

echo ""
echo "=== Test 2: Verify config is not silently dropped ==="
DEBUG=openclaw:security:* openclaw media transcribe \
  --provider test \
  --model test-model \
  --audio @/tmp/test.wav \
  --url http://192.168.1.100:5092/v1/audio/transcriptions 2>&1 | grep -i "allowPrivateNetwork"

echo ""
echo "=== All tests completed ==="

⚠️ Errores comunes

1. Malinterpretar la Jerarquía de Configuración

Error: Los operadores colocan allowPrivateNetwork en la ubicación incorrecta, esperando que se propague automáticamente.

Detalles:

  • models.providers.<id>.request.allowPrivateNetwork — nivel de proveedor (roto en v2026.4.14)
  • tools.media.audio.request.allowPrivateNetwork — nivel de herramienta (funciona, pero debe establecerse explícitamente por herramienta)
  • tools.*.request.allowPrivateNetwork — comodín (solo afecta las herramientas coincidentes)

Enfoque correcto: Hasta que se corrija el error, siempre establezca allowPrivateNetwork a nivel de herramienta para transcripción de audio.


2. Asumir que la Validación de Esquema Pasa Cuando la Configuración Es Ignorada

Error: El esquema valida allowPrivateNetwork como un campo válido, por lo que los operadores asumen que está siendo utilizado.

Detalles: El esquema JSON para models.providers.*.request incluye allowPrivateNetwork (agregado en #63671 para v2026.4.12). Sin embargo, la ruta del código nunca lee este campo para transcripción de audio. Esto crea un falso positivo — la configuración parece válida pero se descarta silenciosamente.

Workaround: Siempre verifique el comportamiento con solicitudes de prueba reales, no solo con validación de esquema.


3. Conflictos de Montaje de Volúmenes Docker

Error: Cuando se usan montajes de volumen para parchar archivos dist, los archivos originales pueden restaurarse al reiniciar el contenedor.

Detalles: Si parchear archivos dist/ directamente en un contenedor: yaml

docker-compose.yml

volumes:

  • ./patched-runner.entries.js:/app/dist/api/worker/runner.entries-abc123.js

La política de reinicio del contenedor o la reconstrucción de la imagen sobrescribirán su parche.

Solución: Use una imagen derivada con el parche incluido: dockerfile FROM openclaw:2026.4.14 COPY patched-runner.entries.js /app/dist/api/worker/runner.entries-abc123.js COPY patched-provider-request-config.js /app/dist/api/providers/provider-request-config-xyz789.js


4. Retraso en la Propagación de Caché

Error: Después de corregir la configuración, la transcripción de audio todavía falla debido a la resolución de proveedor en caché.

Detalles: OpenClaw almacena en caché las configuraciones de proveedor resueltas. Una corrección a models.providers.<id>.request.allowPrivateNetwork puede no tomar efecto hasta que:

  • El TTL del caché expire
  • La aplicación se reinicie
  • El caché se limpie explícitamente

Comandos: bash

Clear configuration cache

rm -rf ~/.openclaw/cache/* rm -rf /tmp/openclaw-*

Or restart the service

systemctl restart openclaw


5. Políticas de Seguridad en Conflicto

Error: Incluso con allowPrivateNetwork: true, una política de seguridad global puede sobrescribirla.

Detalles: Verifique si hay configuraciones en conflicto: json { “security”: { “networkPolicy”: { “allowPrivate”: false // This would override provider-level settings } } }

Verificación: bash openclaw config show –path security


6. Direcciones Privadas IPv6

Error: allowPrivateNetwork puede no cubrir todos los rangos de direcciones IPv6 privadas.

Detalles: Las siguientes pueden seguir siendo bloqueadas incluso con allowPrivateNetwork: true:

  • ::1 (loopback)
  • fc00::/7 (unique local addresses)
  • fe80::/10 (link-local addresses)

Solución: Si usa direcciones IPv6 privadas, verifique que la política SSRF las incluya explícitamente: bash DEBUG=openclaw:security:* openclaw media transcribe … 2>&1 | grep -i “ipv6|private”


7. Conflictos de Verificación TLS/SSL

Error: Los puntos de conexión de red privada a menudo usan certificados autofirmados. Si request.tls.rejectUnauthorized no está configurado, las solicitudes pueden fallar con errores de certificado.

Detalles: Una configuración completa de red privada debe incluir: json { “models”: { “providers”: { “openai”: { “baseUrl”: “https://192.168.1.100:5092/v1”, “request”: { “allowPrivateNetwork”: true, “tls”: { “rejectUnauthorized”: false } } } } } }

🔗 Errores relacionados

Errores directamente relacionados

  • SsrFBlockedError — El error principal visto en esta regresión. Ocurre cuando la política SSRF bloquea una solicitud a un nombre de host o dirección IP no explícitamente permitida.

    [security] blocked URL fetch (url-fetch) target=http://192.168.x.x:5092/v1/audio/transcriptions reason=Blocked hostname or private/internal/special-use IP address

  • UrlFetchError — Fallo general de captura de URL, puede ocurrir si SSRF no es el mecanismo de bloqueo pero la URL es inválida de otra manera.

  • ProviderRequestConfigError — Se lanza cuando la configuración de solicitud no se puede resolver. Puede ocurrir si la cadena de fusión encuentra valores indefinidos.


Problemas históricamente relacionados

  • PR #63671 (v2026.4.12) — Introdujo el esquema models.providers.*.request.allowPrivateNetwork y la implementación inicial. Este PR agregó la capacidad que la v2026.4.14 hace retroceder.

  • Issue #64201 — SSRF bypass via redirect — Preocupación histórica sobre permitir acceso a redes privadas. Cualquier corrección para la regresión actual debe asegurar que los objetivos de redirección también estén sujetos a verificaciones de allowPrivateNetwork.

  • Issue #63847 — Media transcription timeout on large files — Relacionado con la confiabilidad de transcripción de audio; puede compartir rutas de manejo de errores con esta regresión.

  • Issue #64012 — Provider config merge priority unclear — Documenta la confusión sobre qué nivel de configuración toma precedencia. Relevante porque el Error 1 proviene de la configuración del proveedor no incluida en la fusión.


Patrones de Error Similares

Código de errorDescripciónDistinción
NetworkErrorFallo de red genéricoSin componente SSRF; generalmente DNS/conexión rechazada
CertificateErrorFallo de validación TLS/SSLRelacionado con certificados autofirmados de red privada
TimeoutErrorTiempo de espera de solicitudPuede ser mal diagnosticado si SSRF bloquea rápido
AuthenticationErrorFallo de autenticaciónSin relación; indica credenciales incorrectas

Referencia de Depuración

Al reportar problemas relacionados con esta regresión, incluya:

# Environment info
openclaw --version
# Expected: v2026.4.14

# Configuration (sanitized)
cat config.json | jq '.models.providers | keys'

# Debug logs with security trace
DEBUG=openclaw:* node index.js 2>&1 | grep -E "(allowPrivateNetwork|SsrFBlocked|provider-request)"

# Provider resolution trace
DEBUG=openclaw:provider:* node index.js 2>&1 | grep -E "(resolveProvider|ExecutionContext)"

Evidencia y fuentes

Esta guía de solución de problemas fue sintetizada automáticamente por la tubería de inteligencia de FixClaw a partir de las discusiones de la comunidad.