Skip to content

GHSA-9cr9-25q5-8prj CVE-2026-47394 PYSEC-2026-2908

PraisonAI vulnerable to unauthenticated arbitrary file read via MCP workflow.show, workflow.validate, deploy.validate

High PyPI
Severity
0.0 / 10
High · CVSS 4.0
Exploit chance (EPSS)
0.50%
Chance of exploitation in the next 30 days. Higher than 42.79% of scored vulnerabilities.
Known exploited
No
Not in CISA's catalog
Weakness
CWE-200 Exposure of Sensitive Information to an Unauthorized Actor
CWE-22 Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
CWE-862 Missing Authorization
Fix
4.6.40
Upgrade praisonai to this version or later

Description

Summary

The fix for GHSA-9mqq-jqxf-grvw / CVE-2026-44336 is incomplete. The original advisory description named four vulnerable handlers in mcp_server/adapters/cli_tools.py:

"registers four file-handling tools by default, praisonai.rules.create, praisonai.rules.show, praisonai.rules.delete, and praisonai.workflow.show. Each accepts a path or filename string from MCP tools/call arguments… with no containment check."

Commit 68cc9427 ("fix(security): harden MCP rules path handling…") added a _resolve_rule_path() helper and applied it to rules.create, rules.show, and rules.delete. workflow.show was left unchanged. Two adjacent handlers in the same file have the same pattern, workflow.validate and deploy.validate. Neither was mentioned in the original advisory. Both remain unchanged.

The original advisory also identified the dispatcher (server.py:281-298) as a root cause. It accepts unvalidated **kwargs from params["arguments"] with no enforcement against the tool's declared input_schema. That code is unchanged in HEAD as of commit 42221210.

Result: A single unauthenticated MCP tools/call to praisonai.workflow.show returns the contents of any file the host user can read: /etc/passwd, ~/.ssh/id_rsa, ~/.aws/credentials, or any project .env.

Affected functionality

src/praisonai/praisonai/mcp_server/adapters/cli_tools.py:

Lines Tool Bug
63-73 praisonai.workflow.show Returns the full contents of any file the host user can read
42-61 praisonai.workflow.validate Reads any path; YAML parser error messages leak file existence + content fragments
415-432 praisonai.deploy.validate Same pattern as workflow.validate. The config_path="deploy.yaml" default does not constrain the input.

src/praisonai/praisonai/mcp_server/server.py:281-298, _handle_tools_call:

async def _handle_tools_call(self, params: Dict[str, Any]) -> Dict[str, Any]:
    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)        # ← no schema enforcement
    else:
        result = tool.handler(**arguments)

Any JSON arguments the MCP client sends become a **kwargs call to the handler. The original advisory pointed at this code path as the root cause. The May 3 patch did not change it.

Default deployment is exposed

src/praisonai/praisonai/mcp_server/transports/http_stream.py:38-91:

  • host defaults to 127.0.0.1, which is still reachable from any local process or container neighbour on loopback.
  • api_key defaults to None. The auth check at http_stream.py:192-198 is gated on if self.api_key:, so it is skipped when no key is configured. There is no env var or config switch that turns auth on by default.
  • The same handlers are also reachable on the stdio transport, which is the exploitation model the original advisory was written around (Claude Desktop, Cursor, Continue.dev, Claude Code).

Other file-read sinks reachable via the same dispatcher

These were not named in the original advisory. They confirm the bug is dispatcher-wide and not limited to cli_tools.py:

  • mcp_server/adapters/capabilities.py:19-28, praisonai.audio.transcribe(file_path). Opens any host file and ships it to OpenAI Whisper.
  • mcp_server/adapters/extended_capabilities.py:47-62, praisonai.files.create(file_path). Uploads any host file to OpenAI Files. A follow-up call to praisonai.files.content(file_id) (extended_capabilities.py:103-113) returns the bytes.
  • mcp_server/adapters/extended_capabilities.py:243-258, praisonai.ocr_extract(image_path). Opens any image, returns OCR text.

The three handlers in cli_tools.py are the most direct primitives, since they echo the file content back without an OpenAI round-trip.

Proof of Concept

Layout

PraisonAI/
└── poc/
    ├── start_mcp_server.sh         ← starts the real MCP server
    ├── run_mcp_poc_video.sh        ← runs the attack with curl
    ├── venv/                       
    └── output/
        ├── mcp_server_run.log
        ├── mcp_attacker_run.log
        └── synthetic_credentials.txt   (PoC-only fake creds)

start_mcp_server.sh run_mcp_poc_video.sh

The server starter runs the real MCPServer class with register_cli_tools(), same code path praisonai mcp serve --transport http-stream uses. No mocks.

How to reproduce

Terminal 1, start the server:

cd PraisonAI
bash poc/start_mcp_server.sh

Boots MCPServer on 127.0.0.1:8766/mcp with no auth, matching the documented default api_key=None.

Terminal 2, run the attack:

cd PraisonAI
bash poc/run_mcp_poc_video.sh

Six numbered steps. Each one prints the action, runs one curl, prints the JSON-RPC response.

workflow.validate leaks /etc/hosts:

{ "result": { "content": [{ "type": "text",
  "text": "YAML error: while scanning for the next token\nfound character '\\t' that cannot start any token\n  in \"/etc/hosts\", line 7, column 10" }] } }

The parser error message confirms the file exists and includes a fragment of its content.

deploy.validate leaks ~/.ssh/known_hosts:

{ "result": { "content": [{ "type": "text",
  "text": "Error: expected '<document start>', but found '<scalar>'\n  in \"/Users/<victim>/.ssh/known_hosts\", line 1, column 13" }] } }

workflow.show exfiltrates a credential file:

{ "result": { "content": [{ "type": "text",
  "text": "# AWS-style credentials (SYNTHETIC, for PoC only)\n[default]\naws_access_key_id = AKIA-FAKE-EXFIL-KEY-FOR-POC\naws_secret_access_key = synthetic-secret-do-not-actually-exist-12345\n\n# .env-style secrets\nDATABASE_URL=postgres://app:hunter2@db.internal/prod\nSLACK_BOT_TOKEN=xoxb-FAKE-TOKEN-for-poc-only\nOPENAI_API_KEY=sk-FAKE-FOR-POC\n" }] } }

The PoC writes its own synthetic credential file so the demonstration does not depend on the reviewer's real secrets. The same call reads ~/.ssh/id_rsa, ~/.aws/credentials, or any project .env if you point it there.

https://github.com/user-attachments/assets/09511e66-6a52-4fe3-a303-91d1f99cd27a

Impact

  • Confidentiality, High. Any file the praisonai user can read becomes available to the MCP caller. Typical targets are host SSH keys, cloud credentials, API tokens, project .env files, ~/.netrc, ~/.docker/config.json, browser cookie databases, and the system password file.
  • No authentication required. The default is api_key=None (http_stream.py:91). The auth check at http_stream.py:192-198 is wrapped in if self.api_key:, so it does not run when no key is configured.
  • No operator misconfiguration required. This is the documented default.
  • The original advisory's exploitation model still applies. An MCP-connected LLM whose context contains attacker-controlled web pages, documents, or emails can be steered into issuing the same tools/call and returning the response. No operator click is needed beyond "summarise this page".

The original advisory was Critical because the write primitive (rules.create) chained to RCE through .pth injection. This finding is the read half of the same shape. Read alone is enough to take SSH keys, cloud credentials, and tokens, which is usually how the rest of the host gets compromised through credential reuse.

Suggested fix

There are two ways to fix this. Doing both is fine. The dispatcher fix is preferred because it closes the same class of bug for every handler that takes a path-shaped argument, including the OpenAI-backed ones called out earlier.

1. Enforce tool.input_schema in the dispatcher

mcp_server/server.py:281-298. The schemas are already built reflectively from each handler's signature in registry.py:320-376. Validate arguments against the registered schema before calling tool.handler(**arguments) and reject anything that does not match. This covers workflow.show, workflow.validate, deploy.validate, audio.transcribe, files.create, ocr_extract, and any handler added later.

2. Per-handler containment

This is the same shape as the existing _resolve_rule_path() helper added in commit 68cc9427:

# cli_tools.py
def _resolve_workflow_path(file_path: str) -> Path:
    """Restrict workflow file_path to an allowed root."""
    if not isinstance(file_path, str) or not file_path:
        raise ValueError("file_path must be a non-empty string")
    if "\x00" in file_path or file_path.startswith("~"):
        raise ValueError(f"invalid file_path: {file_path!r}")
    workflows_root = Path(os.path.expanduser("~/.praison/workflows")).resolve()
    workflows_root.mkdir(parents=True, exist_ok=True)
    candidate = (workflows_root / file_path).resolve()
    try:
        candidate.relative_to(workflows_root)
    except ValueError:
        raise ValueError(f"invalid file_path: {file_path!r}")
    return candidate

Apply the same helper to:

  • workflow_show(file_path) and workflow_validate(file_path). Restrict to a workflow root.
  • deploy_validate(config_path). Restrict to a deploy-config root or an explicit allowlist.
  • The default="deploy.yaml" fallback resolves into the user's current working directory. Containment is what fixes the bug, but removing that default also makes prompt-injection chains harder.

Affected versions

PyPI

PackageVulnerableFixed in
praisonai
< 4.6.40
4.6.40

How to fix

Move to the fixed version or later with your package manager, update the lockfile, and run your tests. If the package only arrives through another dependency, upgrade that dependency first. If none of its releases pulls a fixed version yet, force the version with your package manager's override.

praisonai PyPI → 4.6.40

pip
pip install --upgrade "praisonai>=4.6.40"
poetry
poetry add "praisonai>=4.6.40"
uv
uv add "praisonai>=4.6.40"

Severity breakdown

CVSS 4.0 0.0

CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N

Attack vector
Network
Attack complexity
Low
Attack requirements
None
Privileges required
None
User interaction
None
Confidentiality (vulnerable system)
High
Integrity (vulnerable system)
None
Availability (vulnerable system)
None
Confidentiality (subsequent systems)
None
Integrity (subsequent systems)
None
Availability (subsequent systems)
None

Weakness

CWE-200: Exposure of Sensitive Information to an Unauthorized Actor

The product exposes sensitive information to an actor that is not explicitly authorized to have access to that information.

How to prevent it
  • Architecture and Design: Compartmentalize the system to have "safe" areas where trust boundaries can be unambiguously drawn. Do not allow sensitive data to go outside of the trust boundary and always be careful when interfacing with a compartment outside of the safe area. Ensure that appropriate compartmentalization is built into the system design, and the compartmentalization allows for and reinforces privilege separation functionality. Architects and designers should rely on the principle of least privilege to decide the appropriate time to use privileges and the time to drop privileges.

CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.

How to prevent it
  • Implementation: Assume all input is malicious. Use an "accept known good" input validation strategy, i.e., use a list of acceptable inputs that strictly conform to specifications. Reject any input that does not strictly conform to specifications, or transform it into something that does. When performing input validation, consider all potentially relevant properties, including length, type of input, the full range of acceptable values, missing or extra inputs, syntax, consistency across related fields, and conformance to business rules. As an example of business rule logic, "boat" may be syntactically valid because it only contains alphanumeric characters, but it is not valid if the input is only expected to contain colors such as "red" or "blue." Do not rely exclusively on looking for malicious or malformed inputs. This is likely to miss at least one undesirable input, especially if the code's environment changes. This can give attackers enough room to bypass the intended validation. However, denylists can be useful for detecting potential attacks or determining which inputs are so malformed that they should be rejected outright. When validating filenames, use stringent allowlists that limit the character set to be used. If feasible, only allow a single "." character in the filename to avoid weaknesses such as CWE-23, and exclude directory separators such as "/" to avoid CWE-36. Use a list of allowable file extensions, which will help to avoid CWE-434. Do not rely exclusively on a filtering mechanism that removes potentially dangerous characters. This is equivalent to a denylist, which may be incomplete (CWE-184). For example, filtering "/" is insufficient protection if the filesystem also supports the use of "\" as a directory separator. Another possible error could occur when the filtering is applied in a way that still produces dangerous data (CWE-182). For example, if "../" sequences are removed from the ".../...//" string in a sequential fashion, two instances of "../" would b
  • Architecture and Design: For any security checks that are performed on the client side, ensure that these checks are duplicated on the server side, in order to avoid CWE-602. Attackers can bypass the client-side checks by modifying values after the checks have been performed, or by changing the client to remove the client-side checks entirely. Then, these modified values would be submitted to the server.
  • Implementation: Inputs should be decoded and canonicalized to the application's current internal representation before being validated (CWE-180). Make sure that the application does not decode the same input twice (CWE-174). Such errors could be used to bypass allowlist validation schemes by introducing dangerous inputs after they have been checked. Use a built-in path canonicalization function (such as realpath() in C) that produces the canonical version of the pathname, which effectively removes ".." sequences and symbolic links (CWE-23, CWE-59). This includes: realpath() in C getCanonicalPath() in Java GetFullPath() in ASP.NET realpath() or abs_path() in Perl realpath() in PHP
  • Architecture and Design: Use a vetted library or framework that does not allow this weakness to occur or provides constructs that make this weakness easier to avoid [REF-1482].
  • Operation: Use an application firewall that can detect attacks against this weakness. It can be beneficial in cases in which the code cannot be fixed (because it is controlled by a third party), as an emergency prevention measure while more comprehensive software assurance measures are applied, or to provide defense in depth [REF-1481].
  • Architecture and Design, Operation: Run your code using the lowest privileges that are required to accomplish the necessary tasks [REF-76]. If possible, create isolated accounts with limited privileges that are only used for a single task. That way, a successful attack will not immediately give the attacker access to the rest of the software or its environment. For example, database applications rarely need to run as the database administrator, especially in day-to-day operations.
  • Architecture and Design: When the set of acceptable objects, such as filenames or URLs, is limited or known, create a mapping from a set of fixed input values (such as numeric IDs) to the actual filenames or URLs, and reject all other inputs. For example, ID 1 could map to "inbox.txt" and ID 2 could map to "profile.txt". Features such as the ESAPI AccessReferenceMap [REF-185] provide this capability.
  • Architecture and Design, Operation: Run the code in a "jail" or similar sandbox environment that enforces strict boundaries between the process and the operating system. This may effectively restrict which files can be accessed in a particular directory or which commands can be executed by the software. OS-level examples include the Unix chroot jail, AppArmor, and SELinux. In general, managed code may provide some protection. For example, java.io.FilePermission in the Java SecurityManager allows the software to specify restrictions on file operations. This may not be a feasible solution, and it only limits the impact to the operating system; the rest of the application may still be subject to compromise. Be careful to avoid CWE-243 and other weaknesses related to jails.
  • Architecture and Design, Operation: Store library, include, and utility files outside of the web document root, if possible. Otherwise, store them in a separate directory and use the web server's access control capabilities to prevent attackers from directly requesting them. One common practice is to define a fixed constant in each calling program, then check for the existence of the constant in the library/include file; if the constant does not exist, then the file was directly requested, and it can exit immediately. This significantly reduces the chance of an attacker being able to bypass any protection mechanisms that are in the base program but not in the include files. It will also reduce the attack surface.
  • Implementation: Ensure that error messages only contain minimal details that are useful to the intended audience and no one else. The messages need to strike the balance between being too cryptic (which can confuse users) or being too detailed (which may reveal more than intended). The messages should not reveal the methods that were used to determine the error. Attackers can use detailed information to refine or optimize their original attack, thereby increasing their chances of success. If errors must be captured in some detail, record them in log messages, but consider what could occur if the log messages can be viewed by attackers. Highly sensitive information such as passwords should never be saved to log files. Avoid inconsistent messaging that might accidentally tip off an attacker about internal state, such as whether a user account exists or not. In the context of path traversal, error messages which disclose path information can help attackers craft the appropriate attack strings to move through the file system hierarchy.
  • Operation, Implementation: When using PHP, configure the application so that it does not use register_globals. During implementation, develop the application so that it does not rely on this feature, but be wary of implementing a register_globals emulation that is subject to weaknesses such as CWE-95, CWE-621, and similar issues.

CWE-862: Missing Authorization

The product does not perform an authorization check when an actor attempts to access a resource or perform an action.

How to prevent it
  • Architecture and Design: Divide the product into anonymous, normal, privileged, and administrative areas. Reduce the attack surface by carefully mapping roles with data and functionality. Use role-based access control (RBAC) [REF-229] to enforce the roles at the appropriate boundaries. Note that this approach may not protect against horizontal authorization, i.e., it will not protect a user from attacking others with the same role.
  • Architecture and Design: Ensure that access control checks are performed related to the business logic. These checks may be different than the access control checks that are applied to more generic resources such as files, connections, processes, memory, and database records. For example, a database may restrict access for medical records to a specific database user, but each record might only be intended to be accessible to the patient and the patient's doctor [REF-7].
  • Architecture and Design: Use a vetted library or framework that does not allow this weakness to occur or provides constructs that make this weakness easier to avoid. For example, consider using authorization frameworks such as the JAAS Authorization Framework [REF-233] and the OWASP ESAPI Access Control feature [REF-45].
  • Architecture and Design: For web applications, make sure that the access control mechanism is enforced correctly at the server side on every page. Users should not be able to access any unauthorized functionality or information by simply requesting direct access to that page. One way to do this is to ensure that all pages containing sensitive information are not cached, and that all such pages restrict access to requests that are accompanied by an active and authenticated session token associated with a user who has the required permissions to access that page.
  • System Configuration, Installation: Use the access control capabilities of your operating system and server environment and define your access control lists accordingly. Use a "default deny" policy when defining these ACLs.

References

Timeline

  1. Published
  2. Last updated by the source
  3. Latest EPSS score
AdvisorySeverityWhy it's related
GHSA-26mh-57q7-jfvr
PraisonAI vulnerable to Remote Code Execution via Broken AST Sandbox
High Also affects praisonai
GHSA-6g6r-q6gw-w8fg
PraisonAI has a Browser Server WebSocket origin validation bypass via unanchored regex (patch bypass of CVE-2026-40289 / GHSA-8x8f-54wf-vv92)
Critical Also affects praisonai
GHSA-pvph-5j39-v8qc
PraisonAI: Origin-validation bypass (startswith prefix match) enables unauthenticated cross-site request forgery against the PraisonAI MCP HTTP server
High Also affects praisonai
GHSA-gfq8-hmph-9gjv
PraisonAI: Authentication fail-open in Recipe server allows unauthenticated access when API key or JWT auth is configured without a secret
High Also affects praisonai
GHSA-2jgc-f764-c5r2
PraisonAI: [Auth Bypass] PraisonAI async Jobs API (`/api/v1/runs`) has no authentication — unauthenticated job execution, result theft, cancel and delete
High Also affects praisonai
GHSA-pvxx-r596-f5qj
PraisonAI: `--api-key` flag on `praisonai serve` is not properly enforced
High Also affects praisonai
GHSA-hmfx-4v44-9qw9
PraisonAI vulnerable to Server-Side Request Forgery via DNS rebinding bypass in webhook_url validation
Medium Also affects praisonai
GHSA-rg5q-pp8p-f7jm
PraisonAI: Webhook SSRF via DNS fail-open in `JobSubmitRequest.validate_webhook_url()` — bypass of CVE-2026-40114
High Also affects praisonai
Is this in your code?

MergeHarbor checks every repository's dependencies against this database, ranks what matters by exploitability and reachability, routes it to the owning team and opens the fix pull request.

Check your repos with MergeHarbor