@@ -63,7 +63,7 @@ Holesail Browser is composed of three parts: a browser extension, a native host
|
|||||||
| `https-proxy.js` | HTTPS reverse proxy on `127.0.0.1:8443`. Presents a wildcard `*.hole.sail` TLS cert. Reads the `Host` header, looks up the local tunnel port, and proxies HTTP. Returns a 502 HTML page for unknown hostnames. |
|
| `https-proxy.js` | HTTPS reverse proxy on `127.0.0.1:8443`. Presents a wildcard `*.hole.sail` TLS cert. Reads the `Host` header, looks up the local tunnel port, and proxies HTTP. Returns a 502 HTML page for unknown hostnames. |
|
||||||
| `connect-proxy.js` | HTTP CONNECT proxy on `127.0.0.1:8442`. Accepts `CONNECT hostname:443`, replies `200 Connection established`, then pipes the raw TCP stream to `127.0.0.1:8443`. |
|
| `connect-proxy.js` | HTTP CONNECT proxy on `127.0.0.1:8442`. Accepts `CONNECT hostname:443`, replies `200 Connection established`, then pipes the raw TCP stream to `127.0.0.1:8443`. |
|
||||||
| `certificate-authority.js` | Generates a 2048-bit RSA root CA (10-year validity) using `node-forge`. Signs wildcard `*.hole.sail` domain certs (1-year). Installs the CA into the OS trust store. Fingerprint-verifies to detect stale entries. |
|
| `certificate-authority.js` | Generates a 2048-bit RSA root CA (10-year validity) using `node-forge`. Signs wildcard `*.hole.sail` domain certs (1-year). Installs the CA into the OS trust store. Fingerprint-verifies to detect stale entries. |
|
||||||
| `ssh-manager.js` | Per SSH session: starts a Holesail client tunnel, spawns `ssh` with a real PTY via `tt-native` (`forkpty`), starts a `bare-ws` WebSocket server, and bridges PTY ↔ WebSocket. |
|
| `ssh-manager.js` | Per SSH session: starts a Holesail client tunnel, spawns `ssh` with a real PTY via `tt-native` (`forkpty`), starts a `bare-ws` WebSocket server, and bridges PTY ↔ WebSocket. SSH is spawned only after the browser WebSocket client connects and sends a ready-signal. Password delivery uses `SSH_ASKPASS` + a named FIFO — public key auth is tried first; a password prompt appears in xterm.js only if key auth fails and no password is saved. |
|
||||||
| `rdp-manager.js` | Per RDP/VNC session: starts a Holesail client tunnel, starts a WebSocket server. VNC: transparent byte pipe. RDP: `node-rdpjs-2` client, converts bitmap updates to JSON. |
|
| `rdp-manager.js` | Per RDP/VNC session: starts a Holesail client tunnel, starts a WebSocket server. VNC: transparent byte pipe. RDP: `node-rdpjs-2` client, converts bitmap updates to JSON. |
|
||||||
| `backup-manager.js` | Creates/restores `tar.gz` backups of `state.json` + all certificates. Supports create, list, restore, delete, and auto-prune by retention count. |
|
| `backup-manager.js` | Creates/restores `tar.gz` backups of `state.json` + all certificates. Supports create, list, restore, delete, and auto-prune by retention count. |
|
||||||
|
|
||||||
|
|||||||
+5
-2
@@ -184,12 +184,15 @@ Start an SSH session.
|
|||||||
"connectionId": "ssh-abc123",
|
"connectionId": "ssh-abc123",
|
||||||
"hsUrl": "hs://abc123...",
|
"hsUrl": "hs://abc123...",
|
||||||
"username": "root",
|
"username": "root",
|
||||||
"port": 22,
|
"password": "",
|
||||||
"cols": 80,
|
"cols": 80,
|
||||||
"rows": 24
|
"rows": 24,
|
||||||
|
"label": "My Server"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`password` is optional. If provided, it is delivered to SSH automatically via `SSH_ASKPASS` and a named FIFO — it is never written to disk or passed on the command line. If omitted, SSH tries public key auth first; if that fails, a password prompt appears in the xterm.js terminal.
|
||||||
|
|
||||||
**Response payload:**
|
**Response payload:**
|
||||||
```json
|
```json
|
||||||
{ "ok": true, "sessionId": "ssh-session-1", "wsPort": 21000, "tunnelPort": 20000 }
|
{ "ok": true, "sessionId": "ssh-session-1", "wsPort": 21000, "tunnelPort": 20000 }
|
||||||
|
|||||||
+5
-4
@@ -101,11 +101,12 @@ The PAC script routes only `*.hole.sail` traffic through the proxy. All other tr
|
|||||||
|
|
||||||
## SSH security
|
## SSH security
|
||||||
|
|
||||||
SSH sessions use the system `ssh` binary with its default security settings:
|
SSH sessions use the system `ssh` binary. Authentication is tried in order: public key, then password.
|
||||||
|
|
||||||
- `StrictHostKeyChecking` is enabled by default — the first connection to a new host will prompt for host key verification
|
- **Public key auth** — keys from `~/.ssh/` and `ssh-agent` are used automatically; no password prompt appears if a key is accepted
|
||||||
- SSH keys and `~/.ssh/known_hosts` are used normally
|
- **Password auth** — passwords are never written to disk; a saved password is held in memory only and delivered to SSH via `SSH_ASKPASS` and a named FIFO pipe (not via the process environment or command line); interactive passwords are collected in xterm.js and delivered the same way
|
||||||
- The Holesail tunnel provides transport; `ssh`'s own encryption is layered on top
|
- **`StrictHostKeyChecking=no` / `UserKnownHostsFile=/dev/null`** — host key verification is disabled for Holesail tunnels because the remote host's identity is already established by the `hs://` public key (Noise mutual authentication at the tunnel layer); adding known-hosts entries for `127.0.0.1` would be misleading since the port changes per session
|
||||||
|
- The Holesail tunnel provides transport; `ssh`'s own encryption is layered on top, giving double encryption
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+63
-27
@@ -20,7 +20,37 @@ ssh binary (system ssh)
|
|||||||
Remote SSH daemon
|
Remote SSH daemon
|
||||||
```
|
```
|
||||||
|
|
||||||
The `ssh` binary is the system's own SSH client — all its features (key auth, agent forwarding, known hosts, etc.) work as normal. The Holesail tunnel provides the transport; `ssh` sees it as a plain TCP connection to `127.0.0.1`.
|
The `ssh` binary is the system's own SSH client — all its features (key auth, agent forwarding, etc.) work as normal. The Holesail tunnel provides the transport; `ssh` sees it as a plain TCP connection to `127.0.0.1`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Authentication
|
||||||
|
|
||||||
|
SSH authentication is handled entirely by the system `ssh` binary. The following methods are supported, tried in order:
|
||||||
|
|
||||||
|
### 1. Public key (automatic, no prompt)
|
||||||
|
|
||||||
|
If you have a key in `~/.ssh/` (e.g. `id_rsa`, `id_ed25519`) or loaded in `ssh-agent`, it is tried first. If the remote server accepts it, you are logged in with no prompt at all.
|
||||||
|
|
||||||
|
### 2. Password — saved on the connection
|
||||||
|
|
||||||
|
If you enter a password when saving the connection, it is stored in memory for the session (never written to disk). When connecting, the password is delivered to SSH automatically via a secure helper mechanism — no prompt appears.
|
||||||
|
|
||||||
|
### 3. Password — typed interactively
|
||||||
|
|
||||||
|
If no public key is available and no password is saved, the terminal displays a password prompt (`[email protected]'s password:`). Type your password and press Enter. The input is masked (no echo).
|
||||||
|
|
||||||
|
> **Note:** The native messaging host launched by Chrome has no controlling terminal (`/dev/tty`), so SSH cannot read passwords directly. Holesail Browser handles this transparently using `SSH_ASKPASS` and a named FIFO pipe — the password prompt appears in xterm.js and the typed password is securely forwarded to SSH without exposing it in the process environment or on disk.
|
||||||
|
|
||||||
|
### Authentication order summary
|
||||||
|
|
||||||
|
| Scenario | What happens |
|
||||||
|
|----------|-------------|
|
||||||
|
| Public key in `~/.ssh/` or `ssh-agent` | Logged in silently, no prompt |
|
||||||
|
| Password saved on connection | Password delivered automatically, no prompt |
|
||||||
|
| No key, no saved password | Password prompt appears in xterm.js |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
@@ -29,17 +59,19 @@ The `ssh` binary is the system's own SSH client — all its features (key auth,
|
|||||||
1. Open the dashboard → **SSH**
|
1. Open the dashboard → **SSH**
|
||||||
2. Click **Add Connection**
|
2. Click **Add Connection**
|
||||||
3. Enter a label, the `hs://` key for the remote peer, and the username
|
3. Enter a label, the `hs://` key for the remote peer, and the username
|
||||||
4. Click **Save** — the connection is stored in `state.json`
|
4. Optionally enter a password (leave blank to type it each time, or to use a public key)
|
||||||
|
5. Click **Save** — the connection definition is stored in `state.json`; the password is kept in memory only and is not persisted
|
||||||
|
|
||||||
### Connecting
|
### Connecting
|
||||||
|
|
||||||
1. Click **Connect** next to a saved connection
|
1. Click **Connect** next to a saved connection
|
||||||
2. An xterm.js terminal opens in the dashboard
|
2. An xterm.js terminal opens in the dashboard
|
||||||
3. The native host starts a Holesail tunnel to the remote peer, then spawns `ssh`
|
3. The native host starts a Holesail tunnel to the remote peer, then spawns `ssh`
|
||||||
|
4. If authentication requires a password and none is saved, a prompt appears in the terminal
|
||||||
|
|
||||||
### Terminal resize
|
### Terminal resize
|
||||||
|
|
||||||
The terminal resizes automatically when you resize the dashboard window. Resize events are sent to the native host, which calls `ioctl(TIOCSWINSZ)` on the PTY and sends an SSH window-change request.
|
The terminal resizes automatically when you resize the dashboard window. Resize events are sent to the native host, which calls `ioctl(TIOCSWINSZ)` on the PTY and sends an SSH window-change request to the remote sshd.
|
||||||
|
|
||||||
### Disconnecting
|
### Disconnecting
|
||||||
|
|
||||||
@@ -57,15 +89,17 @@ Start a new SSH session.
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"connectionId": "ssh-abc123",
|
"connectionId": "ssh-abc123",
|
||||||
"hostname": "myserver.hole.sail",
|
|
||||||
"hsUrl": "hs://abc123...",
|
"hsUrl": "hs://abc123...",
|
||||||
"username": "root",
|
"username": "root",
|
||||||
"port": 22,
|
"password": "",
|
||||||
"cols": 80,
|
"cols": 80,
|
||||||
"rows": 24
|
"rows": 24,
|
||||||
|
"label": "My Server"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The `password` field is optional. If omitted or empty, SSH will use public key auth or prompt interactively.
|
||||||
|
|
||||||
**Response payload:**
|
**Response payload:**
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -120,7 +154,7 @@ List active SSH sessions.
|
|||||||
|
|
||||||
### `setSshConnections`
|
### `setSshConnections`
|
||||||
|
|
||||||
Save SSH connection definitions to `state.json`.
|
Save SSH connection definitions to `state.json`. Passwords are stripped before saving — they are session-only.
|
||||||
|
|
||||||
**Request payload:**
|
**Request payload:**
|
||||||
```json
|
```json
|
||||||
@@ -137,7 +171,10 @@ Save SSH connection definitions to `state.json`.
|
|||||||
|
|
||||||
The dashboard connects to `ws://127.0.0.1:<wsPort>` after receiving the `startSshSession` response.
|
The dashboard connects to `ws://127.0.0.1:<wsPort>` after receiving the `startSshSession` response.
|
||||||
|
|
||||||
|
SSH is not spawned until the WebSocket client connects and sends a ready-signal (`\x00`). This ensures the terminal is live before SSH starts, so any prompts (password, host key) are visible immediately.
|
||||||
|
|
||||||
**Browser → native host:**
|
**Browser → native host:**
|
||||||
|
- First byte `\x00`: ready-signal (triggers SSH spawn)
|
||||||
- Binary frames: raw terminal input (keystrokes, paste)
|
- Binary frames: raw terminal input (keystrokes, paste)
|
||||||
- Text frames: JSON control messages
|
- Text frames: JSON control messages
|
||||||
```json
|
```json
|
||||||
@@ -146,10 +183,6 @@ The dashboard connects to `ws://127.0.0.1:<wsPort>` after receiving the `startSs
|
|||||||
|
|
||||||
**Native host → browser:**
|
**Native host → browser:**
|
||||||
- Binary frames: raw PTY output (terminal data)
|
- Binary frames: raw PTY output (terminal data)
|
||||||
- Text frames: JSON control messages
|
|
||||||
```json
|
|
||||||
{ "type": "exit", "code": 0 }
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -164,19 +197,9 @@ Ports are allocated sequentially and released when the session ends.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
- The `ssh` binary must be installed on the machine running the native host
|
|
||||||
- macOS: included with the OS
|
|
||||||
- Linux: install `openssh-client`
|
|
||||||
- Windows: install OpenSSH via Windows Optional Features or Git for Windows
|
|
||||||
- The remote host must be running an SSH daemon accessible via a Holesail `hs://` key
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Saved connections
|
## Saved connections
|
||||||
|
|
||||||
SSH connections are stored in `state.json` under `sshConnections`:
|
SSH connections are stored in `state.json` under `sshConnections`. Passwords are **never** written to disk — they are held in memory for the current session only and must be re-entered after a native host restart.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -191,7 +214,15 @@ SSH connections are stored in `state.json` under `sshConnections`:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Connections are restored on startup. Active sessions (the running PTY processes) are not persisted — they must be reconnected after a native host restart.
|
---
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- The `ssh` binary must be installed on the machine running the native host
|
||||||
|
- macOS: included with the OS
|
||||||
|
- Linux: install `openssh-client`
|
||||||
|
- Windows: install OpenSSH via Windows Optional Features or Git for Windows
|
||||||
|
- The remote host must be running an SSH daemon accessible via a Holesail `hs://` key
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -199,16 +230,21 @@ Connections are restored on startup. Active sessions (the running PTY processes)
|
|||||||
|
|
||||||
**Connection times out**
|
**Connection times out**
|
||||||
|
|
||||||
- The remote peer may be offline or the `hs://` key may be wrong
|
The remote peer may be offline or the `hs://` key may be wrong. Check `~/.holesail-browser/holesail-browser.log` for tunnel errors.
|
||||||
- Check `~/.holesail-browser/holesail-browser.log` for tunnel errors
|
|
||||||
|
|
||||||
**`ssh: command not found`**
|
**`ssh: command not found`**
|
||||||
|
|
||||||
The `ssh` binary is not in the PATH seen by the native host. Install OpenSSH or ensure `ssh` is in `/usr/bin/ssh` (macOS/Linux) or available in `PATH` (Windows).
|
The `ssh` binary is not in the PATH seen by the native host. Install OpenSSH or ensure `ssh` is in `/usr/bin/ssh` (macOS/Linux) or available in `PATH` (Windows).
|
||||||
|
|
||||||
**Host key verification fails**
|
**Permission denied (publickey,password)**
|
||||||
|
|
||||||
SSH's `StrictHostKeyChecking` applies normally. If you see a host key warning, accept it in the terminal or add the host to `~/.ssh/known_hosts`.
|
- If you have a public key but it is not accepted by the server, SSH falls back to password auth and shows a prompt in the terminal
|
||||||
|
- If you enter the wrong password, SSH exits with "Permission denied" — reconnect and try again
|
||||||
|
- If no authentication method works, check that the username is correct and that the server allows password auth
|
||||||
|
|
||||||
|
**Password prompt does not appear**
|
||||||
|
|
||||||
|
This should not happen with the current implementation. If it does, check `~/.holesail-browser/holesail-browser.log` for `WARNING: askpass setup failed` — this would indicate that `mkfifo` is not available on the system.
|
||||||
|
|
||||||
**Terminal display is garbled**
|
**Terminal display is garbled**
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user