
Laboratorio Docker local para CVE-2026-44338, una omisión de autenticación en el servidor API Flask heredado de PraisonAI.
Este laboratorio demuestra la condición de acceso no autenticado en las rutas de la API heredada. De forma intencionada, utiliza una reproducción segura a nivel de ruta en lugar de un despliegue completo de PraisonAI, de modo que la prueba se mantiene centrada en el fallo de autenticación y no activa flujos de trabajo reales de agentes ni llamadas externas a LLM.
CVE-2026-44338 afecta a las versiones de PraisonAI >= 2.5.6 y <= 4.6.33.
En el servidor API heredado vulnerable, la autenticación estaba deshabilitada por defecto. Como resultado, un llamante no autenticado que pudiera alcanzar el servidor API podía acceder a /agents y activar la ruta de flujo de trabajo /chat sin un token de portador.
El problema se corrigió en PraisonAI 4.6.34 cambiando el comportamiento por defecto para exigir autenticación a menos que se deshabilite explícitamente.
En la versión vulnerable, el servidor API heredado utilizaba valores de autenticación inseguros por defecto:
AUTH_ENABLED = False
AUTH_TOKEN = None
def check_auth():
if not AUTH_ENABLED:
return True
Debido a que check_auth() devolvía True cuando la autenticación estaba deshabilitada, las rutas protegidas fallaban en abierto (fail-open).
Las rutas afectadas incluían:
GET /agentsPOST /chatLa versión corregida cambia la postura por defecto para que la autenticación esté habilitada a menos que se deshabilite explícitamente mediante configuración.
El problema central no era una primitiva de explotación compleja. Provenía de valores por defecto inseguros en el servidor API Flask heredado.
v4.6.33En v4.6.33, la autenticación estaba deshabilitada por defecto:
AUTH_ENABLED = False
AUTH_TOKEN = None
La comprobación de autenticación fallaba entonces en abierto:
def check_auth():
if not AUTH_ENABLED:
return True
Esto significa que la solicitud se aceptaba siempre que la autenticación estuviera deshabilitada, incluso si el llamante no enviaba una cabecera Authorization.
El flujo vulnerable era:
AUTH_ENABLED = False
↓
check_auth() returns True
↓
GET /agents is allowed
POST /chat is allowed
↓
unauthenticated caller can access agent metadata and reach the workflow trigger route
La parte sensible es que /chat no era solo un endpoint de estado. Aceptaba un mensaje del usuario y luego invocaba el ejecutor de flujos de trabajo de PraisonAI usando agents.yaml.
v4.6.34En v4.6.34, el comportamiento por defecto se cambió para exigir autenticación a menos que el operador la deshabilite explícitamente:
AUTH_ENABLED = os.environ.get("PRAISONAI_API_AUTH", "enabled").strip().lower() != "disabled"
AUTH_TOKEN = os.environ.get("PRAISONAI_API_TOKEN") or None
La versión corregida también mejora el manejo de tokens:
secrets.compare_digest()127.0.0.1 por defecto en lugar de exponerse en todas las interfacesEl flujo corregido es:
AUTH_ENABLED = True by default
↓
request must include a valid Bearer token
↓
missing or invalid token returns 401
↓
/agents and /chat are no longer reachable anonymously
Este laboratorio replica esa diferencia a nivel de código fuente:
vuln -> auth disabled by default, unauthenticated requests return 200
patched -> auth required by default, unauthenticated requests return 401
El laboratorio contiene dos servicios locales:
| Servicio | URL | Comportamiento |
|---|---|---|
vuln | http://127.0.0.1:8081 | Reproduce el comportamiento vulnerable de autenticación fail-open |
patched | http://127.0.0.1:8082 | Requiere autenticación mediante token de portador |
Ambos servicios están vinculados únicamente a 127.0.0.1.
La ruta /chat utiliza un ejecutor simulado (dummy) en lugar de un flujo de trabajo real de PraisonAI. Esto proporciona una prueba observable de que la solicitud no autenticada alcanza la ruta de activación del flujo de trabajo sin causar efectos secundarios externos.
.
├── docker-compose.yml
├── vuln
│ ├── Dockerfile
│ └── start_server.py
├── patched
│ ├── Dockerfile
│ └── start_server.py
├── poc
│ └── poc.py
└── .gitignore
└── README.md
docker compose up --build -d
python3 poc/poc.py
El servicio vulnerable permite el acceso no autenticado:
=== vuln ===
[unauthenticated] GET /agents
status: 200
[unauthenticated] POST /chat
status: 200
verdict: LIKELY_VULNERABLE
El servicio corregido bloquea el acceso no autenticado:
=== patched ===
[unauthenticated] GET /agents
status: 401
[unauthenticated] POST /chat
status: 401
verdict: NOT_VULNERABLE_OR_PROTECTED
Resumen final esperado:
vuln: LIKELY_VULNERABLE
patched: NOT_VULNERABLE_OR_PROTECTED
Comprueba la ruta vulnerable:
curl -i http://127.0.0.1:8081/agents
Respuesta vulnerable esperada:
HTTP/1.1 200 OK
Comprueba la ruta corregida:
curl -i http://127.0.0.1:8082/agents
Respuesta corregida esperada:
HTTP/1.1 401 UNAUTHORIZED
Los registros del servidor deberían mostrar la diferencia claramente:
vuln: "GET /agents HTTP/1.1" 200
patched: "GET /agents HTTP/1.1" 401
docker compose down -v
Este laboratorio está pensado únicamente para investigación de seguridad local.
El PoC no:
Aviso de GitHub: GHSA-6rmh-7xcm-cpxj https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-6rmh-7xcm-cpxj
NVD: CVE-2026-44338 https://nvd.nist.gov/vuln/detail/CVE-2026-44338
OSV: GHSA-6rmh-7xcm-cpxj https://osv.dev/vulnerability/GHSA-6rmh-7xcm-cpxj
Código fuente vulnerable: PraisonAI v4.6.33 src/praisonai/api_server.py
https://raw.githubusercontent.com/MervinPraison/PraisonAI/v4.6.33/src/praisonai/api_server.py
Código fuente corregido: PraisonAI v4.6.34 src/praisonai/api_server.py
https://raw.githubusercontent.com/MervinPraison/PraisonAI/v4.6.34/src/praisonai/api_server.py