Technology Apr 23, 2026 · 7 min read

Your Phone as a Terminal: One Command, One QR Code, No SSH Client

It happens a few times a year. I'm away from my laptop, a deploy is stuck, and I want to tail a log or restart a service. The options all have friction. SSH apps on mobile work, but setting up keys on a phone is fiddly. Termius and Blink are polished but proprietary, and they still need a host to re...

DE
DEV Community
by Wes
Your Phone as a Terminal: One Command, One QR Code, No SSH Client

It happens a few times a year. I'm away from my laptop, a deploy is stuck, and I want to tail a log or restart a service. The options all have friction. SSH apps on mobile work, but setting up keys on a phone is fiddly. Termius and Blink are polished but proprietary, and they still need a host to reach. Tunneling a local port to the public internet means picking between a paid tunnel service or standing up a Caddy reverse proxy with auth. Most of the time I just wait until I'm back at a real keyboard.

There's a smaller problem too. Sometimes I want to pair on my own machine from across the room, where an iPad is easier to hold than a laptop. Or I'm running a long build on my desktop and want to glance at it from the couch. None of these cases need full SSH. They need "show me this terminal, somewhere else, right now."

TermBeam is the first project I've seen that aims at that exact shape and doesn't ask for anything up front. One command, one QR code, done.

What Is TermBeam?

TermBeam is a Node CLI by Dor Lugasi-Gal that spawns a pseudo-terminal, serves a mobile-optimized xterm.js UI over Express and WebSocket, and prints a QR code you scan with a phone. The phone loads the terminal in a browser, the browser bridges to the PTY, and you're at a shell. No SSH, no port forwarding, no static IP.

32 stars at time of writing. Created in late February 2026, so about two months old. The npm package is termbeam, the live docs are at termbeam.pages.dev, and the quickest path in is npx termbeam.

The Snapshot

Project TermBeam
Stars 32 at time of writing
Maintainer Solo (Dor Lugasi-Gal). Shipped v1.21.4 the same day I opened the PR
Code health Node.js ≥ 20, built on node:test, CI matrix across 20/22/24, OpenSSF Scorecard, Prettier, Husky
Docs CONTRIBUTING.md, PR template, MkDocs site, README has architecture diagram and threat model link
Contributor UX Feature requests from outside contributors have landed in releases. PR template and commit convention are spelled out
Worth using Yes, for the use case. Mobile terminal access without the usual setup dance

Under the Hood

The architecture is a thin stack. bin/termbeam.js is a shebang'd entry point. src/server/index.js wires up Express routes, a WebSocket server, a session manager, and an auth module. The UI is an xterm.js instance in the browser. WebSocket frames carry PTY input and output in both directions. That's the whole data flow.

The pieces worth looking at are the ones that turn a local PTY into something safe to expose. src/server/auth.js generates a password if you don't give it one, signs cookies, rate-limits login attempts per IP, and manages single-use share tokens for QR auto-login with a five-minute expiry. src/server/sessions.js owns the PTY lifecycle (node-pty under the hood) and keeps sessions alive when all clients disconnect so you can re-attach from another device. src/server/websocket.js enforces auth on every upgrade, validates messages, and rate-limits bad auth attempts before closing the socket.

The tunnel story uses Microsoft's DevTunnel. Ephemeral by default, optionally persistable, and you can bypass the Microsoft login for fully public access if you accept that risk. If you don't want a tunnel at all, --no-tunnel keeps it LAN-only and binds to 127.0.0.1.

What surprised me in a good way: the mobile UI is not a port of a desktop terminal. There's a dedicated touch key bar for the keys a virtual keyboard can't easily produce (arrows, Tab, Ctrl, Esc, copy, paste). Split view auto-rotates between horizontal and vertical based on the device aspect. Share tokens are single-use, which is the right call for a QR pattern. iOS PWA safe-area insets are handled. Someone actually held a phone while building this.

What's rough: the test suite has two pre-existing failures where the SPA's index.html returns 404 because the frontend isn't built when tests run. I hit them on clean main as well, so they're not mine. The src/frontend/ build is decoupled from npm test, which is a reasonable speed decision but traps anyone who runs tests first. The npm test script itself is a one-liner crammed into package.json as an inline node -e that dynamically discovers test files. It works, but it's hard to read.

The Contribution

The reason I dug in was issue #177, filed by an external auditor a couple of weeks before I got there. The report was concrete: two === comparisons on passwords, one in src/server/websocket.js, one in src/server/routes.js, both exposing a byte-by-byte timing side channel. The recommended fix was crypto.timingSafeEqual().

When I ran a broader grep, a third site turned up that the reporter missed. src/server/auth.js:376 compares the HTTP Authorization: Bearer <pw> header with === against the expected Bearer ${password} string. Same bug class, same fix needed. I pulled all three into the same PR.

The fix is a small helper. It SHA-256 hashes both sides, then runs crypto.timingSafeEqual on the digests. Hashing first means two things: the two buffers are always the same length (timingSafeEqual throws on length mismatch), and no information about the password's length leaks through the compare. Non-string inputs return false immediately without touching the hash path.

function safeCompare(a, b) {
  if (typeof a !== 'string' || typeof b !== 'string') return false;
  const ah = crypto.createHash('sha256').update(a).digest();
  const bh = crypto.createHash('sha256').update(b).digest();
  return crypto.timingSafeEqual(ah, bh);
}

The helper is exposed on the createAuth return object so call sites already holding an auth reference don't need to import anything new. It's also exported at module level so the WebSocket test mock can reuse the real implementation instead of reimplementing constant-time compare in a test helper. Eight new test cases cover identical strings, different strings, length mismatches, non-string inputs (null, undefined, number, object), unicode, and the empty-string edge case.

Getting into the codebase was clean. Each module in src/server/ has a clear responsibility. auth.js is self-contained. Tests use node:test with describe/it and node:assert — no mocking framework to learn. The pre-commit hook runs Prettier and node --check automatically, so commits just work if you follow the existing style.

PR #204 is open. The maintainer was shipping v1.21.4 the same hour I opened it, so I'm betting on a fast turnaround based on how other external PRs have landed.

The Verdict

TermBeam is for the person who wants terminal access from a phone and doesn't want to spend a weekend setting it up. The mobile-first UI is the thing. Every decision, from the touch key bar to iPhone PWA insets to the single-use QR tokens, suggests someone who uses it on their own phone and fixes what annoys them. That's the right shape for this kind of project.

The trajectory is steep. Two months old, version 1.21, a docs site, a CI matrix, OpenSSF Scorecard monitoring, a PR template, a CONTRIBUTING.md. Most solo projects this age don't have half of that. External feature requests are landing in releases. A Copilot SDK integration shipped recently. It feels like the author is treating this like a real product, which is rare.

What would push it further: a built frontend in the npm tarball so npm test passes out of the box without a separate build step, a second maintainer to cover the bus factor, and a fleshed-out security page explaining the threat model of exposing a PTY over HTTPS. The fundamentals are there. The polish is already unusual for the age.

Go Look At This

TermBeam on GitHub. Install with npx termbeam and scan the QR. If you're on your own LAN, add --no-tunnel to keep it local.

My PR adding constant-time password compare is here. If you want to contribute, the README's issue list has a few good-first-issue shapes, and the pre-commit hook makes style a non-issue.

This is Review Bomb, a series where I find under-the-radar projects on GitHub, read the code, contribute something, and write it up. If you know a project that deserves more eyeballs, drop it in the comments.

This post was originally published at wshoffner.dev/blog. If you liked it, the Review Bomb series lives there too.

DE
Source

This article was originally published by DEV Community and written by Wes.

Read original article on DEV Community
Back to Discover

Reading List