End-to-End Encrypted Terminal Sharing Has a Second Secret on the Host: shell.online v0.24.1 Takes the Session Password Out of Process Environments
End-to-end encrypted terminal sharing is usually explained from the relay's point of view: the server forwards ciphertext it cannot open, so your terminal output is safe in transit. That is true for shell.online, and it is only half the story. The key that opens a session is derived from an eight-character password, and that password has to live somewhere on the host machine for as long as the session runs. Today's shell.online v0.24.1 release is a security patch about exactly that: where the password sat on the host, which local programs could read it, and how it now travels between the CLI's own processes without touching an environment variable at all.
Last week GitGuardian published a breakdown of how AI coding agents leak credentials, and the pattern it describes is the one this release closes.
The credential a secret scanner never sees
GitGuardian's argument is that the leak surface for coding agents is the endpoint, not the repository. In their words, Cursor, Claude Code, and GitHub Copilot "store credentials across config files, env variables, logs, shell history, and temp files that repository and CI scanners never inspect." The same piece cites their State of Secrets Sprawl 2026 figures: 24,008 unique secrets found in public MCP configuration files, 2,117 of them still valid, and a 3.2 percent secret-leak rate in public commits assisted by Claude Code against a 1.5 percent baseline.
Environment variables are the interesting case because nobody thinks of them as a file. Earlier this year VentureBeat reported on a prompt injection, planted in a pull request title, that got three vendors' GitHub Actions coding agents to leak their own API keys, one of them by posting the key as a PR comment. The enabling detail was mundane: any secret stored as a workflow environment variable is readable by every step, the agent included. On a workstation the equivalent is even simpler. The Linux proc(5) manual page describes /proc/<pid>/environ as "the initial environment that was set when the currently executing program was started via execve(2)", readable by any process that passes a ptrace read check, which in practice means any process of the same user. The page also notes that if a program later modifies its environment, "this file will not reflect those changes." Unsetting a variable after startup buys you nothing.
What end-to-end encrypted terminal sharing actually hides
To see why this matters for a terminal link, it helps to know what the password does. When you run a shell.online session, the CLI prints a URL and a password. The URL carries a random salt in its fragment, and both the host and the browser run PBKDF2-HMAC-SHA256 over the password and that salt for 600,000 iterations to derive an AES-256-GCM frame key. The encryption details page lists what the relay still sees: timing, addresses, encrypted payload sizes, labels, lifecycle metadata, and the grid dimensions sent in plaintext control messages. Keystrokes, output, and snapshots are ciphertext to it. The iteration count is a constant in the e2ee package of the repo.
The consequence is that the password is the entire secret. The salt is public by design. Whoever holds the link and the password derives the same key the browser does, and unless the session was started with --read-only, they can type with the permissions of the wrapped process. So a copy of that password lying around on the host is not a minor hygiene problem. It is the one thing that turns the relay's ciphertext back into your terminal.
The link and the password together are a bearer credential. Anyone holding both can open the session, and unless it is read-only, type into it as the process that is running. Treat a leaked password like a leaked SSH key, and rotate it.
Where the password used to sit, and who could read it
Before v0.24.1, a session started from the web app got its password from the machine daemon through an environment variable named SHELL_ONLINE_E2EE_PASSWORD. The daemon passed it to the wrapper process that way, the wrapper passed it to the background process the same way, and the background process lives as long as the session does, which for a coding agent is hours. The changelog states the exposure plainly: any program running as the same user could read it, with ps -E on a Mac or /proc/<pid>/environ on Linux.
The second bug is the one that should worry anyone sharing an agent. The wrapper never stripped that variable, or the session name, origin, and command variables that travel with it, from the environment of the program being shared. The coding agent inherited all of them, and so did every MCP server it spawned and every install script it ran. A nested share was the visible symptom: a shell command run inside a browser-started session silently reused the parent's browser password, published the parent's command and name as its own, and carried the browser request id, so the web app could attach the parent's launch settings to the wrong session. Running shell password rotate from inside rotated to the same password. The invisible symptom is the point of the GitGuardian piece: the process you are sharing could read the credential that grants access to itself.
How v0.24.1 hands the password over a pipe
The fix lives in one small file, cmd/shell/password_handoff.go, and the whole design fits in a screen. Between the CLI's own processes, the password is now written to the child's standard input through a pipe. The only thing placed in the child's environment is a marker, SHELL_ONLINE_E2EE_PASSWORD_STDIN=1, which says "your password is on stdin" and holds no secret. The child reads stdin to end of file, capped at the 1,024-byte maximum a browser password may have, and after that it is in the same state a background process was always in, with stdin at end of file like the null device. This covers the daemon-to-wrapper hop and the wrapper-to-background hop, on Unix and on Windows.
The inheritance bug is closed in the function that builds the shared program's environment. It now removes the session name, origin, command, and password variables, plus the stdin marker, alongside the internal variables it already stripped. The shared program still sees SHELL_ONLINE=1, so a script can tell it is running inside a share, but nothing it could use to open one. On the accounts side, the service now lets only one session claim a given browser request, so a nested share cannot be mistaken for the one the browser asked for. Setting SHELL_ONLINE_E2EE_PASSWORD yourself, to pick your own password, still works as documented. The difference is that the long-lived process no longer carries it.
The pull request in the shell.online repository that landed this included a before-and-after check on a local relay: on the old build, ps -E found the password on a running shell process and the shared program could see the name, origin, command, and password variables; on the new build, neither is true, and a nested share registers its own command with a fresh password.
Upgrading, and what to do about sessions that are already running
Installing the update does not touch sessions that are already running. They keep the environment they started with until they restart, and browser-started sessions will use the old handoff until the machine daemon is restarted. The release notes say to restart the daemon after upgrading; shell daemon status tells you whether it is running. To bring a host current:
curl -fsSL https://shell.online/install | sh
shell --version
shell list
If a host was running other programs as your user while a session was up, whether that is a coding agent, an MCP server, or an install script, assume the password for that session could have been read and rotate it. The command reference documents shell password rotate as changing the password, the salt, and the cipher generation, disconnecting current viewers and revoking any MCP grants, without restarting the task underneath:
shell password rotate <ID>
Two habits apply regardless of version. Where a viewer only needs to watch, start with shell --read-only; the security model page notes that input is then rejected by both the relay and the CLI. The QR code the CLI prints includes the password, so treat it the same way. And as that page puts it, removing someone from an account cannot erase a password they already know. Rotation is the remedy.
Encrypting the transport was the easy half of this problem, and it is on by default in shell.online; the earlier post on this blog covers how the link itself works. The harder half is that a machine running an autonomous agent is a machine where you cannot assume the process next to yours is friendly. Keeping the session secret out of anything that process can read is a smaller change, and it is the one that makes the first half mean something.
Share a terminal, keep the password to yourself
One command prints a link and a password. The process stays on your machine, the relay sees only ciphertext, and as of v0.24.1 the password never sits in a process environment.
Try shell.online