shell-tunnel
Ultra-lightweight shell tunnel for AI agent integration.
A zero-dependency, single binary that enables AI agents to control remote terminals via REST/WebSocket API.
What it is (and isn't)
shell-tunnel is a gateway for AI agents, not a human-facing web terminal. It exposes
remote command execution as a structured REST/WebSocket API — typed JSON requests and
responses, resource-style sessions — so an agent can drive a remote environment
programmatically. The base platform (sessions, execution, streaming, auth, rate limiting)
is implemented; the differentiating safe control protocol (permission-scoped tokens,
audit trail, self-hosted relay, native MCP tools) is on the roadmap — see
docs/CURRENT-STATUS.md.
Non-Goals
- Not a browser terminal emulator or screen-sharing UI (cf. ttyd / gotty / wetty / sshx) — there is no xterm.js front-end and no human terminal-sharing.
- Not a remote-desktop or mobile control app — the consumer is an agent/program, not a person at a screen.
- Not a general-purpose tunneling / reverse-proxy product — transport is borrowed; the value is the agent-facing control protocol layered on top.
- Not a multi-user collaboration surface.
Features
- Cross-platform: Windows (ConPTY), Linux, macOS (PTY)
- Lightweight: ~2MB binary, minimal resource footprint
- Real-time streaming: WebSocket support for live output
- Secure: capability-scoped bearer tokens (fine-grained
exec/session.read/session.manage, with role presets) and rate limiting (command-content validation primitives ship but are not yet wired into the server — see Input Validation) - Zero-dependency core: the default build links no update/HTTP stack
- Self-updating (opt-in): in-binary updates from GitHub Releases — bundled in official release binaries, enabled for source builds with
--features self-update
Installation
From GitHub Releases (Recommended)
Download the latest binary for your platform:
# Linux x64
# macOS (Apple Silicon)
# Windows (PowerShell)
From crates.io
From Source
Quick Start
# Start server with defaults (localhost:3000, no auth)
# Start with API key authentication
# Start on all interfaces
# Development mode (no security)
CLI Options
| Option | Description | Default |
|---|---|---|
-H, --host <ADDR> |
Host address to bind | 127.0.0.1 |
-p, --port <PORT> |
Port to listen on | 3000 |
-c, --config <FILE> |
Path to config file (JSON) | - |
-k, --api-key <KEY> |
API key for authentication | - |
-l, --log-level <LVL> |
Log level (error, warn, info, debug, trace) | info |
--no-auth |
Disable authentication | false |
--require-auth |
Require auth, auto-generating an API key if none given | false |
--capabilities <C> |
Scope issued token(s) to comma-separated capabilities (e.g. exec,session.read) |
full-control |
--preset <NAME> |
Scope issued token(s) by role preset (operator / read-only / full-control) |
full-control |
--no-rate-limit |
Disable rate limiting | false |
--cors-allow-any |
Allow any CORS origin (opt-in; for browser UIs) | false |
--check-update |
Check for updates and exit (self-update builds only) | - |
--update |
Download and install latest version (self-update builds only) | - |
--no-update-check |
Disable automatic update check on startup (self-update builds only) | false |
-h, --help |
Print help | - |
-V, --version |
Print version | - |
Environment Variables
| Variable | Description |
|---|---|
SHELL_TUNNEL_HOST |
Host address |
SHELL_TUNNEL_PORT |
Port number |
SHELL_TUNNEL_API_KEY |
API key |
SHELL_TUNNEL_LOG_LEVEL |
Log level |
RUST_LOG |
Alternative log level |
API Usage
Health Check
# OK
Execute Command (One-shot)
Response:
Session-based Execution
# Create session
# Response: {"session_id": 1, "session_id_str": "sess-00000001"}
# Execute in session
# Delete session
API Endpoints
Required capability applies only when auth is enabled. A route with no specific
capability just needs a valid token; * (full-control) tokens satisfy every route.
| Method | Endpoint | Description | Required capability |
|---|---|---|---|
GET |
/health |
Health check | (none — no auth) |
GET |
/api/v1 |
API information | (authenticated only) |
GET |
/api/v1/sessions |
List all sessions | session.read |
POST |
/api/v1/sessions |
Create a new session | session.manage |
GET |
/api/v1/sessions/{id} |
Get session status | session.read |
DELETE |
/api/v1/sessions/{id} |
Delete a session | session.manage |
POST |
/api/v1/sessions/{id}/execute |
Execute command in session | exec |
POST |
/api/v1/execute |
Execute command (one-shot) | exec |
WS |
/api/v1/sessions/{id}/ws |
WebSocket streaming | exec |
WS |
/api/v1/ws |
WebSocket one-shot | exec |
Configuration File
Create a JSON config file for complex setups:
Auto-Update
The core binary is zero-dependency and ships without an update stack. In-binary
self-update is an opt-in build feature (self-update): it is bundled in the official
release binaries, so downloads from GitHub Releases already support it. When building
from source, enable it explicitly:
With the feature enabled:
# Check for updates
# Self-update to latest version
Such builds check for updates on startup (can be disabled with --no-update-check).
Builds without the feature omit these flags entirely.
Security
Authentication & Capabilities
- Opaque bearer tokens via the
Authorization: Bearer <token>header. /healthbypasses authentication (for monitoring).- Disabled by default for ease of use. Enable with
--api-key <KEY>, or with--require-authto turn auth on and have a key auto-generated (and logged) at startup.
Capability model. Each token carries a set of capability strings; each route declares a required capability, and access is a set-membership check:
- Capabilities:
exec(run commands),session.read(list/inspect sessions),session.manage(create/delete sessions).*is a wildcard satisfying every check. - Missing/invalid token → 401; valid token lacking the required capability → 403.
- Role presets (convenience, not a wire contract):
operator={exec, session.read, session.manage},read-only={session.read},full-control={*}.
Issuing scoped tokens. Passing --capabilities/--preset turns auth on (a scope with auth
off would be silently ignored), so --preset read-only alone starts a locked server; --no-auth
still overrides. Without --capabilities/--preset, a key is full-control (backward-compatible
— an existing --api-key / --require-auth key can never hit a 403):
# A read-only token (may list/inspect sessions, cannot exec or create)
# A token scoped to exactly these capabilities
Rate Limiting
- Default: 100 requests/minute per IP
- Response headers:
X-RateLimit-Limit,X-RateLimit-Remaining(andRetry-Afteron a 429)
Input Validation
Status: library primitive, not yet enforced by the built-in server.
shell-tunnel ships a CommandValidator primitive (command-length limits, dangerous-pattern
detection for fork bombs / rm -rf / / disk-wipe / shutdown, and null-byte rejection) and a
companion path validator (traversal / null-byte / length). These are public, unit-tested library
primitives, but the built-in server does not currently invoke them on the execute paths — a
command sent to /api/v1/execute (or a session/WS execute) is not pattern-filtered today, and a
working_dir is not traversal-checked.
The primary access control is the capability token: withhold
exec and a token cannot run commands at all. Command-content filtering (dangerous-substring
matching) is a bypassable secondary defence and remains unwired — so a token that does hold
exec can run any command today. Consumers embedding the crate can call
CommandValidator::validate_command directly in the meantime; wiring it into the server is a
deferred product decision.
CORS
- Restrictive by default: no permissive CORS headers are emitted. CORS is a browser-only mechanism, so this has no effect on AI-agent/CLI clients, while it reduces the CSRF / cross-origin surface a malicious web page could exploit against a local instance (the JSON execute endpoints require a preflight, which is not approved).
- Enable permissive CORS for a trusted browser UI with
--cors-allow-any(orsecurity.cors.allow_anyin the config file). - Scope note: CORS does not stop DNS-rebinding attacks (the request is same-origin after rebinding); Host-header validation is the complementary control and is tracked on the roadmap.
License
MIT License - see LICENSE for details.