Vulnerability Research · March 24, 2026
OS Command Injection in PraisonAI
Unsanitized CLI input flows through three call hops into a subprocess sink. No authentication required. Fixed in v4.5.69 with a command allowlist.
Mar 24, 2026 CVSS 9.8
Vulnerability Research · March 25, 2026
Path Traversal in AGiXT
safe_join() builds paths with os.path.normpath() but skips boundary verification, letting any authenticated user read, write, or delete arbitrary server files.
Mar 25, 2026 CVSS 8.1
Vulnerability Research · March 25, 2026
Server-Side Request Forgery in AGiXT
The Custom API Endpoint command passes user-supplied URLs to requests.request() with no validation, letting any authenticated user proxy requests to internal services and cloud metadata endpoints.
Mar 25, 2026 CVSS 8.6

More writeups publishing as disclosures close. Mid-2026.

Vulnerability Research · March 24, 2026

OS Command Injection in PraisonAI

Severity Critical
CVSS 9.8
CVE Pending (MCID15649049)
Fixed v4.5.69

The Vulnerability

PraisonAI accepts MCP server configurations via the --mcp CLI argument. This argument is passed directly to shlex.split() and forwarded through the call chain into anyio.open_process(), a subprocess execution sink, with no validation at any hop.

Taint path
# unsanitized input split, no allowlist check
parts = shlex.split(command)

# raw result forwarded downstream unchanged
cmd, args, env = self.parse_mcp_command(command, env_vars)

# attacker-controlled cmd packed into server params
self.server_params = StdioServerParameters(command=cmd, args=arguments)

# sink: executed as subprocess with no sanitization
process = await anyio.open_process([command, *args])

Root Cause

The developer assumed the --mcp argument would always be operator-supplied and therefore trusted. There is no allowlist of permitted commands, no subprocess isolation, and no validation that the supplied string is a legitimate MCP server invocation.

Passing user-controlled strings to shlex.split() followed by a subprocess call is a well-understood sink pattern. The failure here is architectural: trust was assumed at the boundary rather than enforced.

Impact

An attacker who can influence the --mcp argument achieves full OS command execution as the process user. No authentication required. In any deployment where PraisonAI is exposed to untrusted input, networked environments, or multi-tenant setups, this is directly exploitable.

The Fix

The maintainer addressed this in v4.5.69 by introducing a command allowlist. Only pre-approved executables are now permitted.

v4.5.69 - commit 47bff65
ALLOWED_COMMANDS = {"npx", "uvx", "node", "python"}
if cmd not in ALLOWED_COMMANDS:
    raise ValueError(f"Disallowed command: {cmd}")

Timeline

DateEvent
Feb 27, 2026Vulnerability identified and reported to maintainer
Feb 27, 2026CVE ID requested from MITRE (MCID15649049)
Mar 16, 2026Maintainer shipped fix in v4.5.69 (commit 47bff65)
Mar 24, 2026Public disclosure
Vulnerability Research · March 25, 2026

Path Traversal in AGiXT

Severity High
CVSS 8.1
CVE Pending
Fixed commit 2079ea5

The Vulnerability

AGiXT exposes file operation commands through its extension system: write_to_file, read_file, modify_file, and delete_file. Each calls safe_join() to construct the target path. Despite the name, safe_join() uses os.path.normpath() to resolve the path but never checks that the result stays within the workspace boundary.

Taint path
# attacker-controlled filename enters via POST request
command_args = command.command_args

# forwarded unchanged into command execution
response = await Extensions(...).execute_command(command_name=command_name, command_args=command_args)

# sink: path resolved but never boundary-checked
new_path = os.path.normpath(os.path.join(self.WORKING_DIRECTORY, *paths.split("/")))
# new_path is never verified to start with WORKING_DIRECTORY

Root Cause

The function is named safe_join(), which implies boundary enforcement, but the implementation only calls os.path.normpath(). This resolves ../ sequences syntactically but does not verify the result stays within the intended directory. The correct pattern already existed in Memory.py:195-201 within the same codebase.

The developer applied the correct pattern in one location but not the other. A common failure mode in fast-moving codebases: security patterns applied in one place, not propagated to similar locations added later.

The Fix

commit 2079ea5
base = os.path.realpath(self.WORKING_DIRECTORY)
new_path = os.path.realpath(
    os.path.normpath(os.path.join(self.WORKING_DIRECTORY, *paths.split("/")))
)
if not new_path.startswith(base + os.sep) and new_path != base:
    raise PermissionError(f"Path traversal detected: {paths!r}")

Timeline

DateEvent
Mar 14, 2026Vulnerability identified and reported to AGiXT team
Mar 16, 2026Maintainer pushed fix in commit 2079ea5
Mar 16, 2026CVE ID requested from MITRE
Jun 12, 2026Public disclosure
Vulnerability Research · March 25, 2026

Server-Side Request Forgery in AGiXT

Severity High
CVSS 8.6
CVE Pending
Fixed commit 711f507

The Vulnerability

AGiXT exposes a Custom API Endpoint command that accepts a URL from the user. That URL is passed directly to requests.request() in custom_api() with no allowlist, no blocklist, and no validation against internal address ranges.

Taint path
# attacker-controlled URL enters via POST request
command_args = command.command_args

# sink: URL passed directly to requests with no validation
response = requests.request(
    method=method,
    url=url,
    headers=headers,
    json=data
)

Root Cause

The developer treated the URL parameter as a trusted operator-supplied value. There is no blocklist for loopback addresses, no IMDS protection, and no redirect-following restrictions. The requests library follows redirects by default, enabling redirect-based SSRF chains where an attacker bounces through a public URL to reach an internal target.

Impact

On any cloud-deployed AGiXT instance, an attacker can reach the Instance Metadata Service with a single request:

IMDS credential theft
Target: http://169.254.169.254/latest/meta-data/iam/security-credentials/
Result: AWS/GCP/Azure temporary IAM credentials returned to attacker

The Fix

commit 711f507
def is_safe_url(url: str) -> bool:
    blocked_ranges = [
        "127.0.0.0/8", "169.254.0.0/16",
        "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"
    ]
    ...

if not is_safe_url(url):
    raise ValueError("URL targets a restricted address range")

Timeline

DateEvent
Mar 10, 2026Vulnerability identified and reported to AGiXT team
Mar 12, 2026Maintainer pushed fix in commit 711f507
Mar 12, 2026CVE ID requested from MITRE
Jun 8, 2026Public disclosure