vps-forge: Agents With Root on a VPS Get Auto-Rollback, Self-Bans Drop From 7 in 60 Runs to 0

A developer has released vps-forge, an open-source Agent Skill for Claude Code, Codex, Cursor and other agents that have been given root on a VPS. The problem is a familiar one: in the benchmark, Claude Opus 5.5 without hints banned its own IP via fail2ban in 7 of 60 runs, cutting off the owner's laptop in the process. The core of the skill is a rollback timer, vps-forge-deadman. Before touching SSH, the firewall, sudo or PAM, the agent arms a 5-minute systemd timer. 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, no telemetry

AI-processed from Habr AI; edited by Hamidun News
vps-forge: Agents With Root on a VPS Get Auto-Rollback, Self-Bans Drop From 7 in 60 Runs 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, 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 without hints banned itself in 7 of 60 runs. The model is smart. Nobody simply told it to add its own address to ignoreip before the first fail2ban start.

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 task involves 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 blocked. For example, `AllowUsers deploy` when you log in as admin, or an AllowGroups list you forgot to add yourself to.

Agents have a trap of their own. They keep a single connection open through ControlMaster, so a check like `ssh host true` goes over the old channel. It never touches the new sshd or the new firewall rules: the check comes back green, yet no one can 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 fires without the agent.

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 via `ssh -o ControlPath=none`, and if the login works, runs disarm. If it doesn't, there is nothing to do: after 5 minutes the timer restores everything as it was.

On paper this is 20 lines of bash; in practice it grew to several hundred. Almost every line exists because something broke:

  • Reboot. A timer created by systemd-run lives in memory and vanishes along with the server. Now arm also enables a unit that, at boot, sees the "armed but not disarmed" state and rolls back by itself.
  • nftables. Rollback stopped a service that had been off before arm, and its ExecStop runs `nft flush ruleset`. Along with the needed rule, ufw, fail2ban bans and Docker chains disappeared. 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 treated as disabled and got turned off during rollback. `LC_ALL=C` fixes it.
  • Ban after rollback. The configs came back, but the IP stayed banned. Now arm remembers the SSH client's address, and 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 while armed, and checks that recovery works. A firewall in the provider's control panel cannot be rolled back, because it lives outside the server. There, the only safeguard is ordering: add the new rule first, verify login, then remove the old one.

Cloud image pitfalls and the benchmark

The sneakiest one is cloud-init. Many VPS images ship a `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`. There's more: Debian 12 has no auth.log, Docker publishes ports around 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 uses real QEMU VMs (Debian 12 and 13, Ubuntu 22.04, 24.04, 26.04) and four scenarios, each run three times without the skill and three times with it, for 120 Opus 5.5 runs in total. A judge checks the server from the 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 notification: 16/30 and 30/30
  • Firewall enabled: 46/60 and 60/60
  • Password brute-forcing gets banned: 50/60 and 60/60
  • Root cannot log in: 30/60 and 57/60
  • Agent banned itself: 7/60 and 0/60
  • Lost access: 0/60 and 0/60

The author is upfront about the weak spots. An untouched server scores 25–42. About 70% of the gap comes from three checks that match the skill's own settings: a clean ssh-audit, the root ban and the notification. 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 refining the skill. Only one model was tested, plus four manual GLM-5.3 runs. The benchmark did not prove the rollback works: nobody lost access, even without the skill.

Cost and how to use it

The skill is slower and roughly three times more expensive: $1.87 versus $0.62 per run. What real benefit remains is fail2ban actually banning 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, so you can test it by hand on a throwaway server: arm it for a minute, add `AllowUsers deploy`, confirm that login is blocked, and wait for the ROLLED BACK status. You still need a provider snapshot and console access. RHEL is not supported yet, and the skill does not touch Kubernetes nodes. The code is MIT-licensed with no telemetry, and the test bench lives 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 blocked. For example, `AllowUsers deploy` when you log in as admin, or an AllowGroups list you forgot to add yourself to. Agents have a trap of their own. They keep a single connection open through ControlMaster, so a check like `ssh host true` goes over the old channel. It never touches the new sshd or the new firewall rules: the check comes back green, yet no one can log in. The instruction "verify login from a new connect

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 via `ssh -o ControlPath=none`, and if the login works, runs disarm. If it doesn't, there is nothing to do: after 5 minutes the timer restores everything as it was. On paper this is 20 lines of bash; in practice it grew to several hundred. Almost every line exists because something broke: Reboot. A timer created by systemd-run lives in memory and vanishes along with the

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

The sneakiest one is cloud-init. Many VPS images ship a `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`. There's more: Debian 12 has no auth.log, Docker publishes ports around 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

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…