# An AI agent on your server: approve, then audit

> An agent that can run commands on your server can also lock you out, delete data or read a key it never needed. Two checks cover it: **before** a change, it asks you and a rollback timer waits behind it; **after**, you read what it ran. Stenvakt does the first, ranwhat does the second, and each works without the other.

Source: https://stenvakt.com/guides/ai-agent-on-your-server. Published 2026-11-04. Claude Code, Codex or Cursor driving a Linux server you run yourself. Ubuntu and Debian are the tested ones. Stenvakt is for Mac; ranwhat runs on macOS and Linux with Python 3.9 or newer.

The usual way to let an agent near a server is the worst one: paste the SSH command into the chat and let it run. From then on it holds your key, your shell and your server, and the only record of what it did is a chat scrollback. A better arrangement has three parts: what the agent may *read*, what it may *change*, and how you *check* afterwards.

## Give it reading first

Most of what you want from an agent on a server is reading: which ports are open, which updates are pending, who logged in, whether a backup exists. Reading changes nothing, so it can run without asking. Give the agent a tool that only reads, and it can answer those questions without a shell.

That is what the Stenvakt MCP connection is for. Claude Code, Codex and Cursor connect to the app over [MCP](https://modelcontextprotocol.io/specification) and can list your servers, run the review and preview a fix. No SSH key, password or token goes to the agent: the app on your Mac holds the connection.

## Make every change a proposal

A change is different: a firewall rule, an SSH setting, an update, a reboot. Here the agent should *propose* and you should *approve*. In Stenvakt, an agent's change waits in the app until you accept it, with the commands it would run shown first. The exception is a server you mark as a test server, where agent changes apply without asking. Use that for a throwaway machine, not for production.

Approval is not the only protection. Firewall and SSH steps, the ones that can cut you off, run with safe mode: the server arms its own rollback timer, the app tries a brand-new login, and if that login fails the server puts the old configuration back. Agents cannot turn safe mode off. More on that in [SSH hardening without lockout](https://stenvakt.com/guides/ssh-hardening-without-lockout).

> The rule underneath: the agent proposes, a person decides, the server can undo. If one of the three is missing, the setup depends on nothing going wrong.

## Keep your keys out of its reach

If an agent has a shell on your Mac, it can read anything your user can: `~/.ssh`, `.env` files, cloud credentials. Whatever it reads goes into a model context you do not control. Two habits cut that down:

- **Use a tool, not a shell.** With an MCP tool that holds the connection, the agent never needs the key at all.
- **Use a separate key for anything an agent touches directly.** A key with its own name in `authorized_keys` can be removed in one line without disturbing anyone else, and it shows up under its own name in `journalctl -u ssh`.

## Audit what it ran

Checks before the change are half of it. The other half is knowing afterwards what the agent did, including the commands that never went through an approval screen: a `rm -rf`, a `cat ~/.ssh/id_rsa`, a force push. Agents already write their own history to disk, so there is a record to read.

[ranwhat](https://ranwhat.com) reads that record. It works from the transcripts Claude Code, Codex and other agents have already saved on your machine, so there is nothing to wrap or proxy and nothing leaves the machine. Run it once, without installing:

```
uvx ranwhat check
```

Or look at a window of time, for example after a session where an agent worked on a server:

```
ranwhat watch --days 7
```

It lists the actions worth a second look: credential material read, recursive deletion, destructive git operations, packages published. Each comes with the command and the time. `ranwhat check --fail-on high` exits non-zero when it finds something at that level, so a script or CI job can fail on it.

The two tools check different moments. Stenvakt sits *in front of* a server change; ranwhat reads *behind* the agent, on your Mac, whatever the agent ran. If you give an agent both a Stenvakt connection and a shell, the second is the one that will surprise you, and the one to read.

## A routine that takes five minutes

1. Take a snapshot or a database backup first ([snapshot before changes](https://stenvakt.com/guides/server-snapshot-before-changes), [backups before changes](https://stenvakt.com/guides/database-backups-before-changes)).
2. Let the agent read and propose through the app. Read the commands in each proposal before you accept.
3. Accept the changes you want. Leave safe mode on.
4. When the session is over, run `ranwhat watch --days 1` and read the list.
5. Check `journalctl -u ssh --since today` on the server for logins you do not recognise.

## What this does not cover

Approval does not make a bad proposal good: read it. Safe mode covers firewall and SSH steps, not every change; an update or a deleted file has no timer, which is why the backup comes first. And ranwhat reads what the agents wrote down, so an agent that does not keep a history, or a history that has been cleaned up, leaves nothing to read.

## Sources

1. [Model Context Protocol: specification](https://modelcontextprotocol.io/specification)
2. [ranwhat: flight recorder and authority scanner for AI coding agents](https://ranwhat.com)
3. [OpenBSD manual: ssh(1) (BatchMode, IdentityFile, ForwardAgent)](https://man.openbsd.org/ssh)
