
An AI agent with a shell is a user on your server that takes its orders from text. Claude Code, Codex CLI and OpenClaw run commands as the Linux user that started them, and they read web pages, issues, READMEs and chat messages as they work. If one of those hides instructions, or the model simply gets it wrong, the agent acts with everything that user can reach. These steps shrink that reach on Ubuntu 24.04 or Debian 12, in setup order.
Quick answer: Run the agent as its own Linux user, without sudo or docker group membership, and keep secrets it doesn't need under other accounts. Turn on each tool's guardrails: Claude Code's sandbox in strict mode with bypass disabled, Codex CLI's default sandbox without --yolo, OpenClaw's cautious exec policy. Allow only SSH in and only DNS and HTTPS out for the agent's user, log in with SSH keys, give the agent a git credential for one repository, and record what it runs with auditd.
What an Agent With Shell Access Can Do
Anthropic's security page says it plainly: a Bash command you approve "can still write anywhere your user account can." OpenAI's Codex security page warns: "Prompt injection can cause the agent to fetch and follow untrusted instructions." OpenClaw's prompt injection page notes that this needs no stranger in your DMs: any untrusted content the agent reads, such as web pages, emails, documents or pasted logs, can carry adversarial instructions.
Then the agent can do whatever its user can:
- Read SSH keys,
.envfiles and its own login:~/.claude/.credentials.jsonfor Claude Code,~/.codex/auth.jsonfor Codex,~/.openclaw/for OpenClaw. - Send what it read to any server the network allows.
- Delete or rewrite code and push it with the user's git credentials.
- Leave something that runs later, such as a line in
.bashrc. - With sudo or Docker access, do all of this to the whole server.
OWASP calls this excessive agency and names three causes: excessive functionality, permissions and autonomy. The steps below cut all three.
Step 1: Give the Agent Its Own User Without sudo
Create one Linux user per agent and keep admin work on your own account:
sudo adduser agent
id agent
sudo -l -U agent
id should show no group besides agent, and sudo -l -U should answer User agent is not allowed to run sudo on plus your hostname. Some guides grant sudo during setup: OpenClaw's DigitalOcean guide, which our OpenClaw guide follows, runs usermod -aG sudo openclaw; if you followed it, use openclaw wherever this guide says agent. Take sudo back afterwards. Given a user and a group, deluser removes the user from that group:
sudo deluser openclaw sudo
Keep the user out of the docker group too. Docker's post-install page warns that it "grants root-level privileges to the user." Then check that the agent can't read your files:
sudo -u agent ls /home/youruser
You want Permission denied. adduser's defaults give every user a private group and a closed home directory, but older accounts and images differ. Install packages from your admin account, and reach the agent's account with its own SSH key or sudo -iu agent.
Step 2: Lock Down Secrets and Keep Keys Out of Git
File permissions only stop other users: the agent reads everything its own user owns, mode 600 or not. So secrets it doesn't need, such as production database passwords, cloud keys and payment keys, belong to another account or machine. Services too: give the agent its own database user and API keys with the narrowest rights, never your admin credentials. Run chmod 600 on the .env files it does use, so a second agent can't read them. openclaw security audit --fix tightens OpenClaw's state files; its secrets page recommends 600 for files and 700 for folders.
gitignore doesn't affect files Git already tracks, so untrack .env as well as ignoring it:
echo ".env" >> .gitignore
git rm --cached .env
git commit -m "Stop tracking .env"
Older commits still hold the key. GitHub's advice is to revoke or rotate a leaked secret first, which may make rewriting history unnecessary.
Give the agent a git credential for one repository: a deploy key, read-only unless the agent must push, or a fine-grained token limited to that repository. And don't log in to its account with ssh -A: processes running as that user can use your forwarded keys while you're connected, and the ssh manual says agent forwarding "should be enabled with caution."
Transcripts hold secrets too. Claude Code writes plaintext transcripts of every tool call and result to ~/.claude/projects/, Codex keeps ~/.codex/history.jsonl, and OpenClaw's transcripts can include pasted secrets and command output.
Step 3: Turn On Each Tool's Own Guardrails
Claude Code
Claude Code's sandbox is off by default. On Linux it needs sudo apt-get install bubblewrap socat, and Ubuntu 24.04 adds an AppArmor step that our Claude Code VPS guide covers. Then put this in the agent user's ~/.claude/settings.json, which applies to every project:
{
"env": { "CLAUDE_CODE_SUBPROCESS_ENV_SCRUB": "1" },
"permissions": {
"disableBypassPermissionsMode": "disable",
"deny": ["Read(./.env)", "Read(./.env.*)"]
},
"sandbox": {
"enabled": true,
"failIfUnavailable": true,
"allowUnsandboxedCommands": false,
"filesystem": { "denyRead": ["~/.claude/.credentials.json"] },
"network": {
"allowedDomains": ["github.com", "registry.npmjs.org"],
"strictAllowlist": true
}
}
}
Per the settings reference:
failIfUnavailablestops Claude Code when the sandbox can't start, instead of running commands unsandboxed.allowUnsandboxedCommands: falsestops Claude from retrying a blocked command outside the sandbox.disableBypassPermissionsModerejects--dangerously-skip-permissions.- The deny rules keep
.envfiles from Claude's file tools and from sandboxed commands. denyReadhides Claude's login from shell commands, which can otherwise read most of the machine,~/.sshincluded.- The scrub strips credentials such as
ANTHROPIC_API_KEYfrom the commands Claude starts. On Linux,psandkillthen stop seeing other processes. strictAllowlistrefuses hosts outsideallowedDomains. Each allowed domain is a possible exit for data, and Anthropic namesgithub.comas one, so keep the list short.
From version 2.1.283, interactive sessions start in auto mode, where a classifier reviews actions instead of you. Anthropic's permission modes page says auto mode "does not guarantee safety," but deny rules hold in every mode.
Codex CLI
Codex sandboxes commands by default: on Linux with bubblewrap plus seccomp, with writes limited to the workspace and the network off. Install it with sudo apt install bubblewrap; if Codex on Ubuntu 24.04 warns that it can't create a user namespace, its sandbox page loads an extra AppArmor profile, with the commands in our Codex CLI VPS guide.
Keep the default workspace-write sandbox with on-request approvals, and skip --dangerously-bypass-approvals-and-sandbox (alias --yolo), which OpenAI's own table lists as "No sandbox; no approvals (not recommended)." If a task needs the network, enable it together with the proxy and an allowlist in the agent user's ~/.codex/config.toml:
sandbox_mode = "workspace-write"
approval_policy = "on-request"
[sandbox_workspace_write]
network_access = true
[features.network_proxy]
enabled = true
domains = { "github.com" = "allow", "registry.npmjs.org" = "allow" }
Without the proxy block, network access is unrestricted. codex sandbox linux plus a command shows what the sandbox blocks, and a prefix_rule with decision = "forbidden" in ~/.codex/rules/default.rules stops Codex from running that command prefix outside the sandbox (rules).
OpenClaw
Unless you change it, OpenClaw's exec policy on the gateway host is security full with ask off: no allowlist, no prompts. As the OpenClaw user:
openclaw exec-policy show
openclaw exec-policy preset cautious
openclaw security audit --deep
The cautious preset runs allowlisted commands, asks about the rest and denies when nobody answers. Sandboxing is off by default too; setting agents.defaults.sandbox.mode to all moves every session's tool execution into containers (non-main leaves the agent's main session on the host), and on the Docker backend those containers get no network by default. That backend needs Docker access for the OpenClaw user, which is root by another name, so consider OpenClaw's rootless Podman backend. Our OpenClaw VPS guide covers keeping the Gateway on loopback and vetting skills.
Step 4: Firewall the Agent In and Out
Inbound, allow SSH only. Claude Code's Remote Control "never opens inbound ports," and OpenClaw's dashboard belongs on loopback. UFW is initially disabled on Ubuntu; on Debian 12, install it first:
sudo apt install ufw
sudo ufw allow 22/tcp
sudo ufw enable
sudo ss -ltnp
UFW installs with incoming traffic denied and outgoing allowed. ss -ltnp shows what listens, such as a dev server the agent left on 0.0.0.0. UFW keeps it closed unless Docker published it, because Docker routes those packets before UFW's rules apply; see our Docker and UFW guide.
Outbound is where stolen data leaves. UFW's commands can't match a Linux user, but its rules files take raw iptables rules, and the owner match can. Add these lines to /etc/ufw/before.rules, just above the final COMMIT:
# agent user: only DNS and HTTPS out
-A ufw-before-output -m owner --uid-owner agent -p udp --dport 53 -j ACCEPT
-A ufw-before-output -m owner --uid-owner agent -p tcp -m multiport --dports 53,443 -j ACCEPT
-A ufw-before-output -m owner --uid-owner agent -j REJECT
Repeat them in /etc/ufw/before6.rules with ufw6-before-output as the chain, or the agent can still leave over IPv6, which every Space-Node VPS has next to IPv4. Both files accept loopback traffic earlier, so local services keep working. Reload and test:
sudo ufw reload
sudo -u agent curl -sS -o /dev/null -m 10 https://example.com && echo "HTTPS works"
sudo -u agent curl -sS -o /dev/null -m 10 http://example.com || echo "Port 80 blocked"
ufw status doesn't list these rules. Add port 22 if the agent pushes over SSH. HTTPS to any host stays open, which is what the sandbox allowlists cover; mail, plain HTTP and every other port are closed.
Step 5: Log In With Keys Only
With key login working for your admin account, set PasswordAuthentication no in a file in /etc/ssh/sshd_config.d/, where Ubuntu's OpenSSH guide puts custom settings; OpenSSH's default is yes. Test the config and restart SSH as that guide does, keeping this session open until a new key login works, then check what sshd uses:
sudo sshd -t && sudo systemctl restart ssh.service
sudo sshd -T | grep -E '^(passwordauthentication|permitrootlogin) '
You want passwordauthentication no, and for root prohibit-password (Ubuntu 24.04 prints its alias, without-password) or no. Our SSH hardening guide adds more layers.
Step 6: Log What the Agent Runs
Claude Code's transcripts (30 days by default, cleanupPeriodDays), Codex's history and openclaw logs --follow show what each tool did, but a hijacked agent can edit them, since they live in its account. For a record it can't touch, have auditd log every program the user starts:
sudo apt install auditd
sudo tee /etc/audit/rules.d/agent.rules > /dev/null <<'EOF'
-a always,exit -F arch=b64 -S execve -F uid=agent -k agent-exec
-a always,exit -F arch=b32 -S execve -F uid=agent -k agent-exec
EOF
sudo augenrules --load
sudo ausearch -k agent-exec -i --start today
The audit.rules manual advises one rule per architecture, hence two lines. augenrules merges and loads the files in /etc/audit/rules.d, and ausearch with -i shows user names instead of numbers.
If You Think a Prompt Went Wrong
- Stop the agent. For OpenClaw,
sudo systemctl --user --machine=agent@.host stop openclaw-gateway.servicestops the Gateway; thensudo pkill -u agentends everything else that user runs. - Rotate what it could read: the AI login or API key, the git token or deploy key, and every secret in its files.
- Check what ran:
sudo ausearch -k agent-exec -i --start yesterday,sudo crontab -u agent -l, the user's~/.ssh/authorized_keysand shell startup files, andgit login its projects. - Rebuild when in doubt from a clean image, then restore from git and backups. Backups are not included in Space-Node plan prices, so keep your own or arrange them through a support ticket.
FAQ
Is --dangerously-skip-permissions safe on a VPS?
Not on a server you care about. Anthropic keeps bypass mode for "isolated environments like containers, VMs, or dev containers without internet access," and a VPS is online. As root, Claude Code refuses it anyway: --dangerously-skip-permissions cannot be used with root/sudo privileges for security reasons. For fewer prompts, use auto mode with the sandbox settings above.
Does the agent's user need sudo to install packages? No. Install system packages from your admin account when the agent asks. With sudo or the docker group, a hijacked prompt gets the whole server.
Does the sandbox protect my API key? Partly. Claude Code's sandboxed commands read most of the machine by default and inherit its environment, which is why the settings above hide the login file and scrub credentials. Codex keeps commands offline by default. The agent process itself always holds its key, so give each agent its own key that you can revoke on its own.
Can I run several agents on one VPS? Yes, with one Linux user per agent, so one hijacked agent can't read another's files. OpenClaw's trust model treats each gateway as one trust boundary and prefers one OS user, host or VPS per boundary.
Can a prompt injection reach my agent if only I can message it? Yes. OpenClaw's docs call the content the agent reads, such as fetched pages, emails and attachments, a threat surface of its own, not just the sender. That's why the limits in this guide sit outside the model.
Sources
All opened on 30 October 2026:
- Anthropic, Claude Code docs: Security, Sandboxing, Permissions, Permission modes, Settings reference, Environment variables, The .claude directory and Remote Control
- OpenAI, Codex docs: Agent approvals and security, Sandbox, Rules, Authentication and Advanced configuration
- OpenClaw docs: Security, Trust model, Prompt injection, Secrets, storage and logs, Exec approvals, Approvals CLI, Sandbox modes and backends, Docker backend, Podman backend, Security CLI, Logs CLI, Gateway runbook and DigitalOcean guide
- OWASP: LLM06:2025 Excessive Agency
- Users and sudo: adduser.conf(5), deluser(8), sudo(8) and Docker's post-install steps
- Git and GitHub: gitignore, deploy keys, personal access tokens and removing sensitive data
- Firewall: Ubuntu firewall guide, ufw(8), ufw-framework(8), UFW's before.rules and before6.rules, iptables-extensions(8), Docker's packet filtering and firewalls, ss(8) and the curl manual
- SSH: ssh(1), sshd(8), sshd_config(5) and Ubuntu's OpenSSH guide
- Audit and processes: auditctl(8), audit.rules(7), augenrules(8), ausearch(8), pkill(1), crontab(1), chmod(1), id(1), systemctl and the Debian 12 packages auditd, ufw, bubblewrap and socat