diff --git a/public/blog/banners/terminal-sharing-security-when-infostealers-go-after-coding-agents-what-shell-on.svg b/public/blog/banners/terminal-sharing-security-when-infostealers-go-after-coding-agents-what-shell-on.svg new file mode 100644 index 00000000..f96d58a1 --- /dev/null +++ b/public/blog/banners/terminal-sharing-security-when-infostealers-go-after-coding-agents-what-shell-on.svg @@ -0,0 +1,8 @@ + diff --git a/src/data/blogPosts.json b/src/data/blogPosts.json index c1859313..9357f8aa 100644 --- a/src/data/blogPosts.json +++ b/src/data/blogPosts.json @@ -1858,5 +1858,15 @@ "tags": ["shell-online", "terminal", "ai-agents", "reliability"], "banner": "banners/keep-a-terminal-session-alive-after-a-disconnect-what-a-long-agent-run-on-shell.svg", "iso_date": "2026-10-11" + }, + { + "slug": "terminal-sharing-security-when-infostealers-go-after-coding-agents-what-shell-on", + "title": "Terminal Sharing Security When Infostealers Go After Coding Agents: What shell.online Leaves on Disk, and What to Revoke", + "description": "Terminal sharing security after Gen Digital's infostealer report: the two files shell.online keeps on a host, what each unlocks, and the cleanup order.", + "date": "Oct 15", + "category": "Security", + "tags": ["shell-online", "security", "infostealers", "coding-agents", "terminal-sharing"], + "banner": "banners/terminal-sharing-security-when-infostealers-go-after-coding-agents-what-shell-on.svg", + "iso_date": "2026-10-15" } ] diff --git a/src/pages/blog/terminal-sharing-security-when-infostealers-go-after-coding-agents-what-shell-on.astro b/src/pages/blog/terminal-sharing-security-when-infostealers-go-after-coding-agents-what-shell-on.astro new file mode 100644 index 00000000..c3afb38b --- /dev/null +++ b/src/pages/blog/terminal-sharing-security-when-infostealers-go-after-coding-agents-what-shell-on.astro @@ -0,0 +1,16 @@ +--- +import BlogLayout from '../../layouts/BlogLayout.astro'; + +const bodyContent = `
On September 8, Gen Digital's Jan RubĂn published a report on infostealers that now collect AI agent data. Remus goes after Claude, Cursor and OpenCode. The macOS stealer Djinn is associated with Claude, Codex, Gemini, Cline, OpenCode and Kilo. CallbackBeaver alone showed more than 5,000 samples in a recent 30-day window. The targets are cached access and refresh tokens, MCP configs, and prompt histories. Gen closes with advice for the people who build these tools: keep less on disk, keep auth material apart from history, use the OS credential store, and issue short-lived, narrowly scoped tokens. That advice applies to terminal sharing security as much as to coding agents, so we ran the same audit on our own tool.
shell.online wraps exactly the programs on that list. You run shell claude or shell opencode, and it prints a browser link to that running terminal, an eight-character password and a QR code. The process stays on your machine, and the relay forwards only encrypted frames. (The introductory post on the browser link for any terminal covers the basics.) The question here is narrower: if a stealer runs as you on that machine, what does it find, and what should you revoke afterwards? Everything below was read from the shell.online source on GitHub at the current main branch. The latest CLI release is v0.25.0.
Without an account, this file does not exist. If you link a machine with shell auth, the CLI does an OAuth authorization code exchange with PKCE and a loopback redirect, the pattern in RFC 8252, OAuth 2.0 for native apps. The service rejects any redirect URI that is not a loopback callback and accepts only the S256 challenge method. The resulting tokens go to credentials.json in a shell-online folder under your OS config directory: ~/.config/shell-online/ on most Linux setups, ~/Library/Application Support/shell-online/ on macOS. The SHELL_ONLINE_CONFIG variable overrides the location.
The file is plain JSON, written with mode 0600 inside a 0700 directory. It holds an access token, a refresh token, the expiry, your account email and name, and the remote-start choice. It is not in the macOS Keychain. Gen's report recommends the keychain, and it is a fair comparison: Claude Code's authentication docs say it stores credentials in the encrypted macOS Keychain and falls back to a 0600 file on Linux. File permissions stop other users on the box. They do nothing against code running as you, which is how a stealer runs.
So what does a stolen copy of that file get someone? From the server code:
In short, the account token tells a thief which sessions you run, on which machine, and when. It does not give them a password or any terminal content.
This one matters more, and it exists whether or not you have an account. On Unix hosts, each active session has a record at /tmp/shell-online-<uid>/<id>.json. The directory is 0700, and the CLI will not read sessions from it if another user owns it or its permissions are looser. The file is 0600. It contains the share URL and the session password, because that is what shell list and shell password <ID> read when you ask for them.
A URL plus its password is a complete bearer credential. Anyone holding both can view the session and, unless it is read-only, type with the permissions of the wrapped process. If an infostealer grabs that directory while shell claude is running, it has not stolen a token for a service. It has a live terminal on your machine. The shell.online security page states the general rule plainly: end-to-end encryption does not protect against malicious code on either endpoint. Our encryption design keeps the relay from reading frames. It cannot keep a process running as you from reading a file you can read.
Two more files fall into the same category. If you use --persistent <state-file> so a link survives restarts, that state file is what brings back the same URL and password, so treat it as a credential too. And a --files share exposes a directory on purpose, which a thief with the password reaches like anyone else.
Gen recommends starting from a clean device, and that applies here. If the laptop may still be infected, any new password you generate on it lands in the same readable record. The order we would follow:
The full flag list is on the shell.online command reference.
None of these steps can undo what already happened. Rotation and revocation cut off future access. They cannot recall commands someone typed into a session, or output they already read. If a thief had a live interactive link, treat everything that process could touch as exposed: the repository, the credentials in its environment, and the agent's own login.
Remote start is opt-in for a reason. With it on, your signed-in browser can ask a linked, online machine to start a process, and the machine runs it. When the browser picks a session password, it seals it to a key the machine published, so the accounts service relays an envelope it cannot open. That keeps the service out of your sessions. It does not make remote start a small permission: the docs call it remote execution authority. A machine that only ever runs shell locally does not need it, and shell daemon status shows whether the listener is running.
Measured against Gen's checklist, two gaps are ours. Linked-machine tokens live in a file, not the OS credential store, and the refresh token does not rotate on refresh. Both appear in the open code, in internal/account/credentials.go and the service's token module, so anyone can verify whatever we change next. The short access-token lifetime, per-request revocation checks and per-machine unlinking are what keep the current design manageable.
The session record is a different kind of trade-off. Recovering a password on the host is a feature people use, and anything the CLI can read back, a process running as the same user can read too. If that is the wrong trade for your threat model, keep sessions short with --auto-close, use --read-only when nobody needs to type, and kill sessions you are not watching.
Run shell list on any machine where you share terminals, then compare it with the Machines page in the app. Anything you don't recognize is something to kill, rotate or unlink.
Try shell.online