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 -pand 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
Authorizationheader 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.
# .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
// 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:
| Situation | What to check first | Risk if ignored |
|---|---|---|
| New Next.js project | Confirm .gitignore explicitly includes .env*.local
|
Key versioned from the first commit |
| Variable used in a client component | Verify it does NOT have the NEXT_PUBLIC_ prefix |
Key visible in the browser bundle |
| Repo that's going public or shared | Run `git log --all -p \ | grep "sk-"` before publishing |
| Server error logs | Check the HTTP client isn't logging full headers |
Authorization or x-api-key in plain text in the log |
| Cline or another agent with multiple providers | Confirm each key lives in its own separate variable, not a shared string | Rotating one key breaks all providers at once |
| Compromised key (suspected or confirmed) | Revoke it in the provider's dashboard before investigating the cause | Window 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.
flowchart LR
A[Necesito una API key] --> B{¿Va a un componente cliente?}
B -->|sí| C[No la pongas ahí. Usá un endpoint server-side]
B -->|no| D[.env.local + process.env]
D --> E{¿El repo se va a compartir?}
E -->|sí| F[Revisá git log por patrones sk-]
E -->|no| G[Confirmá .gitignore antes del primer commit]
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:
- Anthropic API Docs: https://docs.anthropic.com/en/api/getting-started
This article was originally published on juanchi.dev
Top comments (0)