Truly keyless SSH.
The world's first SSH client where private keys don't exist - not on servers, not in bastions, not in browsers, not even in memory. Powered by Tide's decentralised cryptography.
Traditional SSH clients have a fundamental problem: private keys. Whether stored on a server, uploaded by users, or generated in the browser, private keys will always be the greatest security liability - they can be stolen, leaked, or compromised.
KeyleSSH eliminates private keys entirely.
Instead of managing keys, KeyleSSH uses Tide technology for all its cryptographic operations. SSH authorization signing happens across a decentralised network of independent nodes called ORKs (Orchestrated Recluders of Keys) - no single point ever holds a complete key. This isn't just distributed, it's truly decentralised (the key never exists as a whole under any single organization).
- No key import, no key storage - Users authenticate via TideCloak (OIDC), receiving a "doken" (delegated token)
- Policy-based authorization - Admins define who can SSH as which SSH user under what role, via Forseti contracts (C# policies executed in sandboxed ORKs)
- Decentralised signing - When SSH needs a authorization signature, ORKs validate against the policy and collaboratively sign the challenge
- Threshold cryptography - The signing key exists mathematically split across multiple independent ORKs; no single node can sign alone
- Blind bastion tunneling - All SSH session are tunneled through an oblivious jumpbox that has no access or knowledge of to the content of the session
The result: enterprise-grade SSH access control without any private keys to manage, rotate, or protect.
- Browser-side SSH via
@microsoft/dev-tunnels-ssh+xterm.js, with no private keys anywhere - SFTP file browser - Browse, upload, download, rename, delete files via split-panel UI
- Quorum-based RBAC, zero-knowledge OIDC login with TideCloak - no passwords, no keys
- Programmable policy enforcement with Forseti contracts for SSH access
- Admin UX: servers, users, roles, policy templates, change requests, sessions, logs
- Browser-based RDP - full Windows remote desktop in a browser tab via IronRDP WASM. No client install, no ports to open, no VPN. See RDP Architecture.
- P2P DataChannel transport - automatic upgrade from HTTP relay to direct peer-to-peer WebRTC, with a Service Worker that silently reroutes all browser fetches through the encrypted DataChannel. See Connection Lifecycle.
- Signal server (
signal-server/) - coordinates P2P connections between browsers and gateways via WebSocket signaling (SDP/ICE), relays HTTP traffic before DataChannel is ready, and generates ephemeral TURN credentials. Deployed with a coturn sidecar for STUN NAT discovery and TURN relay fallback. See Architecture. - Multi-backend routing (
bridges/punchd-bridge) - proxy to multiple HTTP backends and RDP servers from a single gateway. See Multi-Backend Routing.
keylessh/
├── client/ # React UI (xterm.js, SSH client, SFTP browser)
├── server/ # Express API + WebSocket bridge + SQLite
├── shared/ # Shared types + schema
├── signal-server/ # P2P signaling + HTTP relay for punchd-bridge
├── bridges/
│ ├── tcp-bridge/ # Stateless WS↔TCP forwarder (optional)
│ └── punchd-bridge/ # NAT-traversing HTTP reverse proxy gateway
│ └── gateway/ # Gateway source code
├── docs/ # Architecture, deployment, developer guides
└── script/ # TideCloak setup scripts
- Architecture: docs/ARCHITECTURE.md
- Deployment: docs/DEPLOYMENT.md
- Developer guide: docs/DEVELOPERS.md
- Punch'd Bridge — connection lifecycle (portal → relay → P2P → SW takeover), RDP/RDCleanPath, multi-backend routing, DataChannel messages, API endpoints, security & rate limits, sequence diagrams
- Signal Server — WebSocket signaling (SDP/ICE), HTTP relay, gateway registry, TURN credential generation, coturn sidecar for STUN/TURN
git clone https://github.com/sashyo/keylessh.git
cd keylessh/script/tidecloak
./start.shDuring initialization, you'll be prompted to:
- Enter an email to manage your license - Provide a valid email address for your Tide subscription
- Accept the Terms & Conditions - Review the terms at https://tide.org/legal and enter
yoryesto agree
The script will generate an invite link:
🔗 INVITE LINK (use this one):
http://localhost:8080/realms/keylessh/login-actions/action-token?key=...
Open this link in your browser and either:
- Create a new tide account, or
- Sign in with your existing Tide account
The script will detect when linking is complete and continue finishing the setup:
🎉 Tidecloak initialization complete!
cd ../.. # back to keylessh root
npm install
npm run devAccess the KeyleSSH app in your browser at: http://localhost:3000
Important
CSP iframe error? The secure enclave (TideCloak/Heimdall) is loaded in a hidden iframe to share the session ID. If you see a console error like Framing 'http://localhost:XXXX/' violates the Content Security Policy directive: "frame-src ...", add the blocked origin to the frame-src list in server/index.ts. See Troubleshooting for details.
Here's how you set up a locally-hosted SSH server and access it using KeyleSSH:
Note
This guide will show you how to spin up a minimal Alpine docker image on your localhost, enable SSH on it on port 2222, set up a new user on it, and enable it for passwordless, key-base authentication.
- Go to servers ->
Add Server->- Server Name: myserver
- Host: localhost
- Post: 2222
- SSH Users: user
- Click
Add Serverbutton - Status should come up as
Online
- Go to Roles ->
Add Role- Role Name (SSH Role: ✅): user (it'll autochange it to
ssh:user) - Click the
Create Rolebutton
- Role Name (SSH Role: ✅): user (it'll autochange it to
- Go to Users ->
- Click the
Actionbutton (✏️) for the defaultadmin user - Click the
ssh:usertag inAvailable Rolesto move it toAssigned Roles - Click the
Save Changesbutton
- Click the
- Go to Change Requests ->
- Click the
Reviewbutton (👁️) for the useradmin - Confirm User Access Change by clicking the
Ybutton - Click the
Submit Approvalsbutton - Click the
Commitbutton (📤) for the useradmin - Change over to the
Policiestab - Click the
Reviewbutton (👁️) for the policy rolessh:user - Confirm User Access Change by clicking the
Ybutton - Click the
Submit Approvalsbutton - Click the
Commitbutton (📤) for the policy rolessh:user
- Click the
- Expand your user profile (a
AD adminicon at the bottom-left of your KeyleSSH browser windows) ->- Click
Restart sessionto quickly log out and in again
- Click
- Go to Dashboard ->
myserver-> SSH USER:user->Connect->- In the
Terminal Workspace, click theConnectbutton - Copy the "Tide SSH public key" string (click the
Copybutton)
- In the
Spin up an Alpine server with SSH access allowed for user root (password root AS AN EXAMPLE ONLY!) by running this on your local machine's command-line:
sudo docker run -d \
-p 2222:22 \
--name tinyssh \
alpine sh -c "
apk add --no-cache openssh && \
ssh-keygen -A && \
echo 'root:root' | chpasswd && \
sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config && \
sed -i 's/PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config && \
sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config && \
sed -i 's/PasswordAuthentication no/PasswordAuthentication yes/' /etc/ssh/sshd_config && \
/usr/sbin/sshd -D
"Connect to that new server via your command-line SSH:
ssh root@localhost -p 2222(use root as your password)
In the newly created SSH session, enter the following commands - but use the "Tide SSH public key" you copied earlier instead of the "blahblah" one used in this example:
adduser -D -s /bin/sh user
passwd -d user
mkdir -p /home/user/.ssh
chmod 700 /home/user/.ssh
chown user:user /home/user/.ssh
echo "ssh-ed25519 blahblahblah user@keylessh" > /home/user/.ssh/authorized_keys
chmod 600 /home/user/.ssh/authorized_keys
chown user:user /home/user/.ssh/authorized_keysNow return to the KeyleSSH Dashboard page where the "Authorize SSH Session" pop-up is opened, and click the Authorize & Connect button.
Your SSH session to your server myserver will commence.
npm run dev- start server + Vite dev integrationnpm run build- build client + bundle servernpm start- run production build fromdist/npm run check- TypeScript typecheck
PORT=3000
# Optional external TCP bridge (for scaling)
# Bridge verifies JWTs against same tidecloak.json - no shared secret needed
BRIDGE_URL=ws://localhost:8080
# SQLite (file path)
DATABASE_URL=./data/keylessh.dbThe KeyleSSH Server JWT verification config (holding the JWKS keychain) must be downloaded and put here: data/tidecloak.json .
See any of the guides for instructions.
- Authentication:
@tidecloak/react(wraps/uses@tidecloak/js) - Tide Protocol:
heimdall-tide(Policy, PolicySignRequest, TideMemory for signing) - Terminal:
@xterm/xterm - Browser SSH:
@microsoft/dev-tunnels-sshand@microsoft/dev-tunnels-ssh-keys - API state:
@tanstack/react-query - Server:
express,ws - Storage:
better-sqlite3,drizzle-orm
SSH signing uses the Tide Protocol's Policy:1 auth flow with Forseti contracts:
- Admin creates SSH policies via policy templates (defines role, resource, approval type)
- Policies are signed and committed to the ORK network
- When a user connects via SSH, their doken is validated against the policy
- ORKs execute the Forseti contract (C# code in sandbox) to authorize
- If authorized, ORKs collaboratively produce a signature for the SSH challenge
See docs/ARCHITECTURE.md for the full flow diagram.
See docs/DEVELOPERS.md.
KeyleSSH is open source and designed for flexible deployment:
Deploy KeyleSSH for your organization with no usage restrictions. By default, there are no limits on users, servers, or features. Perfect for:
- Enterprise internal deployments
- Development teams
- Personal/homelab use
Just follow the Deployment Guide - no licensing configuration needed.
If you want to offer KeyleSSH as a commercial service with subscription tiers, configure Stripe billing:
- Free tier: 5 users, 2 servers
- Pro tier: 25 users, 10 servers
- Enterprise tier: Unlimited
See SaaS Configuration in the deployment guide.
MIT
