Hoch

PraisonAI: [Auth Bypass] `praisonai serve agents --api-key` is silently ignored — agent-invocation routes (`POST /agents`, `POST /agents/{agent_name}`) run unauthenticated

  • Veröffentlicht: 25.08.2026
  • Aktualisiert: 11.10.2026 15:18

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 _create_agents_app() never reads config["api_key"] 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/{agent_name}) 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.agent_invoke.verify_token guards every /api/v1/... route with Depends(verify_token) 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 serve_agents(..., api_key: Optional[str] = typer.Option(None, "--api-key", help="API key for authentication")):
    ...
    if api_key:
        args.extend(["--api-key", api_key])
    exit_code = handle_serve_command(args)

2. cmd_agents() parses api_key into the spec — and that is the last time it is touched:

python
# cli/features/serve.py — cmd_agents()
spec = { ..., "api_key": {"default": None} }
parsed = self._parse_args(args, spec)
app = self._create_agents_app(parsed)

A grep of the entire cli/features/serve.py for api_key returns only the two spec entries (cmd_agents line ~199 and cmd_unified line ~847). config["api_key"] is never read inside _create_agents_app() / _create_unified_app(); it is never compared, and no dependency is attached.

3. _create_agents_app() 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/{agent_name}")          # n8n compatibility route
async def invoke_single_agent(agent_name: str, request: Request):
    body = await request.json()
    query = body.get("query", "") or body.get("message", "")
    ...
    agent = agent_invoke.get_agent(agent_name)
    result = await loop.run_in_executor(None, agent.start, query)   # no auth anywhere above
    return {"response": str(result)}
 
@app.post(path)                            # default path "/agents"
async def invoke_agents(request: Request, query_data: AgentQuery = None):
    ... agent.start(query) ...

The auth dependency exists — it just isn't applied here

_create_agents_app() mounts the agent_invoke router into the same app:

python
# cli/features/serve.py
if getattr(agent_invoke, 'FASTAPI_AVAILABLE', False) and hasattr(agent_invoke, 'router'):
    app.include_router(agent_invoke.router)

That router properly authenticates every sensitive route:

python
# api/agent_invoke.py
CALL_SERVER_TOKEN = os.getenv('CALL_SERVER_TOKEN')
async def verify_token(request, authorization=Header(None)) -> None:
    ...
    if token != CALL_SERVER_TOKEN:
        raise HTTPException(status_code=401, detail="Unauthorized")
 
@router.get("/api/v1/agents")
async def list_agents(_: None = Depends(verify_token)): ...   # and register/unregister/info all use it

So in the same process GET /api/v1/agents returns 401 without a token, while POST /agents/{agent_name} returns 200. Note also that verify_token reads the CALL_SERVER_TOKEN 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/{agent_name}   body {"query":"..."}   with no / wrong / empty credentials

Proof of Concept

Built the real _create_agents_app() and exercised it over HTTP via FastAPI TestClient. Only praisonaiagents.Agent is stubbed (.start() returns EXEC:<query>), so no real LLM/credentials. CALL_SERVER_TOKEN=expected-secret was set so the sibling /api/v1 router is genuinely armed — making the contrast explicit.

Operator started with: --api-key expected-secret  (CALL_SERVER_TOKEN also set)
 
== Sibling /api/v1 route WITH Depends(verify_token) ==
  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["api_key"] is set, build a shared auth dependency and attach it to every agent-invocation / state-changing route in _create_agents_app() and _create_unified_app() (dependencies=[Depends(verify)]).
  • Reuse / unify with the existing verify_token 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; verify_token reads CALL_SERVER_TOKEN).
  • Use constant-time comparison (hmac.compare_digest); verify_token currently uses !=.
  • Update discovery metadata from auth_modes=["none"] to ["api-key","bearer"] for protected endpoints.
  • Regression tests next to tests/unit/test_serve_unified.py: _create_agents_app({"api_key":"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 _auth_dependency(expected_key: str):
    async def verify(authorization: str | None = Header(None),
                     x_api_key: str | None = Header(None, alias="X-API-Key")):
        token = x_api_key
        if authorization and authorization.startswith("Bearer "):
            token = authorization[7:]
        if not token or not hmac.compare_digest(token, expected_key):
            raise HTTPException(status_code=401, detail="Unauthorized")
    return Depends(verify)

Betroffene Software

Klicken Sie auf eine Technologie, um alle zugehörigen Schwachstellen anzuzeigen.

PlattformPaketAnfällige VersionenBehoben in
Python (PyPI) praisonai < 4.6.58 4.6.58

Verweise

CVSS-Wert 7.3/10
CVSS-Vektor
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L
Schwäche (CWE)
CWE-306
Plattform
Python (PyPI)
Paket
praisonai
Veröffentlicht
25.08.2026
Aktualisiert
11.10.2026 15:18

Quellen und Lizenzen

OSV / GitHub Advisory Database - PYSEC-2026-3894 - PyPA Advisory Database; CC BY 4.0

OSV / GitHub Advisory Database - GHSA-r7v3-x45f-g7hp - CC BY 4.0

Schwachstellenhistorie: praisonai

Alle veröffentlichten Schwachstellen dieses Pakets (Python (PyPI)), neueste zuerst. Zeigt, ob es ein einmaliger Ausrutscher ist oder ein Paket, das man im Auge behalten muss.

Alle (104) im Archiv
Einträge insgesamt
104
Erste → letzte
04.2026 10.2026
Ohne Fix
0
In Angriffen ausgenutzt
0

Verteilung nach Schweregrad

  • Kritisch 27
  • Hoch 60
  • Mittel 17

Veröffentlichungen pro Jahr

  1. 2026: 104
Schweregrad:
  1. Hoch · 7.5 CVE-2026-61439 < 4.6.78 4.6.78
  2. PraisonAI: [Auth Bypass] `praisonai serve agents --api-key` is silently ignored — agent-invocation routes (`POST /agents`, `POST /agents/{agent_name}`) run unauthenticated dieser Eintrag
    Hoch · 7.3 CVE-2026-55538 < 4.6.58 4.6.58
Mehr anzeigen (20) 20 von 87 angezeigt

Erkennen Sie Schwachstellen in Ihrer Anwendung

Mit der DockRay-Integration und aktivierter Softwareinventarisierung prüft DockRay gemeldete Komponentenversionen auf bekannte Schwachstellen und benachrichtigt Sie über bestätigte Treffer. Die Abdeckung hängt von der unterstützten Technologie und den verfügbaren Meldungen ab.

Anwendung verbinden
DockRay Newsletter

Anwendungssicherheit in Ihrem Postfach

Wichtige Schwachstellen, konkrete Empfehlungen und praktische Tipps für Ihre Anwendungen. Wählen Sie Ihre Technologien und verfolgen Sie, was für Ihr Projekt relevant ist.

  • Eine tägliche Zusammenfassung neuer Schwachstellen, auch kritischer.
  • Benachrichtigungen über wichtige Updates.
  • Praktische Tipps zur Sicherheit und Überwachung von Anwendungen.

Leer lassen, um Meldungen zu allen Technologien zu erhalten.

Kostenlos anmelden. In jeder E-Mail mit einem Klick abmelden.