feat(console): browser terminal on this box, gated by moshcode login - #93
Merged
Conversation
Adds `moshcode console`: a real terminal in the browser — arrow keys, history,
full-screen TUIs — because the far end is an actual pty rather than a log view.
moshcode does not implement the terminal. ttyd already is one, so this is an
authenticating reverse proxy in front of it and nothing more. That replaces the
alternative of hand-rolling xterm.js plus raw keystroke relay into the session
mirror, which would have been a far larger surface for a worse terminal.
The gateway runs on the box you want a shell on, NOT inside apps/pwa. The hosted
app is on Railway and the shell is on the dev box; a proxy in apps/pwa would have
to reach back to a port on that box across the internet, which means exposing it.
Instead the box serves its own gateway and asks app.moshcode.sh only whether a
token is valid.
Auth:
- ?token=<moshcode token> is verified once against /api/me, then swapped for a
short-lived HMAC cookie and stripped from the URL by the redirect, so it does
not linger in history or ride along on every request.
- The websocket is authenticated too. It carries the terminal, so an
unauthenticated upgrade would hand out a shell no matter how well the page
itself was guarded.
- Verification fails closed: a network error denies rather than admits.
- The signing secret is per-process, so a restart invalidates every cookie.
Defaults are the safe ones: --bind is 127.0.0.1, ttyd is expected on loopback,
and binding 0.0.0.0 prints a warning pointing at tailscale or a TLS proxy.
Verified end to end against a stub upstream: unauthenticated 401, token exchange
302 with an HttpOnly cookie and a token-free location, then a cookie-bearing
request proxied through to the backend.
13 new tests in test/console.test.mjs, mostly adversarial (forged signature,
swapped user, extended expiry, malformed input, unauthenticated upgrade). Full
suite: 326 tests, 248 pass, 0 fail.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
vu1nz Security Review0 finding(s) in PR #? No security issues found. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
moshcode console— a real terminal in the browser. Arrow keys, history, full-screenTUIs, all of it, because the far end is an actual pty and not a log view.
moshcode does not implement the terminal. ttyd
already is one; this is an authenticating reverse proxy in front of it. That's the
whole idea — it replaces hand-rolling xterm.js plus raw keystroke relay into the
session mirror, which would have been much more code for a worse terminal.
Where it runs, and why that matters
The gateway runs on the box you want a shell on, not inside
apps/pwa.The hosted app is on Railway; the shell is on dev. A proxy living in
apps/pwawouldhave to reach back across the internet to a port on the dev box — i.e. expose it. So
instead the box serves its own gateway and asks
app.moshcode.shexactly onequestion: is this token yours?
Auth
Everything behind this is a shell, so:
?token=is verified once against/api/me, swapped for a short-lived HMAC cookie,and stripped from the URL by the redirect — it doesn't sit in history or ride
along on every request.
upgrade would hand out a shell no matter how well the page was guarded.
--binddefaults to127.0.0.1; binding0.0.0.0prints a warning pointing attailscale or a TLS proxy.
Verified end to end
Against a stub upstream:
Tests
13 new in
test/console.test.mjs, deliberately adversarial: forged signature, cookiewith a swapped user, cookie with an extended expiry, malformed input, and an
unauthenticated websocket upgrade. One of them caught a real bug pre-merge — the
cookie split on
.and every email contains dots, so the user field is base64url now.Full suite: 326 tests, 248 pass, 0 fail, 78 pre-existing skips.
Relationship to #92
#92 (pty capture) is still the thing that records output into the session log for
scrollback and async viewing. This is the live, interactive path. They're
complementary, but if you only want one, this is the one that answers "arrow keys".
🤖 Generated with Claude Code