Skip to content

Agent client (shellwatch-agent)

shellwatch-agent is the Go thin client for the SSH agent proxy. It listens on a local socket and relays SSH agent protocol frames over WebSocket to ShellWatch.

Releases: github.com/rado0x54/ShellWatch/releases?q=agent. Source: agent-client/. Homebrew tap: rado0x54/homebrew-tap.

shellwatch-agent # daemon (default)
shellwatch-agent login # browser-based enrolment via OAuth DCR + PKCE
shellwatch-agent logout # remove stored credentials from the credstore
shellwatch-agent --print-env # print the SSH_AUTH_SOCK export line
FlagEnv varApplies toDescription
--serverSHELLWATCH_SERVERdaemon, login, logoutShellWatch server URL. Default: https://app.shellwatch.ai. Login keys credentials by URL — pass the same value to both login and the daemon.
--tokenSHELLWATCH_TOKENdaemonStatic agent-scoped bearer token. Skips the credstore. Short-lived, not refreshed.
--socketSHELLWATCH_AGENT_SOCKdaemon, --print-envListener path. Default: platform-appropriate (see below).
--insecuredaemon, loginDaemon: allow ws://. Login: allow http://. Local dev only.
--print-env(mode itself)Print export SSH_AUTH_SOCK=<path> (or PowerShell-friendly form on Windows) and exit.

--token and --socket are silently ignored by login / logout. Precedence: CLI flags > environment variables > credstore > defaults.

PlatformPath
Linux$XDG_RUNTIME_DIR/shellwatch-agent.sock if set, else ${TMPDIR:-/tmp}/shellwatch-agent-<uid>.sock
macOS${TMPDIR}/shellwatch-agent-<uid>.sock (launchd-set per-user /var/folders/.../T/, not /tmp)
Windows\\.\pipe\openssh-ssh-agent (matches stock OpenSSH-for-Windows)

The path is stable across restarts, so eval "$(shellwatch-agent --print-env)" from a shell profile works fine.

shellwatch-agent login runs a full OAuth 2.1 authorization_code + PKCE flow against the server’s OAuth authority (Ory Hydra), discovered via RFC 8414:

  1. Fetch ${server}/.well-known/oauth-authorization-server to discover the authorization, token, and registration endpoints.
  2. Register a public client at ${server}/api/hydra/register (mediated DCR — the loopback redirect URI is validated against server policy, which allows loopback by default).
  3. Generate a PKCE verifier and S256 challenge; bind a one-shot HTTP listener on 127.0.0.1:0.
  4. Open the user’s browser to the authorization endpoint (scope=agent offline_access).
  5. The user logs in with their passkey and consents.
  6. Hydra redirects to the loopback URL with a code; the agent verifies state and exchanges it at the token endpoint.
  7. The resulting refresh token is persisted via the credstore. The daemon mints short-lived (~30 min) agent-scoped access tokens from it at startup and renews them silently.

logout only removes the local copy; it does not revoke the grant server-side. To revoke remotely, sign out / revoke the device’s sessions in ShellWatch.

PlatformPrimaryFallback (mode 0600)
macOSKeychain (via security)~/Library/Application Support/shellwatch/credentials
Linuxlibsecret D-Bus (gnome-keyring / KWallet)${XDG_CONFIG_HOME:-~/.config}/shellwatch/credentials
WindowsDPAPI / Credential Manager%AppData%\shellwatch\credentials (parent-ACL protected)

Fallback is used when no keyring is reachable in the current session. Credentials are keyed by server URL — you can hold credentials for multiple instances simultaneously.

The fallback file is JSON, keyed by server URL.

Windows fallback file note: Go’s os.Chmod only flips the read-only bit on Windows, not NTFS ACLs. Protection comes from %AppData%’s parent-directory ACLs, which Windows configures user-only by default. Prefer the keyring on multi-user boxes.

On WebSocket connect to /agent-proxy, the client sends:

HeaderValue
AuthorizationBearer <access-token>
X-ShellWatch-Hostnameos.Hostname()
X-ShellWatch-OSruntime.GOOS (linux, darwin, windows, …)
X-ShellWatch-VersionVersion (set at link time via -ldflags "-X main.Version=...")

The server sanitises these and exposes them to the approver on /sign/:id in a separate “self-reported” block. They are a hint, not authoritative identity. See Concepts → Sign requests.

Requires Go 1.21+.

Terminal window
cd agent-client
make build # version derived from `git describe`
make build VERSION=0.1.0 # pin a specific version

Cross-compile:

Terminal window
GOOS=linux GOARCH=amd64 go build -o shellwatch-agent-linux-amd64 ./cmd/shellwatch-agent/
GOOS=linux GOARCH=arm64 go build -o shellwatch-agent-linux-arm64 ./cmd/shellwatch-agent/
GOOS=darwin GOARCH=amd64 go build -o shellwatch-agent-darwin-amd64 ./cmd/shellwatch-agent/
GOOS=darwin GOARCH=arm64 go build -o shellwatch-agent-darwin-arm64 ./cmd/shellwatch-agent/
GOOS=windows GOARCH=amd64 go build -o shellwatch-agent-windows-amd64.exe ./cmd/shellwatch-agent/
  • Each Unix-socket / named-pipe connection from a local SSH client gets its own outbound WebSocket. Concurrent SSH clients don’t share state.
  • WebSocket-level keepalive detects dead connections within ~1 minute; the dialer reconnects with exponential backoff on the next SSH operation. Laptop sleep, network changes, and brief server outages are handled transparently.
  • SSH_AGENTC_EXTENSION (type 27) frames from newer OpenSSH (session binding) are answered locally with SSH_AGENT_FAILURE rather than forwarded — this works around an ssh2 AgentProtocol parse bug for unknown extensions with payloads. Invisible in normal use.
  • The process exits on first unrecoverable error (e.g. socket already in use, server returns 401). Run it under a process supervisor for auto-restart — the Homebrew formula’s service do block, a launchd plist, or a systemd --user unit all work.
  • OpenSSH 10.3+ on the local client for passkey signing. Earlier versions reject the webauthn-... signature type. See SSH agent proxy → Requires OpenSSH 10.3+.
  • A browser session must be open in ShellWatch for passkey signs to be approvable in real time. Web Push helps when the tab is closed.
  • Windows service installation isn’t yet wired into the CLI — wrap with nssm / sc.exe if you want auto-start at logon. Roadmap item.
  • Custom --server for the brew service. The service do block uses the default app.shellwatch.ai; self-hosted instances need the manual launchd / systemd-user setup. Roadmap item.

See Guides → SSH agent proxy → Troubleshooting and the agent-client README for the canonical list (signature-type errors, missing browser session, sign timeouts, missing credentials after a server-URL mismatch).