diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json new file mode 100644 index 0000000..c99154e --- /dev/null +++ b/.claude-plugin/marketplace.json @@ -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." + } + ] +} diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..e4c40f1 --- /dev/null +++ b/.claude-plugin/plugin.json @@ -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" + } + } +} diff --git a/CLAUDE.md b/CLAUDE.md index 607e2e9..8e2a141 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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` +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` 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. diff --git a/README.md b/README.md index 2ce649b..0a1e1a2 100644 --- a/README.md +++ b/README.md @@ -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: diff --git a/skills/run/SKILL.md b/skills/run/SKILL.md new file mode 100644 index 0000000..4933633 --- /dev/null +++ b/skills/run/SKILL.md @@ -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