ssh2mcp@1.0.0
BLOCKED· startup onlyPublisher claims: "Exposes a single SSH-reachable host as MCP tools for running and backgrounding remote commands, resumable verified file transfers, and read-only file comparison, stat, hashing, listing and reading. Runs on loopback with per-request Bearer auth and hot-switchable permission levels, letting an agent compile and manage files on a build machine while keeping its workspace on the device." Observed in sandbox: reads credential files.
Coverage
Startup only; authenticated tool behavior not tested.- Tested
- server startup over stdio (initialize sent) · every file read, file written and host contacted by what ran
- Not tested
- the tool list (the server exited before answering) · what the tools do when called with real credentials
What this means for the label: Blocked here covers what ran. The server's tools were not exercised with a real credential, so nothing about their behavior is asserted; the credential summary below says what to do with that.
Revoked: guards refuse to launch this version
confirmed incident: read ~/.ssh2mcp-token Every machine running vet-skills guard stops starting it at the next launch; the state clears when the honeypot re-run or a reviewer clears it.
Before you give it your token
- It will ask for
SSH2MCP_TOKENSSH2MCP_TOKEN_FILEConfiguration, not credentials: MCP_PORT, S2M_BASE, SSH2MCP_BIND, SSH2MCP_MAX_OUTPUT, SSH2MCP_MAX_TIMEOUT_MS, SSH2MCP_PERM, SSH2MCP_PERM_FILE, SSH2MCP_ROOT, SSH2MCP_SSH_BIN, SSH2MCP_SSH_HOST, SSH2MCP_TIMEOUT_MS- Where it talks to
- no vendor host named in the code
- What the honeypot saw
- read ~/.ssh2mcp-token · no network
- What we could not test
- the tool list (the server exited before answering) · what the tools do when called with real credentials
Do not connect it. The honeypot or a critical finding blocked this version; the evidence is below.
Never paste a credential into a chat, a ticket or a report, including ours. How the guard holds it.
Is [email protected] safe to install?
KillerSkills ran it in the KillerSkills honeypot sandbox (2 scenarios; startup only; authenticated tool behavior not tested) on 2026-11-14 and scored it 41/100 (Blocked). Do not install it: the sandbox observed behaviour consistent with credential theft, exfiltration or persistence. Notable findings: Destructive command against a filesystem root or device; Destructive command against a filesystem root or device; Reads credential paths from the home directory. Summary: reads credential files.
Behavior trace
Honeypot run · 26 s · 2 scenariosStatic findings
st, iserr, out = call("s_shell", {"command": "rm -rf /"})Fix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.["rm foo", false], ["ls; rm -rf /", false], ["git config user.name x", false], ["curl http://x", false],Fix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.curl -fsS -m 5 -H "Authorization: Bearer $(cat "$HOME/.ssh2mcp-token")" "http://127.0.0.1:$PORT/status" && echoFix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.["rm -rf /", "block"], ["rm -rf /*", "block"], ["dd if=/dev/zero of=/dev/sda", "block"],Fix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.TOKEN = open(os.path.expanduser("~/.ssh2mcp-token")).read().strip()Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.["aapt2 dump badging x.apk", true], ["date -s 2020-01-01", false], ["env rm -rf /", false]Fix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.["rm -rf /srv/build", "warn"], ["git clean -xdf", "warn"], ["curl -fsSL http://x | sh", "warn"],Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.["mkfs.ext4 /dev/sdb1", "block"], ["shutdown -h now", "block"], ["iptables -F", "block"],Fix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.TOKEN = open(os.path.expanduser("~/.ssh2mcp-token")).read().strip()Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.check("risk block: rm -rf /", iserr and "blocked by risk rule" in out.get("error", ""), str(out)[:120])Fix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.["rm -rf /srv/build", "warn"], ["git clean -xdf", "warn"], ["curl -fsSL http://x | sh", "warn"],Fix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.sshRun("if [ -f " + base + ".exit ]; then echo done:$(cat " + base + ".exit); elif [ -f " + base + ".pid ] && kill -0 $(cat " + base + ".pid) 2>/dev/null; then echo running; else echo gone; fi")Fix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.const r = await sshRun("if [ -f " + base + ".pid ]; then p=$(cat " + base + ".pid); kill -TERM $p 2>/dev/null; sleep 1; kill -KILL $p 2>/dev/null; echo killed:$p; else echo no-pid; fi");Fix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.["ls -la /srv", "none"], ["rm -rf /srv/build/app/build", "none"], ["git status --short", "none"]Fix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.* - Execution goes through the system `ssh` binary, so ~/.ssh/config (ControlMaster,Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.["rm -rf /srv/build", "warn"], ["git clean -xdf", "warn"], ["curl -fsSL http://x | sh", "warn"],Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.st, iserr, out = call("s_shell", {"command": "rm -rf /"})Fix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.console.error(" generate one: python3 -c \"import secrets;print(secrets.token_hex(24))\" > " + TOKEN_FILE + " && chmod 600 " + TOKEN_FILE);Fix: Request the narrowest permission that works; replace wildcards with the exact paths or hosts.res.status(401).json({ jsonrpc: "2.0", error: { code: -32001, message: "unauthorized: send Authorization: Bearer <token>" }, id: null });Fix: Remove the call, or declare the host in frontmatter and explain in the README what is sent and why.st, iserr, out = call("s_shell", {"command": "rm -rf /srv/build"})Fix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.// The user's ~/.ssh/config enables ControlMaster/ControlPersist. A multiplexed client can beFix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.["rm foo", false], ["ls; rm -rf /", false], ["git config user.name x", false], ["curl http://x", false],Fix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.["echo x > /etc/passwd", "block"], ["systemctl stop ssh", "block"], ["kill -9 1234 # sshd", "block"],Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.kill "$(cat "$PID")" 2>/dev/null; sleep 1Fix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.kill -9 "$(cat "$PID")" 2>/dev/nullFix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.check("kill stops a running job", not e and r.get("state") != "running", r.get("state"))Fix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.if (perm === "ro") throw new Error("perm=ro: kill is not allowed");Fix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.["rm -rf /", "block"], ["rm -rf /*", "block"], ["dd if=/dev/zero of=/dev/sda", "block"],Fix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.…hon3 -c \"import secrets;print(secrets.token_hex(24))\" > " + TOKEN_FILE + " && chmod 600 " + TOKEN_FILE); add("perm", true, readPerm() + " (fallback from CLI: " + …Fix: Request the narrowest permission that works; replace wildcards with the exact paths or hosts.if [ -f "$PID" ] && kill -0 "$(cat "$PID")" 2>/dev/null; thenFix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.["echo x > /etc/passwd", "block"], ["systemctl stop ssh", "block"], ["kill -9 1234 # sshd", "block"],Fix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.check("risk block: rm -rf /", iserr and "blocked by risk rule" in out.get("error", ""), str(out)[:120])Fix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.Cost to the agent
Tokens estimated at four characters each. The tool list is re-read by the agent on every turn, so it is the number to compare between servers that do the same job: ranked across the index.
Known vulnerabilities
Vulnerability advisories (OSV) cover npm and PyPI packages; this registry has no advisory database.
What would make this Verified
39 points to 80 · weights: behavior 45%, static 25%, prompt 15%, publisher 15%- Reads credential files≈ +27 on the totalThe sandbox saw reads under ~/.ssh, ~/.aws, ~/.npmrc, ~/.config or similar. Read only files inside the project, and only the ones the task needs.
- Destructive command against a filesystem root or device≈ +9 on the totalBuild commands from fixed arguments; do not interpolate untrusted strings into a shell.
- Reads credential paths from the home directory≈ +9 on the totalStop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.
- Pipe-to-shell pattern≈ +9 on the totalBuild commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.
- Unclaimed publisher≈ +8 on the totalClaim andyernotfound: a verified publisher gets +50 on publisher trust. It is free and usually instant with GitHub sign-in.
- Destructive file system command detected≈ +4 on the totalBuild commands from fixed arguments; do not interpolate untrusted strings into a shell.