Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "tarides",
"owner": {
"name": "Tarides",
"url": "https://github.com/tarides"
},
"plugins": [
{
"name": "sudo-proxy",
"source": "./",
"description": "Human-approved sudo for agents — run privileged commands locally or over SSH, with a TUI keypress required on every one and no credential ever stored."
}
]
}
17 changes: 17 additions & 0 deletions .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "sudo-proxy",
"version": "1.1.0",
"description": "Run privileged commands locally or over SSH, with a human keypress approving each one",
"author": {
"name": "Tarides"
},
"homepage": "https://github.com/tarides/sudo-proxy",
"repository": "https://github.com/tarides/sudo-proxy",
"license": "MIT",
"keywords": ["sudo", "ssh", "privileged", "human-in-the-loop", "approval"],
"mcpServers": {
"sudo-proxy": {
"command": "sudo-proxy-mcp"
}
}
}
10 changes: 9 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ cargo build --release --no-default-features # core only (no MCP server)

The version in `Cargo.toml` must match the git tag. When bumping the version:
1. Update `version` in `Cargo.toml`
2. Tag the commit: `git tag v<VERSION>`
2. Update the hardcoded version strings in the packaging metadata so they
don't drift from `Cargo.toml`:
- `server.json` (the `version` field and the `mcpb` package identifier URL)
- `packaging/mcpb/manifest.json`
- `.claude-plugin/plugin.json`
3. Tag the commit: `git tag v<VERSION>`

All binaries read the version from `Cargo.toml` at compile time via `env!("CARGO_PKG_VERSION")`.
The JSON files above carry the string separately because they are consumed by
external tooling (the MCP registry, mcpb, the Claude Code plugin loader) before
any binary runs.
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,27 @@ Point your MCP client at the server — add to the project's `.mcp.json` or
}
```

### Install as a Claude Code plugin

The repo is also a Claude Code [plugin](https://code.claude.com/docs/en/plugins)
and its own marketplace. Instead of editing `.mcp.json` by hand:

```
/plugin marketplace add tarides/sudo-proxy
/plugin install sudo-proxy@tarides
```

The plugin registers the `sudo-proxy` MCP server and ships a skill that
tells the model to reach for sudo-proxy only for privileged or remote
work, to start the server before executing, and to write a clear
description for each command so the approval prompt is easy to judge. It
does **not** install the `sudo-proxy-mcp` binary — that must already be on
your `PATH` (see the `cargo install` / `cargo binstall` steps above). A
tool whose whole point is explicit human consent should not install
itself silently.

### Run commands

Then the model starts a server (opening a terminal with the approval TUI)
and runs commands through it:

Expand Down
71 changes: 71 additions & 0 deletions skills/run/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
---
name: run
description: Run a privileged (sudo) or remote-over-SSH command through sudo-proxy, which shows the user a single-keypress TUI approval prompt before anything executes. Use when a task needs to install packages, edit system files, manage services, or run any command on a remote host — NOT for ordinary unprivileged work in the current directory.
---

# sudo-proxy: run

sudo-proxy runs a command through a human-approval gate: every `execute`
pops a single-keypress Y/N prompt in the user's terminal (and, when
privileged, a live `sudo` password prompt) before the command runs. No
credential is stored; there is no auto-approve mode. It works for local
commands and for commands on a remote host reached over an SSH tunnel.

The `sudo-proxy-mcp` binary must already be on the user's `PATH`
(`cargo install sudo-proxy` or `cargo binstall sudo-proxy`). This plugin
launches the server; it does not install the binary.

## When to use it — and when not to

Use sudo-proxy only when the command genuinely needs it:

- **Privilege escalation** — installing packages, editing system files,
managing services, anything that needs `sudo`.
- **A remote host** — running a command on another machine over SSH.

For everything else — reading files, building, running tests, git, any
unprivileged command in the working tree — keep using the ordinary Bash
tool. sudo-proxy adds a human-approval round-trip on *every* call, so
routing routine work through it just slows the user down.

## How to call it

1. **Start the server first.** Call `start_server` before the first
`execute`. With no arguments it opens a local terminal with the
approval TUI; pass `host` to open an SSH-tunnelled session to a remote
machine. If a server is already running it returns immediately, so
calling it again is cheap and safe.

2. **Execute with a clear description.** Every `execute` takes an `argv`
array and should carry a `description`. That description is what the
user reads in the approval prompt, so make it specific and honest —
"Install nginx" or "Restart the ci-runner service", not "run command".
A good description is the difference between an easy `y` and a puzzled
deny.

3. **Match the host.** Pass the same `host` to `execute` that you passed
to `start_server`; omit it for localhost. Set `privileged: false` for
commands that should run as the current user but still behind the Y/N
gate.

```jsonc
start_server()
execute({ "argv": ["apt", "install", "nginx"], "description": "Install nginx" })

// remote host
start_server({ "host": "ci-runner" })
execute({ "argv": ["systemctl", "restart", "buildkite"],
"host": "ci-runner",
"description": "Restart the Buildkite agent on ci-runner" })
```

Expect denials: the user may press `N`. Treat a denied command as a
deliberate choice, report it plainly, and do not try to route around the
gate.

## Reference

- Tools (`start_server`, `execute`, `status`, `stop_server`,
`update_host`): https://github.com/tarides/sudo-proxy/blob/main/docs/mcp.md
- Security model and threat model:
https://github.com/tarides/sudo-proxy/blob/main/docs/security.md
Loading