Skip to content

feat: SSH host key verification (TOFU)#488

Merged
billchurch merged 31 commits intomainfrom
feature/host-key-verification
Feb 26, 2026
Merged

feat: SSH host key verification (TOFU)#488
billchurch merged 31 commits intomainfrom
feature/host-key-verification

Conversation

@billchurch
Copy link
Owner

Summary

Implements SSH host key verification using the Trust On First Use (TOFU) model. This adds server-side infrastructure for verifying SSH host keys during connection, with three configurable modes: server-only (SQLite), client-only (browser localStorage), and hybrid (server-first with client fallback).

Key changes:

  • Config types & defaults: HostKeyVerificationConfig with Zod schema, env var mapping (WEBSSH2_HOSTKEYVERIFY_*), and resolveHostKeyMode for mode-to-store expansion
  • SQLite key store: HostKeyStore wrapping better-sqlite3 for persistent server-side key storage with lookup/save/delete operations
  • HostKeyService: Fingerprint computation (SHA-256), key lookup across server store, and service factory registration
  • hostVerifier callback: Factory function producing SSH2-compatible hostVerifier callbacks that integrate with Socket.IO for interactive client verification
  • Socket protocol: 6 new hostkey:* Socket.IO events for verify/response/verified/mismatch/alert/rejected flows
  • Permissions broadcasting: Host key config sent to client via existing permissions event
  • Seeding script: scripts/seed-host-keys.ts for pre-populating the SQLite store
  • Integration tests: Full verification flow tests covering all modes and edge cases
  • Documentation: Protocol reference with mermaid diagrams, configuration guides for config.json, env vars, and constants

Testing

  • All 1,214 tests pass across 78 test files
  • 0 lint errors, 0 typecheck errors
  • New unit tests for HostKeyStore, HostKeyService, config processing
  • New integration tests for the full hostVerifier flow

Test plan

  • Verify npm run lint passes with 0 errors
  • Verify npm run typecheck passes
  • Verify npm run test passes all 1,214 tests
  • Verify npm run build succeeds
  • Manual test: connect to unknown host with mode: 'hybrid' — client prompt appears
  • Manual test: reconnect to same host — key is trusted from store
  • Manual test: mode: 'alert' — connection proceeds with warning
  • Manual test: mode: 'server' — keys stored in SQLite only

Add HostKeyServerStoreConfig, HostKeyClientStoreConfig, and
HostKeyVerificationConfig interfaces. Add hostKeyVerification
property to SSHConfig interface.
Add default hostKeyVerification config (disabled, hybrid mode,
prompt on unknown). Add WEBSSH2_SSH_HOSTKEY_* environment variable
mappings. Add clone function for deep-copying the config.
The resolveHostKeyMode function expands mode shorthand (server,
client, hybrid) into serverStore.enabled and clientStore.enabled
flags. Explicit store flags from file/env config override mode
defaults. Includes 10 tests covering all modes, overrides,
immutability, and default values.
Add better-sqlite3 as runtime dependency and @types/better-sqlite3
as dev dependency for the SQLite-backed host key store.
Read-only SQLite store for server-side host key verification.
Supports lookup (trusted/mismatch/unknown) and getAll operations.
Degrades gracefully when database file is missing. Includes 16
tests covering all lookup scenarios, port/algorithm discrimination,
missing DB, and close behavior.
HostKeyService coordinates host key verification using server-side
and client-side stores. Provides config getters, server store lookup
delegation, and SHA-256 fingerprint computation matching OpenSSH
format. Includes 13 tests covering getters, store delegation,
fingerprint determinism, and close behavior.
Add HostKeyService to the Services interface and create it in the
factory when host key verification is enabled. Add Zod validation
schema for HostKeyVerificationConfig to ensure the config passes
through schema validation without being stripped.
Add HOSTKEY_VERIFY, HOSTKEY_VERIFY_RESPONSE, HOSTKEY_VERIFIED,
HOSTKEY_MISMATCH, HOSTKEY_ALERT, and HOSTKEY_REJECTED events
for host key verification client-server communication.
Add createHostKeyVerifier factory that returns an async callback for
SSH2 hostVerifier option. Implements the full decision tree: server
store lookup, client store verification with socket events, timeout
handling, and unknownKeyAction fallback (alert/reject/prompt).

Includes 9 unit tests covering all branches of the verification flow.
Add socket property to SSHConfig interface for host key verification.
SSHServiceImpl now accepts optional HostKeyService via constructor and
creates the hostVerifier callback when both the service and socket are
available. The factory creates HostKeyService before SSHServiceImpl.

Updated verifier to use SSH2 HostVerifier callback-style API with
extractAlgorithm helper to parse algorithm from raw key buffer.
Emit hostKeyVerification config (enabled, clientStoreEnabled,
unknownKeyAction) in the permissions payload after successful auth.
Pass socket reference through SSHConfig for hostVerifier communication.

Update socket contract types, test mocks, and contract assertions to
include the new hostKeyVerification field in permissions.
…issions

Update the legacy SocketAdapter.emitPermissions to also include
hostKeyVerification config in the permissions payload for consistency
with the service-based socket adapter.
Prevents runtime 'Cannot read properties of undefined' errors in
socket-v2 tests when emitAuthSuccess accesses hostKeyVerification.
Tests the full server-side verification pipeline end-to-end using
a temp SQLite DB, real HostKeyService, and createHostKeyVerifier
with mock sockets. Covers all five decision tree branches: trusted
key, mismatch, unknown+reject, unknown+alert, and feature disabled.
Create scripts/host-key-seed.ts CLI tool for managing the SQLite
host key database. Supports --host (SSH probe), --hosts (batch file),
--known-hosts (OpenSSH import), --list, --remove, and --help commands.
Add "hostkeys" npm script to package.json.

Also fix lint issues in the integration test file (proper Socket type
import, vi import from vitest).
- Fix 17 lint errors (duplicate imports, inline import() types,
  negated condition, naming convention, structuredClone)
- Add disconnect cleanup to awaitClientVerification to prevent
  30s resource linger on client disconnect
- Add input validation for hostkey:verify-response payload
  (untrusted client data at security boundary)
- Replace ASCII diagrams with mermaid sequence diagrams and
  flowchart in protocol reference
- Add hostKeyVerification section to CONFIG-JSON.md with all options,
  default config, use case examples, and seeding script usage
- Add 6 WEBSSH2_SSH_HOSTKEY_* env vars to ENVIRONMENT-VARIABLES.md
  with mode behavior table and Docker example
- Add SOCKET_EVENTS host key constants to CONSTANTS.md
- Add hostKeyVerification block to default config example
@socket-security
Copy link

socket-security bot commented Feb 25, 2026

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​types/​better-sqlite3@​7.6.131001007181100
Updatedwebssh2_client@​3.3.0 ⏵ 3.4.077 +1100100 +196100
Addedbetter-sqlite3@​12.6.210010010089100

View full report

…n hostkeys script

- Add hostKeyVerification to /ssh/config HTTP endpoint so the client
  receives the config at page load, before any socket connection. This
  allows the Trusted Host Keys settings section to be visible immediately
  rather than only after authenticating.
- Also emit hostKeyVerification in an early permissions event on socket
  connect (before auth) for the socket-connected-but-not-yet-authenticated
  case.
- Make session-related permissions fields optional in the socket contract
  to support partial pre-auth emissions.
- Add WEBSSH2_SSH_HOSTKEY_DB_PATH env var support to the hostkeys CLI
  script (resolution: --db arg > env var > config.json > default).
- Add data/ to .gitignore.
Add tests for SSH config handler hostKeyVerification response, socket
adapter permissions emission, and host-key-seed CLI helpers. Export
extractDbPathFromConfig, resolveDbPath, and parseArgs for testability.
Fix S2871 unstable .sort() in contract tests, update socket contract
assertions to account for pre-auth permissions emission order.
- Replace hardcoded /tmp paths with :memory: in test configs (S5443)
- Use void for side-effect-only constructor calls (S1848)
- Add localeCompare to .sort() for reliable string ordering (S2871)
- Extract shared test fixtures to reduce cross-file duplication
- Extract helper functions to reduce self-duplication in verifier and
  integration tests
Remove unnecessary type assertion in socket-contracts and fix negated
condition in host-key-seed tests.
Update rollup from 4.57.1 to 4.59.0 to address high severity arbitrary
file write vulnerability. Exception to 2-week age-out policy granted
due to severity. Update SECURITY.md with assessment and review dates.
…4623, S6557, S4043, S7763, S4325, S6594, S7781, S7755, S7784, S7924)

Address 50 SonarQube issues spanning CRITICAL, MAJOR, and MINOR severities:

- Remove unnecessary void operator from test describes (S3735, 12 issues)
- Extract loadFileConfig helper to reduce cognitive complexity in config.ts (S3776)
- Use String#endsWith instead of regex suffix checks in scripts (S6557)
- Use toReversed() to avoid in-place array mutation (S4043)
- Remove redundant undefined argument where default param exists (S4623)
- Fix CSS contrast ratio for WCAG AA compliance (S7924)
- Convert import-then-export to export...from syntax (S7763)
- Remove unnecessary type assertions in test files (S4325)
- Use RegExp.exec() instead of String.match() (S6594)
- Use String#replaceAll() instead of replace with global regex (S7781)
- Prefer .at(-1) over [.length - 1] indexing (S7755)
- Use structuredClone() instead of JSON.parse(JSON.stringify()) (S7784)
- Fix TypeScript index signature access and argument type safety (TS4111, TS2345)
- Add structuredClone to ESLint globals for Node.js 22+
@sonarqubecloud
Copy link

@billchurch billchurch merged commit dc22703 into main Feb 26, 2026
8 checks passed
@billchurch billchurch deleted the feature/host-key-verification branch February 26, 2026 17:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant