Every CVE Search/ Get API key

CVE-2026-55529

PraisonAI: Origin validation bypass in MCP HTTP Stream transport allows browser-mediated unauthenticated tool execution on local MCP server

6.9 MediumFix available

PraisonAI is a multi-agent teams system. Prior to praisonai 4.6.58, the MCP HTTP Stream _validate_origin method accepts request_origin.startswith(allowed), so the attacker-controlled localhost.evil.example HTTP origin matches the localhost allowlist. Without an API key, a malicious webpage can submit tools/call requests to the local MCP server and execute exposed tools. This issue is fixed in version 4.6.58.

Attack profile

How easy it is to exploit

Attack vector
Network
Complexity
High
Privileges needed
None
User interaction
Required

What an attacker can affect

Confidentiality
High
Integrity
Low
Availability
None

Exploit likelihood

0.18%Up 0.1 points since Aug 26, 2026
90%50%10%1%

What each source reports

CVE List Score used 6.9

PraisonAI: Origin validation bypass in MCP HTTP Stream transport allows browser-mediated unauthenticated tool execution on local MCP server

PraisonAI is a multi-agent teams system. Prior to praisonai 4.6.58, the MCP HTTP Stream _validate_origin method accepts request_origin.startswith(allowed), so the attacker-controlled localhost.evil.example HTTP origin matches the localhost allowlist. Without an API key, a malicious webpage can submit tools/call requests to the local MCP server and execute exposed tools. This issue is fixed in version 4.6.58.

Record
CVE-2026-55529
CVSS 3.1
6.9 MediumCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:L/A:N
Weakness
CWE-306, CWE-346
Updated
2026-11-02
EUVDEUVD-2026-65496 6.9

PraisonAI is a multi-agent teams system. Prior to praisonai 4.6.58, the MCP HTTP Stream _validate_origin method accepts request_origin.startswith(allowed), so the attacker-controlled localhost.evil.example HTTP origin matches the localhost allowlist. Without an API key, a malicious webpage can submit tools/call requests to the local MCP server and execute exposed tools. This issue is fixed in version 4.6.58.

Record
EUVD-2026-65496
CVSS 3.1
6.9 MediumCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:L/A:N
Updated
2026-11-02
GitHub AdvisoriesGHSA-wj6g-v78p-6fx3 6.9

PraisonAI has an origin validation bypass in MCP HTTP Stream transport that allows browser-mediated unauthenticated tool execution on local MCP server

Summary

PraisonAI's MCP HTTP Stream transport uses an unsafe prefix match when validating the Origin header. The default localhost allowlist includes origins such as http://localhost, and the validation accepts any origin that starts with an allowed value.

As a result, an attacker-controlled origin such as http://localhost.evil.example passes the localhost origin check.

When the MCP HTTP Stream server is started without an API key, which is the CLI default, this allows a malicious webpage to trigger unauthenticated MCP tools/call requests against a locally running PraisonAI MCP server.

This is best framed as a browser-mediated localhost attack / DNS-rebinding-style Origin validation bypass. The default server binds to 127.0.0.1, so this is not a directly internet-facing unauthenticated API in the default configuration.

Details

Relevant source locations:

  • src/praisonai/praisonai/mcp_server/cli.py
  • src/praisonai/praisonai/mcp_server/transports/http_stream.py
  • src/praisonai/praisonai/mcp_server/server.py
  • src/praisonai/praisonai/mcp_server/adapters/__init__.py
  • src/praisonai/praisonai/mcp_server/adapters/extended_capabilities.py
  • src/praisonai/praisonai/mcp_server/adapters/cli_tools.py
  • src/praisonai/praisonai/capabilities/files.py

The MCP CLI defaults to HTTP host 127.0.0.1, API key None, and allowed origins None unless explicitly configured:

parser.add_argument("--host", default="127.0.0.1")
parser.add_argument("--port", type=int, default=8080)
parser.add_argument("--api-key", default=None)
parser.add_argument("--allowed-origins", default=None, help="Comma-separated allowed origins for security")

The CLI registers all tools and passes the optional API key and allowed origins into the HTTP Stream transport:

register_all()

server.run_http_stream(
    host=parsed.host,
    port=parsed.port,
    endpoint=parsed.endpoint,
    api_key=parsed.api_key,
    cors_origins=cors_origins,
    allowed_origins=allowed_origins,
    session_ttl=parsed.session_ttl,
    allow_client_termination=allow_termination,
    response_mode=parsed.response_mode,
    resumability_enabled=parsed.resumability,
)

When allowed_origins is not explicitly configured and the server binds to localhost, the transport allowlist includes bare localhost origins:

if allowed_origins is None:
    if host in ("127.0.0.1", "localhost", "::1"):
        self.allowed_origins = [
            "http://localhost",
            "http://127.0.0.1",
            "https://localhost",
            "https://127.0.0.1",
        ]

The vulnerable validation accepts origins that merely start with an allowlisted value:

for allowed in self.allowed_origins:
    if request_origin == allowed or request_origin.startswith(allowed):
        return True

Because http://localhost.evil.example starts with http://localhost, it is accepted as a trusted localhost origin.

Authentication is only enforced if an API key is configured:

if self.api_key:
    auth_header = request.headers.get("Authorization", "")
    if not auth_header.startswith("Bearer ") or auth_header[7:] != self.api_key:
        return JSONResponse(
            {"error": "Unauthorized"},
            status_code=401,
        )

The request body is then parsed and dispatched to the MCP server:

body = await request.json()
response = await self.server.handle_message(body)

The MCP server handles tools/call by looking up the named tool and invoking the registered handler with attacker-controlled arguments:

tool_name = params.get("name")
arguments = params.get("arguments", {})

tool = self._tool_registry.get(tool_name)

if asyncio.iscoroutinefunction(tool.handler):
    result = await tool.handler(**arguments)
else:
    result = tool.handler(**arguments)

register_all() registers capability tools, extended capability tools, CLI tools, resources, and prompts:

def…
Record
GHSA-wj6g-v78p-6fx3
CVSS 3.1
6.9 MediumCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:L/A:N
Weakness
CWE-306, CWE-346
Updated
2026-11-02
NVD 6.9

PraisonAI is a multi-agent teams system. Prior to praisonai 4.6.58, the MCP HTTP Stream _validate_origin method accepts request_origin.startswith(allowed), so the attacker-controlled localhost.evil.example HTTP origin matches the localhost allowlist. Without an API key, a malicious webpage can submit tools/call requests to the local MCP server and execute exposed tools. This issue is fixed in version 4.6.58.

Record
CVE-2026-55529
CVSS 3.1
6.9 MediumCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:L/A:N
Weakness
CWE-306, CWE-346
Updated
2026-11-02
OSV 6.9

PraisonAI: Origin validation bypass in MCP HTTP Stream transport allows browser-mediated unauthenticated tool execution on local MCP server

PraisonAI is a multi-agent teams system. Prior to praisonai 4.6.58, the MCP HTTP Stream _validate_origin method accepts request_origin.startswith(allowed), so the attacker-controlled localhost.evil.example HTTP origin matches the localhost allowlist. Without an API key, a malicious webpage can submit tools/call requests to the local MCP server and execute exposed tools. This issue is fixed in version 4.6.58.

Record
CVE-2026-55529
CVSS 3.1
6.9 MediumCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:L/A:N
Weakness
CWE-306, CWE-346
Updated
2026-11-02
OSVGHSA-wj6g-v78p-6fx3 6.9

PraisonAI has an origin validation bypass in MCP HTTP Stream transport that allows browser-mediated unauthenticated tool execution on local MCP server

Summary

PraisonAI's MCP HTTP Stream transport uses an unsafe prefix match when validating the Origin header. The default localhost allowlist includes origins such as http://localhost, and the validation accepts any origin that starts with an allowed value.

As a result, an attacker-controlled origin such as http://localhost.evil.example passes the localhost origin check.

When the MCP HTTP Stream server is started without an API key, which is the CLI default, this allows a malicious webpage to trigger unauthenticated MCP tools/call requests against a locally running PraisonAI MCP server.

This is best framed as a browser-mediated localhost attack / DNS-rebinding-style Origin validation bypass. The default server binds to 127.0.0.1, so this is not a directly internet-facing unauthenticated API in the default configuration.

Details

Relevant source locations:

  • src/praisonai/praisonai/mcp_server/cli.py
  • src/praisonai/praisonai/mcp_server/transports/http_stream.py
  • src/praisonai/praisonai/mcp_server/server.py
  • src/praisonai/praisonai/mcp_server/adapters/__init__.py
  • src/praisonai/praisonai/mcp_server/adapters/extended_capabilities.py
  • src/praisonai/praisonai/mcp_server/adapters/cli_tools.py
  • src/praisonai/praisonai/capabilities/files.py

The MCP CLI defaults to HTTP host 127.0.0.1, API key None, and allowed origins None unless explicitly configured:

parser.add_argument("--host", default="127.0.0.1")
parser.add_argument("--port", type=int, default=8080)
parser.add_argument("--api-key", default=None)
parser.add_argument("--allowed-origins", default=None, help="Comma-separated allowed origins for security")

The CLI registers all tools and passes the optional API key and allowed origins into the HTTP Stream transport:

register_all()

server.run_http_stream(
    host=parsed.host,
    port=parsed.port,
    endpoint=parsed.endpoint,
    api_key=parsed.api_key,
    cors_origins=cors_origins,
    allowed_origins=allowed_origins,
    session_ttl=parsed.session_ttl,
    allow_client_termination=allow_termination,
    response_mode=parsed.response_mode,
    resumability_enabled=parsed.resumability,
)

When allowed_origins is not explicitly configured and the server binds to localhost, the transport allowlist includes bare localhost origins:

if allowed_origins is None:
    if host in ("127.0.0.1", "localhost", "::1"):
        self.allowed_origins = [
            "http://localhost",
            "http://127.0.0.1",
            "https://localhost",
            "https://127.0.0.1",
        ]

The vulnerable validation accepts origins that merely start with an allowlisted value:

for allowed in self.allowed_origins:
    if request_origin == allowed or request_origin.startswith(allowed):
        return True

Because http://localhost.evil.example starts with http://localhost, it is accepted as a trusted localhost origin.

Authentication is only enforced if an API key is configured:

if self.api_key:
    auth_header = request.headers.get("Authorization", "")
    if not auth_header.startswith("Bearer ") or auth_header[7:] != self.api_key:
        return JSONResponse(
            {"error": "Unauthorized"},
            status_code=401,
        )

The request body is then parsed and dispatched to the MCP server:

body = await request.json()
response = await self.server.handle_message(body)

The MCP server handles tools/call by looking up the named tool and invoking the registered handler with attacker-controlled arguments:

tool_name = params.get("name")
arguments = params.get("arguments", {})

tool = self._tool_registry.get(tool_name)

if asyncio.iscoroutinefunction(tool.handler):
    result = await tool.handler(**arguments)
else:
    result = tool.handler(**arguments)

register_all() registers capability tools, extended capability tools, CLI tools, resources, and prompts:

def…
Record
GHSA-wj6g-v78p-6fx3
CVSS 3.1
6.9 MediumCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:L/A:N
Weakness
CWE-306, CWE-346
Updated
2026-11-02
OSVPYSEC-2026-3896 6.9

PraisonAI has an origin validation bypass in MCP HTTP Stream transport that allows browser-mediated unauthenticated tool execution on local MCP server

Summary

PraisonAI's MCP HTTP Stream transport uses an unsafe prefix match when validating the Origin header. The default localhost allowlist includes origins such as http://localhost, and the validation accepts any origin that starts with an allowed value.

As a result, an attacker-controlled origin such as http://localhost.evil.example passes the localhost origin check.

When the MCP HTTP Stream server is started without an API key, which is the CLI default, this allows a malicious webpage to trigger unauthenticated MCP tools/call requests against a locally running PraisonAI MCP server.

This is best framed as a browser-mediated localhost attack / DNS-rebinding-style Origin validation bypass. The default server binds to 127.0.0.1, so this is not a directly internet-facing unauthenticated API in the default configuration.

Details

Relevant source locations:

  • src/praisonai/praisonai/mcp_server/cli.py
  • src/praisonai/praisonai/mcp_server/transports/http_stream.py
  • src/praisonai/praisonai/mcp_server/server.py
  • src/praisonai/praisonai/mcp_server/adapters/__init__.py
  • src/praisonai/praisonai/mcp_server/adapters/extended_capabilities.py
  • src/praisonai/praisonai/mcp_server/adapters/cli_tools.py
  • src/praisonai/praisonai/capabilities/files.py

The MCP CLI defaults to HTTP host 127.0.0.1, API key None, and allowed origins None unless explicitly configured:

parser.add_argument("--host", default="127.0.0.1")
parser.add_argument("--port", type=int, default=8080)
parser.add_argument("--api-key", default=None)
parser.add_argument("--allowed-origins", default=None, help="Comma-separated allowed origins for security")

The CLI registers all tools and passes the optional API key and allowed origins into the HTTP Stream transport:

register_all()

server.run_http_stream(
    host=parsed.host,
    port=parsed.port,
    endpoint=parsed.endpoint,
    api_key=parsed.api_key,
    cors_origins=cors_origins,
    allowed_origins=allowed_origins,
    session_ttl=parsed.session_ttl,
    allow_client_termination=allow_termination,
    response_mode=parsed.response_mode,
    resumability_enabled=parsed.resumability,
)

When allowed_origins is not explicitly configured and the server binds to localhost, the transport allowlist includes bare localhost origins:

if allowed_origins is None:
    if host in ("127.0.0.1", "localhost", "::1"):
        self.allowed_origins = [
            "http://localhost",
            "http://127.0.0.1",
            "https://localhost",
            "https://127.0.0.1",
        ]

The vulnerable validation accepts origins that merely start with an allowlisted value:

for allowed in self.allowed_origins:
    if request_origin == allowed or request_origin.startswith(allowed):
        return True

Because http://localhost.evil.example starts with http://localhost, it is accepted as a trusted localhost origin.

Authentication is only enforced if an API key is configured:

if self.api_key:
    auth_header = request.headers.get("Authorization", "")
    if not auth_header.startswith("Bearer ") or auth_header[7:] != self.api_key:
        return JSONResponse(
            {"error": "Unauthorized"},
            status_code=401,
        )

The request body is then parsed and dispatched to the MCP server:

body = await request.json()
response = await self.server.handle_message(body)

The MCP server handles tools/call by looking up the named tool and invoking the registered handler with attacker-controlled arguments:

tool_name = params.get("name")
arguments = params.get("arguments", {})

tool = self._tool_registry.get(tool_name)

if asyncio.iscoroutinefunction(tool.handler):
    result = await tool.handler(**arguments)
else:
    result = tool.handler(**arguments)

register_all() registers capability tools, extended capability tools, CLI tools, resources, and prompts:

def…
Record
PYSEC-2026-3896
CVSS 3.1
6.9 MediumCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:L/A:N
Updated
2026-11-02

Affected products

Affected packages

Affected packages
PackageAffectedFixed
praisonai
PyPI
before 4.6.58
4.6.58
praisonai
PyPI
before 4.6.58, plus listed versions
0.0.10.0.100.0.110.0.120.0.13
Show 45 moreShow less
0.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.58

History

History
RecordedChangedSources
first indexedCVE List, EUVD, GitHub Advisories, NVD, OSV

Dates are when Every CVE recorded each change. Upstream dates may differ.

In short

CVE-2026-55529 is a medium vulnerability (CVSS 6.9) in Mervinpraison Praisonai, published August 25, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.18%, the 7th percentile. Fixed in praisonai 4.6.58.

References

This page doesn't list everything

Left out here: 698 versions and the rest of 3 source descriptions. The JSON record has them.

MoveEnter Open