The safest way to store an AI API key is to keep it in your OS keychain or a password manager, inject it into each tool only at launch, and give every tool its own key with a hard spend limit and an expiry date. If a key still leaks, revoke it at the provider first, then rotate.
This guide is about the keys you give AI coding agents: OpenAI, Anthropic, OpenRouter and Google Gemini keys that sit on a developer laptop and get used by tools that read files and run commands. Every setting and command below was checked against official docs, man pages and source code in October 2026.
Disclosure: we build Atomic Agent, an open source coding agent. It appears in practices 5 and 8, including its weak spot: it has no keychain support and stores saved keys in plaintext files.
Why AI API keys need extra care in 2026
AI keys now leak in three ways at once: through hosted consoles that store your provider keys, through infostealers that target coding agent folders, and through commits written with AI help. Each one costs real money, because a leaked model key is billed to you within minutes of being used.
Hosted consoles. On October 17, 2026, OpenCode said on X that a breach “impacted 1.4% of OpenCode Console users”, and its customer email warned that third-party provider keys saved in a workspace, such as OpenAI and Anthropic keys, may have been exposed (DEV Community). Anyone who saved a key there should treat it as leaked: revoke it at the provider and check usage, as in the checklist below.
Infostealers. On September 8, Gen Digital reported that the Amatera and Remus stealers collect local data from Claude, Cline, Continue, Cursor and OpenCode, and that the macOS stealer Djinn adds Codex. On October 15, Symantec reported that the Aoki stealer takes .claude.json, Codex config.toml and .env files. Our companion piece lists where 13 AI coding agents store your API keys, which is the same list of paths a stealer works from.
Commits. GitGuardian’s State of Secrets Sprawl 2026 counted 28.65 million new secrets on public GitHub in 2025. Leaks of AI service keys reached 1.27 million, up 81% year over year, and commits made with Claude Code assistance leaked secrets at 3.2% against a 1.5% baseline.
8 best practices for storing AI API keys
The eight practices below run from cheapest to most involved: split and scope your keys, cap their spend, make them expire, move them off plaintext files, inject them at runtime, think twice before uploading them to hosted consoles, scan for leaks, and use a local model where you can. The first three take about ten minutes per provider.
OpenAI’s own API key safety guide also lists eight practices. Ours overlap with it and add what changes when the key’s main user is a coding agent on your laptop.
1. Use one key per tool and project, with the least permissions it needs
Give each agent, project and teammate its own key. When a key leaks, you then know which tool lost it, you can revoke it without breaking everything else, and its usage graph tells you what the attacker did.
OpenAI. Create keys inside a project (Organization settings, pick the project, API Keys, + Create new secret key). OpenAI recommends project keys for teams and says sharing a key is against its Terms of Use. At creation you choose All, Restricted or Read Only; Restricted lets you set None, Read or Write per endpoint, so a coding agent can get Write on the endpoints it calls (such as Responses or Chat Completions), Read on Models so it can list them, and None elsewhere (Assign API key permissions). Each project also gets its own rate limits and spend controls.
Anthropic. On the API keys page of the Claude Console, a key can be scoped to one workspace, and an organization gets 100 workspaces by default (more through your account team). Make a workspace per agent or project (“coding-agent-laptop”, “ci”) and scope each key to it. Anthropic recommends identity-backed keys (personal or service account) over legacy workspace keys.
OpenRouter and Gemini. OpenRouter keys are cheap to create, so make one per tool at openrouter.ai/keys. For Gemini, new keys made in AI Studio since May 28, 2026 are authorization keys restricted to the Gemini API by default, and the API now rejects unrestricted standard keys (Gemini API keys).
2. Set a hard spend limit on every key
A spend limit turns a leaked key from an open-ended bill into a bounded one. Set the limit at the provider, because most agents do not enforce budgets for API-key providers (Atomic Agent does not), and make it a hard limit that rejects requests, since an alert alone lets traffic continue.
OpenAI. In Project settings, open Limits, then Spend, choose Edit spend limit, enter a monthly amount and turn on Enforce a hard limit. The same toggle exists at the organization level. Once hit, requests return 429 with project_spend_limit_exceeded or organization_spend_limit_exceeded. OpenAI notes that “enforcement is not instantaneous, so recorded spend can slightly exceed the configured amount” (spend limits guide).
Anthropic. In the Console, go to Workspaces, select the workspace and open Spend limits to cap monthly spending and set alert thresholds. Rate limits per workspace live on the Rate limits page. Combined with practice 1, this gives every agent its own monthly ceiling.
OpenRouter. You set a credit limit per key when you create it, with an optional daily, weekly or monthly reset. OpenRouter’s authentication docs say: “A key with no limit lets a leaked key or a runaway agent spend your entire balance.” Keys can also be created and capped through the Management API, which is handy for scripted rotation.
Gemini. Project spend caps are experimental: in AI Studio, open the Spend page, then Monthly spend cap and Edit spend cap. Billing data lags by about 10 minutes, and Google says “you are subject to overages for around a 10 minute latency period” (billing docs, Google blog). Billing tiers also carry built-in monthly caps, starting at $250 for Tier 1. Keys have no billing settings of their own, so put an agent’s key in its own project if you want a separate cap. One trade-off: a thief who burns through the cap also pauses your legitimate traffic until it resets. The billing-account tier cap pauses every project on that billing account, and on Prepay a leaked key can drain the shared credit balance, after which all keys in all linked projects stop working.
3. Make keys expire and rotate them on a schedule
Expiring keys limit how long a stolen copy stays useful, and they force you to practice rotation before an incident. Anthropic and OpenAI let you set an expiry when you create a key, and OpenRouter does through its API; for keys without one, rotate on a calendar reminder using the create, switch, verify, delete order.
Anthropic. When you create a key, pick an expiration preset: 3 hours, 1 day, 7 days, 30 days, a custom duration, or Never. Admins can set a maximum expiry for the organization, which removes the Never option. You cannot change expiry after creation. Anthropic emails a warning 7 days before expiry for keys that live at least 14 days, and 1 day before for keys that live at least 7 days; requests with an expired key return 401 authentication_error (authentication docs). For a laptop key used by a coding agent, 30 days is a sensible default.
OpenAI. Project keys can be created with an expiration date, after which “any requests from those keys will be rejected”, and admins can enforce a maximum API key lifetime at the organization or project level (best practices). Project limits cannot exceed the organization limit.
OpenRouter. Keys created through the Management API take an optional expires_at timestamp (create key API), and keys created for you during onboarding come with a $100 credit limit and expire after 180 days. A management key can create and delete your other keys, so give it an expiry too (management keys). For the switch itself, OpenRouter’s rotation cookbook describes the order: create the new key, update your apps, then delete the old one, and “always verify your new key is working in production before deleting the old one.”
Gemini. We found no expiry setting for Gemini keys. Google’s guidance is to delete unused keys and rotate periodically, and since May 7, 2026, unrestricted keys left dormant for an extended period get blocked automatically.
4. Keep keys out of plaintext files: use the OS keychain
A key in ~/.zshrc, a .env file or an agent’s config file is one plaintext copy that any program running as you can read. The OS keychain encrypts it at rest, and on macOS you can make it ask before every read. These commands prompt for the value, so the key never lands in your shell history.
macOS (security, built in):
# Store (-T "" means no app is trusted silently; the trailing -w prompts for the value)security add-generic-password -a "$USER" -s openai-api-key -U -T "" -w
# Read it backsecurity find-generic-password -a "$USER" -s openai-api-key -w
# Use it for one command onlyOPENAI_API_KEY="$(security find-generic-password -a "$USER" -s openai-api-key -w)" your-agentBy default, man security says, “the application which creates an item is trusted to access its data without warning.” Here that app is security itself, so without -T "" any script running as you could read the key silently while the keychain is unlocked. With -T "", every read shows a prompt: click Allow, not Always Allow. The cost is a prompt on every launch, including the Claude Code and Pi lookups in practice 5. -U updates the item if it exists. Avoid the -A flag, which the man page describes as allowing “any application to access this item without warning (insecure, not recommended!)”.
Linux (secret-tool, from libsecret; needs a running Secret Service such as GNOME Keyring or KWallet, see the man page):
secret-tool store --label='OpenAI API key' service openai account "$USER" # promptssecret-tool lookup service openai account "$USER"Headless servers often have no Secret Service running, so practice 5 is the better fit there.
Windows. Microsoft’s SecretManagement and SecretStore PowerShell modules give you an encrypted local vault. Microsoft now calls them feature complete and no longer in active development, though still patched for security.
Install-Module Microsoft.PowerShell.SecretManagementInstall-Module Microsoft.PowerShell.SecretStoreRegister-SecretVault -Name SecretStore -ModuleName Microsoft.PowerShell.SecretStore -DefaultVaultSet-Secret -Name OPENAI_API_KEY -Secret (Read-Host -AsSecureString)$env:OPENAI_API_KEY = Get-Secret -Name OPENAI_API_KEY -AsPlainTextyour-agentRemove-Item Env:OPENAI_API_KEYThe variable stays set for the whole PowerShell session, so remove it once the agent exits.
If a tool only accepts a file, keep it owner-only with chmod 600 and its folder at chmod 700, list .env in .gitignore, and keep it out of synced folders. A keychain narrows the exposure without closing it: once you click Allow, or if you skip -T "", a process running as you can still get the key. And whatever the storage, never let an agent cat a .env file. An agent that reads a file sends its contents to the model as-is, and Atomic Agent, for one, also keeps tool output in its local traces without redaction.
5. Inject keys at runtime with a secrets manager
Runtime injection means the key lives in a vault and exists only in the environment of the process you launch. Nothing is written to the project folder, and changing the key in one place updates every machine. This is the practical way to give a coding agent a key without saving it in the agent’s config.
1Password CLI. Put secret references in an env file and wrap the command with op run (1Password docs):
# agent.env contains references, safe to keep in the repo:# OPENAI_API_KEY="op://Private/OpenAI coding agent/credential"op run --env-file="./agent.env" -- your-agent
# Or read a single valueop read "op://Private/OpenAI coding agent/credential"1Password’s docs also warn: “You should assume that processes on your computer can access the environment of other processes run by the same user.”
Bitwarden Secrets Manager. Authenticate with a machine account token in BWS_ACCESS_TOKEN, then run bws run -- 'your-agent' (Bitwarden CLI docs). bws run exposes each secret as an environment variable named after the secret, so name it OPENAI_API_KEY in Bitwarden. Keep BWS_ACCESS_TOKEN out of ~/.zshrc, or you have simply moved the plaintext problem to a new file. The free plan covers up to 2 users, 3 projects and 3 machine accounts with unlimited secrets.
Doppler and Infisical. Doppler’s free Developer plan covers 3 users, and the command is doppler run -- your-agent (CLI docs). Infisical’s free plan includes 5 identities and unlimited projects; current CLI versions use infisical sm run -- your-agent, while version 0.43.145 and earlier use infisical run -- your-agent (CLI docs).
Several coding agents can also pull keys from the keychain themselves or read them from the environment:
- Claude Code runs an
apiKeyHelpershell command and uses its output as the key. In~/.claude/settings.json:"apiKeyHelper": "security find-generic-password -a \"$USER\" -s anthropic-api-key -w". Store the item first with the macOS command above, using-s anthropic-api-key; with-T "", Claude Code triggers a prompt each time it runs the helper. - Codex CLI defaults to a plaintext
~/.codex/auth.json. Addcli_auth_credentials_store = "keyring"to~/.codex/config.tomlto keep its stored credentials in the OS keyring (defaults.toml). Log in again after the switch, then check whether~/.codex/auth.jsonis still there and remove it, since we have not confirmed that Codex deletes it. - Pi accepts a key value that starts with
!and runs it as a shell command, such as!security find-generic-password -s anthropic-api-key -w(Pi providers doc). - Atomic Agent (ours) has no keychain support, and keys you type into its setup go to plaintext 0600 files in
~/.atomic-agent. In the terminal you can avoid that: launch it with the key exported (command below), add OpenRouter in the provider setup and leave the key field empty. Atomic Agent then readsOPENROUTER_API_KEYfrom the environment on every launch (resolve-llm-api-key.ts) and writes nothing to disk:
OPENROUTER_API_KEY="$(security find-generic-password -a "$USER" -s openrouter-api-key -w)" atomic-agentIf the same key also sits in ~/.atomic-agent/.env, the exported variable wins, but delete the file copy anyway. This works in the terminal only: the desktop app saves the key inline in config.json, and a saved key always wins.
6. Think twice before saving provider keys in vendor-hosted consoles
Some tools offer a web page where you paste your OpenAI or Anthropic key so their gateway can call the model for you. That can be useful for shared team setups, and it also creates a second copy of the key on servers you do not control. If you do not need the gateway, keep the key on your machine.
Three examples, described from each vendor’s own docs:
- OpenCode Console “stores the credential and serves each provider through its gateway” (Console docs). This is the system involved in the October 17 incident. Keys added only in the local OpenCode CLI were outside it.
- Kilo has an optional BYOK page at app.kilo.ai/byok where you can paste Anthropic, OpenAI, Google and other keys for the Kilo Gateway to use (Kilo BYOK docs).
- OpenRouter BYOK stores your provider keys encrypted in workspace settings and charges a fee of 5% of the normal cost after a free monthly allowance (BYOK docs).
If you use any of them, upload a key created only for that service, with its own spend limit and expiry from practices 2 and 3. Then a breach on their side means revoking one key with a known ceiling.
7. Scan for leaked keys before and after you commit
Run a secret scanner as a pre-commit hook and over your existing history, because a key that reaches a public repo can be found by automated scanners within minutes. GitHub’s push protection covers most AI providers on public repos, with gaps for Gemini and for private repos. Newer tools also scan what your coding agent sends to the model.
gitleaks (repo), which Anthropic’s help center recommends:
brew install gitleaksgitleaks git -v # scan the current repo's historygitleaks dir -v ~/projects # scan a folder, including untracked .env filesIt also ships a pre-commit hook for the pre-commit framework.
TruffleHog (repo) checks whether found keys are live by testing them against the provider:
brew install trufflehogtrufflehog git file://. --results=verified,unknowntrufflehog filesystem ~/projectsggshield from GitGuardian scans files and history (ggshield secret scan repo .) and can hook into coding agents. ggshield machine setup detects installed agents, including Cursor, Claude Code, Codex, Copilot CLI and Kiro, and writes hooks that block a prompt, command, file read or MCP call containing a secret before the model sees it (ggshield AI tools docs). It needs a GitGuardian account and API key, and scanning goes through GitGuardian’s API, so the prompts and file contents the hook inspects are sent to GitGuardian. gitleaks and TruffleHog scan on your machine. By default the same command also installs git hooks and plants a honeytoken; pass --no-git-hooks or --no-honeytokens to skip them. If ggshield cannot reach its API, it allows the action with a warning.
GitHub push protection and partner reporting. GitHub’s supported patterns table shows which AI keys it blocks on push and which it reports to the provider when they appear in public repos:
- Partner and push protected: OpenAI, Anthropic (including admin and service account keys), OpenRouter, Groq, xAI, Hugging Face, Replicate and Azure OpenAI.
- Push protected only, no partner report: Mistral AI, DeepSeek, Cohere and Perplexity.
- Gemini: the
google_gemini_api_keypattern is neither a partner pattern nor push protected, and the genericgoogle_api_keypattern is reported but not blocked on push.
GitHub itself does not revoke anything: it reports the leak, and the provider decides. Anthropic says it deactivates exposed keys GitHub reports and emails you (help center). Secret scanning is free on public repos. Private repos need GitHub Secret Protection on an organization plan, and a private repo on a personal account gets no secret scanning at all (GitHub docs), which is why a local pre-commit hook matters.
8. Use local models for work that does not need a cloud key
The only key that cannot leak is one you never created. A coding agent running a local model through llama.cpp, Ollama or LM Studio needs no provider key, so a stolen laptop or a compromised config folder exposes no billing credential. Local models fit tasks like refactors, tests and offline work.
Atomic Agent (we build it) defaults to a local llama-server on 127.0.0.1, which needs no key, and its Ollama and LM Studio presets send requests without an Authorization header. You can add a cloud provider later and apply practices 1 to 7 to that one key. The agent sends anonymous usage telemetry by default, which you turn off with analytics.enabled: false in the config or the Privacy toggle in the desktop app. You can download Atomic Agent here, and for a wider list of options, see our sister project’s roundup of the best local AI agents.
The first hour after a key leaks
Revoke the key in the provider’s dashboard first, then create a scoped replacement, check usage from the suspected leak date, set hard limits, and confirm the old key is dead. Deleting the key from your agent, repo or a hosted console leaves any copy an attacker already has fully working.
GitHub’s remediation guide states: “Simply removing the secret from the codebase, pushing a new commit, or deleting and recreating the repository do not prevent the secret from being exploited.”
- Revoke at the provider. OpenAI: rotate or revoke on the API Keys page. Anthropic: Console, API keys, the three-dot menu next to the key, Delete API Key (Anthropic help); choose Disable instead if you want a reversible stop while you investigate. OpenRouter: delete it at openrouter.ai/settings/keys. Gemini: disable or delete it on the Google Cloud console credentials page. Google’s checklist swaps in the new key first to avoid downtime; in an active leak, accept the downtime and disable the old key now.
- Create a narrower replacement. One key for the affected tool only, scoped to a project or workspace, with an expiry and, on OpenAI, Restricted permissions.
- Check usage from the leak date. OpenAI Usage page, Anthropic Console usage and cost, OpenRouter activity, Gemini usage in AI Studio plus Cloud billing reports. Look for models you never call and traffic at odd hours. Ask the provider about refunds for usage you did not make.
- Set hard limits before you go back to work. OpenAI hard spend limit, Anthropic workspace spend limit, OpenRouter per-key credit limit, Gemini project spend cap.
- Confirm the old key is dead. Read it into a variable without echoing it, then call each provider. Only the exact error below proves the key is dead: 401 for OpenAI, Anthropic (
authentication_error) and OpenRouter, and 400 withAPI_KEY_INVALIDfor Gemini. Any other code means read the body before you trust it.
read -rs OLD_KEY # paste the revoked key; nothing is shown or saved to history
# OpenAI: dead = 401 (invalid_api_key in the body)curl -s -w "\n%{http_code}\n" https://api.openai.com/v1/models \ -H "Authorization: Bearer $OLD_KEY"
# Anthropic: dead = 401 with "authentication_error" in the bodycurl -s -w "\n%{http_code}\n" https://api.anthropic.com/v1/models \ -H "x-api-key: $OLD_KEY" -H "anthropic-version: 2023-06-01"
# OpenRouter: dead = 401 (use /api/v1/key; /api/v1/models answers 200 with any key)curl -s -o /dev/null -w "%{http_code}\n" https://openrouter.ai/api/v1/key \ -H "Authorization: Bearer $OLD_KEY"
# Gemini: dead = 400 with "API_KEY_INVALID" in the bodycurl -s -w "\n%{http_code}\n" https://generativelanguage.googleapis.com/v1beta/models \ -H "x-goog-api-key: $OLD_KEY"
unset OLD_KEYWe ran these with deliberately invalid keys in the sk-... format and got exactly those responses. Watch for two traps. A live Anthropic personal or service account key that is not scoped to one workspace returns 400 invalid_request_error when the anthropic-workspace-id header is missing (Anthropic docs), so a 400 from Anthropic says nothing about revocation. And OpenRouter’s /api/v1/models returns 200 with any key, so it cannot prove a key is dead. We have not tested a Gemini key that is disabled rather than deleted, which may answer with a different code. After revocation, scrub the key from git history, shell history, old .env copies and agent transcripts.
Provider controls at a glance
OpenAI has the widest set of controls: expiring keys, hard spend limits, per-endpoint permissions and an IP allowlist. Anthropic matches it on expiry and spend limits through workspaces. OpenRouter relies on per-key credit limits and API-set expiry, and Gemini leans on project caps and Google Cloud key restrictions, with the weakest GitHub coverage.
| Control | OpenAI | Anthropic | OpenRouter | Gemini |
|---|---|---|---|---|
| Key expiry | Expiration date on project keys; max lifetime policy | Presets 3h to 30 days, custom or never; max expiry policy | Optional expires_at via Management API; onboarding keys 180 days | None found |
| Hard spend limit | Org and project, returns 429 | Per workspace, with alerts | Per key, optional daily, weekly or monthly reset | Project cap (experimental, ~10 min lag) plus tier caps |
| Scoped keys | Restricted permissions per endpoint; per project | Scoped to one workspace | One key per tool, no endpoint scopes | Auth keys limited to Gemini API by default |
| IP allowlist | Yes | Not found in docs we checked | Not found in docs we checked | Yes, via Cloud key restrictions |
| GitHub partner report | Yes | Yes, key deactivated | Yes | Only via the generic google_api_key pattern |
| GitHub push protection | Yes | Yes | Yes | No |
Sources: OpenAI, Anthropic, OpenRouter, Google Cloud key restrictions, GitHub patterns, all checked in October 2026.
Frequently asked questions
Where to start today
Start with the ten-minute wins: make one key per agent, put a hard spend limit on it and give it an expiry. Then move that key into your keychain or password manager and launch the agent through a lookup or op run, and add gitleaks or ggshield as a pre-commit hook.
If a key leaks anyway, the order is always the same: revoke at the provider, replace with a narrower key, check usage, cap spend and confirm the old key returns an error. To see which files on your own machine hold keys right now, run the checks in where AI coding agents store your API keys. And for work that does not need a cloud model, a local setup such as Atomic Agent removes the key from the picture entirely.
Provider settings, commands and limits were checked against official documentation in October 2026. The status codes in the leak checklist come from our own test with deliberately invalid keys; a revoked real key is expected to behave the same, but we did not revoke a live key to confirm it.


