{"id":"GHSA-3w55-933p-8pv5","kind":"vulnerability","aliases":["GHSA-3w55-933p-8pv5"],"related":[],"title":"Strands Agents: Local Privilege Escalation via Symlink Attack in Default Session File Storage","description":"## 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","summary":"The Strands Agents FileSessionManager stores session data by default in the world-writable /tmp/strands/sessions/ directory, using predictable paths and writing through a .tmp file that follows symbolic links. A local user can plant a symlink to make the process overwrite any file the victim account can write, or inject tampered session history that the agent then trusts.","severity":"high","cvss":7.1,"epss":null,"exploited":false,"exploited_at":null,"products":[{"name":"strands-agents","fixed":["1.48.0"],"affected":"< 1.48.0","ecosystem":"pypi"}],"ecosystems":["pypi"],"tags":["ai-tooling","cwe-59","linux","macos","python","self-hosted","strands-agents"],"not_affected_if":["custom-session-directory","no-session-persistence","single-user-host"],"how_to_check":"- Check the installed version with `pip show strands-agents`, and confirm it is below `1.48.0`.\n- Look for a default session directory at `/tmp/strands/sessions/` on hosts running the SDK.\n- Check whether the application uses `FileSessionManager` without setting a custom storage directory.","fix":"- Upgrade `strands-agents` to `1.48.0` or later.\n- Until upgraded, pass a user-private storage directory to `FileSessionManager` instead of the `/tmp` default.","status":"open","published_at":"2026-10-27T15:34:33+00:00","first_seen_at":"2026-10-27T15:39:16+00:00","triaged_at":"2026-10-27T15:39:47+00:00","updated_at":"2026-10-27T15:39:47+00:00","category":"ai-tooling","confidence":0.8,"triage":{"at":"2026-10-27T15:39:47Z","model":"claude-haiku-5-5","output":{"fix":"- Upgrade `strands-agents` to `1.48.0` or later.\n- Until upgraded, pass a user-private storage directory to `FileSessionManager` instead of the `/tmp` default.","reach":"local","title":"Strands Agents: local users can overwrite files via symlinks in session storage","impact":"high","summary":"The Strands Agents FileSessionManager stores session data by default in the world-writable /tmp/strands/sessions/ directory, using predictable paths and writing through a .tmp file that follows symbolic links. A local user can plant a symlink to make the process overwrite any file the victim account can write, or inject tampered session history that the agent then trusts.","category":"ai-tooling","nicknames":[],"who_cares":["python","strands-agents","self-hosted","linux","macos"],"confidence":0.8,"needs_auth":"yes","worst_case":"local user overwrites victim files or injects malicious agent history","analyst_note":"","how_to_check":"- Check the installed version with `pip show strands-agents`, and confirm it is below `1.48.0`.\n- Look for a default session directory at `/tmp/strands/sessions/` on hosts running the SDK.\n- Check whether the application uses `FileSessionManager` without setting a custom storage directory.","needs_analyst":false,"not_affected_if":["custom-session-directory","single-user-host","no-session-persistence"],"who_is_affected":"Exposed systems run strands-agents below 1.48.0 with session persistence through FileSessionManager on a shared host, where other local users can write to /tmp. Systems that set a private session directory or run on a single-user machine with no other local accounts face much less risk. The sources do not say whether the default applies to every SDK language binding.","user_interaction":"no","needs_special_setup":"yes","severity_if_unknown":"high"},"cost_usd":0.000794,"input_tokens":4806,"output_tokens":626,"severity_before":"high"},"analyst_status":null,"analyst_note":null,"search":"'-123':180C '-59':31B '-8':201C,495C '/.strands/sessions':551C '/home/victim/.bashrc':280C '/src/strands/session/file_session_manager.py':156C '/strands/sessions':165C '/tmp':558C '/tmp/strands/sessions':55B,108C,170C '/tmp/strands/sessions/session_':266C '/tmp/strands/sessions/session_test':435C '/tmp/symlink_attack_proof.txt':452C '1':159C,254C,261C,321C,543C '116':179C '2':173C,210C,274C,314C,331C,508C,559C '3':217C,285C,343C,573C '3w55':16A,21A '4':237C,299C,357C,584C '5':594C '58':167C '8pv5':18A,23A '933p':17A,22A 'account':86B 'achiev':301C 'add':595C 'agent':2A,40B,43B,96B,348C,408C,415C 'agents':26A 'ai':28B 'ai-tool':27B 'applic':287C,333C 'arbitrari':139C,234C,255C,373C 'as-i':249C 'ascii':212C 'atom':577C 'attack':8A,133C,252C,262C,275C,300C,322C,339C,351C,377C,381C,417C,442C 'attacker-control':338C,380C 'caus':158C,230C 'check':360C 'cleanup':531C 'code':302C 'command':352C 'compon':152C 'compromis':409C 'config':398C 'content':298C,383C,523C 'control':340C,382C 'convers':341C 'craft':324C 'creat':265C,276C,443C,566C 'cron':311C,401C 'current':590C 'cwe':30B 'data':47B,106C,145C,207C,246C,290C,412C,554C 'default':10A,49B,102C,160C,549C 'detail':151C 'detect':604C 'dir':434C,438C,471C,540C 'directori':56B,113C,162C,271C,330C,550C,586C 'encod':199C,493C 'ensur':211C 'equival':487C 'escal':5A,260C,394C 'etc':313C 'everi':354C 'execut':303C,350C 'exist':439C 'exploit':135C 'f':192C,205C,208C,459C,499C,506C,518C,520C 'f.read':524C 'f.write':460C 'face':370C 'fals':213C 'file':12A,65B,83B,116C,140C,176C,236C,244C,256C,284C,295C,312C,326C,368C,374C,386C,451C,456C,482C,511C,516C,522C,537C,602C 'filesessionmanag':44B,101C 'filesessionmanager._write_file':486C 'fix':542C 'follow':67B,128C,219C,291C,416C,571C 'futur':355C 'ghsa':15A,20A 'ghsa-3w55-933p-8pv5':14A,19A 'histori':93B,342C 'hmac':599C 'hmac/signature':241C 'home/strands/sessions':555C 'host':37B 'id':502C,527C 'impact':372C 'import':422C 'indefinit':419C 'indent':209C,507C 'inject':90B,142C,149C,317C,344C,413C,504C,529C 'inotifi':273C 'instead':556C,579C 'instruct':346C,418C 'integr':239C,359C,597C 'interact':356C 'job':402C 'json':297C,424C 'json.dump':206C,500C 'key':400C 'line':166C,178C 'link':69B,130C 'linux':32B 'linux/macos':172C 'load':248C,337C 'local':3A,71B,132C,258C,376C,392C 'login':307C 'maco':33B 'make':78B 'malici':143C 'match':588C 'messag':325C,345C 'method':177C 'monitor':268C 'n':203C,466C,497C 'new':270C 'newlin':202C,496C 'next':305C 'nofollow':564C 'o':563C,565C,567C 'ok':440C 'open':122C,196C,218C,454C,489C,514C 'option':596C 'origin':461C 'os':423C 'os.makedirs':436C 'os.open':561C 'os.path.exists':474C 'os.path.join':469C 'os.replace':126C,214C 'os.symlink':480C 'os.unlink':477C,532C,535C 'output':525C 'overwrit':81B,138C,257C,293C,375C,396C 'overwritten':465C,512C 'ownership':587C 'path':59B,117C,123C,186C,193C,216C,229C,449C,468C,476C,479C,484C,491C,534C 'pattern':121C,432C 'persist':147C,315C,407C 'place':224C 'plant':74B 'pre':223C,264C 'pre-creat':263C 'pre-plac':222C 'predict':58B,115C,184C,581C 'prevent':361C 'print':519C 'privat':548C 'privileg':4A,259C,393C 'process':80B,391C 'prompt':148C,316C 'protect':189C 'py':155C 'python':34B,190C,421C 'refus':569C 'reproduct':420C 'resolv':168C 'restor':334C 'root':157C 'sdk':364C 'self':36B 'self-host':35B 'servic':406C 'session':11A,46B,92B,105C,144C,243C,289C,319C,329C,336C,367C,411C,433C,437C,470C,501C,526C,539C,601C 'session.json.tmp':279C,472C 'setup':427C 'shell':306C,397C 'shutil':426C 'shutil.rmtree':538C 'simul':428C 'ssh':399C 'ssh/authorized_keys':310C 'storag':13A,161C 'store':45B,104C 'strand':1A,39B,42B,154C 'strands':25A 'strands-ag':38B 'strands-agents':24A 'strands-pi':153C 'suffix':583C 'suggest':541C 'summari':99C 'symbol':68B,129C 'symlink':7A,76B,188C,220C,225C,278C,292C,444C,467C,475C,478C,483C,490C,533C,572C 'systemd':404C 'tamper':91B,245C,320C,605C 'target':235C,294C,309C,450C,455C,481C,510C,515C,521C,536C 'tempfil':425C 'tempfile.gettempdir':164C 'tempfile.mkstemp':575C 'test':503C,528C 'tmp':64B,124C,185C,191C,194C,197C,215C,228C,447C,582C 'tool':29B 'true':441C,505C,530C 'trust':98B,365C 'use':57B,114C,544C,560C,574C 'user':72B,405C,547C,591C 'user-priv':546C 'utf':200C,494C 'valu':371C 'vector':253C 'verif':240C,598C 'verifi':509C,585C 'via':6A,272C,318C,395C,410C 'victim':85B,286C,332C,390C,485C 'vulner':150C,430C 'w':125C,198C,457C,492C 'without':187C 'world':53B,111C 'world-writ':52B,110C 'writabl':54B,112C,283C,387C 'write':61B,88B,120C,175C,181C,231C,288C,323C,379C,431C,448C,578C,593C 'wron':568C 'xdg':553C","nicknames":[],"mitigations":[],"who_affected":"Exposed systems run strands-agents below 1.48.0 with session persistence through FileSessionManager on a shared host, where other local users can write to /tmp. Systems that set a private session directory or run on a single-user machine with no other local accounts face much less risk. The sources do not say whether the default applies to every SDK language binding.","tier":"B","verified":{},"analyst_revisit_at":null,"first_public_at":"2026-10-27T15:34:33+00:00","kev":null,"cvss_vector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H","cvss_by":"GitHub","poc_count":0,"sources":[{"source":"ghsa","external_id":"GHSA-3w55-933p-8pv5","url":"https://github.com/advisories/GHSA-3w55-933p-8pv5","severity":"high","published_at":"2026-10-27T15:34:33+00:00","modified_at":"2026-10-27T15:34:33+00:00","first_seen_at":"2026-10-27T15:39:16+00:00","fetched_at":"2026-10-27T15:39:16+00:00","note":null}],"documents":[],"notice":"Free to read at a human pace. An API with keys and an MCP server are in preparation; early access: hello@infrastack.ai"}