Skip to main content

SafeReach

Read-only production diagnostics over SSH, as an MCP server.

Gives an AI agent a competent read-only SRE's eyes on your fleet — journal, service state, disk, memory, processes, sockets, container logs and inspect, HTTP health checks, across many hosts in parallel — with the hands removed.

The agent can investigate. It cannot change anything, read a secret, escape into a shell, or reach a host it wasn't granted.

PyPI Python CI MCP server License


Quick start

uvx safereach@0.4.0 enroll --all      # set up every server you can already ssh to
uvx safereach@0.4.0 install           # register with your agents

Two commands. No sudo required, no config file to edit, nothing installed globally.

If ssh myserver works today, the agent can diagnose myserver.


Architecture

The validator runs twice, on both sides of the SSH connection. That is the central design decision and the reason this can be pointed at production.

Architecture: the validator runs on your machine and again on the host behind an SSH forced command; output comes back redacted

Why twice. The MCP server runs on the agent's own machine, so a check that lives there is one the agent's environment can influence — via a compromised server, a poisoned context, or a prompt injection arriving in a log line the agent just read. Client-side validation is a user-experience feature: it fails fast and explains why. The shim is the control that actually holds, because it sits outside everything the agent can reach.

The request lifecycle and the defence-in-depth layers are drawn out in docs/architecture.md.


Threat model

What this is built to hold against, and what it is not. The picture is the claim: the agent can influence everything in the amber zone, and none of it is a control. The red zone is root-owned on the host, reads nothing from the wire, and is what actually decides.

Trust boundaries: what the agent can influence (context, MCP server, hosts.yaml, client validator) versus what the host enforces (sshd, shim, policy file, diag account, sudoers, socket proxy), and what is assumed trusted

Assumed hostile: the agent, and everything in its context. A prompt injection in a log line the agent just read is the expected attack, not an edge case. Everything the agent can reach — the MCP server, hosts.yaml, the local audit log — is therefore treated as influenceable. The controls that count live on the host: the shim with its embedded spec, its root-owned policy file, the forced-command key, sudoers, and the sshd Match block.

Assumed trusted: root on the managed host, the operator running enroll, and the machine enroll runs from at the moment it runs. If any of those is compromised, this tool is not the boundary.

What holds by construction, what cannot, how secrets are kept off the wire, and how state changes are made unreachable: SECURITY.md.


Installation

Requirements

Where Needs
Your machine (runs the MCP server) Python 3.11+, OpenSSH client, uv or pipx. Linux and macOS tested; Windows via WSL.
Each managed host Any Python 3, OpenSSH server. For --hardened: sudo, systemd, and Docker if you want container diagnostics. Debian/Ubuntu and RHEL-family tested.
The agent Anything that speaks MCP over stdio: Claude Code, Claude Desktop, Codex, Cursor, Windsurf, VS Code, Zed, Gemini CLI. safereach install registers with each.

Nothing is installed on a managed host beyond one stdlib-only Python file and, in hardened mode, one container.

uvx safereach@0.4.0 --help

Nothing installed globally, and it is the one launch form that works identically for every agent. A registration pointing at /home/you/project/.venv/bin/... breaks the moment anything moves; uvx does not.

The version pin is deliberate. Bare uvx safereach refetches from PyPI on every launch — fine for most tools, wrong for one holding production SSH keys, since it is a standing supply-chain exposure on the component whose entire job is being a security boundary. safereach install pins automatically; --unpinned opts out, and is not recommended.

Alternative — a persistent install

uv tool install safereach==0.4.0

To work from a checkout, see CONTRIBUTING.md.


Connecting your agents

safereach install --list     # which agents are on this machine, and where each keeps its config
safereach install            # register with every one of them
safereach install cursor     # or name the ones you want
Detected agents
     Agent               Config
────────────────────────────────────────────────────────────────────
ok   Claude Code         via the `claude-code` CLI
ok   Claude Desktop      ~/.config/Claude/claude_desktop_config.json
ok   Codex CLI           ~/.codex/config.toml
--   Cursor              ~/.cursor/mcp.json
ok   VS Code / Copilot   via the `vscode` CLI
--   Zed                 ~/.config/zed/settings.json

Detection is per user on this machine: a CLI on your PATH (Claude Code, VS Code) or the agent's config directory being present, even if it has never written a config. Claude Code and VS Code are registered through their own CLIs; every other agent gets one entry merged into its config file under the key that agent uses, with a timestamped backup taken first and your other MCP servers left exactly as they were. Re-running is idempotent. safereach uninstall removes only safereach's entry.

The registered command is uvx safereach@<this version>, so agents keep launching the build you enrolled with until you upgrade on purpose. An agent that is installed but not detected — another user's, or a non-standard config path — can be named explicitly. Details and the full list of supported agents: docs/agents.md.


Setting up hosts

enroll — the default

safereach enroll myserver          # one host
safereach enroll --all             # everything in ~/.ssh/config

Uses the SSH access you already have. No sudo. It generates a dedicated keypair, copies the shim to ~/.local/bin/, and appends one entry to the remote authorized_keys:

command="/home/you/.local/bin/safereach-shim",no-pty,no-port-forwarding,no-agent-forwarding,no-X11-forwarding,no-user-rc ssh-ed25519 AAAA…

Your other entries are untouched, so your own SSH is unaffected — but the agent's key can invoke nothing except the shim, whatever is sent to it. Same mechanism as borg serve --restrict-to-path, rrsync and gitolite.

Enrolment then verifies the restriction by attempting an escape, and refuses to record the host if that escape succeeds.

enroll --hardened — for production

safereach enroll myserver --hardened --elevated dmesg-recent

Needs sudo on the target once. Additionally:

  • creates an unprivileged diag user — no sudo, not in the docker group
  • installs the shim and its policy root-owned, so the account cannot rewrite what it is allowed to run
  • starts a read-only Docker socket proxy on a unix socket only diag can reach, pinned by image digest
  • writes an exact-match sudoers entry for enabled recipes only — never sudo itself
  • adds an sshd Match User diag drop-in (no forwarding, no TTY, session cap), validated before it is kept
  • makes the audit log append-only, so the account cannot erase its trail
  • offers only the enrolled key to the host, so a personal key that is also authorised there can never win the handshake

For a production fleet, set defaults.production: true in hosts.yaml. The server then refuses to start if any host is in client-only mode (require_shim: false) or resolves through ~/.ssh/config, so the weaker modes are a startup error rather than a warning.

The full footprint on the host, and what each piece defends against, is in SECURITY.md.

Production checklist

Before pointing an agent at a host that matters:

  • Enrolled with --hardened, never plain enroll or discover
  • defaults.production: true in hosts.yaml, so weaker modes cannot creep back in
  • safereach hosts shows only the hosts you meant, every one enrolled
  • safereach doctor shows every host hardened, shim fingerprint matching
  • curl_targets lists only the host:port pairs the agent needs, or is empty
  • --allow-exec off, or on with explicit --exec-container and --exec-path
  • --elevated recipes limited to what is needed (dmesg-recent is usually enough)
  • hosts.yaml and the key under ~/.config/safereach/keys are mode 0600
  • Agent registered with safereach install (version-pinned uvx), not --unpinned
  • Host audit log /var/log/safereach.jsonl shipped to wherever your other logs go
  • Re-enrolled one non-critical host first after any upgrade that says "re-enrol"

Taking a host out again is one command, and it proves the key is dead before it forgets the host:

safereach unenroll myserver --yes          # keeps the diag account and the audit log
safereach unenroll myserver --yes --remove-user --purge-log

Naming and renaming hosts, the comparison of the three enrolment modes, removing a host, and reading logs inside containers: docs/hosts.md.


Tools the agent sees

Tool Purpose
list_hosts Aliases, descriptions, security mode. Never hostnames, users or key paths — those are yours, via safereach hosts.
select_host Pin a server for the session.
describe_commands The allowlist in readable form — what may run, and how.
run_command Validate → execute → structured result. host is optional.
run_on_hosts Same, fanned out concurrently. The "who else is broken" tool.
run_in_container Read-only commands inside a container, for logs not on stdout.
run_elevated One named recipe (e.g. dmesg-recent). A name, never a command line.
check_connectivity Reachability, auth, and the shim version handshake.

host is optional. One server configured → used directly. Several → the user is asked via the client's elicitation UI and the answer is remembered for the session. No elicitation support → an error naming the options, so the agent asks in conversation. It never guesses.


Upgrading

Agents are pinned to a version (uvx safereach@X.Y.Z), so nothing changes until you say so. The changelog says which of these steps a release needs:

uvx safereach@X.Y.Z install         # repoint every agent at the new version
safereach shim-update --all         # when the release changed the allowlist or the shim
safereach enroll <host> --hardened  # when the release changed root-owned host state

Coming from 0.1.1? Go straight to 0.4.0 and run all three. Coming from 0.3.0, only the first. Until shim-update runs, a host on the old rules is refused, with a message naming the fingerprints — a host quietly running an older, looser allowlist is the failure mode this tool exists to prevent. Enrolment is idempotent, so re-running it is always safe.


Troubleshooting

safereach hosts           # every configured host: alias, address, user, port, mode
safereach doctor          # config, keys, connectivity, shim versions
safereach doctor --fix    # re-push a drifted shim
safereach validate "journalctl -u nginx -n 200" --host myserver

Every message the tool can produce, and what to do about it: docs/troubleshooting.md.


Documentation

SECURITY.md Threat model in full, the four secret-protection layers, non-destruction by construction, the hardened host footprint, and how to report a vulnerability
docs/architecture.md Request lifecycle and defence-in-depth diagrams
docs/agents.md Which agents are supported, how each is detected and registered, launchers, uninstalling
docs/hosts.md Listing hosts, naming and renaming, enrolment modes compared, removing a host, container inspection
docs/troubleshooting.md Symptoms, causes, and the SSH config override
CONTRIBUTING.md Development setup, the test layers, extending the allowlist, releasing
CHANGELOG.md What changed, and what to run on each host after upgrading

Contributing

Contributions are welcome, and the bar is written down in CONTRIBUTING.md. Security issues go through SECURITY.md, not the issue tracker.


Licence

MIT — see LICENSE.

Metadata

Release files for safereach 0.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for safereach 0.4.0
File Size Uploaded
safereach-0.4.0.tar.gz 237.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for safereach 0.4.0
File Interpreter ABI Platform
safereach-0.4.0-py3-none-any.whl Python 3 none any Details

Total release size: 350.4 kB

Release files / safereach-0.4.0.tar.gz

Download URL safereach-0.4.0.tar.gz
Size 237.3 kB
Tags Source
SHA-256 checksum
How to use checksums
55d4a5df0d64cf548fbcb9204dc07a12c8e8a4e3afc6e2a15fab1589e1a9b87b
BLAKE2b-256 checksum
How to use checksums
350be6bf293cc5d683a1acc9393477c9071e4dcbc2929478ba2364e707195655
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 12, 2026.

Transparency log

Release files / safereach-0.4.0-py3-none-any.whl

Download URL safereach-0.4.0-py3-none-any.whl
Size 113.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5df853a6355fc13036f63fff7201bf27f58794a39cebdfd49b5190b63f1be4a3
BLAKE2b-256 checksum
How to use checksums
64dfe92208a53435db5622ec5ff2804d55556404e9623ba89123f5c56fead360
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 12, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.4.0 This release

2 release files

0.3.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page