CVE-2026-55530

mediumpriority · track · 0OT risk · elevatedEPSS 0.002 · 6 %ileUS
grimoire priorityP3 · Track
Exploitable?
—
no known exploitation
Likely / severe?
0.17% (6th) EPSS
CVSS 6.1 · medium
Is it mine?
1 products
Check your exposure
Who says so?
1 sources
osv

EPSS · scored 2026-10-27

PraisonAI is a multi-agent teams system. Prior to praisonaiagents 1.6.58, ast_grep_rewrite lacks the @require_approval decorator used by sibling mutation tools. With dry_run=False, an agent-controlled call can pass --update-all and a broad path to rewrite matching files without the expected authorization gate. This issue is fixed in version 1.6.58.

01

cvss

Technical severity, 0–10. One row per scoring source.

6.1
010

base score 0–10 · higher = more severe · hover a vector to decode

v3.16.1mediumCVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:Lsecondary · security-advisories@github.comexploitability 1.8 · impact 4.2
v3.16.1mediumCVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:Lsecondary · OSV
v3.15.6mediumCVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:L/E:Usecondary · CVSS-BT (t0sche)
02

ssvc

A stakeholder decision — act, attend, or track — from exploitation and impact.

Exploitation: poc · Automatable: no · Technical Impact: partialcoordinatorcisa_ssvcUS
03

weaknesses

The CWE weakness classes this flaw belongs to.

04

affected products (1)

Products and version ranges known to be affected.

05

advisories (2)

Vendor and ecosystem advisories describing this.

  • GHSA-cfxv-8fw8-rwpvpraisonaiagents: ast_grep_rewrite rewrites arbitrary files without the @require_approval gate enforced on every sibling mutation tool2026-08-25ghsa · osvUS

    **Target:** PraisonAI (`MervinPraison/PraisonAI`) **Affected component:** `praisonaiagents/tools/ast_grep_tool.py` — `ast_grep_rewrite` **Affected versions:** master at `ce97667156a116c50b4a3d1aa21e09f048903fda`; reproduced against the current `praisonaiagents` PyPI release (`praisonaiagents` <= 1.6.52). ## Summary Tools in `praisonaiagents/tools/` that modify on-disk state or run code are uniformly wrapped with `@require_approval`, which routes the call through an interactive approval flow before the body runs and fails closed — on denial (or with no approval backend configured) it raises `PermissionError` and the side effect does not occur. This is applied at every sibling mutation entry point: | File | Line | Symbol | Risk level | |---|---|---|---| | `file_tools.py` | 212 | `copy_file` | high | | `file_tools.py` | 239 | `move_file` | high | | `file_tools.py` | 266 | `delete_file` | high | | `edit_tools.py` | 38 | `EditTools.edit_file` | high | | `edit_tools.py` | 155 | `edit_file` | high | | `shell_tools.py` | 32 | `execute_command` | critical | | `python_tools.py` | 352 | `execute_code` | critical | `ast_grep_tool.py:149` `ast_grep_rewrite` is structurally a sibling of these but has no decorator and no `from ..approval import require_approval` import. With `dry_run=False` (LLM-controllable), it builds `sg --pattern <P> --rewrite <R> --lang <L> --update-all <path>` (lines 204–211) and calls `subprocess.run(cmd, ...)` (line 215), modifying every file under `path` matching the pattern. There is no approval gate, no `_validate_path` workspace check, and no `cwd=` sandboxing. The function is registered as a top-level tool (`__init__.py:182`) and exposed via the `code_intelligence` built-in profile (`profiles.py`). A secondary defect: on the `dry_run=False` path `ast_grep_rewrite` returns the literal string `No changes made` to the caller even when it modified files (the "No changes made" return at `ast_grep_tool.py:230` is reached on this path), so an operator inspecting tool output sees no record that a write occurred. ## Proof of concept Single script, clean venv, `praisonaiagents` from PyPI, `ast-grep` CLI installed. `PRAISONAI_AUTO_APPROVE` is removed from the environment first, so no env-bypass is in play. ```python import os, tempfile, textwrap os.environ.pop("PRAISONAI_AUTO_APPROVE", None) workdir = tempfile.mkdtemp(prefix="poc-") target = os.path.join(workdir, "target.py") open(target, "w").write(textwrap.dedent(""" def safe_function(x): return x + 1 def hello(name): return 'hi ' + name """)) # Positive: undecorated tool rewrites the file. from praisonaiagents.tools.ast_grep_tool import ast_grep_rewrite ast_grep_rewrite( pattern="def $FN($$$): return $$$", replacement="def $FN($$$): import os; os.environ['POC_CANARY']='1'; return $$$", lang="python", path=workdir, dry_run=False, ) # Negative control: decorated sibling triggers the approval flow. from praisonaiagents.tools.edit_tools import edit_file edit_file(file_path=target, old_text="def hello(name):", new_text="def hello(name): # X") ``` Result, verified: `ast_grep_rewrite` rewrote `target.py` to contain the injected `import os; os.environ['POC_CANARY']='1'` payload, no approval prompt fired, and the call returned `No changes made`. The subsequent `edit_file` call in the same process rendered the Tool Approval Required panel and, on denial, raised `PermissionError("Execution of edit_file denied: User denied")` without modifying its target. Same process, same approval backend — the only difference is the missing decorator on `ast_grep_rewrite`. ## Threat model An LLM agent running locally whose tool surface includes `ast_grep_rewrite` (via the `code_intelligence` profile or direct import). Triggers: the operator asks the agent to refactor code, or prompt-injection in fetched docs / RAG context / any LLM-visible input steers the agent to call `ast_grep_rewrite` with attacker-chosen `pattern`, `replacement`, and `path` (the `dry_run` field is in the LLM-visible tool schema, so `dry_run=False` is requestable). The agent can then rewrite any file the host process can write — source trees, build configs, dotfiles, the agent's own source. With `path="/"` the rewrite is filesystem-wide. Because the rewrite injects arbitrary text, pointing it at a file that is later imported or executed turns this write primitive into code execution — the basis for the escalation noted in the CVSS line. No operator prompt and no audit record of the modification. ## Suggested fix ```diff --- a/praisonaiagents/tools/ast_grep_tool.py +++ b/praisonaiagents/tools/ast_grep_tool.py @@ from praisonaiagents._logging import get_logger from typing import Optional, List +from ..approval import require_approval @@ +@require_approval(risk_level="high") def ast_grep_rewrite( pattern: str, replacement: str, ``` `high` matches the file-modifying siblings; `critical` is defensible given the write→exec escalation. In the same patch: add a `_validate_path` workspace boundary check (cf. `edit_tools.py:27`); fix the `No changes made` return so it reflects actual modifications; apply the decorator to `ast_grep_scan` (`ast_grep_tool.py:243`) if it can write. `ast_grep_search` is read-only and can stay undecorated. A regression test asserting `ast_grep_rewrite` requires approval (alongside the other mutation tools) would have caught this at review time. ## Coordinated disclosure - Kai Aizen / SnailSploit — `kai@snailsploit.com` — PGP on request.

  • PYSEC-2026-3901praisonaiagents: ast_grep_rewrite rewrites arbitrary files without the @require_approval gate enforced on every sibling mutation tool2026-09-10vendor · osvUS

    **Target:** PraisonAI (`MervinPraison/PraisonAI`) **Affected component:** `praisonaiagents/tools/ast_grep_tool.py` — `ast_grep_rewrite` **Affected versions:** master at `ce97667156a116c50b4a3d1aa21e09f048903fda`; reproduced against the current `praisonaiagents` PyPI release (`praisonaiagents` <= 1.6.52). ## Summary Tools in `praisonaiagents/tools/` that modify on-disk state or run code are uniformly wrapped with `@require_approval`, which routes the call through an interactive approval flow before the body runs and fails closed — on denial (or with no approval backend configured) it raises `PermissionError` and the side effect does not occur. This is applied at every sibling mutation entry point: | File | Line | Symbol | Risk level | |---|---|---|---| | `file_tools.py` | 212 | `copy_file` | high | | `file_tools.py` | 239 | `move_file` | high | | `file_tools.py` | 266 | `delete_file` | high | | `edit_tools.py` | 38 | `EditTools.edit_file` | high | | `edit_tools.py` | 155 | `edit_file` | high | | `shell_tools.py` | 32 | `execute_command` | critical | | `python_tools.py` | 352 | `execute_code` | critical | `ast_grep_tool.py:149` `ast_grep_rewrite` is structurally a sibling of these but has no decorator and no `from ..approval import require_approval` import. With `dry_run=False` (LLM-controllable), it builds `sg --pattern <P> --rewrite <R> --lang <L> --update-all <path>` (lines 204–211) and calls `subprocess.run(cmd, ...)` (line 215), modifying every file under `path` matching the pattern. There is no approval gate, no `_validate_path` workspace check, and no `cwd=` sandboxing. The function is registered as a top-level tool (`__init__.py:182`) and exposed via the `code_intelligence` built-in profile (`profiles.py`). A secondary defect: on the `dry_run=False` path `ast_grep_rewrite` returns the literal string `No changes made` to the caller even when it modified files (the "No changes made" return at `ast_grep_tool.py:230` is reached on this path), so an operator inspecting tool output sees no record that a write occurred. ## Proof of concept Single script, clean venv, `praisonaiagents` from PyPI, `ast-grep` CLI installed. `PRAISONAI_AUTO_APPROVE` is removed from the environment first, so no env-bypass is in play. ```python import os, tempfile, textwrap os.environ.pop("PRAISONAI_AUTO_APPROVE", None) workdir = tempfile.mkdtemp(prefix="poc-") target = os.path.join(workdir, "target.py") open(target, "w").write(textwrap.dedent(""" def safe_function(x): return x + 1 def hello(name): return 'hi ' + name """)) # Positive: undecorated tool rewrites the file. from praisonaiagents.tools.ast_grep_tool import ast_grep_rewrite ast_grep_rewrite( pattern="def $FN($$$): return $$$", replacement="def $FN($$$): import os; os.environ['POC_CANARY']='1'; return $$$", lang="python", path=workdir, dry_run=False, ) # Negative control: decorated sibling triggers the approval flow. from praisonaiagents.tools.edit_tools import edit_file edit_file(file_path=target, old_text="def hello(name):", new_text="def hello(name): # X") ``` Result, verified: `ast_grep_rewrite` rewrote `target.py` to contain the injected `import os; os.environ['POC_CANARY']='1'` payload, no approval prompt fired, and the call returned `No changes made`. The subsequent `edit_file` call in the same process rendered the Tool Approval Required panel and, on denial, raised `PermissionError("Execution of edit_file denied: User denied")` without modifying its target. Same process, same approval backend — the only difference is the missing decorator on `ast_grep_rewrite`. ## Threat model An LLM agent running locally whose tool surface includes `ast_grep_rewrite` (via the `code_intelligence` profile or direct import). Triggers: the operator asks the agent to refactor code, or prompt-injection in fetched docs / RAG context / any LLM-visible input steers the agent to call `ast_grep_rewrite` with attacker-chosen `pattern`, `replacement`, and `path` (the `dry_run` field is in the LLM-visible tool schema, so `dry_run=False` is requestable). The agent can then rewrite any file the host process can write — source trees, build configs, dotfiles, the agent's own source. With `path="/"` the rewrite is filesystem-wide. Because the rewrite injects arbitrary text, pointing it at a file that is later imported or executed turns this write primitive into code execution — the basis for the escalation noted in the CVSS line. No operator prompt and no audit record of the modification. ## Suggested fix ```diff --- a/praisonaiagents/tools/ast_grep_tool.py +++ b/praisonaiagents/tools/ast_grep_tool.py @@ from praisonaiagents._logging import get_logger from typing import Optional, List +from ..approval import require_approval @@ +@require_approval(risk_level="high") def ast_grep_rewrite( pattern: str, replacement: str, ``` `high` matches the file-modifying siblings; `critical` is defensible given the write→exec escalation. In the same patch: add a `_validate_path` workspace boundary check (cf. `edit_tools.py:27`); fix the `No changes made` return so it reflects actual modifications; apply the decorator to `ast_grep_scan` (`ast_grep_tool.py:243`) if it can write. `ast_grep_search` is read-only and can stay undecorated. A regression test asserting `ast_grep_rewrite` requires approval (alongside the other mutation tools) would have caught this at review time. ## Coordinated disclosure - Kai Aizen / SnailSploit — `kai@snailsploit.com` — PGP on request.

06

also known as (2)

Other names and identifiers for this entity.

07

sources

Which sources assert this record, and when.

published 2026-08-25modified 2026-08-25
08

references (3) nvd

External citations — patches, advisories, analysis.

scry

Attackers weaponise vulnerabilities like this against internet-exposed services. See what is exposed on your perimeter with a free passive scan.

Free scan