Skip to content
andreisminskPublic

About

surf-ssh - productivity tool for work via ssh, browser-based

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

16 Commits

Folders and files

Repository files navigation

Surf SSH

A developer productivity tool that provides a browser-based file system explorer and terminal for remote machines accessible via SSH. Primary feature - enhances traditional SSH terminal experience with possibility to browse and preview remote files in single workspace.
To make SSH session experience complete - it also adds local console to the workspace. Thus users have in hands all that they usually need while working with SSH quickly accesible - remote terminal, file browser and preview, plus local console. It leverages existing SSH configurations (including complex ProxyJump setups) without requiring any agent software on the remote machine.

Features

  • File browsing — Lazy-loaded directory tree with depth and entry caps
  • File viewing — Text files with syntax highlighting, Markdown with relative image resolution, HTML in sandboxed iframe
  • Remote terminal — Interactive PTY terminal via WebSocket (xterm.js)
  • Local console — Local PTY terminal (bash/zsh/sh on Unix, PowerShell/CMD/WSL on Windows) via WebSocket
  • Liveness tracking — Server-initiated ping/pong heartbeat; a background reaper closes SSH connections for dead clients
  • Auto-exit — Daemon shuts down automatically when the browser disconnects (opt out with --no-auto-exit)
  • Daemon mode — Run without a specific host for LaunchDaemon/systemd; clients pick a host from the UI
  • Security — Local-only binding, session token → HttpOnly cookie, path traversal prevention, SSRF mitigation via CSP
  • 2FA (daemon mode) — Optional TOTP challenge for direct browser access; CLI path (surf-ssh url) unchanged
  • Zero config — Reuses ~/.ssh/config host aliases and keys
  • Ad-hoc hosts — Connect to hosts without ~/.ssh/config entries via password auth (browser prompt or CLI user@host); passwords are held in daemon memory only, never written to disk
  • Free-form commands — Paste an ssh/scp command string: surf-ssh open --cmd "ssh root@host -i ~/.ssh/key -p 2222" — parsed into a config entry under a memorable name; ephemeral by default, --save <name> makes it permanent
  • Cross-platform — Works on macOS and Windows

Quick Start

# Install
pip install -e .

# Build the UI
cd ui && npm install && npm run build && cd ..

# Open a remote host (auto-exit on, opens browser)
surf-ssh open my-server

# From an ssh command string (ephemeral — removed on exit)
surf-ssh open --cmd "ssh root@10.0.0.5 -i ~/.ssh/key -p 2222"

# From an ssh command string (persistent — saved in ~/.ssh/config)
surf-ssh open --cmd "ssh root@10.0.0.5 -i ~/.ssh/key -p 2222" --save prod-jump

# Without opening a browser
surf-ssh open my-server --no-browser

# Keep daemon running after browser closes
surf-ssh open my-server --no-auto-exit

# Daemon mode — no host, clients pick from UI (for LaunchDaemon/systemd)
# Prints an authenticated URL — open it to establish a session
surf-ssh daemon

# Enable 2FA for direct browser access (one-time)
surf-ssh setup-2fa

# Daemon mode with browser
surf-ssh daemon --browser

# Daemon on a custom port
surf-ssh daemon -p 9443

# List available hosts from ~/.ssh/config
surf-ssh hosts

The daemon starts on https://localhost:8443. surf-ssh open opens a browser automatically; surf-ssh daemon does not. A self-signed CA certificate is generated on first run and stored in ~/.surf-ssh/.

Architecture

Browser (React SPA)  ←HTTPS→  Local Python Daemon  ←SSH/SFTP→  Remote Machine
                              (FastAPI + AsyncSSH)
  • Backend: Python 3.10+, FastAPI, Uvicorn, AsyncSSH
  • Frontend: React, Vite, xterm.js, react-markdown
  • CLI: Typer + Rich

See SURF-SSH-ARCH.md for the full architecture document and SURF-SSH-CODER.md for coding guidelines.

Project Structure

surf-ssh/
├── src/
│   ├── main.py                 # CLI entry point
│   ├── daemon/                 # FastAPI server, TLS, static serving
│   ├── ssh/                     # Config parser, connection pool, SFTP client
│   ├── api/                     # REST + WebSocket endpoints
│   └── security/               # Path validation, session auth
├── ui/                         # React SPA
│   └── src/
│       ├── components/         # FileTree, FileViewer, TerminalView, etc.
│       ├── hooks/              # useConnection, useFileSystem, useTerminal
│       └── api/                # API client
├── tests/
│   ├── unit/                   # Path validator, session auth, config parser
│   └── integration/            # API endpoint tests
└── pyproject.toml

API Endpoints

Method Path Description
GET /api/v1/hosts List connected/available hosts
GET /api/v1/hosts/{host}/status Get connection status and platform
GET /api/v1/hosts/{host}/home Resolve the host's home directory
GET /api/v1/hosts/{host}/tree Get directory tree (lazy, depth-limited)
GET /api/v1/hosts/{host}/file Stream file content
GET /api/v1/hosts/{host}/stat Get file metadata
GET /api/v1/hosts/{host}/download Download binary file
WS /api/v1/hosts/{host}/terminal Interactive remote terminal session
WS /api/v1/local/terminal Interactive local terminal session
GET /api/v1/local/shells List available local shells
WS /api/v1/liveness Server-initiated ping/pong heartbeat
GET /api/v1/auth/exchange Exchange URL token for cookie
GET /api/v1/health Health check

Security

  • Local only: Daemon binds to 127.0.0.1 exclusively
  • Session auth: Token passed via URL on first load, exchanged for HttpOnly + Secure + SameSite=Strict cookie; sessions expire after 24h and are stored as sha256-hashed filenames with 0600 permissions
  • WebSocket auth: All WS endpoints (remote terminal, local console, liveness) validate the session cookie before accepting the handshake
  • Path traversal: Rejects .. components, resolves symlinks via sftp.realpath() with re-validation, and contains static asset serving inside ui/dist
  • SSRF: CSP img-src 'self' blocks external image loads from rendered content
  • HTML sandbox: sandbox="" iframe prevents script execution and API access
  • Liveness reaper: A background task periodically closes SSH connections whose clients have stopped responding to heartbeats, preventing connection leaks — including frozen terminal sessions
  • TLS keys: Private keys are written with 0600 permissions
  • Docs off by default: OpenAPI/Swagger UI disabled unless SURF_SSH_DEV=1
  • Update check: At most one outbound GitHub request per 24h; disable with --no-update-check
  • 2FA (daemon mode): surf-ssh setup-2fa enables a TOTP challenge for direct browser access (https://localhost:8443 → 6-digit code → session). Rate-limited (5 attempts/min, exponential lockout). Recovery: backup codes or surf-ssh disable-2fa. See dev-docs/2FA-ARCH.md
  • Ad-hoc password auth: Hosts without config entries connect via password (browser prompt or surf-ssh open user@host). Passwords live in daemon memory only — never on disk, never in logs. Host keys use TOFU with explicit fingerprint confirmation, stored in ~/.surf-ssh/known_hosts (not your ~/.ssh). Auth attempts are rate-limited per host (5/min). See dev-docs/PASSWORD-AUTH-ARCH.md

Daemon Mode

surf-ssh daemon starts the server without a specific host. The browser shows a host picker listing all aliases from ~/.ssh/config. This is designed for persistent deployment via LaunchDaemon (macOS) or systemd (Linux).

Port behavior: The daemon uses a strict port — if a healthy daemon is already running, it exits cleanly (exit 0). If the port is occupied by another process, it exits with an error (exit 1). This prevents duplicate daemons and works correctly with service managers' restart policies.

Auto-exit: Always off in daemon mode. Use surf-ssh open if you want the daemon to shut down when the browser disconnects.

Killing a Stale Daemon

If port 8443 is occupied by a stale process, find and kill it:

macOS / Linux:

lsof -ti :8443 | xargs kill -9
# or
pkill -9 -f surf-ssh

Windows (CMD):

for /f "tokens=5" %a in ('netstat -ano ^| findstr :8443 ^| findstr LISTENING') do taskkill /PID %a /F

Windows (PowerShell):

Get-Process -Id (Get-NetTCPConnection -LocalPort 8443).OwningProcess | Stop-Process -Force

Testing

# Python tests
pytest tests/unit -v
pytest tests/integration -v

# React tests
cd ui && npm test

Configuration

Configuration and certificates are stored in ~/.surf-ssh/:

~/.surf-ssh/
├── ca.pem          # Self-signed CA certificate
├── ca-key.pem      # CA private key
├── localhost.pem   # Localhost certificate
├── localhost-key.pem
├── config.json     # User preferences
└── sessions/       # Active session tokens

Feedback

We welcome feedback, bug reports, and suggestions:

License

Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)

Copyright © 2026 Andrei Suvorov

You are free to:

  • Share — copy and redistribute the material in any medium or format
  • Adapt — remix, transform, and build upon the material

Under the following terms:

  • Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made.
  • NonCommercial — You may not use the material for commercial purposes.
  • ShareAlike — If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original.
  • No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits.

Full license text: https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode

Disclaimer of Warranties

THIS WORK IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE WORK OR THE USE OR OTHER DEALINGS IN THE WORK.

About

surf-ssh - productivity tool for work via ssh, browser-based

Resources

Stars

1 star

Watchers

0 watching

Forks

Contributors

Languages