Browser-based SSH and SFTP — no extensions, no plugins, no VPN.
SSHamrock repackages Google's Secure Shell Chrome extension as a standalone web page. It ships as a single server that serves the web client and proxies the SSH connections on the same port.
git clone https://github.com/tehrhart/sshamrock.git
cd sshamrock
sudo bash quickstart.shThe installer prompts for your public hostname and identity provider, then handles everything: Python venv, relay server, static files, systemd service. Point any reverse proxy at port 8080 and you're live.
For DigitalOcean, AWS, GCP, or any cloud provider that supports startup scripts:
- Open
sshamrock-bootstrap.sh - Edit the settings at the top (hostname, Cloudflare team/audience, tunnel token)
- Paste the entire script as your VM's User Data / startup script
The VM boots, installs all dependencies (including cloudflared), configures the relay, and comes up ready. Logs at /var/log/sshamrock-init.log.
This works with any cloud VM — DigitalOcean Droplets, AWS EC2, GCP Compute Engine, Azure VMs, etc.
Browser (any Chromium) SSHamrock Server SSH Target
┌─────────────────────┐ ┌──────────────────────┐ ┌──────────────┐
│ hterm terminal │ │ FastAPI (Python) │ │ │
│ OpenSSH (WebAssembly)│─wss│ static web client │ │ sshd │
│ Relay client JS │────│ /v4/connect → TCP │────│ │
└─────────────────────┘ └──────────────────────┘ └──────────────┘
▲ ▲
└──── Authenticating reverse proxy ────┘
- User visits the page and authenticates via your identity provider
- The browser loads a full SSH client — hterm + OpenSSH compiled to WebAssembly
- User picks a host and the browser opens a WebSocket to the same server
- The relay bridges the WebSocket to a TCP connection to the target
- All SSH encryption happens inside the browser — the relay sees only opaque ciphertext
SSHamrock itself doesn't authenticate users — it relies on a reverse proxy in front of it that handles SSO and passes identity headers. Any proxy that terminates auth and forwards to port 8080 will work:
| Proxy | How it works |
|---|---|
| Cloudflare Access | Cloudflare Tunnel → port 8080. JWT in Cf-Access-Jwt-Assertion header. First-class support via RELAY_IDENTITY_PROVIDER=cloudflare-access. |
| Google IAP | GCE/GKE backend → port 8080. JWT in x-goog-iap-jwt-assertion header. First-class support via RELAY_IDENTITY_PROVIDER=gcp-iap. |
| nginx + oauth2-proxy | oauth2-proxy handles SSO, nginx forwards to port 8080. Set RELAY_IDENTITY_PROVIDER=none and RELAY_AUTH_REQUIRED=false (proxy handles auth). |
| Tailscale / ZeroTier | Mesh VPN limits who can reach the server. Same config as above. |
| Any other | Anything that authenticates the user and proxies to port 8080. |
The quickstart installer prompts for Cloudflare or GCP IAP details. For other proxies, choose "none" and let your proxy handle authentication.
- SSH and SFTP in the browser — interactive terminal or command-line SFTP
- SSH key management — import private keys, encrypted at rest with a passphrase (PBKDF2 + AES-256-GCM via Web Crypto API). Browser password manager can save the passphrase. First available key is auto-selected.
- Saved connections — profiles persist in localStorage
- URL-driven connections — pre-populate, autoconnect, and integrate with other tools (see below)
- Subdomain routing —
host123.ssh.example.comauto-connects tohost123 - Session resumption — relay buffers data during brief disconnects (v4 protocol)
- Single binary deployment — one server, one port, one container
SSHamrock connections can be fully controlled via URL parameters, making it easy to integrate with wikis, ticketing systems, CMDBs, or any tool that can produce a link.
| Parameter | Example | Description |
|---|---|---|
host |
?host=db.internal |
Target hostname |
user |
?user=root |
Username (SSH prompts if omitted) |
port |
?port=2222 |
Port (default: 22) |
mode |
?mode=sftp |
ssh or sftp |
key |
?key=id_ed25519 |
Select a specific imported key |
autoconnect |
?autoconnect=1 |
Skip the form, connect immediately |
Examples:
# Pre-populate form
https://ssh.example.com/?user=root&host=db.internal
# One-click connect (prompts for key passphrase, then connects)
https://ssh.example.com/?user=deploy&host=web01.prod&autoconnect=1
# SFTP to a file server
https://ssh.example.com/?host=files.internal&mode=sftp&autoconnect=1
# No username — SSH will prompt
https://ssh.example.com/?host=bastion.internal&autoconnect=1
When BASE_DOMAIN is configured in config.js, SSHamrock extracts the target hostname from the subdomain:
https://db-server.ssh.example.com → connects to "db-server"
https://web01.ssh.example.com → connects to "web01"
With a Cloudflare wildcard DNS record (*.ssh.example.com) and a wildcard Access policy, users can reach any internal host by typing its name as a subdomain. Combine with ?autoconnect=1 for a zero-click experience after SSO.
To enable, edit src/js/config.js:
export const BASE_DOMAIN = 'ssh.example.com';Config lives at /etc/ssh-relay/env (written by the installer). Key settings:
RELAY_PUBLIC_HOST=ssh.example.com # Your public hostname
RELAY_PUBLIC_PORT=443 # Public-facing port
RELAY_IDENTITY_PROVIDER=cloudflare-access # or gcp-iap, or none
RELAY_AUTH_REQUIRED=true
RELAY_STATIC_DIR=/opt/ssh-relay/static
# Target policy (optional) — restrict which hosts the relay can reach
RELAY_TARGET_ALLOWLIST=10.0.0.0/8 # Comma-separated CIDRsFull configuration reference: nassh-proxy docs.
SSHamrock writes two log streams by default:
| Log | Location | Format | Contents |
|---|---|---|---|
| Audit log | /var/log/sshamrock/audit.jsonl |
JSON, one event per line | Handshakes, session start/close, identity, target host/port, duration, bytes transferred |
| Access log | journalctl -u ssh-relay |
Plain text (uvicorn) | HTTP requests, WebSocket connections, Python errors, startup messages |
The audit log rotates automatically (100 MB per file, 10 backups = 1 GB total).
Example audit events:
{"event": "handshake", "ts": "2026-04-22T...", "identity": {"email": "alice@example.com"}, "source_ip": "72.34.128.248"}
{"event": "session.start", "ts": "2026-04-22T...", "identity": {"email": "alice@example.com"}, "target_host": "db.internal", "target_port": 22}
{"event": "session.close", "ts": "2026-04-22T...", "duration_seconds": 847.3, "bytes_to_target": 15230, "bytes_from_target": 98412}Querying logs with jq:
# Who connected today?
jq -r 'select(.event=="session.start") | "\(.ts) \(.identity.email) → \(.target_host)"' \
/var/log/sshamrock/audit.jsonl
# Sessions longer than 1 hour
jq 'select(.event=="session.close" and .duration_seconds > 3600)' \
/var/log/sshamrock/audit.jsonl
# Live tail
tail -f /var/log/sshamrock/audit.jsonl | jq .Additional log sinks (configure in /etc/ssh-relay/env):
| Sink | Setting | Use case |
|---|---|---|
| Splunk HEC | RELAY_LOG_SINKS=stderr,file,splunk |
Central SIEM |
| Palo Alto User-ID | RELAY_LOG_SINKS=stderr,file,pan |
Firewall user mapping |
Network: The relay transports opaque SSH ciphertext — it cannot read passwords, keys, or session content. Loopback, link-local, and cloud metadata IPs are blocked by default.
Browser: A strict Content Security Policy (script-src 'self') prevents XSS. SSH private keys are encrypted at rest with PBKDF2 + AES-256-GCM and only decrypted into the WASM filesystem during an active connection. Stale keys are wiped on startup (crash recovery). URL parameters are stripped from browser history.
Headers: COOP/COEP enable SharedArrayBuffer for the WASM worker. Referrer-Policy: no-referrer and X-Content-Type-Options: nosniff are set on all responses.
| Data | Visible to relay? |
|---|---|
| Who connected (JWT identity) | Yes |
| Target host and port | Yes |
| SSH traffic content | No (encrypted) |
| Passwords typed in SSH | No (encrypted) |
| SSH private keys | No (never leave browser) |
The repo includes a pre-built dist/ directory with everything needed. If you want to modify the web client or update the nassh components:
# Prerequisites: Node.js 18+, libapps checkout
bash build/assemble.sh /path/to/libapps
cp ~/.config/google-chrome/Default/Extensions/iodihamcpbpeioajjeobimgagajmlibd/*/plugin/wasm/ssh.wasm dist/plugin/wasm/| Component | Source |
|---|---|
| Terminal | hterm |
| SSH client | OpenSSH → WebAssembly |
| Relay protocol | Corp Relay v4 |
| Relay server | nassh-proxy (FastAPI/Python) |
| Key encryption | Web Crypto API (PBKDF2 + AES-256-GCM) |
The web client wrapper (this repo) is MIT licensed. The underlying nassh/hterm/ssh_client components are BSD licensed by The Chromium Authors.
