GuidesSecurity

Securing a VPS for Claude Code and Codex: the keys they hold

Where Claude Code and Codex keep their logins on a Linux server, why each can read the other's, and the settings or separate users that close it off.

October 14, 2026The Everpod team
The short answer

Locking down the server itself is the well-documented half: no public SSH, keys only, automatic security updates. The half most checklists skip is what the agents carry. On Linux, Claude Code keeps its login in ~/.claude/.credentials.json and Codex in ~/.codex/auth.json, each a plain file readable by anything running as your user, and on a server with no desktop keyring that is the only place either can keep it. Run both agents under one user and each can read the other’s login, along with your SSH key and GitHub token. So keep long-lived keys off the box, give each agent its own Linux user or set each agent to keep out of the rest of your home directory, and keep the agents’ network closed except where the work needs it.

What the keys on the box are worth

Each agent stores a sign-in that keeps working without you, and the tools around them add more. On a server they sit in your home directory:

CredentialWhere it lives on LinuxWhat it opensUntil
Claude Code’s login (/login)
~/.claude/.credentials.json, mode 0600Your Claude plan: model requests and Remote ControlIt lapses or you sign out; Claude Code renews it in the background
claude setup-token
Printed once, saved nowhere by Claude Code; wherever you put CLAUDE_CODE_OAUTH_TOKENModel requests on your Claude plan, though not Remote ControlOne year, or until revoked
Codex, signed in with ChatGPT
~/.codex/auth.json, plain textCodex on your ChatGPT planCodex refreshes the tokens as it runs
Codex, with an API key
~/.codex/auth.json, the key in clearSpending on that API accountYou revoke the key
GitHub CLI (gh)
A credential store, or a plain-text file when it finds noneWhatever the token’s scope allows; a classic token reaches every repository you canThe token’s expiry, if it has one
SSH private key
~/.ssh/Every server and repository that trusts itYou remove the public half everywhere

OpenAI’s authentication page is blunt about its file: “treat ~/.codex/auth.json like a password: it contains access tokens.” Its own fallback for signing in a server with no browser is to copy that file across from a machine that has signed in, which shows the file alone is the sign-in. Anthropic’s authentication docs give the Linux location and mode for Claude Code’s, and describe the setup token as a “one-year OAuth token”; the setup-token guide covers keeping that one out of shell history.

Mode 0600 keeps other users out. It does nothing against programs running as you, and both agents, every command they start, every script in a repository they build and every package they install run as you.

On a server, the keyring is a file

Codex can keep its login in the operating system’s credential store instead (cli_auth_credentials_store = "keyring" in its config). On an Ubuntu 24.04 server with no desktop session, as of Codex 0.162.1, that fails at sign-in:

Error logging in: failed to write OAuth tokens to keyring: Platform secure storage failure: no secret service provider or dbus session found

With the auto setting, and with no setting at all, the same sign-in wrote auth.json at mode 0600. Claude Code on Linux uses its file in every case. The GitHub CLI’s manual says it stores the token in the system credential store and, if it finds none, “will fallback to writing the token to a plain text file”. On a headless server, plan as if every one of these is a file in your home directory.

Two agents, one user: each can read the other’s login

Both agents run their shell commands in a sandbox if you let them, and neither sandbox hides your home directory by default.

Codex. On Linux its sandbox is bubblewrap with a seccomp filter: the filesystem is read-only except, in its workspace-write mode, the project, and the network is off. Reads are not limited. Run through codex sandbox, which runs one command in Codex’s sandbox with no model involved, on a test account holding made-up credential files, in the read-only mode it uses by default and in workspace-write, the mode of Codex’s Auto preset:

Command inside Codex’s sandboxRead-onlyWorkspace-write
cat ~/.claude/.credentials.json
Printed itPrinted it
cat ~/.ssh/id_ed25519, cat ~/.config/gh/hosts.yml
Printed themNot run
touch a file in the project
Read-only file systemCreated
touch a file in the home directory
Read-only file systemRead-only file system
curl https://example.com
Could not resolve host200 with network access on

With the network off, the command itself has nowhere to send a key, but what it prints lands in the agent’s context, and an agent following someone else’s instructions can still carry it out through a tool that does reach the network, or a file it commits and pushes. Switch the network on for package installs (sandbox_workspace_write.network_access = true) and the command can post it anywhere directly, unless you also turn on Codex’s network proxy with a list of allowed domains.

Claude Code. Its sandbox is off until you turn it on. When it is on, Anthropic’s table of defaults says sandboxed commands can read “Most of the machine, including credential files such as ~/.ssh and ~/.aws/credentials”, and “There is no built-in credential deny list, so only the files and variables you list are restricted.” It does stop commands from writing to its own .credentials.json. Without the sandbox, Claude Code’s permissions page lists ls, cat, head, grep, find and other read-only commands that it “runs without a permission prompt in every mode”. Unless you turn on the setting below, a cat ~/.codex/auth.json does not stop to ask you.

None of this needs a rogue agent. The agent only has to follow an instruction planted where it reads: a README, an issue, a web page, a dependency’s install script. That is prompt injection, and it is why OpenAI’s security page warns that with the sandbox bypassed inside a dev container, “a malicious project can exfiltrate anything available inside the devcontainer, including Codex credentials.”

Closing it off

A Linux user for each agent

The simplest fix lets the operating system do it. On Ubuntu 24.04 a new user’s home directory is created with mode 0750 (HOME_MODE in /etc/login.defs), closed to other users, so an agent running as one user cannot read the other’s login whatever its settings say:

sudo adduser --disabled-password --gecos "" claude-agent
sudo adduser --disabled-password --gecos "" codex-agent
sudo -iu claude-agent    # install and sign in to Claude Code here
sudo -iu codex-agent     # and Codex here

Each user signs in to its own agent and gets only the repositories and keys that agent needs. Give neither of them sudo, and never run an agent as root. You log in as yourself and switch with sudo -iu. The cost is two of everything: two clones, two sets of tools in user space, two places to update.

Or one user, with each agent told what not to read

For Codex, a permission profile denies the paths to every command it runs. In Codex 0.162.1 the profile has to be named as default_permissions: with a [permissions] table and no such line, Codex refuses to load its config at all (“config defines `[permissions]` profiles but does not set `default_permissions`”).

# ~/.codex/config.toml
default_permissions = "locked"

[permissions.locked]
extends = ":workspace"

[permissions.locked.filesystem]
"~/.claude" = "deny"
"~/.ssh" = "deny"
"~/.config/gh" = "deny"
"~/.git-credentials" = "deny"
"~/.npmrc" = "deny"
"~/.docker" = "deny"
"~/.aws" = "deny"
"~/.codex/auth.json" = "deny"

It covers only the paths it names, so add any other credential file the machine holds, such as another agent’s login. With this as the default, the same cat commands returned “Permission denied”, files in the project could still be written, the network stayed off, and codex login status still read as signed in: Codex reads its own login itself, outside the sandbox, so denying auth.json to its commands does not sign it out. The rules are OpenAI’s permissions page: deny blocks reads and writes and wins over an equally specific read or write.

For Claude Code, the setting made for this is blockReadsOutsideWorkingDirectories (Claude Code 2.1.257 or later). With it on, Claude’s file tools refuse reads outside the folders you are working in, and a command that reads such a path, cat included, asks you first in every mode, auto mode included. With the sandbox on as well, sandboxed commands lose read access to your home directory and the other folders that hold users’ files, apart from your working directories and the parts of ~/.claude that commands need. That covers every credential file in the home directory, not only the ones you think of: ~/.git-credentials, npm’s and Docker’s configs, and any other agent’s login. In ~/.claude/settings.json, so it applies in every project:

{
  "permissions": {
    "blockReadsOutsideWorkingDirectories": true
  },
  "sandbox": {
    "enabled": true,
    "failIfUnavailable": true
  }
}

claude doctor accepted this file with no settings error on Claude Code 2.1.299. If your work needs reads elsewhere in the home directory, list the credential paths instead: Read deny rules stop Claude’s file tools and commands like cat, though not, in Anthropic’s words, “a Python or Node script that opens files itself”, and the sandbox’s credentials list with mode deny stops every process a sandboxed command starts. failIfUnavailable matters on a server: by default, if the sandbox cannot start (on Ubuntu 24.04, bubblewrap needs an AppArmor profile before it can make its namespaces; the Codex on Linux guide shows the fix, which both agents need), Claude Code runs commands unsandboxed. With it set, Claude Code exits at startup instead.

Keep tokens out of the environment as well. A key exported in ~/.bashrc is inherited by every command an agent runs. Claude Code strips credentials from the commands, hooks and MCP servers it starts when CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1 is set (it leaves GitHub tokens in place). Codex passes your environment through unless told otherwise: its shell_environment_policy can start commands from a trimmed set, and the automatic removal of variables named with KEY, SECRET or TOKEN is off by default (ignore_default_excludes defaults to true).

What to keep off the machine

Then assume something will leak anyway, and make it cheap: an API key with a spending limit set at the provider, tokens with expiry dates, and work kept on branches you review before it reaches anything that matters.

The server half

The steps every checklist gives are still worth doing, and each has its own guide: reach the machine over Tailscale SSH and close the public SSH port, or at least turn off password logins; let security updates install themselves; and check what is listening, because a dev server an agent starts on 0.0.0.0 is open to anyone who finds the port.

If you would rather not do the network and update steps yourself, a developer pod comes with them done. It accepts no incoming connections from the open internet: no open ports and no public SSH. A server your agent starts is reachable from your devices and from nowhere else. By default the machine does not start connections to your other devices. Once you have joined the machine to your Tailscale network, switched off its key expiry and logged in, we remove the access we used to set it up. From then on Everpod has no login on it and no key. We do not log in to fix or update it: security updates install themselves, and the machine is yours to administer. We host the server it runs on, so we can restart it or restore one of its daily backups when you ask us to. You sign in to each agent with your own subscription or API key. No model usage is included, and your sign-ins never pass through us. The agents’ logins live on it as on any server, so the per-agent users and settings above are yours to add there too.

Run Claude Code, Codex, OpenCode, Pi, Hermes or OpenClaw on an always-on developer pod.

A developer pod is a permanent cloud computer, set up and secured for you, with your pick of Claude Code, Codex, OpenCode, Pi, Hermes and OpenClaw installed. Run more agents at once than your laptop can hold, and keep them working when it’s closed. From $24 a month, built in about ten minutes.