Atomic Agent mascot with a flashlight and checklist inspecting a wall of safe deposit lockers, some sealed with keypads and some with golden keys lying in the open or taped to the door
Security

Where 13 AI Coding Agents Store Your API Keys, File by File

Published
Reading time
14 min read
Tag

13 AI coding agents checked in October 2026: the exact file that holds your API keys on each OS, its permissions, keychain support and vendor key stores.

This is a reference table of where 13 AI coding agents keep your API keys on disk: Aider, Atomic Agent, Claude Code, Cline CLI, Codex CLI, Crush, Gemini CLI, GitHub Copilot CLI, Goose, Kilo CLI, OpenCode, Pi and Qwen Code. For each one we list the exact file path per OS, the file format, its permissions, whether the OS keychain is used, and whether the vendor offers a hosted place to store your provider keys. Every row links to the doc or source file it came from.

Disclosure: we build Atomic Agent. It sits in the table in alphabetical order like everyone else, and its row includes the weak spot: plaintext files, no keychain.

Why key files matter after October 17

Two things happened in the same month. On October 17, 2026, OpenCode told some Console users that provider keys saved in the hosted Console may have been exposed. And security researchers published two reports showing infostealers now collect AI coding agent files from developer machines. So the question “where is my key?” now has two halves.

The first half is the vendor. OpenCode said on X on October 17 that the breach “impacted 1.4% of OpenCode Console users”, and its customer email warned that third-party provider keys saved in a workspace may have been exposed (DEV Community). Keys added only in the local CLI were outside that system.

The second half is your own disk. On September 8, Gen Digital reported that the Amatera stealer targets data from Cline and Continue, the Remus stealer targets Claude, Cursor and OpenCode, and Djinn Stealer on macOS adds Codex, Gemini and Kilo to that list. On October 15, Symantec reported that the Aoki stealer takes .claude.json, Codex config.toml and .env files, plus tokens for GitHub Copilot, Cursor and Gemini. Stealers work from lists of known paths. The table below is that list, so you can check those paths first.

Key findings

Out of 13 agents, 2 use the OS keychain by default on every OS and 2 more use it in part. 9 write plaintext files by default, and 7 of those 9 restrict the file to 0600. Three vendors offer a hosted place for your provider keys: OpenCode, Kilo and GitHub.

  • Keychain by default on every OS: 2 of 13. Goose (provider keys) and GitHub Copilot CLI (its GitHub login).
  • Keychain by default in part: 2. Claude Code on macOS only, and Gemini CLI for an API key typed into the app when a keychain is available (never on WSL). Their other paths are files.
  • Keychain as an option: 2. Codex CLI with cli_auth_credentials_store = "keyring", and Pi with a key defined as a shell command that reads the macOS Keychain.
  • Plaintext file by default: 9 of 13. Aider, Atomic Agent, Cline CLI, Codex CLI, Crush, Kilo CLI, OpenCode, Pi and Qwen Code.
  • 0600 permissions: 7 of those 9. Atomic Agent, Cline CLI, Codex CLI, Crush, Kilo CLI, OpenCode and Pi set owner-only mode in code. Aider and Qwen Code write with your default umask, which on most systems means 0644.
  • Vendor-side key store: 3 of 13. OpenCode Console and Kilo’s web BYOK page are optional and personal. GitHub lets Copilot enterprise owners, and org owners they allow, add provider keys (public preview). Charm’s Hyper (BYOK on its Enterprise plan) and Pi’s alpha Radius gateway list BYOK without documenting storage, so we left them out of the count.
  • Fallbacks are plaintext. Claude Code on Linux and Windows, Goose without a keyring, Copilot CLI without a keychain (it asks first) and Gemini CLI’s OAuth login all end up in a file.

How we checked

We checked every agent in October 2026 against the release listed in the table. For 11 of the 13 we read the source code that writes the key file; for Claude Code and GitHub Copilot CLI we relied on official docs, because both are closed source.

Where we read source, we used the default branch at the time of checking and the release tag where it mattered: OpenCode at tag v1.19.3 plus its V2 docs, and Aider at tag v0.86.0. Atomic Agent was checked against its code at v0.6.9 and Desktop v0.0.3 (main as of October 15, 2026). “Permissions” means the mode the code sets on macOS and Linux. Windows has no POSIX modes; unless a row says otherwise, those files inherit the folder’s access list (Atomic Agent also restricts .env to your account with icacls). We did not run any agent or inspect a real install, so a value marked “not documented” means we found no statement either way.

The table: where each agent keeps your keys

Most agents keep keys in a JSON or dotenv file in your home folder, readable only by your user account. Goose and Copilot CLI lean on the OS keychain everywhere, Claude Code on macOS, and Gemini CLI for typed keys. OpenCode and Kilo are the two with an optional web page that stores your provider keys on their side.

AgentVersion checkedKey file path(s)FormatPermissionsKeychainVendor stores keys?Source
Aiderv0.86.0~/.aider/oauth-keys.env (OpenRouter login); otherwise your own .env or .aider.conf.yml in home, git root or current folderdotenv / YAMLumask default (no explicit mode)NoneNodocs, code
Atomic Agentv0.6.9, Desktop v0.0.3~/.atomic-agent/.env (terminal), ~/.atomic-agent/config.json (desktop), all OSdotenv / JSON0600 (config.json since v0.6.7); .env also owner-only ACL on WindowsNoneNocode, code
Claude Codev2.1.302macOS: Keychain. Linux and macOS fallback: ~/.claude/.credentials.json. Windows: %USERPROFILE%\.claude\.credentials.json. Console sign-in profile: ~/.config/anthropic (Windows: %APPDATA%\Anthropic)Keychain item / JSON0600 (Linux, macOS fallback); profile ACL on WindowsDefault on macOSNodocs
Cline CLIcli-v3.0.73~/.cline/data/settings/providers.json, all OSJSON0600, folder 0700NoneNocode
Codex CLIrust-v0.163.0~/.codex/auth.json (Windows: %USERPROFILE%\.codex\auth.json), holding ChatGPT tokens or your OpenAI API keyJSON0600 (macOS, Linux)Opt-in (keyring)Nodocs, code
Crushv0.98.1~/.local/share/crush/crush.json. Windows: %LOCALAPPDATA%\crush\crush.jsonJSON0600NoneEnterprise only (Hyper BYOK), storage not documentedcode, Hyper
Gemini CLIv0.64.0Typed key: keychain item gemini-cli-api-key, fallback ~/.gemini/gemini-credentials.json. Google login: ~/.gemini/oauth_creds.json. Env key: project .gemini/.env or .env first, then ~/.gemini/.envKeychain / obfuscated JSON / JSON / dotenv0600 (fallback file, oauth_creds.json); .env is yours to manageDefault for typed key (not on WSL)Nocode, code
GitHub Copilot CLIv1.0.98GitHub token: keychain service copilot-cli, fallback ~/.copilot/config.json (asks first). BYOK: env vars onlyKeychain / JSONNot documentedDefaultEnterprise or org owners (preview)docs, BYOK
Goosev1.55.0Keyring service goose; fallback ~/.config/goose/secrets.yaml (Windows: %APPDATA%\Block\goose\config\secrets.yaml)Keyring / YAML0600 (macOS, Linux)DefaultNocode
Kilo CLIv7.8.12~/.local/share/kilo/auth.json, all OSJSON0600NoneYes, optional (app.kilo.ai/byok)code, docs
OpenCodev1.19.3; V2 pre-release (2.0.32 tag)v1: ~/.local/share/opencode/auth.json. V2: ~/.local/share/opencode/opencode.db, all OSJSON / SQLite0600 (auth.json; opencode.db in V2)NoneYes, optional (Console)code, V2 docs
Piv1.3.0~/.pi/agent/auth.json, all OSJSON0600, folder 0700Opt-in (shell command)Unclear (Radius alpha)code
Qwen Codev0.25.2~/.qwen/settings.json (env block, written by /auth), or your own .qwen/.envJSON / dotenvumask default (no explicit mode)NoneNodocs

Paths starting with ~ mean your home folder on every OS, including Windows, unless the row says otherwise. Most agents above also read keys from environment variables. That keeps keys out of the agent’s files, and where the variable itself comes from is up to you.

Agent by agent

Each entry below gives the path in code font, what the agent does by default, any setting that moves the file, and the one change that moves your key somewhere better, if the agent offers one. Entries are in the same alphabetical order as the table, Atomic Agent included.

Aider

Aider never writes your provider keys unless you use its OpenRouter login, which appends OPENROUTER_API_KEY to ~/.aider/oauth-keys.env with no explicit file mode (onboarding.py at v0.86.0). Otherwise it reads keys from flags, environment variables, a .env file (or one passed with --env-file) or .aider.conf.yml in your home folder, git root or current folder (API keys doc). Aider adds .aider* to .gitignore, and .env too if one exists in the git root. Its latest release, v0.86.0, is from August 9, 2025. Run chmod 600 ~/.aider/oauth-keys.env if the file exists.

Atomic Agent

Atomic Agent keeps cloud keys in ~/.atomic-agent/.env when you set them up in the terminal, and inline in ~/.atomic-agent/config.json when you use the desktop app. Both are plaintext, written 0600 on macOS and Linux (owner-only-file.ts); config.json got that mode in v0.6.7, so older installs tighten it on the next save. On Windows, .env is also restricted to your account with icacls (windows-acl.ts), while config.json inherits the folder’s access list. There is no keychain support and no Atomic-hosted key store, and the default provider is a local model that needs no key. The desktop app passes config to the agent on stdin, which keeps keys out of the process list.

Claude Code

Claude Code saves its login in the encrypted macOS Keychain. On Linux, and as the macOS fallback when the Keychain rejects the write, it uses ~/.claude/.credentials.json with 0600; on Windows the same file sits under %USERPROFILE% and is restricted to your account by default (authentication docs). A Console sign-in also keeps a profile in ~/.config/anthropic (%APPDATA%\Anthropic on Windows), and CLAUDE_CONFIG_DIR moves the credentials. An ANTHROPIC_API_KEY from your environment is not written to disk; Claude Code only remembers whether you approved it. Anthropic has no hosted store for third-party keys. On Linux, the practical move is a secret manager feeding the environment variable at launch.

Cline CLI

Cline CLI writes provider keys to ~/.cline/data/settings/providers.json, mode 0600 inside a 0700 folder, with no keychain (provider-settings-manager.ts). Passing -k on a run also saves the key there (CLI reference), and CLINE_DIR, CLINE_DATA_DIR or CLINE_PROVIDER_SETTINGS_PATH move it. Enterprise remote config sets providers centrally, while members’ keys stay local. One caveat: org settings, which can include access keys for prompt storage, land in .cline/enterprise/ inside your project, and Cline’s docs say to gitignore it.

Codex CLI

Codex CLI defaults to cli_auth_credentials_store = "file", which writes ~/.codex/auth.json (ChatGPT tokens, or your OpenAI API key if you log in with one) with 0600 on macOS and Linux (defaults.toml). OpenAI’s auth docs say to treat that file like a password, and CODEX_HOME moves it. Set cli_auth_credentials_store = "keyring" in ~/.codex/config.toml to move it to the OS keychain, auto to try the keychain first, or ephemeral to keep it in memory only. Keys for custom providers normally come from the env_key variable at runtime and are never written to auth.json, though the discouraged experimental_bearer_token setting puts a key in plain text in config.toml (model-provider-info).

Crush

Crush writes keys you paste in the UI to ~/.local/share/crush/crush.json (%LOCALAPPDATA%\crush\crush.json on Windows) as providers.<id>.api_key, and OAuth tokens, refresh tokens included, under providers.<id>.oauth, with 0600 (store.go). There is no keychain dependency, and CRUSH_GLOBAL_DATA or XDG_DATA_HOME move the file. Crush also reads environment variables, so you can skip the UI prompt and feed keys from a secret manager. Charm’s optional Hyper gateway advertises bring-your-own frontier-lab keys on its Enterprise plan; we found no documentation of how it stores them.

Gemini CLI

Gemini CLI has three storage paths. An API key typed into its dialog goes to the OS keychain under gemini-cli-api-key when one is available (never on WSL), falling back to ~/.gemini/gemini-credentials.json, encrypted with AES-256-GCM under a key derived from a fixed passphrase, your hostname and your username, which any program running as you can recompute (fileKeychain.ts). A Google login is saved as plaintext ~/.gemini/oauth_creds.json with 0600 unless you set GEMINI_FORCE_ENCRYPTED_FILE_STORAGE=true, which moves it to the keychain (service gemini-cli-oauth) or the encrypted file (oauth2.ts). GEMINI_API_KEY in a .env file is plaintext; Gemini only reads it, checking the project’s .gemini/.env and .env before ~/.gemini/.env.

GitHub Copilot CLI

Copilot CLI stores your GitHub OAuth token in the OS keychain under copilot-cli, and asks before falling back to plaintext ~/.copilot/config.json (authenticate docs). BYOK keys come from COPILOT_PROVIDER_API_KEY, and we found no step that saves them (BYOK docs). On the vendor side, Copilot Business and Enterprise let enterprise owners, and org owners they allow, add provider keys for custom models, handled server-side and in public preview (BYOK concepts). The CLI is closed source, so this row rests on docs.

Goose

Goose stores provider keys in the system keyring under the service goose on macOS, Linux and Windows (base.rs). If no keyring service is reachable, or GOOSE_DISABLE_KEYRING is set in the environment or config.yaml, it falls back to plaintext ~/.config/goose/secrets.yaml (%APPDATA%\Block\goose\config\secrets.yaml on Windows), written 0600 on macOS and Linux. On a headless Linux box without a keyring service, check whether that file exists. Goose has no vendor-hosted key store.

Kilo CLI

Kilo CLI is a fork of OpenCode and inherits its local design: plaintext ~/.local/share/kilo/auth.json (%USERPROFILE%\.local\share\kilo\auth.json on Windows) with 0600 (auth/index.ts). Kilo also offers an optional web page, app.kilo.ai/byok, where you can paste Anthropic, OpenAI, Google and other keys so the Kilo Gateway uses them (BYOK docs). That page puts a copy of your key on Kilo’s side (Kilo is now owned by Anaconda). If you want keys on your machine only, use /connect locally and skip the web page.

OpenCode

The current release, v1.19.3, writes provider keys from /connect to ~/.local/share/opencode/auth.json with 0600 (auth/index.ts); its opencode.db already holds Console login tokens. The V2 line, published as tags with npm latest still on 1.19.3, imports auth.json into opencode.db, created with 0600, and writes new keys only there (V2 docs); neither the docs nor the migration delete the old file. On Windows both sit under %USERPROFILE%\.local\share\opencode. We found no keychain or encryption code. The breached system was the optional Console, which “stores the credential and serves each provider through its gateway” (Console docs). Our OpenCode vs Claude Code comparison puts the two key stores side by side.

Pi

Pi writes keys to ~/.pi/agent/auth.json, mode 0600 inside a 0700 folder (auth-storage.ts); an existing file keeps its old mode, and PI_CODING_AGENT_DIR moves it. A key can instead be a shell command such as !security find-generic-password ..., which reads it from the macOS Keychain at use time (providers doc). That is the easiest keychain setup in this list. Earendil’s optional Radius gateway, in early alpha, lists “BYOK credential providers” in its API, and its storage is not documented (Radius agent doc).

Qwen Code

A key typed into Qwen Code’s /auth dialog lands in an env block in plaintext ~/.qwen/settings.json, written with no explicit file mode, so a new file usually gets 0644 (auth docs). The docs headline that one-file settings.json setup but advise .qwen/.env for security, and environment variables work too. A legacy Qwen OAuth token sits in ~/.qwen/oauth_creds.json with 0600. We found keychain code for MCP tokens only, and no hosted store for third-party keys. Run chmod 600 ~/.qwen/settings.json after setup.

How to check your own machine

Run the commands below to see which of these files exist on your machine and who can read them. On macOS and Linux, a mode of -rw------- means owner-only (0600); anything else (ignore a trailing @ or +) means another account may be able to read it. None of these commands print key values.

List every file from the table that exists:

Terminal window
for f in ~/.aider/oauth-keys.env ~/.aider.conf.yml ~/.atomic-agent/.env \
~/.atomic-agent/config.json ~/.claude/.credentials.json ~/.claude.json \
~/.config/anthropic ~/.cline/data/settings/providers.json ~/.codex/auth.json \
~/.codex/config.toml ~/.local/share/crush/crush.json ~/.gemini/.env \
~/.gemini/oauth_creds.json ~/.gemini/gemini-credentials.json ~/.copilot/config.json \
~/.config/goose/secrets.yaml ~/.local/share/kilo/auth.json \
~/.local/share/opencode/auth.json ~/.local/share/opencode/opencode.db \
~/.pi/agent/auth.json ~/.qwen/settings.json ~/.qwen/.env ~/.qwen/oauth_creds.json; do
[ -e "$f" ] && ls -la "$f"
done

Find stray key files deeper in your home folder, including project .env files. On macOS this walks Desktop, Documents and Downloads, so the system may ask for folder access; you can deny it.

Terminal window
find ~ -maxdepth 4 \( -name auth.json -o -name .credentials.json \
-o -name oauth_creds.json -o -name '*.env' -o -name .env \) \
-not -path '*/node_modules/*' 2>/dev/null

Show only the ones your group or other accounts can read (on macOS every local user shares the staff group), then tighten the ones you find:

Terminal window
find ~ -maxdepth 4 \( -name auth.json -o -name '*.env' -o -name .env \) \
\( -perm -004 -o -perm -040 \) 2>/dev/null
[ -f ~/.aider/oauth-keys.env ] && chmod 600 ~/.aider/oauth-keys.env
[ -f ~/.qwen/settings.json ] && chmod 600 ~/.qwen/settings.json

On macOS, check whether keychain items exist (this prints metadata only, no secret):

Terminal window
security find-generic-password -s goose
security find-generic-password -s copilot-cli
security find-generic-password -s gemini-cli-api-key
security find-generic-password -s gemini-cli-oauth

On Windows, in PowerShell:

Terminal window
Get-ChildItem $HOME -Recurse -Depth 5 -Force -ErrorAction SilentlyContinue `
-Include auth.json,.credentials.json,oauth_creds.json,crush.json,providers.json,secrets.yaml,.env,*.env

Finally, make sure none of them is in git: git ls-files | grep -E '(^|/)(\.env|auth\.json)$' inside each project.

What to do if one of these files leaked

Treat any key in a leaked file as compromised and revoke it at the provider. Deleting the key from the agent, or deleting the file, does nothing to a copy someone already has. Then issue a new key, check usage since the suspected leak date, and set a spend limit.

  1. Revoke the key in the provider dashboard (OpenAI, Anthropic, Google, OpenRouter and so on).
  2. Create a new key, ideally one per tool, so the next leak has a smaller blast radius.
  3. Check usage and billing from the suspected leak date, and ask the provider about refunds for usage you did not make.
  4. Set a monthly spend limit at the provider.
  5. Confirm the old key now returns 401.
  6. If the file was readable by others or synced somewhere, fix that before you paste the new key in.

We cover this step by step in how to store AI API keys securely.

Frequently asked questions

FAQ

API key storage in AI coding agents, answered.

  • Which AI coding agents store API keys in the OS keychain?

    Two do on every OS: Goose for provider keys and GitHub Copilot CLI for its GitHub login. Two more do in part: Claude Code on macOS only, and Gemini CLI for an API key typed into the app when a keychain is available. Codex CLI and Pi can use the keychain if you change one setting or point the key at a Keychain command. The other seven write plaintext files.

  • Is a key file with 0600 permissions safe?

    Partly. 0600 stops other user accounts on the same machine from reading the file. It does not stop a program running as you, which is how infostealers and malicious npm packages work, and Gen Digital and Symantec both reported stealers in 2026 that collect AI coding agent files.

  • Did the OpenCode breach expose keys stored by the OpenCode CLI?

    No, based on what OpenCode has disclosed so far. The October 2026 incident hit the hosted OpenCode Console, where users had saved provider keys for a shared gateway. Keys added only in the local CLI sit in auth.json or opencode.db on your machine, and OpenCode's announcement limited the breach to Console users.

  • Are environment variables safer than a key file?

    Only if the variable is filled from a password manager or keychain when you launch the agent. A key exported in ~/.zshrc or kept in a .env file is one more plaintext file that any process running as you can read. GitHub Copilot CLI reads BYOK keys from environment variables only, and Codex CLI custom providers normally do too, though its experimental_bearer_token setting can put a key in plain text in config.toml.

  • Does Atomic Agent encrypt my API keys?

    No. Atomic Agent keeps cloud provider keys in plaintext files under ~/.atomic-agent, written with 0600 permissions on macOS and Linux (config.json since v0.6.7), with .env also locked to your account on Windows, and it has no OS keychain support. Its default provider is a local model on 127.0.0.1 that needs no key, and there is no Atomic-hosted store for provider keys.

Which setup keeps your keys safest?

The safest setup is a key that lives in the OS keychain or a password manager, reaches the agent at launch, and exists nowhere else. Goose does that by default, Codex CLI and Pi do it with one change, and most of the others accept an environment variable you can fill from a secret manager.

If your agent only offers a file, keep it at 0600, keep it out of git and synced folders, and give that agent its own key with a spend limit. Skip optional web pages that store provider keys unless you need a shared team gateway, because each one adds a second copy of the key on someone else’s servers. A local model is the only setup in this table that needs no provider key at all; Atomic Agent defaults to one, and you can download it here.

We will update this table when any of the 13 agents changes where it stores keys. If you maintain one of them and a row is out of date, tell us and we will fix it with a link to your change.


Get new posts
by email

One email when we post something new.

Share
Written by
Nadya Dudka Product, Atomic Agent
Andrew Dyuzhov SEO and growth

Run your local agent
in one click