Share a Terminal Session in the Browser Without Handing Over the Filesystem: shell.online v0.14 File Access
This summer's coding-agent security research kept landing on the same shape of bug: the agent stays inside its sandbox, writes a file, and something trusted on the host runs that file later. Today we shipped shell.online v0.14, which adds file access to a shared terminal, so the timing calls for a plain description of the design. When you share a terminal session in the browser with shell.online, a viewer gets the live process and nothing else. As of v0.14 you can additionally opt in one directory. This post covers the exact flags, what the browser can and cannot ask for, and why the file layer is read-only and kept apart from the terminal itself. The basics of the live link are in our earlier post on the browser link for any terminal.
The pattern behind this summer's sandbox escapes
Pillar Security published its Week of Sandbox Escapes series on July 20, covering seven vulnerabilities across Cursor, Codex, Gemini CLI, and Antigravity. Their one-sentence summary of the mechanism: the agent only had to write something that a trusted component outside the sandbox would later run, load, scan, or treat as safe. They sorted the findings into four failure modes, and two of them are about files: workspace configurations that are really executable code, and command allowlists that trust a command name instead of the full invocation.
On September 2, The Hacker News reported Manifold Security's GitSpawn findings. A repository's own .git/config can set core.fsmonitor to an attacker's command, and the command executes as the user, outside the agent's sandbox and without an approval prompt, the moment an agent refreshes the Git index. Seven agents were affected, Claude Code, Codex, and Cursor among them.
Then on September 10, Upstarts Media covered Accomplish, a Tel Aviv startup that reported further sandbox escapes to Anthropic, Cursor, and OpenAI. One of the fixes took about 50 days. Co-founder Or Hiltch's line in that piece was that there is a lot of talk about security now and it does not really show in how products get built.
The thread running through all three is a file in the workspace that was treated as inert when it was not. That is the framing we used for the file layer in v0.14.
What --files adds when you share a terminal session in the browser
shell --files claude
shell --files-root ./artifacts python train.py
File access is off unless one of those flags is present. The first scopes it to the command's working directory. The second picks a different root and implies the first. The CLI opens the root with Go's os.Root, so parent traversal and symlink escapes fail at the filesystem boundary instead of in a string-prefix check. The implementation is short and worth reading: shared_files.go in the shell.online source. Directory listings skip symlinks and anything that is neither a regular file nor a directory, and a read of a device file or other non-regular file is refused.
The file service understands exactly three request types: capabilities, list, and read. There is no write, no rename, no delete, and no execute. A viewer cannot put a file into the shared root through the browser, which is the whole class of problem in the research above. If the wrapped process writes a file, that is the process acting with its own permissions, exactly as it would with no share running.
Nothing is pushed. The browser receives no listing and no contents until it asks, and the Files control only appears in the viewer when the host opted in. Reads are pulled by the viewer in 16 KiB chunks, so a slow phone connection cannot apply back-pressure to the PTY. Previews are capped at 16 MiB and downloads at 128 MiB. A file over the cap is refused rather than truncated. The CLI keeps at most eight file requests in flight and answers a ninth with a BUSY error. The full flag list for this release is in the v0.14.0 changelog entry of the shell.online repository.
Encrypted end to end, or not at all
Files ride the same end-to-end encrypted WebSocket as the terminal frames. The CLI encrypts the listing and each chunk before they reach the relay, and the browser decrypts with the key derived from the ten-character password the CLI printed. The URL fragment carries only a random salt. What the relay routes is a request ID and an encrypted frame size; it does not see paths, names, or contents. That is the same boundary described on the shell.online end-to-end encryption page for terminal output.
Because of that, the file flags refuse to combine with --no-e2ee. The CLI exits and says that --files and --files-root require end-to-end encryption. There is no transport-only mode for files.
The credential model has not changed. Anyone holding both the URL and the password can open the share, and if the share is interactive they can also type with the permissions of the wrapped process. Adding --files widens what a holder of that pair can read, so the decision to opt in a directory deserves the same care as the decision to send someone the password.
The URL and the password together are a bearer credential. With --files, that credential also reads every regular file under the chosen root. Pick a narrow root such as ./artifacts rather than a home directory, and use shell password rotate to revoke a pair without restarting the process.
Read-only viewers, Refstream previews, and a scoped agent
A link created with shell --read-only --files can open the opted-in files but still cannot type into the process; browser input is blocked by the server as before. That combination fits a training run. A viewer watches the log and opens the latest metrics file from a phone, and there is no path by which they could send a keystroke.
v0.14 also adds a renderer dropdown. xterm.js remains the default. The alpha Refstream renderer turns filename-like terminal output into previews backed by the file service, which is handy when a build prints the path of a report. The rule that matters: terminal text is never treated as filesystem authority. A path printed by the process is only a hint the viewer can click. Every open still goes through the same rooted, read-only request path, so a process that prints ../../etc/passwd produces a denial from the CLI, not a preview.
Refstream also offers a Connect agent invitation, and it is separate from file access. You copy one scoped invitation into an agent chat and choose either read, which lets the agent read, search, and wait for terminal output, or control, which also lets it type, run commands, and send key combinations such as Ctrl-C. The invitation is revocable and bound to one live session. Disconnecting the agent does not stop the terminal, and files stay unavailable unless the host separately started the share with a file flag. The agent skill published at shell.online/skill instructs an agent to enable file sharing only when the operator explicitly asks, and to say which root it shared.
Edge cases worth knowing before you rely on it
- Network loss. The process and PTY stay on your machine. A dropped browser tab reconnects to the same session without restarting anything, sessions run in the background by default, and shell attach rejoins locally while browser access stays live. The reliability notes describe the reconnect behaviour.
- A file changes mid-transfer. Each read carries a token derived from the path, size, modification time, and mode. If the file changes between chunks, the CLI answers with a CHANGED error instead of sending a chunk from the new version, so a preview is never stitched together from two versions of a file.
- Auto-close. The share ends when the wrapped process exits, and file access ends with it. shell --auto-close 5m sets an earlier deadline.
- Persistence. shell --persistent with a state file keeps the same URL and password across restarts. The root is just a flag on the command you rerun, and the only combination the CLI forbids for the file flags is the one with --no-e2ee.
- Directory size. A listing returns at most 256 entries per directory, directories first and then files sorted by name. A huge flat output folder will be cut off in the panel, so point --files-root at a subdirectory instead.
All of this is in the CLI reference under the files topic, and shell help files prints the same text offline. shell.online is MIT licensed and built by us at Pilot Protocol; the file service, the relay worker, and the browser panel are all in the public repository, so every claim in this post can be checked against the code.
Share the terminal, and only the files you chose
One command prints a URL and a password. Add --files or --files-root to let viewers open a single directory over the same encrypted channel, read-only, with nothing sent until they ask.
Try shell.online