{"id":47802152,"url":"https://github.com/morgaesis/ssh-guard","last_synced_at":"2026-04-03T17:06:45.611Z","repository":{"id":347192142,"uuid":"1179949044","full_name":"morgaesis/ssh-guard","owner":"morgaesis","description":"LLM-powered SSH command filter for AI agents. Evaluate every command before execution.","archived":false,"fork":false,"pushed_at":"2026-03-27T10:51:55.000Z","size":554,"stargazers_count":0,"open_issues_count":5,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-27T12:49:17.775Z","etag":null,"topics":["agent-safety","ai-agents","command-filter","devops","llm","safe-ssh","security","ssh","ssh-wrapper"],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/morgaesis.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":"ROADMAP.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-12T14:46:04.000Z","updated_at":"2026-03-27T10:51:58.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/morgaesis/ssh-guard","commit_stats":null,"previous_names":["morgaesis/ssh-guard"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/morgaesis/ssh-guard","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/morgaesis%2Fssh-guard","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/morgaesis%2Fssh-guard/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/morgaesis%2Fssh-guard/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/morgaesis%2Fssh-guard/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/morgaesis","download_url":"https://codeload.github.com/morgaesis/ssh-guard/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/morgaesis%2Fssh-guard/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31364727,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-03T15:19:21.178Z","status":"ssl_error","status_checked_at":"2026-04-03T15:19:20.670Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agent-safety","ai-agents","command-filter","devops","llm","safe-ssh","security","ssh","ssh-wrapper"],"created_at":"2026-04-03T17:06:44.605Z","updated_at":"2026-04-03T17:06:45.576Z","avatar_url":"https://github.com/morgaesis.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ssh-guard\n\nSSH wrapper that sends every command to an LLM for approval before execution. Give your AI agents SSH access without giving them the keys to the kingdom.\n\n```bash\nssh-guard prod-server 'ls -la /etc/nginx/'\n# drwxr-xr-x 8 root root 4096 Mar 10 14:22 .\n# -rw-r--r-- 1 root root 1482 Mar 10 14:22 nginx.conf\n# ...\n\nssh-guard prod-server 'rm -rf /etc/nginx/'\n# ssh-guard: DENIED (risk=9) - Recursive deletion of system config directory.\n```\n\nZero output on approval. Denied commands print the reason and risk score.\n\n![ssh-guard demo](./docs/demo.svg)\n\n## Why\n\nAI agents (Claude Code, Aider, OpenHands, CrewAI, LangChain, etc.) increasingly need SSH access to remote servers for debugging, log analysis, and ops tasks. But a single hallucinated `rm -rf` or `kubectl delete namespace` can take down production.\n\nssh-guard sits between the agent and SSH. Every command gets evaluated by a fast, cheap LLM call (Gemini Flash by default via OpenRouter, ~0.001c per decision) before it reaches the server. Approved commands run silently. Denied commands return an error with explanation.\n\n## Install\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/morgaesis/ssh-guard/main/install.sh | bash\n```\n\nOr build from source: `cargo install --path .`\n\nSee [INSTALL.md](INSTALL.md) for all options (manual download, specific versions, provider setup, agent integration).\n\n## Quick start\n\n```bash\n# Set your API key (OpenRouter, or any OpenAI-compatible endpoint)\nexport SSH_GUARD_API_KEY=\"your-key-here\"\n# Or: export OPENROUTER_API_KEY=\"your-key-here\"\n\n# Use it like ssh\nssh-guard myserver 'uptime'\nssh-guard myserver 'cat /var/log/syslog'\nssh-guard myserver 'sudo systemctl status nginx'\n\n# These will be denied in readonly mode:\nssh-guard myserver 'rm -rf /tmp/*'\nssh-guard myserver 'systemctl restart nginx'\n```\n\n## Modes\n\nThree built-in policies, set via `SSH_GUARD_MODE`:\n\n| Mode       | Default | Use case                                                                                                                                                  |\n| ---------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `readonly` | Yes     | Agents that only need to observe. Blocks all writes, installs, service changes.                                                                           |\n| `paranoid` |         | Like readonly, but also blocks reading file contents, env vars, logs. Only structural metadata (ls, ps, df, etc). Prevents secret exfiltration.           |\n| `safe`     |         | Agents that need to do work. Allows targeted writes and service restarts, blocks destructive/broad operations (rm -rf, reboot, kubectl delete namespace). |\n\n```bash\nSSH_GUARD_MODE=safe ssh-guard server 'systemctl restart myapp'  # allowed\nSSH_GUARD_MODE=paranoid ssh-guard server 'cat /etc/passwd'      # denied\n```\n\nAll modes evaluate `sudo` by the underlying command, not the keyword itself:\n\n```bash\nsudo ls /etc/nginx/        # readonly: allowed (read operation)\nsudo rm -rf /etc/nginx/    # readonly: denied  (write operation)\nsudo systemctl restart app # safe: allowed     (targeted restart)\n```\n\n## Configuration\n\nAll configuration via environment variables or `.env` files.\n\nssh-guard walks up from your current directory to `/` looking for `.env` files (closest wins), so you can scope config per project.\n\n| Variable                | Default                                         | Description                            |\n| ----------------------- | ----------------------------------------------- | -------------------------------------- |\n| `SSH_GUARD_API_KEY`     | `$OPENROUTER_API_KEY`                           | LLM API key (required)                 |\n| `SSH_GUARD_API_URL`     | `https://openrouter.ai/api/v1/chat/completions` | Any OpenAI-compatible endpoint         |\n| `SSH_GUARD_MODEL`       | `google/gemini-2.0-flash-001`                   | Model for command evaluation           |\n| `SSH_GUARD_API_TYPE`    | `openai`                                        | `openai` or `anthropic`                |\n| `SSH_GUARD_MODE`        | `readonly`                                      | `readonly`, `paranoid`, or `safe`      |\n| `SSH_GUARD_PROMPT`      | (per mode)                                      | Custom system prompt (overrides mode)  |\n| `SSH_GUARD_PASSTHROUGH` | (none)                                          | Always-allow commands, comma-separated |\n| `SSH_GUARD_LOG`         | (none)                                          | Audit log file path                    |\n| `SSH_GUARD_REDACT`      | `true`                                          | Redact secrets from command output     |\n| `SSH_GUARD_SSH_BIN`     | `/usr/bin/ssh`                                  | Path to real ssh binary                |\n| `SSH_GUARD_TIMEOUT`     | `30`                                            | LLM call timeout (seconds)             |\n| `SSH_GUARD_MAX_TOKENS`  | `512`                                           | Max LLM response tokens                |\n\nSee [`.env.example`](.env.example) for a copyable template.\n\n## Agent integration\n\nPoint your agent's SSH command at `ssh-guard` instead of `ssh`.\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eClaude Code (CLAUDE.md)\u003c/b\u003e\u003c/summary\u003e\n\n```markdown\n# SSH Access\n\nUse `ssh-guard` instead of `ssh` for all remote commands.\nNever use interactive SSH sessions.\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eOpenHands / SWE-Agent\u003c/b\u003e\u003c/summary\u003e\n\n```bash\n# In agent config or sandbox setup\nexport SSH_GUARD_API_KEY=\"...\"\nexport SSH_GUARD_MODE=readonly\nalias ssh=ssh-guard\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eLangChain / CrewAI tool definition\u003c/b\u003e\u003c/summary\u003e\n\n```python\nimport subprocess\n\ndef ssh_command(host: str, command: str) -\u003e str:\n    \"\"\"Execute a command on a remote host via ssh-guard.\"\"\"\n    result = subprocess.run(\n        [\"ssh-guard\", host, command],\n        capture_output=True, text=True, timeout=60,\n        env={**os.environ, \"SSH_GUARD_MODE\": \"readonly\"}\n    )\n    if result.returncode != 0:\n        return f\"DENIED: {result.stderr.strip()}\"\n    return result.stdout\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eGeneric: alias ssh to ssh-guard\u003c/b\u003e\u003c/summary\u003e\n\n```bash\n# In the agent's shell init or .env\nalias ssh=ssh-guard\nexport SSH_GUARD_API_KEY=\"...\"\n```\n\n\u003c/details\u003e\n\n## Output redaction\n\nWhen `SSH_GUARD_REDACT=true` (default), command output is filtered through pattern-based redaction before reaching the agent:\n\n```CFG\nDB_PASSWORD=hunter2         -\u003e  DB_PASSWORD=[REDACTED]\nexport API_TOKEN=\"sk-...\"   -\u003e  export API_TOKEN=\"[REDACTED]\"\n-----BEGIN PRIVATE KEY----  -\u003e  -----BEGIN PRIVATE KEY---- [REDACTED]\n```\n\nPatterns matched: `*_TOKEN`, `*_KEY`, `*_SECRET`, `*_PASSWORD`, `*_CREDENTIAL`, `bearer`, PEM blocks, `sk-*` prefixed strings, JWTs.\n\n## Audit logging\n\n```bash\nexport SSH_GUARD_LOG=/var/log/ssh-guard.log\n```\n\nLogs denials always. Logs approvals only when risk score \u003e= 4. Format:\n\n```logs\n[2025-03-12T14:22:01+00:00] DENIED risk=9 cmd=rm -rf / reason=Recursive deletion of root filesystem\n[2025-03-12T14:22:15+00:00] APPROVED risk=4 cmd=sudo cat /etc/hosts reason=Reading system config file\n```\n\n## Limitations\n\nBe honest about what this is and isn't:\n\n- **Not a sandbox.** ssh-guard is a policy gate, not an isolation boundary. A sufficiently creative command chain might get past the LLM. Use it as defense-in-depth alongside proper IAM, restricted users, and network segmentation.\n- **No interactive sessions.** Agents get command execution only. Humans should use `ssh` directly.\n- **LLM latency.** Each command adds ~0.5-2s for the LLM call. Use `SSH_GUARD_PASSTHROUGH` for commands you know are safe to skip the check.\n- **Fail-closed.** If the LLM call fails or returns unparseable output, the command is denied.\n\n## Recording a demo\n\nA [VHS tape file](./demo.tape) is included for generating the terminal recording:\n\n```bash\n# Install VHS: https://github.com/charmbracelet/vhs\nvhs demo.tape  # produces docs/demo.svg\n```\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmorgaesis%2Fssh-guard","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmorgaesis%2Fssh-guard","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmorgaesis%2Fssh-guard/lists"}