← 建站 · SEO · AI 内容服务All posts
Your LLM API Key Is a Production Credential. Start Treating It Like One.

Your LLM API Key Is a Production Credential. Start Treating It Like One.

2026-09-22 · #devops #llm #cicd #api

Every team I have watched handle an LLM key leak made the same category error, and it has nothing to do with carelessness. They filed the API key under configuration — a string you paste into a settings panel — instead of under credentials. Configuration gets committed, copied into a Notion doc, and pasted into a Slack thread. Credentials get a rotation policy.

That distinction is the whole problem. An OpenAI or Anthropic key is a bearer token: whoever holds it is you, for as long as it lives, with no second factor and no per-request verification. The blast radius is not data exfiltration. It is your invoice.

Why LLM keys leak more easily than database passwords

A database password tends to be protected by network topology. The database is in a VPC, the app server is in the VPC, and a stolen string is useless unless the attacker is also inside. LLM API keys are called over the public internet from anywhere, which means the string alone is sufficient. There is no network layer to fall back on.

Three properties make them worse than a typical SaaS key:

There is also a resale market, which is what makes this worth an attacker's time. Paid model access gets resold as cheap API capacity. The buyer runs a real workload; you pay the bill.

The five places these keys actually end up

This is not a theoretical list. These are the paths that show up over and over in post-incident writeups.

1. Git history after a "quick fix." Someone commits .env, a scanner flags it, they add .env to .gitignore and push again. The blob is still in the object database, and every fork and clone still has it. Deleting a file in a new commit does not delete it from history.

2. Client-side bundles. Any variable prefixed for client exposure — NEXT_PUBLIC_, VITE_, EXPO_PUBLIC_ — is compiled into JavaScript that ships to the browser. If a server-side key ends up behind one of those prefixes, it is public the moment you deploy. The build succeeds. Nothing warns you.

3. CI logs and artifacts. A debug echo $OPENAI_API_KEY in a workflow, or an uploaded jest output directory that captured the environment. CI logs are often readable by far more people than production secrets are.

4. Docker image layers. COPY . . before RUN that needs the key, or an ARG baked into the final stage. Anyone who can pull the image can read it, including anyone downstream of a registry misconfiguration.

5. The new one: agent config files. MCP server configs, .cursorrules side files, local agent runner settings. These are plain JSON on disk, usually outside the repo, usually not covered by whatever secret scanning you run against the repo. Info-stealer malware enumerates exactly these locations because they are high-yield and unguarded.

The audit

Six checks, roughly in order of value per minute spent.

Scan history, not just HEAD. gitleaks detect or trufflehog git file://. against the full history, including all branches. Run it in CI on every push so a new leak fails the build rather than surfacing nine months later.

Grep your build output. After a production build, search dist/, .next/, or your static bundle for your providers' key prefixes (sk-, sk-ant-). If one appears, you have shipped a credential. This takes under a minute and catches the entire class of prefix mistakes.

Separate keys per environment, with hard spend caps. One key per environment, one per service if you can. Most providers now let you set a monthly spend limit per key — check your provider's docs for the current mechanism, because these features change quickly. A per-key cap converts "unlimited liability" into "worst case is $50 and the key stops working."

Baseline your spend. Record tokens and cost per key per day. You do not need sophisticated anomaly detection; you need to know what normal looks like so a 4x day is visible the next morning instead of on the invoice. Alert on new models being called and on unusual request volume outside your working hours.

Audit the key list by hand, quarterly. Every provider console has a key list with a creation date and last-used date. Anything with no recent legitimate use gets revoked. This is boring and it is the single highest-yield check on the list.

Write the rotation runbook before you need it. Rotation is an order-of-operations problem, and the order is counterintuitive.

Rotation order: revoke the key first

When access is compromised, the instinct is to change the password. For credential theft, that is close to useless. Active sessions and issued API keys survive a password reset unless you revoke them explicitly, and in some publicly reported account takeovers the attacker had also minted additional API keys under the victim's account — so access persisted through every password change.

The correct order:

  1. Revoke the exposed key (or all keys) in the provider console.
  2. Revoke active sessions and OAuth grants, not just the password.
  3. Delete any key you do not recognise, including ones created under your account by someone else.
  4. Rotate the password last, and enable 2FA if it was not on.
  5. Mint a replacement with a spend cap and a narrower scope than the one you just killed.
  6. Go back and find the leak path, or you will be doing this again in a month.

Step 6 is the one teams skip. Revocation stops the bleeding; it does not close the wound.

If you only have twenty minutes

Do the build-output grep, set a spend cap on every key, and turn on secret scanning for the repo. That covers the two most common leak paths and converts the worst-case outcome from an open-ended bill into a bounded one.

The mental model shift is the part that sticks, though: an LLM API key is a production credential with a credit card attached. It belongs in the same rotation policy as your database password, not in a .env file someone pasted into a chat window.


I wrote a longer version of this focused on the subscription side — how to tell whether a paid AI account is being used by someone else, and what the usage data can and cannot tell you — over on Toolkit Creators.

Originally published on DEV Community (2026-09-22). That account was suspended in September 2026, so this post now lives here.
Need a site built, or want your existing one to actually get found?
See what I do →

Free diagnosis first — no pitch, no obligation.