If you created an Anthropic API key before July 8, 2026, it never expires. Anthropic added optional expiration dates on that date, and the change explicitly did not touch existing keys. So a sk-ant- key that got pasted into a repo, a config file, or a deploy log months ago is still accepting requests today, and it will keep accepting them until a human deletes it.
That is the part founders get wrong. People assume a forgotten credential eventually dies on its own. It doesn't. Anthropic keys have no built-in shelf life unless you opted into one, and the opt-in only became possible three weeks ago.
TL;DR
Anthropic keys start with sk-ant- (admin keys use sk-ant-admin) and, unless you set an expiration after July 8, 2026, they never expire. If yours leaked: revoke it in the Anthropic Console first, then scrub git history, then move the key into a server-side environment variable with no NEXT_PUBLIC_ or VITE_ prefix. When you create the replacement, set an expiry instead of leaving it on Never.
Where Anthropic Keys Actually Leak
The Anthropic key leaks we see on scanned apps are not usually a dramatic breach. They're one of four boring mistakes.
Agent and MCP config files committed to the repo. A .claude/ directory or an MCP server config with the key sitting in an env block. These files feel like local tooling, so they don't get the same scrutiny as source code, and they end up in the first git add ..
A public prefix on the variable name. NEXT_PUBLIC_ANTHROPIC_API_KEY or VITE_ANTHROPIC_API_KEY. Both prefixes exist specifically to copy a value into the browser bundle. The name is doing exactly what it says, just not what you wanted.
Keys pasted into agent context that gets logged. You paste a key into a prompt so the assistant can "test the call." That prompt now lives in a session transcript, a log file, or a debugging artifact you forget about.
Preview deploys from Lovable, Bolt, and v0. The preview URL is public, the build includes whatever env vars the platform bundled, and nobody remembers to clean up the throwaway project.
A sk-ant-admin key is not the same risk class as a normal API key. Admin keys talk to the Admin API, which manages organization-level resources including the API keys themselves. If an admin key leaked, treat it as an account-level incident, not a billing one.
How to Tell If Your Key Leaked
Three checks. Do all of them, because a key can be in your bundle and your history and your config, and fixing one place leaves the other two live.
Check 1: Your deployed JavaScript bundle
Open your live site, press F12, go to Sources, and search across all files for sk-ant. You can also do it from the terminal without a browser:
# Pull your deployed JS and grep it
curl -s https://yourapp.com | grep -oE '/[^"]+\.js' | sort -u | \
while read f; do curl -s "https://yourapp.com$f" | grep -o 'sk-ant-[A-Za-z0-9_-]*' ; done
Any hit means the key is readable by every visitor. There is no partial version of this problem.
Check 2: Your git history
Deleting the file does not remove the key. Git keeps every version of every file forever.
# Every commit that ever contained an Anthropic key
git log --all -p -S 'sk-ant-' --oneline
# Config files people forget are tracked
git log --all --full-history -- .env .env.local .claude/ .mcp.json
If either command returns anything, the key is in history and anyone who cloned or forked the repo has a copy.
Check 3: Your agent and MCP config files
This is the one that's specific to Claude users and the one most often missed.
# Look for the key in local agent config, not just source
grep -rn "sk-ant-" \
--include="*.json" --include="*.toml" --include="*.yaml" --include="*.yml" \
--exclude-dir=node_modules . 2>/dev/null
# And check whether those files are tracked by git
git ls-files | grep -E '^\.claude/|\.mcp\.json|mcp.*\.json'
CheckYourVibe scans your deployed app for keys in the JavaScript bundle, including the sk-ant- pattern. It checks what a stranger's browser can read, which is a different question from what's in your source tree.
The Fix, in Six Steps
Find which key leaked.
You probably have more than one key on the account. Before you revoke anything, match the leaked value to a specific key so you don't kill a working production key by accident.
The Console shows a masked version of each key with the last few characters visible. Compare those characters against the value you found in your bundle, history, or config. Note the key's name and the date it was created.
If you cannot match it to anything in the list, it may have been created under a different workspace or a different organization. Check both before assuming it's already gone.
Revoke it in the Anthropic Console.
Open the Console, go to the API keys list, and delete the exposed key. Deletion takes effect immediately, so if production traffic depends on that key, create the replacement and deploy it first, then delete the old one.
Order of operations for a live app:
- Create the new key (with an expiration, see step 5).
- Update the environment variable on your hosting platform.
- Redeploy and confirm the app works.
- Delete the old key.
For a leaked key on a side project with no traffic, skip the choreography and delete it now.
Scrub the key out of git history.
Rotation stops the old key from working, but if the repo is public or has forks, the value stays visible in history and future readers will assume it's live. Clean it.
pip install git-filter-repo
# Remove config files that carried the key
git filter-repo --path .env --invert-paths
git filter-repo --path .mcp.json --invert-paths
git filter-repo --path .claude/settings.local.json --invert-paths
# Or replace the literal value everywhere it appears
echo 'sk-ant-YOUR-LEAKED-VALUE==>REDACTED' > /tmp/replacements.txt
git filter-repo --replace-text /tmp/replacements.txt
Force-push to every remote afterward, and tell collaborators to re-clone rather than pull. A stale local clone will happily push the old history back.
Move the key server-side.
If the leak came from a bundled variable, revoking the key just resets the clock. The next key leaks the same way on the next deploy.
Wrong, because the prefix ships the value to the browser:
// app/components/Chat.tsx
const res = await fetch("https://api.anthropic.com/v1/messages", {
headers: {
"x-api-key": process.env.NEXT_PUBLIC_ANTHROPIC_API_KEY!,
"anthropic-version": "2023-06-01",
},
});
Right, because the key never leaves the server:
// app/api/chat/route.ts
import Anthropic from "@anthropic-ai/sdk";
import { NextRequest, NextResponse } from "next/server";
const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
export async function POST(req: NextRequest) {
const { message } = await req.json();
const reply = await client.messages.create({
model: "claude-sonnet-4-5",
max_tokens: 1024,
messages: [{ role: "user", content: message }],
});
return NextResponse.json({ reply });
}
Your component calls /api/chat. Your Route Handler calls Anthropic. On Vite-based builders (Lovable, most SvelteKit apps), the same rule applies to VITE_: drop the prefix and put the call in a Netlify function, a Supabase Edge Function, or your own backend.
| Wrong (reaches the browser) | Right (server only) |
|---|---|
NEXT_PUBLIC_ANTHROPIC_API_KEY | ANTHROPIC_API_KEY |
VITE_ANTHROPIC_API_KEY | ANTHROPIC_API_KEY |
NEXT_PUBLIC_CLAUDE_KEY | ANTHROPIC_API_KEY |
Set an expiration on the replacement key.
This option did not exist before July 8, 2026. Now, when you create a key, you can pick a preset lifetime, a custom date, or Never. Choose something other than Never. Details are in the next section.
Add pre-commit scanning so it can't happen again.
brew install gitleaks # or: go install github.com/zricethezav/gitleaks/v8@latest
# Scan what you already have
gitleaks detect --source . --verbose
# Block future commits
cat > .git/hooks/pre-commit << 'EOF'
#!/bin/sh
gitleaks protect --staged --verbose
if [ $? -ne 0 ]; then
echo "Gitleaks found potential secrets. Commit blocked."
exit 1
fi
EOF
chmod +x .git/hooks/pre-commit
Then keep agent config out of the repo entirely:
cat >> .gitignore << 'EOF'
.env
.env.local
.claude/settings.local.json
.mcp.json
EOF
Your Key Can Leak From Your Laptop, Not Just Your Repo
Everything above assumes the key escaped through code you wrote. There's a second path that has nothing to do with your repo.
On July 11, 2026, the jscrambler npm package was published with a backdoor. The payload was a cross-platform infostealer, and its target list is the interesting part: alongside cloud credentials and crypto wallets, it specifically harvests AI coding assistant configuration files, including Claude, Cursor, and Windsurf configs. That's where a lot of people keep an ANTHROPIC_API_KEY.
Affected versions: 8.14.0, 8.16.0, 8.17.0, 8.18.0, and 8.20.0. Version 8.22.0 is clean.
# Do you have it, directly or as a transitive dependency?
npm ls jscrambler
# If you do, check which version resolved
npm ls jscrambler --all | grep jscrambler
If any of the affected versions show up, upgrade to 8.22.0 and then revoke every credential that was sitting in a config file on that machine. Not just the Anthropic key. The whole drawer.
This is a supply-chain attack on an npm package. Anthropic was not breached, and Anthropic's systems were not involved. The distinction matters for your response: there is nothing to wait for from a provider, and nobody is going to rotate the key for you. The credential was read off a developer machine, which means the fix is entirely on your side.
Full write-up from Socket: socket.dev/blog/jscrambler-supply-chain-attack.
Set an Expiration on Your New Key
This is the newest thing you can do about Anthropic key exposure, and almost nobody has turned it on yet.
Since July 8, 2026, Anthropic API keys support optional expiration dates. When you create a key, you pick one of:
- A preset lifetime.
- A custom expiration date.
- Never, which is the old behavior.
The Admin API exposes the setting as an expires_at field, so you can audit it programmatically instead of clicking through the Console. Anthropic also sends email warnings ahead of expiry for keys with a lifetime of seven days or more, which means you get a heads-up rather than a surprise 401 in production.
Two things to understand before you rely on it:
Existing keys were not changed. This is not retroactive. Every key you made before July 8, 2026 is still set to Never, and it will stay that way until you delete it or create a replacement with an expiry. Go look at your key list. The old ones are the ones to worry about.
Expiration is damage control, not prevention. A 90-day key that leaks on day one is exposed for 90 days. What it does is cap the blast radius of the leaks you never find out about, which is the majority of them. A key created in January with Never set is a permanent liability. The same key with a 90-day expiry is a temporary one.
A reasonable default: 90 days for keys used by deployed services (long enough that rotation isn't constant, short enough that a forgotten leak dies), and something much shorter for keys you make to test something locally. The seven-day threshold on email warnings is a useful floor. Below that, you won't get a reminder.
When you rotate, name the key after where it lives (prod-api-render, local-dev-laptop) instead of leaving it as the default. When you later find a leaked sk-ant- value, the name tells you instantly which system breaks when you delete it. Step 1 of this guide gets much faster.
FAQ
Is it safe to put my Anthropic API key in a .env file?
Yes, as long as that .env file is git-ignored and the variable has no NEXT_PUBLIC_ or VITE_ prefix. A .env file read by a server process is the normal way to store the key. It stops being safe the moment the file gets committed, the variable name gets a public prefix that bundles it into your frontend, or a build step copies .env into a deployed artifact that anyone can fetch.
What can someone do with my sk-ant key?
They can make Claude API calls billed to your account until you revoke the key or hit your spend limit. That's the main damage: your credits, your rate limits, your invoice. A standard sk-ant- key doesn't give access to your Claude.ai conversations or your account login. Keys with the sk-ant-admin prefix are a bigger problem, because Admin API keys manage organization-level resources including other API keys.
Do Anthropic API keys expire?
Only if you asked for it. Anthropic added optional expiration dates on July 8, 2026, with preset options, a custom date, or Never. Existing keys were explicitly unaffected, so any key created before that date runs forever until someone deletes it. That's why a key pasted into a repo in early 2026 is still live today.
How do I know if my leaked Anthropic key was actually used?
Check usage in the Anthropic Console for the exposure window and look for volume you can't account for: requests at hours you weren't working, models you don't use, token counts well above your normal pattern. A flat graph isn't proof of safety, though. Some attackers use a stolen key slowly to stay under the radar. Revoke it either way.
Can I call the Anthropic API directly from my frontend?
No. Any key shipped to a browser is public, and no CORS setting or obfuscation changes that. Put the call in a Next.js Route Handler, a Netlify or Vercel function, a Supabase Edge Function, or your own backend. Your frontend calls your endpoint, and your endpoint is the only thing holding the key.
Check your deployed app for sk-ant- keys in the JavaScript bundle, exposed environment variables, and other secrets a visitor can read. Free scan, no signup required.