Skip to content
← Back to blog

Claude API Key Security: Why .env Is Not Optional

Pasting a Claude or DeepSeek API key straight into your code "just to test it quick" leaves a trail in your git history and your logs. A no-nonsense guide to environment variables, rotation, and what to check before sharing a repo.

September 9, 2026 · 7 min read · 11 reads

  • #nextjs
  • #anthropic
  • #LLM
  • #seguridad

You open a route.ts file in a Next.js project, you need to test a Claude API call fast, and the temptation is to write const apiKey = "sk-ant-api03-..." right at the top. "I'll pull it out later," you think. The problem is that "later" almost never arrives before the first git commit, and once that key is in the history, removing it from the current file didn't remove it from anywhere.

My take is simple and has no nuance: never hardcode an API key, not even "just to test." Logs and git history don't forgive. This isn't a style rule, it's a decision with measurable consequences the exact moment someone clones that repo, or when an error log ends up in a monitoring tool you don't control.

The Real Problem: Where That "Just for Testing" Key Actually Ends Up

When you're working with Cline connected to OpenRouter, or directly to the Anthropic API, the typical flow is: you generate the key in the provider's dashboard, you need it somewhere in the code so the HTTP client can use it, and that's the fork in the road. One path leads to a config file that git ignores. The other leads to a string literal that git happily versions.

The difference between those two paths doesn't show up on day one. It shows up when:

  • someone runs git log -p and finds the key in a commit from three months ago, even though you already deleted it from the current file
  • an unhandled error in the HTTP client logs the full Authorization header to the console or to some logging service
  • the repo goes public, or someone clones it to collaborate and now has access to billing that isn't theirs

None of these scenarios require anyone to "hack" anything. They just require the key to have sat in plain text somewhere you don't control, once it left your machine.

What Anthropic's Official Docs Say (and What They Don't)

Anthropic's getting-started documentation is clear on one point: the API key is passed as the x-api-key header on every request, and it explicitly recommends not exposing it in client-side code or public repositories. That's the limit of what the docs guarantee: they tell you the auth mechanism and warn you about the obvious risk.

What the docs don't tell you — because it's not their job — is how to structure your project so that key never accidentally makes it into a commit. That's a project architecture decision, not something an API flag solves for you. That's where your own judgment kicks in, not a literal reading of the docs.

The Recipe People Use, and Why It Fails

The pattern I keep seeing repeated in personal projects and tutorial examples is this: create a .env file at the root, drop the key in there, and trust that "it's probably in Next.js's default .gitignore." Sometimes it is. Sometimes the project started from an old create-next-app scaffold, or someone renamed the file to .env.production without checking whether that variant is also ignored.

The hidden cost isn't the .env itself. It's the false sense of security that comes from having "a separate file" without verifying that git is actually ignoring it. A classic counterexample: someone copies .env to .env.backup to have a quick reference, and that differently-suffixed file doesn't match any pattern in the .gitignore. The key sits there, versioned, with a filename that doesn't even look suspicious in a quick diff.

Another common mistake, more subtle: passing the key as a prop into a client-side React component. If the component runs in the browser, any variable prefixed with NEXT_PUBLIC_ ends up in the JavaScript bundle the browser downloads. That's not a Next.js bug, that's documented behavior: those variables are public by design. Putting a Claude key there is as literal an exposure as pasting it into the code.

bash
# .env.local (never versioned, Next.js ignores it by default)
ANTHROPIC_API_KEY=sk-ant-api03-xxxxx
OPENROUTER_API_KEY=sk-or-v1-xxxxx

# read server-side, never with the NEXT_PUBLIC_ prefix
typescript
// route.ts - server-side, the key never reaches the browser
const apiKey = process.env.ANTHROPIC_API_KEY;
if (!apiKey) {
  throw new Error("Falta ANTHROPIC_API_KEY en las variables de entorno");
}

Checklist Before Touching Any LLM Provider's API Key

This is the matrix I use as sound judgment, not as an absolute guarantee of anything:

SituationWhat to check firstRisk if ignored
New Next.js projectConfirm .gitignore explicitly includes .env*.localKey versioned from the first commit
Variable used in a client componentVerify it does NOT have the NEXT_PUBLIC_ prefixKey visible in the browser bundle
Repo that's going public or sharedRun git log --all -p | grep "sk-" before publishingKey exposed in history, even if it's gone from current code
Server error logsCheck the HTTP client isn't logging full headersAuthorization or x-api-key in plain text in the log
Cline or another agent with multiple providersConfirm each key lives in its own separate variable, not a shared stringRotating one key breaks all providers at once
Compromised key (suspected or confirmed)Revoke it in the provider's dashboard before investigating the causeWindow of misuse while you're still debugging

The rotation point deserves a clarification: revoke first, investigate after. Not the other way around. The gap between "I suspect it leaked" and "I disabled it" is time you don't control.

What You Can't Conclude From This

This guide doesn't replace an automated secret scan in CI, and it's not a security audit. Tools like git-secrets or pre-commit hooks that detect key patterns add a layer the human eye doesn't cover on every commit. I also don't have public evidence of specific Claude or DeepSeek key leak incidents to cite here: what exists is the documented header-based auth mechanism, and the known behavior of Next.js with NEXT_PUBLIC_ variables. The rest is architectural judgment, not measured data.

If your project already has a case of a key exposed in production, this isn't enough: at that point the move is revoke, rotate, and audit access with the provider's own tools, not read a blog post.

I touched this same principle — separating what runs server-side from what reaches the client — from a different angle when I wrote about cache and revalidation in Next.js: the server/client boundary isn't just a performance question, it's also the boundary between what secrets exist and what secrets don't.

FAQ

Can I use the same Claude API key in development and production? Technically yes, but it's not advisable. If the dev key leaks in a repo or a debug log, the blast radius includes production. Using separate keys per environment limits that radius.

Is it enough to put the key in .env without the .local suffix? Depends on the exact .gitignore configuration of the project. Next.js's default ignores .env*.local, but a plain .env without that suffix might not be covered. Check the file, don't assume.

What's the difference between handling Anthropic's key vs. OpenRouter's? The mechanism changes (different headers, different key formats), but the handling principle is identical: never in versioned code, never in public client variables, always in server-side environment variables.

Does Cline expose the keys I use with OpenRouter or Anthropic? Cline reads them from the extension's local config or from system environment variables, it doesn't write them into the project's code. The risk shows up when the user manually copies that key into a repo file "to have it handy."

Is it worth rotating the key periodically even without any suspicion of a leak? It's a sound practice in any credential system, though there's no universal frequency rule. What is concrete: if there's a suspected leak, rotation isn't periodic, it's immediate.

Is an .env.example file with no real values safe to version? Yes, as long as it only contains variable names without real values (ANTHROPIC_API_KEY=). It's a common practice to document what variables a project needs without exposing anything.

My Stance

There's no legitimate shortcut here. The "just for testing" excuse is the same excuse used for leaving a console.log that ends up in production, except here the cost isn't an annoying log line: it's a key with access to an LLM provider's billing. If you're building a project with multiple providers — something I touched on when I wrote about monitoring AI agent traffic with Sniffnet — the discipline of keeping separate environment variables per provider isn't paranoia, it's the only way rotating one key doesn't mean breaking the other three integrations that depend on the same .env.

The next concrete step, before you write another line of code that calls an LLM API: open your project's .gitignore and confirm it actually ignores the environment files you're using. Don't assume it.

Original source:

Looking for this approach on your team?

Explore my technical case studies or discuss a senior role, architecture and technical leadership.

Comments (0)

What do you think of this?

Drop your comment in 10 seconds.

We only use your login to show your name and avatar. No spam.

No comments yet. Be the first — your take matters most when we're few.

Related Articles