Shellguard

by jonchun

302 downloads
Not rated
GitHub

About

ShellGuard is an MCP server that lets LLM agents securely SSH into remote Linux hosts with read-only shell access. Instead of copy-pasting terminal output, your AI can run safe diagnostics, inspect logs, query systems, and troubleshoot production, staging, or dev environments aut

Details

Author
jonchun
Downloads
302
Categories
Developer Tools, AI, Automation, Infrastructure

- Exposes seven read-only tools: connect, execute, list_commands, disconnect, provision, download_file, sleep
- Parses shell commands into AST and rejects unsafe syntax
- Validates commands against an allowlist with explicit denylist
- Supports native (built-in) and system (local ssh binary) SSH modes
- Verifies host keys with accept-new, strict, or off modes
- Probes and provisions diagnostic tools (rg, jq, yq) on remote hosts

Setting up with Highlight

This MCP is not yet compatible with Highlight’s one-click setup. However, you can still use it with Highlight by following these steps:

  1. Download and install Highlight from highlightai.com/download
  2. Navigate to the plugins tab and select "Add Custom Plugin"
  3. Configure the plugin with the settings below
    Plugin Name Shellguard
    Command (node, npx, python, etc.)

    Please refer to the README for specific instructions on how to obtain API keys or other required environment variables.

  4. Enable "Start Automatically" if you want the plugin to start when Highlight launches

From the repository

Install via Homebrew (brew install jonchun/tap/shellguard), curl, or Go install. Then add it as a stdio MCP server to your client (Cursor, Claude Desktop, Claude Code, OpenCode, VS Code, Zed, Roo Code) using the command shellguard with no arguments. Configuration for SSH can be set in a YAML file (~/.config/shellguard/config.yaml) or via environment variables.

Claude Desktop / Cursor

Paste into your MCP client config file to install this server.

{
    "mcpServers": {
        "shellguard": {
            "shellguard": {
                "command": "shellguard"
            }
        }
    }
}

McpServers

{
    "shellguard": {
        "command": "shellguard"
    }
}

ShellGuard

Stop copy-pasting terminal output into your AI. Let your LLM SSH in and look around.

ShellGuard is an MCP server that gives LLM agents read-only bash access to remote servers over SSH. Connect your AI to production, staging, or dev servers and let it run diagnostics, inspect logs, query databases, and troubleshoot -- hands-free.

Commands are restricted to a curated set of read-only tools. Destructive operations are blocked with actionable suggestions so the LLM can self-correct and keep investigating:

- wget -r -> "Recursive downloading is not allowed"
- tail -f -> "Follow mode hangs until timeout. Use tail -n 100 for recent lines."
- sed -> "Stream editing can modify files -- read-only access only. Use grep for searching."
- $HOME/file -> "Variable expansion will not expand. Use absolute paths."

Demo

Here's a demo of ShellGuard with OpenCode against a simulated server. It works extremely well against real servers too.

<video src="https://raw.githubusercontent.com/jonchun/shellguard/refs/heads/main/docs/videos/shellguard-demo.mp4" controls width="100%"></video>

Quick Start

Install

brew install jonchun/tap/shellguard

Or download the latest binary:

curl -fsSL https://raw.githubusercontent.com/jonchun/shellguard/main/install.sh | sh

Or with Go:

go install github.com/jonchun/shellguard/cmd/shellguard@latest

Configure with an MCP Client

ShellGuard starts as a stdio MCP server -- no arguments needed. Add it to your MCP client of choice:

<details>
<summary><b>Cursor</b></summary>

Go to: Settings -> Cursor Settings -> MCP -> Add new global MCP server

Or paste this into your ~/.cursor/mcp.json file. You can also install per-project by creating .cursor/mcp.json in your project folder. See Cursor MCP docs for more info.

{
  "mcpServers": {
    "shellguard": {
      "command": "shellguard"
    }
  }
}

</details>

<details>
<summary><b>Claude Desktop</b></summary>

Add the following to your Claude Desktop config file. See Claude Desktop MCP docs for more info.

- macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
- Windows: %APPDATA%\Claude\claude_desktop_config.json

{
  "mcpServers": {
    "shellguard": {
      "command": "shellguard"
    }
  }
}

</details>

<details>
<summary><b>Claude Code</b></summary>

Run this command. See Claude Code MCP docs for more info.

claude mcp add shellguard -- shellguard

</details>

<details>
<summary><b>OpenCode</b></summary>

Add this to your OpenCode configuration file. See OpenCode MCP docs for more info.

{
  "mcp": {
    "shellguard": {
      "type": "local",
      "command": ["shellguard"],
      "enabled": true
    }
  }
}

</details>

<details>
<summary><b>VS Code / GitHub Copilot</b></summary>

Add the following to your VS Code settings.json or .vscode/mcp.json. See VS Code MCP docs for more info.

User settings (settings.json)

{
  "mcp": {
    "servers": {
      "shellguard": {
        "type": "stdio",
        "command": "shellguard"
      }
    }
  }
}

Workspace config (.vscode/mcp.json)

{
  "servers": {
    "shellguard": {
      "type": "stdio",
      "command": "shellguard"
    }
  }
}

</details>

<details>
<summary><b>Zed</b></summary>

Add the following to your Zed settings file (~/.config/zed/settings.json). See Zed MCP docs for more info.

{
  "context_servers": {
    "shellguard": {
      "command": {
        "path": "shellguard",
        "args": []
      }
    }
  }
}

</details>

<details>
<summary><b>Roo Code</b></summary>

Go to: Roo Code Settings -> MCP Servers -> Edit MCP Settings

Or add the following to your Roo Code MCP settings file. See Roo Code MCP docs for more info.

{
  "mcpServers": {
    "shellguard": {
      "command": "shellguard"
    }
  }
}

</details>

What It Does

ShellGuard exposes 7 tools to the LLM:

| Tool | Description |
| --------------- | ------------------------------------------------------------- |
| connect | Establish an SSH connection to a remote host |
| execute | Run a read-only shell command on the remote host |
| list_commands | List available commands, optionally filtered by category |
| disconnect | Close SSH connection(s) |
| provision | Deploy diagnostic tools (rg, jq, yq) to the remote host |
| download_file | Download a file from the remote host via SFTP (50MB limit) |
| sleep | Wait between diagnostic checks (max 15s) |

The LLM connects to a server, runs commands, and reads the output -- the same workflow you'd do manually, but without the context-switching.

How It Works

Every command goes through a pipeline before reaching the remote host:

1. Parse -- bash is parsed into an AST. Shell tricks (semicolons, redirections, command substitution, etc.) are rejected at the syntax level.
2. Validate -- commands, flags, and arguments are checked against a curated allowlist of commands (with an explicit denylist). Default-deny.
3. Reconstruct -- arguments are re-quoted to prevent injection.
4. Execute -- the command runs over SSH with per-command timeouts and output truncation.

For full details, see ARCHITECTURE.md.

SSH Configuration

Authentication

ShellGuard tries authentication methods in this order, stopping at the first success:

| Priority | Method | Source | On failure |
| -------- | ------ | ------ | ---------- |
| 1 | Explicit key | identity_file parameter in connect | Fatal -- connection fails immediately |
| 2 | ssh-agent | SSH_AUTH_SOCK unix socket | Silent -- skipped |
| 3 | Default keys | ~/.ssh/id_ed25519, id_ecdsa, id_rsa | Silent -- skipped |

Passphrase-protected keys are silently skipped during default key discovery. If you specify a passphrase-protected key via identity_file, the connection will fail. Add the key to your agent first: ssh-add ~/.ssh/my_key.

SSH Modes

ShellGuard supports two SSH modes:

| Mode | Description |
| ---- | ----------- |
| native | (Default) Uses Go's built-in SSH library. Reads ~/.ssh/config for HostName, User, Port, and IdentityFile. Lightweight, no external dependencies. |
| system | Uses the local ssh binary. Full ~/.ssh/config support including ProxyJump, ProxyCommand, Match blocks, and all other OpenSSH features. Requires ssh to be installed. |

If you connect through bastion hosts, use ProxyJump, or rely on Match blocks in your SSH config, enable system mode:

ssh:
  mode: system
export SHELLGUARD_SSH_MODE=system

If mode is set to system but the ssh binary is not found, ShellGuard logs a warning and falls back to native mode.

Native mode limitations: Match directives, ProxyJump, ProxyCommand, and ForwardAgent are not supported. Use system mode if your infrastructure requires these features.

System mode notes:
- Host key verification is handled entirely by OpenSSH. The host_key_checking and known_hosts_file settings only apply in native mode.
- Connections are multiplexed using OpenSSH ControlMaster, so only the first connection per host pays the SSH handshake cost.

Host Key Verification

ShellGuard verifies SSH host keys using ~/.ssh/known_hosts. Three modes are available:

| Mode | Behavior |
| ---- | -------- |
| accept-new | (Default) Trust-on-first-use. Unknown hosts are accepted and written to known_hosts. Key changes are rejected. |
| strict | Require the host key to already exist in known_hosts. Unknown hosts are rejected. |
| off | Disable host key verification entirely. |

If a host key has changed, remove the old entry from your known_hosts file.

Configuration

Settings can be specified in a YAML config file or via environment variables. Environment variables take precedence.

Config file location: $XDG_CONFIG_HOME/shellguard/config.yaml (default: ~/.config/shellguard/config.yaml)

ssh:
  mode: "native"                   # native | system
  connect_timeout: "10s"           # default 10s
  retries: 2                       # default 2
  retry_backoff: "250ms"           # default 250ms
  host_key_checking: "accept-new"  # accept-new | strict | off (native mode only)
  known_hosts_file: "~/.ssh/known_hosts"  # native mode only

| YAML field | Environment variable | Default | Description |
| ---------- | -------------------- | ------- | ----------- |
| ssh.mode | SHELLGUARD_SSH_MODE | native | SSH mode: native (built-in) or system (uses local ssh binary) |
| ssh.connect_timeout | SHELLGUARD_SSH_CONNECT_TIMEOUT | 10s | TCP + SSH handshake timeout |
| ssh.retries | SHELLGUARD_SSH_RETRIES | 2 | Connection/execution retry attempts |
| ssh.retry_backoff | SHELLGUARD_SSH_RETRY_BACKOFF | 250ms | Base backoff (exponential: backoff * 2^attempt) |
| ssh.host_key_checking | SHELLGUARD_SSH_HOST_KEY_CHECKING | accept-new | Host key verification mode (native mode only) |
| ssh.known_hosts_file | SHELLGUARD_SSH_KNOWN_HOSTS_FILE | ~/.ssh/known_hosts | Path to known_hosts file (native mode only) |

Toolkit Provisioning

Remote servers don't always have the tools you want. On connect, ShellGuard probes for rg, jq, and yq. If any are missing, the LLM can call provision to deploy them:

| Tool | Version | Architectures |
| -------------- | ------- | --------------- |
| rg (ripgrep) | 14.1.1 | x86_64, aarch64 |
| jq | 1.7.1 | x86_64, aarch64 |
| yq | 4.52.2 | x86_64, aarch64 |

Binaries are downloaded from GitHub Releases with SHA-256 verification, cached locally, and deployed to ~/.shellguard/bin/ on the remote host.

Library Usage

ShellGuard can be used as a Go library:

package main

import (
"context"
"log/slog"
"os"

"github.com/jonchun/shellguard"
)

func main() {
ctx := context.Background()
logger := slog.New(slog.NewTextHandler(os.Stderr, nil))

err := shellguard.RunStdio(ctx, shellguard.Config{Logger: logger})
if err != nil {
os.Exit(1)
}
}

See the Custom Configuration and Custom Executor sections below for advanced usage.

Custom Configuration

import (
    "github.com/jonchun/shellguard"
    "github.com/jonchun/shellguard/manifest"
    "github.com/jonchun/shellguard/server"
)

manifests, _ := manifest.LoadEmbedded()
// Add or remove commands as needed

core, err := shellguard.New(shellguard.Config{
Manifests: manifests, // Custom registry (nil = embedded defaults)
Executor: myCustomExecutor, // Custom backend (nil = SSH)
Name: "my-server", // MCP server name
Version: "1.0.0", // MCP server version
})

Custom Executor Backend

Implement the server.Executor interface to use non-SSH backends:

type Executor interface {
    Connect(ctx context.Context, params ssh.ConnectionParams) error
    Execute(ctx context.Context, host, command string, timeout time.Duration) (ssh.ExecResult, error)
    ExecuteRaw(ctx context.Context, host, command string, timeout time.Duration) (ssh.ExecResult, error)
    SFTPSession(host string) (ssh.SFTPClient, error)
    Disconnect(host string) error
}

Testing

make test          # Run all tests
make test-race     # Run with race detector
make lint          # Run go vet

Project Structure

shellguard/
  shellguard.go          # Top-level constructor (New, RunStdio)
  cmd/shellguard/        # CLI entrypoint
  server/                # MCP server core, tool registration, Executor interface
  parser/                # Shell AST parser (mvdan.cc/sh/v3)
  validator/             # Command/flag/SQL validation engine
  manifest/              # YAML command registry (embed.FS)
    manifests/           # allowed command manifests
    manifests/denied/    # denied command manifests
  ssh/                   # SSH manager, ShellQuote, ReconstructCommand
  output/                # Output truncation (64KB cap)
  toolkit/               # Diagnostic tool provisioning (rg, jq, yq)

License

Apache License 2.0. See LICENSE for details.

No reviews yet — be the first

Sign in to leave a review

Use Google, GitHub, or an email account so ratings stay tied to real people.

Email sign in

No reviews posted yet.