Sign-ins and the relay
Tools on the machine need to be signed in: Claude Code, Codex, gh, gcloud, aws and the rest. Some sign-ins can be copied from this computer. Others must happen on the machine, and those are the interesting ones, because a machine in the cloud has no browser and the sign-in page wants to redirect back to localhost.
wsp makes the page open in your own browser and makes the callback land back inside the machine. Only the URL and a port number ever travel. No code and no token is read by anything in wsp.
Four kinds of sign-in
| kind | what happens | examples |
|---|---|---|
| oauth | a browser page with a callback to a local port | Claude Code, Codex, gcloud, aws, wrangler, vercel, netlify, fly, supabase, railway, doppler, gemini, cloudflared, pi |
| device | a code typed on a page the machine names | gh, hermes |
| key | a key pasted or exported | opencode |
| none | nothing to run on a headless machine | op, kubectl |
Each has a status command wsp asks on the machine to prove the login, gh auth status, wrangler whoami, claude auth status and so on, and never anything the login itself printed. Some have a no-browser fallback for a retry: aws configure sso --use-device-code, supabase login --no-browser, codex login --device-auth.
Copy, machine, key or skip
The Sign-ins screen, and wsp recipe --signin, answer each login one of four ways.
- copy brings the login from this computer. A macOS Keychain item is read with your consent, through the system's own dialog, and placed as the tool's file on the machine: the GitHub CLI's
hosts.yml, for example. A credential file is copied. A Claude CodeapiKeyHelperis run here and its key lands in a file on the machine. Nothing is cached or logged, and a refused dialog flips the row to sign in on the machine at no cost. - machine means you sign in on the builder before the seal, through the relay below. This is the only answer for a browser or device flow.
- key sets an API key in the machine's environment. The value is typed hidden, rides the environment and never a command line, and lands in
/etc/profile.d/wsp-secrets.shat mode 600. One variable is asked once however many rows want it. - skip leaves the tool signed out.
The Claude Code OAuth credential is never copied, whatever you tick. The vendor's terms forbid a host to collect or intermediate it, and a copy would transit the wsp process. Sign in on the machine, or use an API key.
The relay
Here is what happens when a tool on the machine asks for a browser.
-
The shim. The image sets
BROWSER=/usr/local/bin/wsp-openand puts the same script at/usr/local/bin/xdg-open, since only some tools readBROWSERand the rest execxdg-openby name. The shim is a few lines of POSIX shell: it posts the URL to a root-only unix socket the helper listens on and exits at once, because gh, gcloud and aws block until it returns.DISPLAYis unset for every terminal, because the image shipsDISPLAY=:0with no X server behind it, which gcloud, gemini and railway read as a browser existing. -
The port. The helper reads the callback port out of the URL's
redirect_uri, accepted only forlocalhost,127.0.0.1or[::1]with an explicit port from 1024 up. For a tool whose URL names no port, aws for one, it pairs the request with the next loopback listener that appears within five seconds. A listener already there when the request came is not the flow's, so a dev server on 3000 never gets your laptop's 3000 bound. -
The forward. The host on your computer listens on that same port, on both
127.0.0.1and::1, and tunnels every connection back to the machine's loopback, dialing127.0.0.1then::1because a Node tool listening onlocalhostbinds[::1]only. -
The page. The URL is shown to you: a banner in the app naming the site, a hyperlink in the terminal, or a JSON line for an agent. You click. The page opens in your browser. When you finish, the provider redirects to
localhost:<port>on your computer, the tunnel carries it to the tool waiting on the machine, and the tool completes.
Some tools print the URL without asking for a browser. The helper scans terminal output for URLs too, cleaning hyperlink escapes, colour codes and readline's soft wraps first, so the same forward works for them.
A callback forward lives three minutes with no listener spotted and at most fifteen once one is. Ports under 1024 are refused, since binding them here would need root. A callback that arrives while the link to the machine is redialing is held for up to a minute rather than failed. The host never logs the URL.
In the terminal
When you run wsp init in a terminal, each login answered machine runs in a terminal on the builder shown in yours, the way ssh would. The URL the tool prints is re-shown as a hyperlink with one line: press o to open it on this computer. The offer lasts sixty seconds and ends the moment you type anything printable, though Enter and arrow keys do not count, since gh asks for an Enter before its own open. If the machine asked for a page that returns through a forwarded port, o opens that page instead of the printed link.
After the pass, a summary offers per failed row: r to retry, f to retry with the tool's no-browser fallback when it has one, s to skip.
For an agent
Under --non-interactive or --json, nothing is drawn. Each sign-in prints the page to open, the code when the flow shows one, and the command that opens it, then waits:
{"event":"sign-in","tool":"gh","label":"GitHub CLI login","browserUrl":"https://github.com/login/device","code":"8F4A-C21B","nextCommand":"open 'https://github.com/login/device'","waitSeconds":960}
The run asks the tool's own status command on the machine every five seconds for the first minute, then every twenty, and moves on when it says signed in or when the wait passes. A page arriving with a callback port wins over anything the tool printed. The outcome is one more line, sign-in-result, with state signed-in or not-signed-in and a note. The run seals either way.
Each login gets what the tool itself waits plus a minute, else fifteen minutes.
What never travels
- Private keys under
~/.ssh, GPG keys,.envfiles and.netrc, whatever is ticked. - Any value exported in your shell files whose name contains KEY, TOKEN, SECRET or PASSWORD. The lines are cut from the copy and the names are listed so you can set them on the machine.
- The Claude Code OAuth credential.
- Codes and tokens from sign-in pages. The relay reads a port number and nothing else.