# clsh puts a real PTY on the internet, with tmux persistence and demo mode

> my-claude-utils/clsh streams real terminal sessions from a developer's machine to a phone browser over a tunnel, with a custom mobile keyboard and persistence through tmux. Its security section is unusually specific about what it does, and just as specific about the one setting that decides whether your shell is on the public internet.

**my-claude-utils/clsh** — Access your terminal and your AI agent from any device — phone, tablet, desktop.

- Repository: https://github.com/my-claude-utils/clsh
- Website: https://clsh.dev
- Stars: 525 · Forks: 54
- Language: TypeScript
- License: MIT
- Published: 2026-09-10 · Updated: 2026-09-10 · Language: en
- Canonical page: https://hysenlabs.com/projects/my-claude-utils-clsh

## The default tunnel needs no account, which is the whole risk

The quick start is one command, and the sentence after it is the one to read twice:

```bash
npx clsh-dev
```

Start it, and a QR code prints to the console; scan it on a phone and you have a shell.

The path that gets you there by default is a free SSH tunnel service that needs no signup and no token. The README states that plainly, twice, and it is the correct engineering trade for a first run: nothing to configure, nothing to pay, no account to leave behind.

It is also the reason to think before you leave it running. A tunnel of that kind publishes a URL that reaches a real pseudo-terminal on your machine, running your own login shell, with whatever is in your home directory. The tool's own security section says this more directly than the quick start does, noting that the project gives remote terminal access to your machine, so any vulnerability there could mean full machine compromise.

So the default is fine for five minutes on a trusted network and a poor default for a laptop you take to a conference. The optional permanent-URL path with a static domain is better precisely because it forces you to obtain a tunnel token and name your own subdomain, which makes the exposure a decision rather than a side effect.

## Authentication is a single-use token, then a hash, then a biometric

Once the tunnel is up, the security design is laid out in a layer-by-layer table, and it is more specific than most projects of this kind manage to be.

Authentication starts with one-time bootstrap tokens that are single-use and expire in five minutes, followed by scrypt password hashing with a stated cost parameter, key length and random salt, and then optional WebAuthn or Face ID. The token itself is passed in the URL fragment rather than as a query parameter, with the stated reason that a fragment is never sent to servers, and the WebSocket authenticates on its first message rather than in the connection string, which is the right choice because connection strings end up in proxy logs.

Transport is described as enforced over the tunnels, with cross-origin checking restricted to known origins and the usual three security headers. Authentication endpoints are rate limited to between five and ten requests per fifteen minutes, the WebSocket upgrade validates origin, caps payloads at 64KB, and bounds-checks resize dimensions, which is a detail worth noting because an unbounded resize message is a cheap way to make a browser allocate.

Password comparison is constant-time, and the biometric credentials for the installed app are held server-side so they can be restored in another context. Disclosure is handled with a published policy, a security contact address, a stated 48-hour response commitment and a promise to credit reporters.

## Eight sessions survive a restart only if tmux is installed

Persistence is delegated, and the condition is worth knowing before you rely on it.

The agent spawns real pseudo-terminals through a native module, up to eight at once, each one running an actual login shell with colours and full interactivity rendered in the browser. When tmux is present on the machine, sessions are wrapped inside it using control mode, which is what makes them outlive the server: stop the process, start it again, and the sessions are still there with their scrollback. When tmux is absent there is a described graceful fallback, which in practice means the sessions do not survive.

The feature list states the same thing from the user's side, listing session persistence as automatic detection with a fallback if tmux is not installed, and it is the only optional dependency in the whole stack that changes behaviour rather than failing.

The concurrency limit of eight is also a shape. It is enough to keep a shell, a coding agent, and an editor open at once, which is what the README's diagram shows, and not enough to be a workstation multiplexer. The diagram is honest about the layout: one agent process on your machine holding numbered sessions, with the last line marked as the cap.

## The mobile keyboard is the part with the most numbers in it

Almost everything else in this project is plumbing. The keyboard is where the craft is, and it is specified tightly enough to be reproducible.

There are two layouts: a phone-shaped one with six rows and large keys, and a compact five-row one that mirrors a laptop keyboard. Sticky modifiers let you tap shift, control, option or command once and have it apply to the next key, which is the difference between a terminal being usable with a thumb and not. Key repeat has a stated delay and interval, in milliseconds, so a held key behaves the way a hardware keyboard does rather than the way a touch event does. A context strip puts the keys you actually need for this tool at the top, and the list is revealing: escape, the first five function keys, and three commands that read as commit, diff and plan, plus interrupt.

That strip is the tell. A general-purpose terminal client would put control characters and arrows there. This one assumes you are running a coding agent, and it puts a version-control vocabulary in the thumb zone instead.

Six skins ship, from the phone style through a laptop finish, an RGB gaming theme, a custom painted option, an amber retro look and a white one. On a device with a physical keyboard the whole layer is irrelevant, which is worth stating because that is the situation most readers are in.

## A static URL needs a token you can revoke; the default one you cannot

The permanent-URL path is documented properly, and reading it explains the shape of the risk.

For a stable address you install the tunnel client, add an authentication token to its configuration, create a free static domain on the tunnel provider's dashboard, and then put two values in the environment file: the token and the subdomain. The result is a URL that survives restarts, which is what makes it usable as a home-screen install rather than something you re-scan every time.

The tradeoff is now explicit. A named subdomain is a permanent address for a shell on your machine, protected by a credential you chose and can revoke, rather than a rotating address you cannot revoke in advance. Both are reasonable; only one of them is a decision.

The environment sample is where the configuration surface actually lives, and it is short. A tunnel token, an optional mail delivery key for magic-link sign-in, the server port, the port the tunnel forwards to with a note about how the development proxy splits the two, a signing secret for tokens that is generated if left empty, a flag to suppress opening a browser, and a default shell for new sessions with the two valid values named. The signing secret being auto-generated is worth noticing, since a generated secret changes on restart and would invalidate sessions.

## The package is marked private and pinned at zero point zero point one

Two fields in the package manifest sit oddly against the release history.

The manifest names the project, marks it private, and gives it version 0.0.1. The release history, meanwhile, runs from 0.1.0 through 0.1.8 to 0.1.9, with the newest tagged in mid-March and named for native keyboard support. So the manifest version has never tracked the released versions; the tags are where the real numbers live, and anyone reading the manifest to find out what is installed will be reading 0.0.1.

Private is the other one. Nothing here is published to a registry, so installation is by running a package runner against the project name rather than by adding a dependency, which is consistent with a tool that starts a server on your machine. It also means there is no package to audit after the fact.

The rest of the manifest is a modern monorepo shape: a workspace glob over packages, a task runner driving build, lint, typecheck and test, and a pinned package manager. Two lifecycle scripts deserve attention. One runs after install to patch the native terminal module, which is the kind of script that deserves reading before you install anything that touches a shell. The other installs a hook from the scripts directory into your repository, and it is written to succeed whether or not that succeeds, which means a fresh clone silently has no hook.

## Demo mode animates a terminal when nothing is reachable

One feature deserves its own note because it is the kind of thing that causes confusion later.

The mobile client includes a demo mode, described as scripted terminal animations shown when no backend is reachable. That is a sensible thing to have: the live demo link on the project page needs something to render, and a person who scans the QR code with the wrong app should see something that explains itself rather than an error.

The risk is entirely about knowing which one you are looking at. A terminal view that looks real, running plausible output, when the agent is not actually running, is exactly the state in which a person believes a command has been executed when it has not. The mitigation is not technical: the mode has to be visible at a glance, and since the sessions this tool creates include coding agents that act on your behalf, mistaking one for the other is worth guarding against.

Everything else in the mobile section is ordinary good practice: the app installs to a home screen and runs fullscreen without browser chrome, the custom keyboard replaces the system keyboard rather than sitting above it, and safe-area insets are handled so the layout works on notched devices.

## Conclusion

Use it if you want to drive a coding agent or a shell from a phone and you control both ends, and read the tunnel section before the first run rather than after. The default path needs no account and no token, which is exactly why it also means a shell on your machine is one URL away from anyone who finds that URL. Check four things: that the token in your tunnel configuration is the only credential between your shell and the internet, that your session survives the tunnel dropping, that you have decided whether persistence through tmux is a feature or a hazard on a machine you carry, and that the demo mode cannot be mistaken for a live session. The security documentation is good enough that you can audit this yourself, which is a better position than either trusting it or ignoring it.

## FAQ

### What is my-claude-utils/clsh?

A tool that streams real terminal sessions from your own machine to a browser on a phone, tablet or desktop over a tunnel. It spawns actual pseudo-terminals running your login shell, renders them with colour and interactivity, and adds a custom mobile keyboard with sticky modifiers and six skins.

### Does clsh need an account or a signup?

Not on the default path. It starts with one command and connects through a free SSH tunnel service, with no signup and no token, and prints a QR code containing the address. A permanent URL needs more work: installing the tunnel client, adding an authentication token, and reserving a static subdomain.

### How does clsh protect the terminal it exposes?

With single-use bootstrap tokens that expire in five minutes, scrypt password hashing with a random salt, optional biometric authentication, a bootstrap token passed in the URL fragment so it is never sent to servers, WebSocket authentication on the first message rather than in the query string, origin validation on upgrade, a 64KB payload cap, rate limiting on authentication endpoints and constant-time password comparison.

### Do clsh sessions survive a restart?

Only when tmux is installed on your machine. When it is present, sessions are wrapped inside it in control mode, so they outlive the server with their scrollback intact; when it is absent there is a graceful fallback, which means the sessions do not survive. The agent supports up to eight concurrent sessions.

### What does clsh need before it will run?

Node.js 20 or newer, and macOS or Linux. Running it starts a backend agent and the web frontend, spawns the terminal sessions through a native pseudo-terminal module, and prints a QR code. Two install-time scripts also run: one patches the native module, and one installs a repository hook that is allowed to fail silently.

## Sources

- [License: MIT](https://github.com/my-claude-utils/clsh/blob/main/LICENSE)
- [my-claude-utils/clsh on GitHub](https://github.com/my-claude-utils/clsh)
- [Project website](https://clsh.dev)
- [README](https://github.com/my-claude-utils/clsh/blob/main/README.md)
- [Releases](https://github.com/my-claude-utils/clsh/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/my-claude-utils-clsh
