Why the same API key that shipped your prototype becomes a critical bug in production

Obfuscating an API key harder doesn't work because any shipped binary can be decompiled; the real fix is moving the key off the device entirely.

1:49 video3 min readWatch on YouTube

The instinct, when you realize your API key is sitting in your app's source code, is to hide it better: scramble the string, split it into pieces, obscure it somehow. None of that actually works, and understanding why points at the real fix.

The same key, a flipped risk

A bundled API key works perfectly fine during development. It's only once you ship the app that it becomes a vulnerability, and the key itself hasn't changed at all, only the environment around it. A shipped binary can always be decompiled, string and all, no matter how cleverly that string was scrambled beforehand. Obfuscation raises the effort required to extract the key. It doesn't remove the key from the binary, and removal is the only thing that actually matters here.

Dev code versus release code

Here's the concrete version of the problem. During development, an app calls the Claude API directly, with something like .apiKey("sk-ant-...") sitting right there in the source. Before release, that line has to change to something like .proxied(headers:[...]), pointed at a small relay server run by your own company. That relay server sits between the app and the API, injecting the real key on the server side rather than shipping it inside the app at all.

Why the threat model changes

The reason this matters comes down to who's holding the device. On your own development machine, the key just needs to work; nobody hostile has access to that environment. On a device someone else holds, the key needs to survive them taking the binary apart, and no amount of scrambling changes that outcome. Only removing the key from the device entirely does. That's the actual shift between prototype and production: not the key itself, but whether an untrusted party can physically hold the thing the key lives inside.

One boundary, not everywhere

On release, the fix is that the app never holds the real key at all. It sends a request token instead, and the relay server injects the actual key server-side before forwarding the request on to Claude. Importantly, this boundary isn't needed everywhere. A server you fully control, one no end user ever touches directly, can keep using a bundled key directly without this relay layer, because nobody outside your control ever gets to hold that binary apart. The moment a user could hold the binary, though, the key has to move off the device.

The real fix

A key you can decompile isn't actually hidden, it's just harder to read at that moment, and "harder to read" is not a security boundary, it's a delay. The real fix isn't finding a better hiding place inside the app. It's moving the key off the device entirely, into a relay server that only your company controls.

Key takeaways

  • A bundled API key that works fine in development becomes a vulnerability the instant the app ships, because a shipped binary can always be decompiled.
  • Obfuscating or scrambling the key string doesn't remove the risk, it only adds friction to extracting it.
  • The fix is a backend relay: the app sends a request token instead of the real key, and a server your company controls injects the actual key server-side.
  • This relay boundary is only necessary once an untrusted user can hold the binary; a server you fully control can keep using a bundled key directly.
  • The underlying shift from prototype to production is about who can physically access the code, not about the key itself.

Try it yourself

Try this prompt with Claude: "I'm building an app that calls the Claude API directly with a bundled key, and I need to ship it. Walk me through the backend relay pattern I need: what my app should send instead of the key, what the minimum relay server needs to do, and how it keeps the real key safe even if someone decompiles my app." Run that against your own app before you ship it. This explainer is part of Humanitarians AI's Claude Basics series.

Chapters

  1. 0:00How do I hide my API key better?
  2. 0:10Same key, flipped risk
  3. 0:25The anchor: dev code vs. release code
  4. 0:41Why: same lock, different holder
  5. 0:54The anchor returns: one boundary, not everywhere
  6. 1:13Carry-out
  7. 1:22Your turn
  8. 1:44Outro
Full transcript(auto-generated, with timestamps)

How do I hide my API key better?

[0:00]Someone asks how to hide their API key better before shipping. Wrong fix, you don't hide it, you move it. Why the same key that shipped your prototype becomes a bug in production. A bundled key works perfectly in

Same key, flipped risk

[0:11]Development and turns into a shipping vulnerability the moment you release the app. The instinct is to hide it harder, scramble the string, split it in pieces. None of that holds. A shipped binary can always be decompiled string and all, however well it was scrambled. Here's

The anchor: dev code vs. release code

[0:25]The concrete case. In development, the app calls Cloud directly, API key SKA right there in the source. Before release, that line has to change to proxied headers and a small relay server at your company. Calm sits in between injecting the real key on the server side. Here's why the threat model

Why: same lock, different holder

[0:42]Changed. On your own machine, the key just needs to work. On a device someone else holds, the key needs to survive them taking the binary apart and no amount of scrambling changes that, only removing the key from the device does.

The anchor returns: one boundary, not everywhere

[0:55]So, on release, the app never holds the real key. It sends a request token and the relay injects the actual key server side before forwarding to Cloud. That boundary isn't needed everywhere. A server you fully control that no user ever touches can keep using the bundled key directly. The moment a user could hold the binary, the key has to move. A

Carry-out

[1:13]Key you can decompile isn't hidden, it's just harder to read. The real fix isn't a better hiding place, it's moving the key off the device entirely.

Your turn

[1:22]Your turn. Here's the prompt, read it with me. I'm building an app that calls the Cloud API directly with a bundled key and I need to ship it. Walk me through the back end relay pattern I need, what my app should send instead of the key, what the minimum relay server needs to do, and how it keeps the real key safe even if someone decompiles my app. Lay 'em in for bear. Why the same API key that shipped your prototype becomes a critical bug in

Outro

[1:45]Production. Lay 'em in for bear.

More from HAI

Humanitarians AI Lyrical Literacy Project