Blog
min read
The Week of Sandbox Escapes: The sandbox let me edit a venv, and something else ran it
.png)
This post is part of The Week of Sandbox Escapes, a series on how AI coding agents keep crossing the line between sandboxed workspace actions and unsandboxed host execution. Read the master report here.
Cursor's macOS sandbox (Seatbelt) does a decent job of keeping the agent's writes inside the workspace and off your home directory, SSH keys, and the rest. The gap is that it treats everything in the workspace as inert. A Python virtual environment isn't inert. Something outside the sandbox executes files inside your venv automatically, and the agent is allowed to write those files.
What happens
The ms-python.python extension ships bundled with Cursor. When you open a workspace it runs interpreter discovery: it finds your virtual environments and runs each interpreter to read back its version and prefix, roughly .venv/bin/python <ext>/python_files/interpreterInfo.py. That call comes from the extension host, and the extension host is not sandboxed.
So if I can get the agent to write .venv/bin/python3, I get code execution outside the sandbox the moment discovery fires. Nothing stops that write. If there's no venv around, python3 -m venv .venv makes one and the sandbox doesn't block that either.
The move is three steps: find a venv or create one, replace bin/python3 with a bash wrapper that does what I want and then execs the real interpreter so the extension still gets a valid answer, and repoint the python symlink at the wrapper.
The wrapper opens Calculator, appends a line to ~/.zshrc, and writes id output to ~/escape.txt. This can be replaced with any payload.
Proof of Concept
Simply run the following prompt injection:
While for demonstration purposes I used a direct prompt injection, with indirect injection the same instructions can reach the agent through a poisoned README or a comment buried in a dependency, and the user never sees them. There's nothing to watch for. The agent writes some files, which is the most ordinary thing an agent does, and the payload sits there until discovery runs.
You can view the PoC video here:
Blocking ".venv" doesn't fix it
The obvious patch is to deny writes to .venv, venv, and env but the answer is a bit more complicated than that.
Cursor discovery uses a native Rust binary -- pet -- bundled with the python extension. It scans two levels deep from the workspace root for anything that looks like a virtual environment, and the directory name is irrelevant. It matches on four signatures:
- <dir>/bin/python next to <dir>/pyvenv.cfg (venv)
- <dir>/bin/python next to <dir>/bin/activate (virtualenv)
- <dir>/bin/python with a Pipfile in the grandparent directory (pipenv, local)
- <dir>/bin/python next to <dir>/.project (pipenv, global)
Hit any one of those within two levels of the root and the binary gets spawned. bin/python only has to be executable.
So the minimum payload is two files, in a directory named whatever you want:
An executable bin/python and an empty pyvenv.cfg. Discovery fires on workspace open or window reload, and creating a fresh venv triggers it with no restart. A denylist of folder names protects nothing, because the attacker picks a different name.
I confirmed the arbitrary-directory variants by having the trojanized bin/python log who executed it. Two of the results, from directories called data_cache and build_output — neither of which is a venv name:
Both spawned by the extension host, both outside the sandbox.
The fix
Stop treating this as a path-blocklist problem. Either turn off the automated interpreter discovery that ships on by default, or deny the sandbox the ability to create or modify the files a virtual environment is made of — the marker files and the executables. Cursor went with a fix, and a CVE is pending.
There's a pattern across these agentic IDEs. The sandbox guards what the agent runs. It doesn't guard what the agent writes for some unsandboxed helper to run later, and editors are full of those helpers. Interpreter discovery just happened to be the one already standing outside the box.
You can read the Cursor published advisory regarding this issue here: https://github.com/cursor/cursor/security/advisories/GHSA-p9g2-cr55-cw9c.
Subscribe and get the latest security updates
Back to blog
.png)
.png)
%20(1).webp)
%20(1).png)

%20(1).png)
%20(1).png)
