GHSA-r7v3-x45f-g7hp: High severity pip/PraisonAI vulnerability

Published Aug 25, 2026
·
Updated

Summary praisonai serve agents exposes HTTP routes that invoke registered agents. The CLI advertises --api-key with help text "API key for authentication", parses it, and forwards it into ServeHandler. But createagentsapp() never reads config["apikey"] again and installs no auth dependency or middleware on its direct routes. The configured key is a no-op flag. As a result, an unauthenticated network caller can invoke exposed agents (POST /agents and POST /agents/{agentname}) even when the operator passed --api-key. Requests with no credentials, a wrong Authorization: Bearer, a wrong X-API-Key, or an empty bearer all reach agent.start(). The failure is made sharper by the fact that a working auth dependency already exists in the same module — praisonai.api.agentinvoke.verifytoken guards every /api/v1/... route with Depends(verifytoken) and is mounted into the very same app. The direct n8n-compat routes simply do not use it.

Technical Detail Source-to-sink trace 1. CLI advertises and forwards --api-key: python cli/commands/serve.py @app.command("agents") def serveagents(..., apikey: Optional[str] = typer.Option(None, "--api-key", help="API key for authentication")): ... if apikey: args.extend(["--api-key", apikey]) exitcode = handleservecommand(args) 2. cmdagents() parses apikey into the spec — and that is the last time it is touched: python cli/features/serve.py — cmdagents() spec = { ..., "apikey": {"default": None} } parsed = self.parseargs(args, spec) app = self.createagentsapp(parsed) A grep of the entire cli/features/serve.py for apikey returns only the two spec entries (cmdagents line ~199 and cmdunified line ~847). config["apikey"] is never read inside createagentsapp() / createunifiedapp(); it is never compared, and no dependency is attached. 3. createagentsapp() imports FastAPI, HTTPException, Request — no Depends, no Header, no auth middleware. Every HTTPException raised in the agents routes is 400/404/500 (validation / not-found / execution error); none is 401. 4. Sink — unauthenticated request reaches agent.start(): python cli/features/serve.py @app.post("/agents/{agentname}") # n8n compatibility route async def invokesingleagent(agentname: str, request: Request): body = await request.json() query = body.get("query", "") or body.get("message", "") ... agent = agentinvoke.getagent(agentname) result = await loop.runinexecutor(None, agent.start, query) # no auth anywhere above return {"response": str(result)} @app.post(path) # default path "/agents" async def invokeagents(request: Request, querydata: AgentQuery = None): ... agent.start(query) ...

The auth dependency exists — it just isn't applied here createagentsapp() mounts the agentinvoke router into the same app: python cli/features/serve.py if getattr(agentinvoke, 'FASTAPIAVAILABLE', False) and hasattr(agentinvoke, 'router'): app.includerouter(agentinvoke.router) That router properly authenticates every sensitive route: python api/agentinvoke.py CALLSERVERTOKEN = os.getenv('CALLSERVERTOKEN') async def verifytoken(request, authorization=Header(None)) -> None: ... if token != CALLSERVERTOKEN: raise HTTPException(statuscode=401, detail="Unauthorized") @router.get("/api/v1/agents") async def listagents(: None = Depends(verifytoken)): ... # and register/unregister/info all use it So in the same process GET /api/v1/agents returns 401 without a token, while POST /agents/{agentname} returns 200. Note also that verifytoken reads the CALLSERVERTOKEN env var — not the CLI --api-key — so the CLI option feeds no auth path at all. Trigger conditions praisonai serve agents --file agents.yaml --host 0.0.0.0 --port 8765 --api-key expected-secret POST /agents/{agentname} body {"query":"..."} with no / wrong / empty credentials Proof of Concept Built the real createagentsapp() and exercised it over HTTP via FastAPI TestClient. Only praisonaiagents.Agent is stubbed (.start() returns EXEC:<query>), so no real LLM/credentials. CALLSERVERTOKEN=expected-secret was set so the sibling /api/v1 router is genuinely armed — making the contrast explicit. Operator started with: --api-key expected-secret (CALLSERVERTOKEN also set) == Sibling /api/v1 route WITH Depends(verifytoken) == GET /api/v1/agents [no creds ] -> HTTP 401 GET /api/v1/agents [wrong bearer] -> HTTP 401 == Direct agent-invocation route (the bug) == POST /agents/owned [no creds ] -> HTTP 200 {'response': 'EXEC:hello'} POST /agents/owned [wrong bearer ] -> HTTP 200 {'response': 'EXEC:hello'} POST /agents/owned [wrong x-api-key] -> HTTP 200 {'response': 'EXEC:hello'} POST /agents/owned [empty bearer ] -> HTTP 200 {'response': 'EXEC:hello'} POST /agents [no creds ] -> HTTP 200 {'response': 'EXEC:hi'} The auth mechanism works for /api/v1 (401) and is entirely absent on the direct /agents routes (200), despite --api-key being configured. Equivalent HTTP trigger in a fully installed environment bash praisonai serve agents --file agents.yaml --host 0.0.0.0 --port 8765 --api-key expected-secret curl -sS -X POST http://TARGET:8765/agents/owned \ -H 'Content-Type: application/json' --data-binary '{"query":"hello"}' -> 200 {"response":"..."} (expected: 401 Unauthorized)

Impact - Direct primitive: unauthenticated agent invocation despite a configured API key. - Misleading control (aggravating): because the CLI advertises --api-key as authentication, operators may deliberately expose the service (e.g. --host 0.0.0.0, reverse proxy, n8n integration) believing it is protected, increasing the real-world likelihood of exposure. - Downstream: exposed agents commonly hold LLM provider credentials, RAG/memory, browser/search, MCP, or shell/file tools; the bypass lets an attacker drive those capabilities. Baseline impact is unauthorized LLM cost + access to agent responses.

Suggested Mitigation - When config["apikey"] is set, build a shared auth dependency and attach it to every agent-invocation / state-changing route in createagentsapp() and createunifiedapp() (dependencies=[Depends(verify)]). - Reuse / unify with the existing verifytoken so the direct /agents routes and the /api/v1 routes share one mechanism, and wire the CLI --api-key into that mechanism (today it feeds nothing; verifytoken reads CALLSERVERTOKEN). - Use constant-time comparison (hmac.comparedigest); verifytoken currently uses !=. - Update discovery metadata from authmodes=["none"] to ["api-key","bearer"] for protected endpoints. - Regression tests next to tests/unit/testserveunified.py: createagentsapp({"apikey":"secret",...}) → POST /agents/{name} with no creds / wrong Authorization / wrong X-API-Key returns 401; correct key succeeds. python import hmac from fastapi import Header, HTTPException, Depends def authdependency(expectedkey: str): async def verify(authorization: str | None = Header(None), xapikey: str | None = Header(None, alias="X-API-Key")): token = xapikey if authorization and authorization.startswith("Bearer "): token = authorization[7:] if not token or not hmac.comparedigest(token, expectedkey): raise HTTPException(statuscode=401, detail="Unauthorized") return Depends(verify)

Affected Software

1 affected componentFixes available
pip/PraisonAI<4.6.58
4.6.58

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade pip/PraisonAI to a version that resolves this vulnerability.

    Fixed in 4.6.58

Event History

Aug 25, 2026
Advisory Published
via GitHub·02:56 PM
Data Sourced
via GitHub·02:56 PM
DescriptionSeverityWeaknessAffected Software

Frequently Asked Questions

1

Which deployments are exposed?

Any PraisonAI deployment running `praisonai serve agents` that makes the direct agent routes reachable to network callers is exposed. This includes deployments started with `--api-key`, because that setting is not enforced on those routes.

2

Does an attacker need credentials or prior access?

No. A remote caller can invoke agents through `POST /agents` or `POST /agents/{agent_name}` without credentials. Missing credentials, invalid bearer tokens, invalid `X-API-Key` values, and empty bearer tokens all still reach `agent.start()`.

3

Are all agent API routes affected in the same way?

No. The direct n8n-compatible routes are unprotected, while `/api/v1/...` routes use the existing `verify_token` dependency. Exposure should be assessed specifically for the direct `/agents` routes.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203