Skip to content

Reimplement SSH server in Rust using bashkit#1

Open
chaliy wants to merge 11 commits intomainfrom
claude/reimplement-ssh-rust-Tu4mM
Open

Reimplement SSH server in Rust using bashkit#1
chaliy wants to merge 11 commits intomainfrom
claude/reimplement-ssh-rust-Tu4mM

Conversation

@chaliy
Copy link
Copy Markdown

@chaliy chaliy commented Apr 6, 2026

Summary

  • Reimplement the SSH server in Rust using russh (SSH protocol) and bashkit (sandboxed bash interpreter), replacing the TypeScript ssh2 + just-bash stack
  • Mount docs directory read-only via bashkit's realfs feature with path traversal protection
  • Add line editor with arrow keys, history, and readline shortcuts for interactive shell sessions
  • Port security attack surface tests and add LRU cache unit tests (40 tests total)

What's included

Core modules (crates/supabase-ssh/src/):

  • ssh.rs — SSH server with exec/shell modes, probabilistic soft/hard connection limit ramp, per-IP concurrency, idle timeout, graceful shutdown (SIGTERM/SIGINT)
  • bash.rs — bashkit sandbox setup with execution limits (1000 commands, 1000 loops, 50 recursion depth, 10s timeout, 1MB output), memory limits, session limits, realfs read-only mount, custom ssh command blocker
  • session.rs — Interactive shell REPL over SSH channels
  • line_editor.rs — Terminal input handling: arrow keys, history (100 entries), Ctrl+A/E/U/K/W/C/D/L, Home/End, Delete, backspace, insert-in-middle
  • cache.rs — LRU command cache (safe because VFS is read-only)

Tests (40 total):

  • 8 cache unit tests (LRU eviction, promotion, stats, max output skip)
  • 10 line editor unit tests (all key interactions)
  • 4 integration tests (SSH handshake, exec, exit codes, realfs mount)
  • 18 security tests ported from attacks.test.ts (infinite loops, output flooding, string amplification, recursion depth, command count, sed/awk loops, brace expansion, read-only FS, timeout enforcement, concurrency)

Security parity with TS version:

  • All auth methods go through connection limit checks
  • defenseInDepth is N/A (JS-specific — bashkit has no host runtime to escape into)
  • bashkit's coarser limits (global timeout + command count + output cap) cover the same threats as just-bash's 13 granular per-builtin limits

Test plan

  • RUST_MIN_STACK=8388608 cargo test — all 40 tests pass
  • cargo test --test integration — SSH protocol: banner, exec, exit codes, realfs doc reading
  • cargo test --test security — all 18 attack surface tests pass
  • cargo test --lib — cache + line editor unit tests
  • Manual: PORT=2222 cargo run then ssh -p 2222 localhost 'cat /supabase/docs/...'
  • Manual: interactive shell with arrow keys and history

https://claude.ai/code/session_01NFHnNXnCq5kXxuWmRPdmdW

claude added 11 commits April 6, 2026 00:04
Replace the TypeScript SSH server (ssh2 + just-bash) with a pure Rust
implementation using russh for the SSH protocol and bashkit for the
sandboxed bash interpreter.

Modules:
- ssh.rs: SSH server with exec/shell modes, connection limits, per-IP
  concurrency tracking, and graceful auth handling
- bash.rs: bashkit sandbox setup with execution limits, virtual
  filesystem, and docs mounting at /supabase/docs
- session.rs: interactive shell REPL over SSH channels with line
  editing, Ctrl+C/D handling, and prompt with cwd tracking
- cache.rs: LRU command cache for read-only filesystem results
- main.rs: entry point with env-based configuration matching the
  TypeScript server's environment variables

https://claude.ai/code/session_01NFHnNXnCq5kXxuWmRPdmdW
Three tests verifying end-to-end SSH functionality:
- TCP banner exchange
- Full SSH handshake + exec `echo hello world` via bashkit
- Non-zero exit code for failing commands (`false`)

https://claude.ai/code/session_01NFHnNXnCq5kXxuWmRPdmdW
- bash.rs: Use bashkit's `realfs` feature with `mount_real_readonly_at`
  to mount the host docs directory read-only at /supabase/docs instead
  of an empty in-memory overlay
- ssh.rs: Enforce session_timeout_secs via tokio::time::timeout around
  the shell session, sending a colored timeout message before disconnect
- lib.rs: Add library crate so integration tests can access create_bash
- tests: Add exec_cat_doc_from_realfs_mount test that writes a real file
  to a temp dir, mounts it via realfs, and reads it back over SSH

https://claude.ai/code/session_01NFHnNXnCq5kXxuWmRPdmdW
Replace the raw byte-by-byte input handler with a proper line editor
that processes ANSI escape sequences over SSH channels.

Supports:
- Arrow keys: left/right cursor movement, up/down history navigation
- Home/End keys (and Ctrl+A/Ctrl+E)
- Ctrl+U (kill to start), Ctrl+K (kill to end), Ctrl+W (kill word)
- Ctrl+C (cancel line), Ctrl+D (EOF or delete char)
- Ctrl+L (clear screen)
- Backspace and Delete key
- Insert in middle of line with proper redraw
- Command history (100 entries, dedup)

10 unit tests covering all key interactions.

https://claude.ai/code/session_01NFHnNXnCq5kXxuWmRPdmdW
Fixes:
- Add SessionLimits (10K total commands, 500 exec calls) and
  MemoryLimits (10K array entries, 1MB variable storage) to match
  the defense-in-depth of the TS version
- Tighten execution timeout from 30s to 10s (matching TS execTimeout)
- Add max_total_loop_iterations (10K) and max_input_bytes (1MB)
- Fix auth bypass: all auth methods (none/password/pubkey) now go
  through the same connection limit check
- Fix exec error handling: bashkit ResourceLimit errors now return
  stderr + exit code 1 to the client instead of dropping the connection

Tests ported from attacks.test.ts:
- Infinite loops (while true, until false)
- Output flooding (bounded to 1MB)
- String/memory amplification
- Array construction limits
- Recursion depth (max_function_depth=50)
- Command count exhaustion (1500 commands)
- sed loop amplification
- Read-only realfs mount (write/mkdir/rm rejected)
- In-memory writes sandboxed (can't reach host)
- Execution timeout enforcement (<15s)
- Concurrent instance fairness

https://claude.ai/code/session_01NFHnNXnCq5kXxuWmRPdmdW
Replace the hard cutoff at max_connections with a linear probability
ramp matching the TS implementation:

- Below soft_limit (80%): always accept
- Between soft and hard: linearly increasing drop probability
- Above hard_limit (100%): always reject

e.g. with max_connections=100: at 80 conns 0% drop, at 90 conns 50%
drop, at 100+ conns 100% drop.

https://claude.ai/code/session_01NFHnNXnCq5kXxuWmRPdmdW
- Register custom `ssh` builtin via bashkit's Builtin trait that returns
  a helpful error message matching the TS version's behavior
- Add per-connection idle timeout: spawns a watcher task that checks
  last_activity every 5s and disconnects after idle_timeout_secs (60s
  default) with a friendly reconnect message
- Add graceful shutdown: SIGTERM/SIGINT triggers a select! that stops
  the server and allows 2s drain for in-flight commands

https://claude.ai/code/session_01NFHnNXnCq5kXxuWmRPdmdW
Tests:
- 8 cache unit tests (miss, hit, cwd differentiation, stats, LRU
  eviction, LRU promotion, max output skip, update without eviction)
- 4 new security tests (brace expansion bomb, command substitution
  depth, arithmetic tight loop, awk infinite loop)

README documents:
- Quick start (keygen, cargo run, ssh connect)
- All env vars with defaults
- Architecture overview (module layout)
- Security model summary
- Build and test commands
- Docker example

Total: 40 tests (18 unit + 4 integration + 18 security)

https://claude.ai/code/session_01NFHnNXnCq5kXxuWmRPdmdW
0.1.15 includes 20+ security fixes: regex size limits, memory
exhaustion caps, sandbox escape fix, credential leak prevention,
header injection mitigation, AWK getline/printf constraints, curl
multipart sanitization, path traversal prevention, and recursion
depth limits in Python conversions.

No API changes — drop-in replacement. Updated test stack size
requirement from 8MB to 16MB for recursion depth test.

https://claude.ai/code/session_01NFHnNXnCq5kXxuWmRPdmdW
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.

2 participants