Strands Agents: Local Privilege Escalation via Symlink Attack in Default Session File Storage
GHSA-3w55-933p-8pv5 · published 27 Oct 2026
How serious is it
- Severity: high, CVSS score 7.1 out of 10.
- No exploit prediction (EPSS) score yet.
- Not on the CISA list of exploited vulnerabilities.
- No public exploit in Exploit-DB.
Is your app affected by GHSA-3w55-933p-8pv5?
Paste this prompt into Claude Code, Cursor or any AI coding tool: it runs a free Shippable scan of your project and fixes what it finds. Built with Lovable, Bolt or v0? Scan your live app. Or upload your package file.
Who is affected
strands-agents(PyPI) < 1.48.0
How to check
- Run
pip show strands-agentsin your project to see the installed version, including copies pulled in by other packages. - Or paste your lockfile into the checker: it lists every package with a known vulnerability, this one included.
- Or run
npx shippable-dev scan localhost:3000in your project folder. It reads your lockfile and flags this vulnerability along with every other known one.
How to fix
Copies a prompt for Claude Code, Cursor or Codex: it checks your lockfiles for this and upgrades it.
- Upgrade
strands-agentsto 1.48.0 or later, commit the lockfile and redeploy. - If a vulnerable version was live, check your logs and rotate the secrets that server could read.
The advisory
## Summary
The `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.
## Vulnerability Details
**Component:** `strands-py/src/strands/session/file_session_manager.py`
**Root Cause:**
1. Default storage directory is `tempfile.gettempdir() + "/strands/sessions/"` (line 58) — resolves to `/tmp/strands/sessions/` on Linux/macOS
2. The `_write_file()` method (lines 116-123) writes to a predictable `.tmp` path without symlink protection:
```python
tmp = f"{path}.tmp"
with open(tmp, "w", encoding="utf-8", newline="\n") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
os.replace(tmp, path)
```
3. `open()` follows symlinks — a pre-placed symlink at the `.tmp` path causes writes to an arbitrary target file
4. No integrity verification (HMAC/signature) on session files — tampered data is loaded as-is
## Attack Vectors
### 1. Arbitrary File Overwrite (Local Privilege Escalation)
1. Attacker pre-creates `/tmp/strands/sessions/session_<id>/` (or monitors for new directories via inotify)
2. Attacker creates a symlink: `session.json.tmp` → `/home/victim/.bashrc` (or any writable file)
3. Victim application writes session data → follows symlink → overwrites target file with JSON content
4. Attacker achieves code execution on next shell login (or targets `.ssh/authorized_keys`, cron files, etc.)
### 2. Persistent Prompt Injection via Session Tampering
1. Attacker writes crafted message files into the session directory
2. Victim application restores the session → loads attacker-controlled conversation history
3. Injected messages instruct the agent to execute attacker commands on every future interaction
4. No integrity check prevents this — the SDK trusts all session files at face value
## Impact
- **Arbitrary file overwrite** — local attacker can write attacker-controlled content to any file writable by the victim process
- **Local privilege escalation** — via overwriting shell configs, SSH keys, cron jobs, or systemd user services
- **Persistent agent compromise** — via session data injection, the agent follows attacker instructions indefinitely
## Reproduction
```python
import os, json, tempfile, shutil
# Setup: simulate the vulnerable write pattern
session_dir = "/tmp/strands/sessions/session_test/"
os.makedirs(session_dir, exist_ok=True)
# Attacker: create symlink at the .tmp write path
target_file = "/tmp/symlink_attack_proof.txt"
with open(target_file, "w") as f:
f.write("ORIGINAL - should not be overwritten\n")
symlink_path = os.path.join(session_dir, "session.json.tmp")
if os.path.exists(symlink_path):
os.unlink(symlink_path)
os.symlink(target_file, symlink_path)
# Victim: FileSessionManager._write_file() equivalent
with open(symlink_path, "w", encoding="utf-8", newline="\n") as f:
json.dump({"session_id": "test", "injected": True}, f, indent=2)
# Verify: target file overwritten
with open(target_file) as f:
print(f"Target file content: {f.read()}")
# Output: {"session_id": "test", "injected": true}
# Cleanup
os.unlink(symlink_path)
os.unlink(target_file)
shutil.rmtree(session_dir)
```
## Suggested Fix
1. **Use a user-private default directory** — `~/.strands/sessions/` or `$XDG_DATA_HOME/strands/sessions/` instead of `/tmp/`
2. **Use `os.open()` with `O_NOFOLLOW | O_CREAT | O_WRONLY`** — refuses to follow symlinks
3. **Use `tempfile.mkstemp()`** for atomic writes instead of predictable `.tmp` suffix
4. **Verify directory ownership** matches the current user before writing
5. **Add optional integrity verification** (HMAC) on session files to detect tampering
Check your whole app
Dependencies are only half of it. Free and read-only: before you push, run npx shippable-dev scan localhost:3000 in your project folder, no account needed. For your live app, you prove it's yours with a one-line tag first.
References
- github.com/strands-agents/harness-sdk/security/advisories/GHSA-3w55-933p-8pv5
- github.com/strands-agents/harness-sdk/pull/2937
- github.com/strands-agents/harness-sdk/commit/8658b9e0d57427b77b69343262184d76d8e63e8f
- github.com/strands-agents/harness-sdk/releases/tag/python/v1.48.0
- github.com/advisories/GHSA-3w55-933p-8pv5
- github.com/strands-agents/harness-sdk
Merged from GitHub Advisory Database, OSV. Last updated 28 Oct 2026. All vulnerabilities · RSS · JSON