AgenticCLI
▮ shipguardis a free, local security gate for AI-built apps — 5 checks in ~2s.$ npm i -g @agenticcli/shipguard

Claude Code hardcoded a Stripe key. Here's how it happens.

8 min · jun 2026 · severity: critical · category: secrets · also at /llms.txt

Short answer: AI coding agents hardcode secrets because a literal value is the shortest path to a passing test, and linters stay silent because a hardcoded string is valid syntax. The fix is a local security gate — shipguard scan — that knows sk_live_ is a Stripe secret and exits non-zero before you deploy.

TL;DR

  • AI agents hardcode secrets because the literal value is the shortest path to a passing test.
  • Linters don't catch it — a hardcoded key is valid syntax.
  • Gate it with a non-zero exit code before deploy: shipguard scan.

Ask an agent to “make checkout work” and it will make checkout work. It will read your Stripe docs, write the handler, wire the button — and if your key happens to be in the prompt context, paste it straight into the source. The test passes. The diff looks clean. You ship.

The exact pattern

It almost always looks like this — a working value used directly instead of an environment lookup:

- const key = "sk_live_4f8aBc92xK…"+ const key = process.env.STRIPE_KEY // shipguard: payments:hardcoded-key · critical

Agents do this because the literal value is the shortest path to a passing test. Nothing in the loop pushes back — the linter is happy, the types check, the demo works. The only thing that would catch it is a gate that runs after the agent and before the deploy.

Why linters don't catch it

Linters check style and syntax, not consequence. A hardcoded string is valid JavaScript. What's needed is a check that knows sk_live_ is a Stripe secret, knows it's in a file that ships to a client bundle, and refuses with a non-zero exit code rather than printing a warning the agent will ignore.

We ran it through the gate

Here's the actual output from scanning the repo above — a real Next.js checkout the agent wrote, before any human review:

zsh — ~/checkout0.6s
dev in ~/checkout on mainshipguard scan --changed
ShipGuard v0.1.0 — security gate · scanning 9 changed files
CRITICAL secrets:hardcoded-api-key
src/lib/stripe.ts:4
"sk_live_4f8aBc92xK…" committed to source
Fix: move to process.env.STRIPE_KEY
CRITICAL payments:webhook-unverified
src/app/api/stripe/route.ts:8
webhook handler skips signature check
Fix: stripe.webhooks.constructEvent()
Risk Score: 81/100 DO NOT SHIP
Status: BLOCKEDexit 1
scanned 9 files in 0.6s

fig.1 — one command, ~0.6s, two criticals caught before deploy.

ShipGuard's secrets check matches 40+ key formats — locally, in about 2 seconds, without your code leaving the machine.

Gate it before the deploy

Add the scan to your pre-push hook or deploy script. Exit codes do the rest: exit 0 ships, exit 1 blocks. Agents understand exit codes natively — which means the same gate that protects you from your agent also works for your agent.

FAQ

Why do AI coding agents hardcode secrets?
The literal value is the shortest path to a passing test. Nothing in the agent loop pushes back — the linter is happy, the types check, the demo works — so the key ends up in source.
Why don't linters catch hardcoded API keys?
Linters check style and syntax. A hardcoded string is valid JavaScript, so it passes. Catching it needs a rule that knows sk_live_ is a Stripe secret and refuses with a non-zero exit code.
How do I stop this before deploy?
Run a local security gate like ShipGuard in your pre-push hook or CI. shipguard scan exits non-zero when it finds a hardcoded secret, blocking the deploy — no code leaves your machine.

────────[ ▮ gate ]────────

Don't ship the next one.

Free, local, no account. Catches this exact bug class before deploy.

$npx @agenticcli/shipguard scan