A Read-Only Terminal Link for Your Coding Agent, After Plugin4Shell
On September 17, researchers at Air published Plugin4Shell, a remote code execution flaw in the plugin systems of Claude Code, Codex, GitHub Copilot, and Gemini CLI. The practical fallout this week is a lot of people looking over a lot of shoulders: security teams checking agent versions, leads asking what plugins a given laptop has installed, teammates watching an agent run to see what it touches. Most of those observers need eyes and nothing else. A read-only terminal link is the smallest grant that gives them that, and this post walks through how shell.online builds one, where the restriction is enforced, and what a viewer still gets to see.
What Plugin4Shell says about how far an agent reaches
The bug is in SHA pinning, the mechanism that is supposed to lock an installed plugin to one reviewed commit. In Air's words, the agent "checks out the exact commit the marketplace pinned but never verifies it landed there." An attacker who controls the plugin's repository creates a branch named like the pinned hash, the checkout resolves to the branch, and the pin still looks honored. GitHub refuses branch names that resemble commit SHAs, but The Register's coverage carries the researchers' answer to that: marketplaces can also live on Bitbucket or self-hosted git servers, which do not.
The zero-click part comes from auto-update. Per Help Net Security's writeup, the same checkout re-runs on background auto-update, which is the default in Claude Code and Codex, so a swapped commit reaches plugins that are already installed with no user action. Anthropic fixed Claude Code in 2.1.179 and OpenAI fixed Codex in 0.146.0. Air reports that Microsoft has not shipped a fix for Copilot, and that Google deprecated Gemini CLI instead of patching it.
The sentence worth keeping from the Air post is the impact statement: full compromise of the agent and its host, "and with it full access to every asset and every piece of data the agent can reach." A coding agent runs as the developer. It holds their SSH keys, their cloud credentials, their checkout of the monorepo. Anything that can put keystrokes into that process inherits the same reach.
To be plain about scope: a read-only link does nothing against Plugin4Shell itself. The fix for that is updating the agent. The connection is narrower. Once you accept that the agent process is one of the most privileged things on a laptop, every attachment to it deserves the same question a plugin does: what can this thing do if the wrong party ends up holding it?
Creating a read-only terminal link for an agent session
A normal share on shell.online, which turns any terminal process into a browser link, is interactive. The command prints a URL, a password, and a QR code, and anyone holding the URL and password can view and type. Adding one flag before the command changes the grant:
shell --read-only claudeThe agent starts in a local PTY on your machine and keeps running in the background. The session summary the CLI prints has an Access line, and for this session it reads "view only" next to "end-to-end encrypted". The caption under the QR code changes too. An interactive session warns that anyone who scans it can view and type in the session; a read-only one says they can view it. The same access mode shows up in the access column of shell list, and the browser labels the terminal as read-only so the viewer knows why their keyboard does nothing.
For a review with a natural end, give it one. A name helps whoever is looking at a list of sessions later:
shell --read-only --name "plugin audit" --auto-close 30m codexThe session always closes when the wrapped process exits. The auto-close value only adds an earlier deadline, and it accepts combined units like 1h30m or a date. A missing or invalid value exits with status 2 and never gets run as the command, which matters when the thing being wrapped is an agent with your permissions.
You still work in the session yourself. The flag blocks browser input, and shell attach with the session ID is a local PTY attach on the host machine, so the operator keeps typing from their own terminal while everyone on the link watches. Ctrl-X then D detaches and leaves both the process and the share running.
Where the input is rejected
A view-only mode that lives in the web page is a suggestion. Anyone can open DevTools, patch the client, or write WebSocket frames by hand. The security model page describes the enforcement as happening twice: ordinary browser input and confirmed EOF are rejected independently by the relay and by the CLI before they reach the PTY.
The relay check has been there since read-only links shipped in 0.4.0, where the changelog records access mode being stored as immutable session metadata and input being rejected inside the Worker. The CLI check is the one that runs on your own hardware, so it holds no matter what the hosted side does. In the open-source CLI it is a small function in cmd/shell/session_unix.go. After a frame is decrypted, its opcode goes through acceptsViewerInput, which returns true only when the session is not read-only and the opcode is Input or ConfirmedEOF. In a read-only session neither is ever written to the PTY. The EOF case is there because a Ctrl-D that ends the agent's shell is input as far as the wrapped process is concerned.
Access mode is also fixed at creation. The CLI reference states that it cannot be weakened after creation. Persistent sessions follow the same rule: if a state file was created read-only and you start it again without the flag, the CLI refuses with an error that the persistent state's access mode does not match the requested flags, instead of quietly reissuing the same URL with typing enabled. If a reviewer needs to type, start a separate interactive session and hand it over deliberately.
What a read-only viewer still gets
Read-only removes the keyboard. It removes nothing from the screen.
The URL and password together are a bearer credential. For an interactive share, anyone holding both can view and type with the operating-system permissions of the wrapped process. For a read-only share, anyone holding both can see everything the terminal prints, including any secret the agent happens to echo.
So the usual discipline applies: send the link and the password over different channels, and do not run a review session in a shell where tokens get printed. Terminal traffic is end-to-end encrypted by default, which keeps contents away from the relay, but the relay does see metadata, and the security page lists access mode and the command label among it.
Files are a separate capability with their own switch. If the host started the session with --files (or --files-root for a different directory), a read-only viewer can still browse and read the opted-in directory on demand. They cannot type, but they can read. For an audit link over an agent's working tree that may be exactly what you want. If it is not, leave the flag off, and no listing or file contents are available to any browser.
One smaller effect is easy to miss. Viewers never own the PTY size, but a connected phone moves the whole session to a portrait grid, and the mobile docs note that read-only phones count too. A reviewer opening the link on a handset will reflow the agent's TUI for everyone until they leave.
Network loss, leaked links, and ending the review
The process does not depend on the link. If the reviewer's connection drops, or the host's does, the CLI and browser reconnect with backoff and the returning viewer gets a screen snapshot from a bounded local buffer. An ordinary share stays recoverable for 12 hours after its host disconnects. The agent keeps working the whole time.
If the link ends up somewhere it should not be, rotate it:
shell password rotate <id>That generates a new password and URL salt, disconnects current viewers, and leaves the wrapped process running. The old credential stops working even for viewing. When the review is over, shell kill with the session ID stops the process and the share together, or you let the agent exit and the session closes on its own. No terminal transcript is kept by the relay, so there is nothing to clean up on the hosted side afterwards. We covered the basic sharing flow in more detail in the introduction to live terminal links.
Plugin4Shell came down to a verification step that everyone assumed was happening and was not. The read-only flag is worth using for the opposite reason: the check sits in a few lines of Go that anyone can read, and it runs on the machine that owns the process.
Give reviewers a link that cannot type
Install the CLI, run shell --read-only in front of your agent command, and send the URL and password to whoever needs to watch. No SSH, VPN, or account required.
Try shell.online