Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
284c167
Add HostKeyVerification types to config
billchurch Feb 25, 2026
1d2e5de
Add host key verification defaults and env variable mapping
billchurch Feb 25, 2026
2b3bf67
Add resolveHostKeyMode for mode-to-store-flag expansion
billchurch Feb 25, 2026
fbb93e8
Install better-sqlite3 for host key server store
billchurch Feb 25, 2026
56e776b
Implement HostKeyStore SQLite wrapper with tests
billchurch Feb 25, 2026
5f27587
Implement HostKeyService with fingerprint computation
billchurch Feb 25, 2026
6263cb7
Register HostKeyService in service factory and add Zod schema
billchurch Feb 25, 2026
cc23da0
feat(host-key): add hostkey socket events to constants
billchurch Feb 25, 2026
62ef1cb
feat(host-key): implement hostVerifier callback factory
billchurch Feb 25, 2026
eddf34e
feat(host-key): wire hostVerifier into SSH service connect flow
billchurch Feb 25, 2026
47a0f70
feat(host-key): send host key config with permissions event
billchurch Feb 25, 2026
1a69405
feat(host-key): add hostKeyVerification to legacy socket-adapter perm…
billchurch Feb 25, 2026
0a76bea
fix(tests): add hostKeyVerification to socket-v2 mock config
billchurch Feb 25, 2026
c4949e2
Add integration tests for host key verification flow
billchurch Feb 25, 2026
5d5330e
Add host key seeding script and fix lint in integration tests
billchurch Feb 25, 2026
8ac6677
docs: add host key verification configuration and usage guide
billchurch Feb 25, 2026
ce2be9e
docs: add host key verification socket protocol reference
billchurch Feb 25, 2026
6f53016
fix: address code review findings for host key verification
billchurch Feb 25, 2026
c1c83ba
docs: add host key verification to configuration reference
billchurch Feb 25, 2026
97cb173
feat: send host key verification config pre-auth and honor env vars i…
billchurch Feb 25, 2026
486aa42
test: add host key verification test coverage and fix SonarQube issues
billchurch Feb 25, 2026
6315836
fix: resolve SonarQube quality gate failures on PR #488
billchurch Feb 25, 2026
ad14af9
fix: resolve lint errors in test files
billchurch Feb 26, 2026
dbea2df
fix: update rollup to 4.59.0 for GHSA-mw96-cpmx-2vgc path traversal fix
billchurch Feb 26, 2026
142db67
fix: resolve markdownlint warnings in host-key-protocol.md
billchurch Feb 26, 2026
7ae7e63
feat: update webssh2_client dependency to version 3.4.0
billchurch Feb 26, 2026
b33dc6d
fix: resolve SonarQube issues across codebase (S3735, S3776, S2871, S…
billchurch Feb 26, 2026
3bec992
fix: remove void operator from test files (S3735)
billchurch Feb 26, 2026
a6163d6
fix: reduce awaitClientVerification params to options object (S107) a…
billchurch Feb 26, 2026
bdd7991
fix: use eslint-disable for no-new in side-effect constructors
billchurch Feb 26, 2026
226f181
chore: update emitSocketLog type usage and adjust mock parameters
billchurch Feb 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,6 @@ config.json.backup

release-artifacts/

data/

DOCS/plans/*
171 changes: 170 additions & 1 deletion DOCS/configuration/CONFIG-JSON.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,19 @@ These settings are now managed client-side.
"envAllowlist": ["ONLY_THIS", "AND_THAT"],
"maxExecOutputBytes": 10485760,
"outputRateLimitBytesPerSec": 0,
"socketHighWaterMark": 16384
"socketHighWaterMark": 16384,
"hostKeyVerification": {
"enabled": false,
"mode": "hybrid",
"unknownKeyAction": "prompt",
"serverStore": {
"enabled": true,
"dbPath": "/data/hostkeys.db"
},
"clientStore": {
"enabled": true
}
}
},
"options": {
"challengeButton": true,
Expand Down Expand Up @@ -419,3 +431,160 @@ These options can also be configured via environment variables:
- `WEBSSH2_SSH_SFTP_TIMEOUT`

See [ENVIRONMENT-VARIABLES.md](./ENVIRONMENT-VARIABLES.md) for details on environment variable format and examples.

### Host Key Verification

SSH host key verification provides TOFU (Trust On First Use) protection against man-in-the-middle attacks. It supports three modes of operation: server-only (SQLite store), client-only (browser localStorage), and hybrid (server-first with client fallback).

#### Configuration Options

- `ssh.hostKeyVerification.enabled` (boolean, default: `false`): Enable or disable host key verification. When disabled (the default), all host keys are accepted without verification.

- `ssh.hostKeyVerification.mode` (`'server'` | `'client'` | `'hybrid'`, default: `'hybrid'`): Operational mode. `server` uses only the SQLite store, `client` uses only the browser localStorage store, `hybrid` checks the server store first and falls back to the client store for unknown keys. The mode sets sensible defaults for which stores are enabled, but explicit store flags override mode defaults.

- `ssh.hostKeyVerification.unknownKeyAction` (`'prompt'` | `'alert'` | `'reject'`, default: `'prompt'`): Action when an unknown key is encountered (no match in any enabled store). `prompt` asks the user to accept or reject, `alert` allows the connection with a warning, `reject` blocks the connection.

- `ssh.hostKeyVerification.serverStore.enabled` (boolean): Whether the server-side SQLite store is active. Defaults are derived from `mode` but can be overridden explicitly.

- `ssh.hostKeyVerification.serverStore.dbPath` (string, default: `'/data/hostkeys.db'`): Path to the SQLite database file. The application opens it read-only. Use `npm run hostkeys` to manage keys.

- `ssh.hostKeyVerification.clientStore.enabled` (boolean): Whether the client-side browser localStorage store is active. Defaults are derived from `mode` but can be overridden explicitly.

#### Default Host Key Verification Configuration

```json
{
"ssh": {
"hostKeyVerification": {
"enabled": false,
"mode": "hybrid",
"unknownKeyAction": "prompt",
"serverStore": {
"enabled": true,
"dbPath": "/data/hostkeys.db"
},
"clientStore": {
"enabled": true
}
}
}
}
```

> **Note:** Host key verification is disabled by default. Set `enabled` to `true` to activate it.

#### Use Cases

**Enable with hybrid mode (recommended):**
```json
{
"ssh": {
"hostKeyVerification": {
"enabled": true,
"mode": "hybrid"
}
}
}
```
Server store is checked first. If the key is unknown on the server, the client's browser store is consulted. Unknown keys prompt the user.

**Server-only mode (centrally managed keys):**
```json
{
"ssh": {
"hostKeyVerification": {
"enabled": true,
"mode": "server",
"unknownKeyAction": "reject"
}
}
}
```
Only the server SQLite store is used. Unknown keys are rejected — administrators must pre-seed keys via `npm run hostkeys`.

**Client-only mode (no server database):**
```json
{
"ssh": {
"hostKeyVerification": {
"enabled": true,
"mode": "client"
}
}
}
```
Only the client browser store is used. Users manage their own trusted keys via the settings UI.

**Alert-only (log but don't block):**
```json
{
"ssh": {
"hostKeyVerification": {
"enabled": true,
"mode": "server",
"unknownKeyAction": "alert"
}
}
}
```
Unknown keys show a warning indicator but connections proceed. Useful for monitoring before enforcing.

**Override mode defaults with explicit flags:**
```json
{
"ssh": {
"hostKeyVerification": {
"enabled": true,
"mode": "server",
"serverStore": { "enabled": true, "dbPath": "/data/hostkeys.db" },
"clientStore": { "enabled": true }
}
}
}
```
Mode is `server` but `clientStore.enabled` is explicitly set to `true`, making it behave like hybrid. Explicit flags always take precedence over mode defaults.

#### Seeding the Server Store

Use the built-in CLI tool to manage the SQLite database:

```bash
# Probe a host and add its key
npm run hostkeys -- --host server1.example.com

# Probe a host on a non-standard port
npm run hostkeys -- --host server1.example.com:2222

# Import from OpenSSH known_hosts file
npm run hostkeys -- --known-hosts ~/.ssh/known_hosts

# List all stored keys
npm run hostkeys -- --list

# Remove keys for a host
npm run hostkeys -- --remove server1.example.com

# Use a custom database path
npm run hostkeys -- --db /custom/path/hostkeys.db --host server1.example.com
```

#### Environment Variables

These options can also be configured via environment variables:
- `WEBSSH2_SSH_HOSTKEY_ENABLED`
- `WEBSSH2_SSH_HOSTKEY_MODE`
- `WEBSSH2_SSH_HOSTKEY_UNKNOWN_ACTION`
- `WEBSSH2_SSH_HOSTKEY_DB_PATH`
- `WEBSSH2_SSH_HOSTKEY_SERVER_ENABLED`
- `WEBSSH2_SSH_HOSTKEY_CLIENT_ENABLED`

See [ENVIRONMENT-VARIABLES.md](./ENVIRONMENT-VARIABLES.md) for details.
- `WEBSSH2_SSH_SFTP_MAX_FILE_SIZE`
- `WEBSSH2_SSH_SFTP_TRANSFER_RATE_LIMIT_BYTES_PER_SEC`
- `WEBSSH2_SSH_SFTP_CHUNK_SIZE`
- `WEBSSH2_SSH_SFTP_MAX_CONCURRENT_TRANSFERS`
- `WEBSSH2_SSH_SFTP_ALLOWED_PATHS`
- `WEBSSH2_SSH_SFTP_BLOCKED_EXTENSIONS`
- `WEBSSH2_SSH_SFTP_TIMEOUT`

See [ENVIRONMENT-VARIABLES.md](./ENVIRONMENT-VARIABLES.md) for details on environment variable format and examples.
18 changes: 18 additions & 0 deletions DOCS/configuration/CONSTANTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,30 @@ Used in:

- `app/security-headers.ts` (`SECURITY_HEADERS`, `createCSPMiddleware`)

## SOCKET_EVENTS (Host Key Verification)

Location: `app/constants/socket-events.ts`

The following socket events were added for host key verification:

| Constant | Event Name | Direction | Description |
|----------|-----------|-----------|-------------|
| `HOSTKEY_VERIFY` | `hostkey:verify` | Server → Client | Request client to verify an unknown host key |
| `HOSTKEY_VERIFY_RESPONSE` | `hostkey:verify-response` | Client → Server | Client's accept/reject/trusted decision |
| `HOSTKEY_VERIFIED` | `hostkey:verified` | Server → Client | Key verified successfully, connection proceeds |
| `HOSTKEY_MISMATCH` | `hostkey:mismatch` | Server → Client | Key mismatch detected, connection refused |
| `HOSTKEY_ALERT` | `hostkey:alert` | Server → Client | Unknown key warning (connection proceeds) |
| `HOSTKEY_REJECTED` | `hostkey:rejected` | Server → Client | Unknown key rejected by policy |

See [host-key-protocol.md](../host-key-protocol.md) for full payload schemas and sequence diagrams.

## Where These Are Used

- Routing and connection setup: `app/routes-v2.ts`, `app/connection/connectionHandler.ts`
- Middleware and security: `app/middleware.ts`, `app/security-headers.ts`
- SSH behavior and env handling: `app/services/ssh/ssh-service.ts`
- Socket behavior: `app/socket-v2.ts`, `app/socket/adapters/service-socket-adapter.ts`
- Host key verification: `app/services/host-key/host-key-verifier.ts`

## Conventions

Expand Down
61 changes: 61 additions & 0 deletions DOCS/configuration/ENVIRONMENT-VARIABLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,67 @@ The server applies security headers and a Content Security Policy (CSP) by defau
| `WEBSSH2_SSH_OUTPUT_RATE_LIMIT_BYTES_PER_SEC` | number | `0` (unlimited) | Rate limit for shell output streams (bytes/second). `0` disables rate limiting |
| `WEBSSH2_SSH_SOCKET_HIGH_WATER_MARK` | number | `16384` (16KB) | Socket.IO buffer threshold for stream backpressure control |

### Host Key Verification

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `WEBSSH2_SSH_HOSTKEY_ENABLED` | boolean | `false` | Enable or disable SSH host key verification |
| `WEBSSH2_SSH_HOSTKEY_MODE` | string | `hybrid` | Verification mode: `server`, `client`, or `hybrid` |
| `WEBSSH2_SSH_HOSTKEY_UNKNOWN_ACTION` | string | `prompt` | Action for unknown keys: `prompt`, `alert`, or `reject` |
| `WEBSSH2_SSH_HOSTKEY_DB_PATH` | string | `/data/hostkeys.db` | Path to the SQLite host key database (opened read-only by the app) |
| `WEBSSH2_SSH_HOSTKEY_SERVER_ENABLED` | boolean | *(from mode)* | Override: enable/disable server-side SQLite store |
| `WEBSSH2_SSH_HOSTKEY_CLIENT_ENABLED` | boolean | *(from mode)* | Override: enable/disable client-side browser store |

#### Host Key Verification Examples

**Enable hybrid mode (server-first, client fallback):**

```bash
WEBSSH2_SSH_HOSTKEY_ENABLED=true
WEBSSH2_SSH_HOSTKEY_MODE=hybrid
```

**Server-only with strict rejection of unknown keys:**

```bash
WEBSSH2_SSH_HOSTKEY_ENABLED=true
WEBSSH2_SSH_HOSTKEY_MODE=server
WEBSSH2_SSH_HOSTKEY_UNKNOWN_ACTION=reject
WEBSSH2_SSH_HOSTKEY_DB_PATH=/data/hostkeys.db
```

**Client-only (no server database needed):**

```bash
WEBSSH2_SSH_HOSTKEY_ENABLED=true
WEBSSH2_SSH_HOSTKEY_MODE=client
```

**Docker with host key database volume:**

```bash
docker run -d \
-p 2222:2222 \
-v /path/to/hostkeys.db:/data/hostkeys.db:ro \
-e WEBSSH2_SSH_HOSTKEY_ENABLED=true \
-e WEBSSH2_SSH_HOSTKEY_MODE=server \
webssh2:latest
```

#### Mode Behavior

The `mode` sets sensible defaults for which stores are enabled:

| Mode | Server Store | Client Store |
|------|-------------|-------------|
| `server` | enabled | disabled |
| `client` | disabled | enabled |
| `hybrid` | enabled | enabled |

Explicit `WEBSSH2_SSH_HOSTKEY_SERVER_ENABLED` and `WEBSSH2_SSH_HOSTKEY_CLIENT_ENABLED` override mode defaults.

See [CONFIG-JSON.md](./CONFIG-JSON.md) for `config.json` examples and the seeding script usage.

### SFTP Configuration

| Variable | Type | Default | Description |
Expand Down
Loading
Loading