vps-forge: root-enabled agents on a VPS get automatic rollback, self-bans drop from 7 of 60 to 0

A developer has released vps-forge, an open-source Agent Skill for Claude Code, Codex, Cursor and other agents that are given root on a VPS. The problem is a familiar one: in the benchmark, Claude Opus 5.5 with no hints banned its own IP through fail2ban in 7 of 60 runs, cutting off the owner's laptop along with it. The core of the skill is a rollback timer, vps-forge-deadman. Before editing SSH, the firewall, sudo or PAM, the agent arms a 5-minute systemd timer, and if a fresh login isn't confirmed, the configs revert on their own. Across 120 runs, the score rose from 79–92 to 99–100 out of 100. • A run costs $1.87 versus $0.62 without the skill • Nobody lost access, even without the skill • MIT-licensed code, no telemetry

AI-processed from Habr AI; edited by Hamidun News
vps-forge: root-enabled agents on a VPS get automatic rollback, self-bans drop from 7 of 60 to 0
Source: Habr AI. Collage: Hamidun News.
Listen to article
Article Fact Sheet & Key Figures
ParameterDetails
Primary sourceHabr AI ↗
Editor-in-Chief
Key metric #1A run costs $1.87 versus $0.62 without the skill
Key metric #2Nobody lost access, even without the skill
Key metric #3MIT-licensed code, no telemetry

An agent with root on a fresh VPS installs fail2ban, checks key-based login, tries a password, and on the fifth failed attempt bans the IP the checks are coming from. That is, your IP. After that every command times out, and you start hunting for the web console in your host's control panel. In the benchmark by the author of vps-forge, Claude Opus 5.5 with no hints banned itself in 7 of 60 runs. The model is smart; nobody just told it to add its own address to ignoreip before fail2ban first starts.

vps-forge is an open-source Agent Skill: a set of instructions and bash scripts that Claude Code, Codex, Cursor and other agents load when the conversation turns to a server. Install it with `npx skills add mountain-beaver/vps-forge -g`. There is a plugin for Claude Code and a zip archive for Claude.ai.

Why "be careful" doesn't work

A broken sshd_config looks like a working one. `sshd -t` stays silent, the reload succeeds, the current session stays alive, and new logins are already shut out: for example, `AllowUsers deploy` when you are admin, or an AllowGroups entry you forgot to add yourself to.

Agents have a trap of their own. They hold a single connection via ControlMaster, so the check `ssh host true` goes over the old channel. It never touches the new sshd or the new firewall rules: the check is green, and you still can't log in. The instruction "verify login from a new connection" doesn't help once the agent has already lost access. You need a mechanism that works without it.

A dead man's pedal for sshd

Before every change to SSH, the firewall, sudo or PAM, the agent runs `vps-forge-deadman arm`. The script snapshots the configs and starts a 5-minute systemd timer. The agent changes the config, logs in again with `ssh -o ControlPath=none` and, if the login works, runs disarm. If it doesn't, nothing needs to be done: after 5 minutes the timer restores everything to how it was.

On paper it was 20 lines of bash; it ended up at several hundred. Almost every line corresponds to something that broke:

  • Reboot. A timer created with systemd-run lives in memory and disappears with the server. Now arm also enables a unit that, at boot, sees the "armed but not disarmed" state and rolls back on its own.
  • nftables. The rollback stopped a service that had been disabled before arm, and its ExecStop contains `nft flush ruleset`. Along with the needed rule, ufw, fail2ban bans and Docker chains vanished. Now only the tables from the config being rolled back are removed.
  • Russian ufw locale. The script looked for the string `Status: active`, but ufw prints "Состояние: активен". The firewall was considered off and got disabled during rollback. Fixed with `LC_ALL=C`.
  • Ban after rollback. The configs came back, but the IP stayed banned. Now arm records the SSH client's address and the rollback unbans it. Under sudo the SSH_CLIENT variable is absent, so the address is taken from the parent process's environment.

All of this runs in CI: a container with a real systemd, a test that breaks sshd, the firewall and fail2ban, reboots the container in the armed state and checks recovery. A firewall in the provider's control panel can't be rolled back, because it sits outside the server. There the only safeguard is ordering: add the new rule first, check the login, then remove the old one.

Cloud image pitfalls and the benchmark

The sneakiest one is cloud-init. Many VPS images ship `50-cloud-init.conf` with `PasswordAuthentication yes`. sshd takes the first value it reads, and files are read alphabetically, so the popular advice to use `99-hardening.conf` does nothing. The skill's file is named `00-vps-forge.conf`, and the result is verified with `sshd -T`. Also: Debian 12 has no auth.log, Docker publishes ports bypassing ufw, the Oracle Cloud image has its own REJECT rule in iptables, and Coolify connects to its own host over SSH as root, so `PermitRootLogin no` breaks deployment.

The test bench: real QEMU VMs (Debian 12 and 13, Ubuntu 22.04, 24.04, 26.04), four scenarios, each run three times without the skill and with it — 120 Opus 5.5 runs. A judge checks the server from outside before and after a reboot. Without the skill the score is 79–92 out of 100; with it, 99–100.

  • ssh-audit with no failures: 4/60 without the skill, 60/60 with it
  • SSH login alert: 16/30 and 30/30
  • Firewall enabled: 46/60 and 60/60
  • Password brute force gets banned: 50/60 and 60/60
  • Root cannot log in: 30/60 and 57/60
  • Agent banned itself: 7/60 and 0/60
  • Loss of access: 0/60 and 0/60

The author is upfront about the weak spots. An untouched server scores 25–42. About 70% of the difference comes from three checks that match the skill's own settings: a clean ssh-audit, the root ban and the alert. One person wrote both the judge and the skill, and the independent Lynis barely moves: 69 and 71, 72 and 72, 64 and 72, 73 and 72. Three of the four scenarios were used while the skill was being refined. There is one model, plus four manual runs of GLM-5.3. The benchmark did not prove the rollback: nobody lost access, including without the skill.

Price and how to use it

The skill is slower and roughly three times more expensive: $1.87 versus $0.62 per run. The real remaining benefit is fail2ban that actually bans on a running server (6 of 15 without the skill, 15 of 15 with it), an enabled firewall and zero self-bans.

In practice: before the first SSH change, check that the agent ran `vps-forge-deadman arm`. If it didn't, the skill wasn't loaded and there is no safety net. The script also works without an agent, and you can test it by hand on a server you don't need: arm it for a minute, drop in `AllowUsers deploy`, confirm login is closed, and wait for the ROLLED BACK status. You still need a provider snapshot and console access. RHEL is not yet supported, and the skill does not touch Kubernetes nodes. The code is MIT-licensed with no telemetry, and the test bench is in the bench/ directory of the mountain-beaver/vps-forge repository.

Story

Key Questions Answered

Why "be careful" doesn't work?

A broken sshd_config looks like a working one. `sshd -t` stays silent, the reload succeeds, the current session stays alive, and new logins are already shut out: for example, `AllowUsers deploy` when you are admin, or an AllowGroups entry you forgot to add yourself to. Agents have a trap of their own. They hold a single connection via ControlMaster, so the check `ssh host true` goes over the old channel. It never touches the new sshd or the new firewall rules: the check is green, and you still can't log in. The instruction "verify login from a new connection" doesn't help o

What are the key details on A dead man's pedal for sshd?

Before every change to SSH, the firewall, sudo or PAM, the agent runs `vps-forge-deadman arm`. The script snapshots the configs and starts a 5-minute systemd timer. The agent changes the config, logs in again with `ssh -o ControlPath=none` and, if the login works, runs disarm. If it doesn't, nothing needs to be done: after 5 minutes the timer restores everything to how it was. On paper it was 20 lines of bash; it ended up at several hundred. Almost every line corresponds to something that broke: Reboot. A timer created with systemd-run lives in memory and disappears with th

What are the key details on Cloud image pitfalls and the benchmark?

The sneakiest one is cloud-init. Many VPS images ship `50-cloud-init.conf` with `PasswordAuthentication yes`. sshd takes the first value it reads, and files are read alphabetically, so the popular advice to use `99-hardening.conf` does nothing. The skill's file is named `00-vps-forge.conf`, and the result is verified with `sshd -T`. Also: Debian 12 has no auth.log, Docker publishes ports bypassing ufw, the Oracle Cloud image has its own REJECT rule in iptables, and Coolify connects to its own host over SSH as root, so `PermitRootLogin no` breaks deployment. The test bench:

ZK
Zhemal Khamidun — CPO at Alpina Digital, product manager of AlpinaGPT. Building AI products since 2019. Hamidun News is a pet project that grew into a production aggregator with 6,000+ readers.
✦ Hands-On AI Engineering · Hamidun Academy

Build & Ship Production AI Agents Without Boilerplate Code

Start your free 72-hour trial at Hamidun Academy (academy.hamidun.com) — interactive lessons on Claude Code & Cursor, live Tech Radar, and production agent templates.

Start 72h Free Trial →Vibe-Coding Intensive
What do you think?
Loading comments…