{"schema":"threatline.record/2","record":{"id":"GHSA-3w55-933p-8pv5","aliases":["GHSA-3w55-933p-8pv5"],"summary":"Strands Agents: Local Privilege Escalation via Symlink Attack in Default Session File Storage","details":"## Summary\n\nThe `FileSessionManager` defaults to storing session data in `/tmp/strands/sessions/` (a world-writable directory) using predictable file paths and a write pattern (`open(path + \".tmp\", \"w\")` → `os.replace()`) that follows symbolic links. A local attacker can exploit this to overwrite arbitrary files or inject malicious session data for persistent prompt injection.\n\n## Vulnerability Details\n\n**Component:** `strands-py/src/strands/session/file_session_manager.py`\n\n**Root Cause:**\n1. Default storage directory is `tempfile.gettempdir() + \"/strands/sessions/\"` (line 58) — resolves to `/tmp/strands/sessions/` on Linux/macOS\n2. The `_write_file()` method (lines 116-123) writes to a predictable `.tmp` path without symlink protection:\n   ```python\n   tmp = f\"{path}.tmp\"\n   with open(tmp, \"w\", encoding=\"utf-8\", newline=\"\\n\") as f:\n       json.dump(data, f, indent=2, ensure_ascii=False)\n   os.replace(tmp, path)\n   ```\n3. `open()` follows symlinks — a pre-placed symlink at the `.tmp` path causes writes to an arbitrary target file\n4. No integrity verification (HMAC/signature) on session files — tampered data is loaded as-is\n\n## Attack Vectors\n\n### 1. Arbitrary File Overwrite (Local Privilege Escalation)\n1. Attacker pre-creates `/tmp/strands/sessions/session_<id>/` (or monitors for new directories via inotify)\n2. Attacker creates a symlink: `session.json.tmp` → `/home/victim/.bashrc` (or any writable file)\n3. Victim application writes session data → follows symlink → overwrites target file with JSON content\n4. Attacker achieves code execution on next shell login (or targets `.ssh/authorized_keys`, cron files, etc.)\n\n### 2. Persistent Prompt Injection via Session Tampering\n1. Attacker writes crafted message files into the session directory\n2. Victim application restores the session → loads attacker-controlled conversation history\n3. Injected messages instruct the agent to execute attacker commands on every future interaction\n4. No integrity check prevents this — the SDK trusts all session files at face value\n\n## Impact\n\n- **Arbitrary file overwrite** — local attacker can write attacker-controlled content to any file writable by the victim process\n- **Local privilege escalation** — via overwriting shell configs, SSH keys, cron jobs, or systemd user services\n- **Persistent agent compromise** — via session data injection, the agent follows attacker instructions indefinitely\n\n## Reproduction\n\n```python\nimport os, json, tempfile, shutil\n\n# Setup: simulate the vulnerable write pattern\nsession_dir = \"/tmp/strands/sessions/session_test/\"\nos.makedirs(session_dir, exist_ok=True)\n\n# Attacker: create symlink at the .tmp write path\ntarget_file = \"/tmp/symlink_attack_proof.txt\"\nwith open(target_file, \"w\") as f:\n    f.write(\"ORIGINAL - should not be overwritten\\n\")\n\nsymlink_path = os.path.join(session_dir, \"session.json.tmp\")\nif os.path.exists(symlink_path):\n    os.unlink(symlink_path)\nos.symlink(target_file, symlink_path)\n\n# Victim: FileSessionManager._write_file() equivalent\nwith open(symlink_path, \"w\", encoding=\"utf-8\", newline=\"\\n\") as f:\n    json.dump({\"session_id\": \"test\", \"injected\": True}, f, indent=2)\n\n# Verify: target file overwritten\nwith open(target_file) as f:\n    print(f\"Target file content: {f.read()}\")\n    # Output: {\"session_id\": \"test\", \"injected\": true}\n\n# Cleanup\nos.unlink(symlink_path)\nos.unlink(target_file)\nshutil.rmtree(session_dir)\n```\n\n## Suggested Fix\n\n1. **Use a user-private default directory** — `~/.strands/sessions/` or `$XDG_DATA_HOME/strands/sessions/` instead of `/tmp/`\n2. **Use `os.open()` with `O_NOFOLLOW | O_CREAT | O_WRONLY`** — refuses to follow symlinks\n3. **Use `tempfile.mkstemp()`** for atomic writes instead of predictable `.tmp` suffix\n4. **Verify directory ownership** matches the current user before writing\n5. **Add optional integrity verification** (HMAC) on session files to detect tampering","categories":["Libraries & packages"],"product":"strands-agents","vendor":"","ecosystem":"pip","affected_ranges":[{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-3w55-933p-8pv5","source_modified_at":"2026-10-27T15:34:33Z","fetched_at":"2026-10-27T16:27:21.584480Z","package":{"ecosystem":"pip","name":"strands-agents"},"range":"< 1.48.0"},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-3w55-933p-8pv5","source_modified_at":"2026-10-27T15:47:09.668142875Z","fetched_at":"2026-10-27T16:27:21.584480Z","package":{"name":"strands-agents","ecosystem":"PyPI","purl":"pkg:pypi/strands-agents"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.48.0"}]}],"versions":["0.0.1","0.1.0","0.1.1","0.1.2","0.1.3","0.1.4","0.1.5","0.1.6","0.1.7","0.1.8","0.1.9","0.2.0","0.2.1","0.3.0","1.0.0","1.0.1","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.28.0","1.29.0","1.3.0","1.30.0","1.31.0","1.32.0","1.33.0","1.34.0","1.34.1","1.35.0","1.36.0","1.37.0","1.38.0","1.39.0"]}],"fixed_versions":[{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-3w55-933p-8pv5","source_modified_at":"2026-10-27T15:34:33Z","fetched_at":"2026-10-27T16:27:21.584480Z","package":{"ecosystem":"pip","name":"strands-agents"},"version":"1.48.0"},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-3w55-933p-8pv5","source_modified_at":"2026-10-27T15:47:09.668142875Z","fetched_at":"2026-10-27T16:27:21.584480Z","package":{"name":"strands-agents","ecosystem":"PyPI","purl":"pkg:pypi/strands-agents"},"version":"1.48.0"}],"cvss":7.1,"severity":"High","kev":{"listed":false},"epss":{"probability":null,"percentile":null,"score_date":null},"published_at":"2026-10-27T15:34:33Z","modified_at":"2026-10-27T15:47:09.668142875Z","provenance":[{"source":"GitHub Advisory Database","url":"https://github.com/advisories/GHSA-3w55-933p-8pv5","fetched_at":"2026-10-27T16:27:21.584480Z","source_modified_at":"2026-10-27T15:34:33Z"},{"source":"OSV","url":"https://osv.dev/vulnerability/GHSA-3w55-933p-8pv5","fetched_at":"2026-10-27T16:27:21.584480Z","source_modified_at":"2026-10-27T15:47:09.668142875Z"}],"sources":["GitHub Advisory Database","OSV"],"references":[{"url":"https://github.com/strands-agents/harness-sdk/security/advisories/GHSA-3w55-933p-8pv5","source":"GitHub Advisory Database"},{"url":"https://github.com/strands-agents/harness-sdk/pull/2937","source":"GitHub Advisory Database"},{"url":"https://github.com/strands-agents/harness-sdk/commit/8658b9e0d57427b77b69343262184d76d8e63e8f","source":"GitHub Advisory Database"},{"url":"https://github.com/strands-agents/harness-sdk/releases/tag/python/v1.48.0","source":"GitHub Advisory Database"},{"url":"https://github.com/advisories/GHSA-3w55-933p-8pv5","source":"GitHub Advisory Database"}],"locations":[],"motoko_assessment":{"status":"requires_architecture_review","automatic_remediation":false},"classification":{"method":"product-first software-layer rules","rules_version":"2.1","matched_on":"ecosystem","confidence":"heuristic","reviewed":false},"change_timestamp":"2026-10-27T15:47:09.668142875Z","content_hash":"f5aa7d726dc6ef21660c2af45ef9b88818798592a88e37fd83ee5b036acc7b5f"}}
