Skip to content
Agentic Control Plane
Incident series · Part 12 of 9
Stop Your Agent From… →

Make Your AI Coding Agent Ask Before It Runs Commands Over ssh

David Crowe David Crowe · · Updated · 4 min read
ssh proxmox homelab tool-policy approvals claude-code opencode
Share X HN LinkedIn

In a r/Proxmox thread, someone asked how people handle ssh keys for the agents they run in their homelab. One answer stood out. A Claude Code user had given the agent a restricted account on the hypervisor, with a sudoers file that allows pct create 300 but not pct destroy 300. They then tried to make the agent ask before every ssh-plus-sudo command, and gave up. The agent kept finding new ways to call ssh that slipped the rule, and reviewing every shell command by hand stopped being reviewing.

That’s the problem in two sentences. Sudoers on the target is a good limit. The gap is between what the agent means to do and the box. The agent’s own permission list matches text, and the agent isn’t bound by the text.

What your agent can reach today

An agent with a working ssh key reaches whatever you can reach from that shell. Each of these is one tool call: ssh pve 'sudo pct destroy 300', ssh pve reboot, docker exec web rm -rf /data, qm guest exec 100 -- shutdown. The agent sees a bash call. It doesn’t see that the part that matters is a quoted string that runs somewhere else.

The quiet version is worse: ssh pve <<EOF with a script body, or ssh pve < deploy.sh. There’s no command word, so a rule that looks for “ssh followed by a command” finds nothing. The remote shell runs the whole file.

Three steps

Step 1: Install the hook

For Claude Code, Cursor, Codex CLI or opencode:

curl -sf https://agenticcontrolplane.com/install.sh | bash

Every shell command now goes past a hook before it runs. Restart the agent.

Step 2: Run in audit mode on a box you can lose

Leave the workspace in audit mode for a day. Give the agent the key it will have later and a task that touches the target. Every ssh, docker, pct and qm call lands in the activity log, so you can read what the agent really did to the box before anything is held. One person in that thread did this by hand, with a second model reading what the first one’s account had done. The log does it for you.

Step 3: Switch to enforce

In enforce mode, a command that runs somewhere the hook can’t see is held for your approval before it leaves the machine. You don’t write a rule. This is what the check returned for the spellings from the thread, on an interactive Claude Code session when we ran it in September 2026:

ssh pve 'sudo pct destroy 300'
  → held: ceiling guard: runs a command on another host the hook can't inspect (ssh), requires human approval

FOO=1 ssh pve "sudo qm stop 100"
  → held: ceiling guard (ssh), requires human approval

command ssh pve "sudo qm stop 100"
  → held: ceiling guard (ssh), requires human approval

sudo -u root ssh pve 'pct destroy 300'
  → held: ceiling guard (ssh), requires human approval

s''sh pve "sudo qm stop 100"
  → held: ceiling guard (ssh), requires human approval

ssh pve <<EOF
sudo qm stop 100
EOF
  → held: ceiling guard (ssh), requires human approval

ssh pve < deploy.sh
  → held: ceiling guard (ssh), requires human approval

bash -c "ssh pve sudo reboot"
  → blocked: hardline floor: system shutdown/reboot, no approval reaches it

ssh pve 2>&1 | tee log
  → allow

Each ssh line resolves to the same rule key, Bash.ssh, however the binary is dressed up. The check goes by what the command runs, not by its text. The last line is a plain interactive login with output piped to a file, so it isn’t held. The reboot isn’t held either. It’s on the always-blocked list, and approving it isn’t an option.

Where the hold shows up depends on the agent. Claude Code shows it in the session, and you answer there. On the others, the call is blocked in the agent and waits on the console Approvals page and in the approval email. Approve it, then ask the agent to retry. In an unattended run, a held call is denied until someone approves it on the console.

You can tighten this further. A rule on Bash.ssh covers every spelling above. For runs nobody is watching, set Bash.ssh, Bash.pct and Bash.qm to Never on the background tier in the console (Policies → Agents → background). An unattended run then can’t touch a container or VM, and interactive sessions still get the approval prompt.

What this is not

It isn’t a sandbox. The call is held on your machine before anything goes out, and a person decides. A few spellings still get through, so weigh them: xargs -I{} ssh host {}, find -exec ssh host {}, su -c "ssh …", and an ssh ProxyCommand that runs a local command before connecting. Those are tracked. They’re the reason to keep the restricted account and the sudoers file on the target. The two layers cover different things. The target’s limit bounds what a session can do once it’s in. The hold on your machine bounds what gets sent, and logs every call that did.

The check only reads shell command text. It won’t see an ssh connection opened by some other tool. And ACP runs as a hook on your machine, so turning hooks off in the agent turns it off too.

If you already run an agent against a Proxmox host, a jump box or a fleet of containers, the install is the same one command. Try audit mode against your own setup first and read what the agent has been doing.

Share X HN LinkedIn
Get the next data drop
What agents actually cost, new tool-surface captures, and the occasional incident post-mortem — sent when we publish something worth your inbox, not on a schedule. Unsubscribe anytime.
Share: Twitter LinkedIn
More in Stop Your Agent From…
  1. 1. How to block rm -rf in Claude Code, Codex and Cursor
  2. 2. How to stop a coding agent from dropping your production database
  3. 3. Stop your AI agent from deleting files outside your project
  4. 4. How to stop Claude Code, Codex and Cursor from force-pushing
  5. 5. AI coding agents leaking .env secrets: stop it in 3 steps
  6. 6. Stop your AI agent from leaking PII through tool calls
  7. 7. Stop your AI agent from burning through your API budget
  8. 10. Stop an AI agent from escalating IAM permissions on AWS, GCP and Azure
  9. 12. Make Your AI Coding Agent Ask Before It Runs Commands Over ssh · you are here
Related posts

← back to blog