Root cause
Injection & unsafe execution
The Cursor-coauthored hardening commit introduced disabled mode as an unconditional early return ahead of all token checks.
How AI contributed: Flawed AI-written codePublished Jun 18, 2026 in
mervinpraison/praisonaiAll 19 findings in this repository
Cursor-assisted authentication code added an environment switch that skipped every credential check. On an externally reachable server configured with that switch, anyone could call the agent-invocation API and trigger registered agents.
verify_token returned as soon as PRAISONAI_CALL_AUTH=disabled, before parsing or comparing any token. The final fix records each server bind address and refuses the bypass unless that address is explicitly loopback.
Root cause
The Cursor-coauthored hardening commit introduced disabled mode as an unconditional early return ahead of all token checks.
Fix
The fix stops trusting the request Host header, requires the server's actual bind address to be recorded, and permits PRAISONAI_CALL_AUTH=disabled only when that configured address is explicitly localhost.
AI-assisted fix: Cursor · cursor[bot]
Case facts
@@ -29,14 +29,26 @@ logger = logging.getLogger(__name__) # Authentication import os CALL_SERVER_TOKEN = os.getenv('CALL_SERVER_TOKEN')+_CALL_AUTH_DISABLED = os.getenv('PRAISONAI_CALL_AUTH', '').lower() == 'disabled'+ async def verify_token( request: Request, authorization: Optional[str] = Header(None) ) -> None: """Verify API token for authentication."""- if not FASTAPI_AVAILABLE or not CALL_SERVER_TOKEN:- return # No authentication if FastAPI unavailable or no token set+ if not FASTAPI_AVAILABLE:+ return+ if _CALL_AUTH_DISABLED:+ return+ if not CALL_SERVER_TOKEN:+ raise HTTPException(+ status_code=503,+ detail=(+ "CALL_SERVER_TOKEN is not configured. Set CALL_SERVER_TOKEN or "+ "PRAISONAI_CALL_AUTH=disabled to run without authentication."+ ),+ ) token = None @@ -38,9 +38,9 @@ def _call_auth_disabled() -> bool: return os.getenv('PRAISONAI_CALL_AUTH', '').lower() == 'disabled' -def _configured_bind_host() -> str:+def _configured_bind_host() -> Optional[str]: """Server bind address from env only — never trust client Host headers."""- return os.getenv('PRAISONAI_CALL_BIND_HOST', '127.0.0.1')+ return os.getenv('PRAISONAI_CALL_BIND_HOST') async def verify_token(@@ -52,10 +52,13 @@ async def verify_token( return if _call_auth_disabled(): bind_host = _configured_bind_host()- if bind_host not in _LOCALHOST_HOSTS:+ if bind_host is None or bind_host not in _LOCALHOST_HOSTS: raise HTTPException( status_code=503,- detail="PRAISONAI_CALL_AUTH=disabled is only permitted for localhost binding",+ detail=(+ "PRAISONAI_CALL_AUTH=disabled is only permitted for localhost binding; "+ "set PRAISONAI_CALL_BIND_HOST to 127.0.0.1 when binding locally"+ ), ) warnings.warn( "PRAISONAI_CALL_AUTH=disabled bypasses authentication; "@@ -372,7 +372,9 @@ def run_server(port: int, host: str = "127.0.0.1", use_public: bool = False): if host == "0.0.0.0" and not use_public: print("WARNING: Server is binding to all network interfaces (0.0.0.0). " "This exposes the service to your local network.")- ++ os.environ["PRAISONAI_CALL_BIND_HOST"] = host+ uvicorn.run(app, host=host, port=port, log_level="warning") def main(args=None):@@ -894,7 +894,10 @@ Launch PraisonAI servers with unified discovery support. def _run_server(self, app: Any, host: str, port: int, reload: bool) -> None: """Run the server with uvicorn."""+ import os import uvicorn++ os.environ["PRAISONAI_CALL_BIND_HOST"] = host uvicorn.run(app, host=host, port=port, reload=reload) AI-assisted change b8ec1774dace48e3caa2c71340e0e7bf3535d754d15bcd9f1dfa8966adb3fd7f · Fix 5533252079394df33b2420d6395a27e779ff4c0a4b7eefcbf7f5313be37e56af