# Your AI agent just wrote an API key directly into the code. Can your users see it?

<p class="meta-line">Mik Bry · 2026-08-25 · ~8 min read · KB · Launch Readiness</p>

<div class="article-hero">

![A single ornate key floats mid-air, its shaft ending in glowing circuit-board traces instead of metal teeth.](/assets/2026-08-25-kb-secrets-and-api-keys-hero.png)

</div>

Your AI coding tool needed to talk to Stripe, or OpenAI, or some other service, so it asked you to paste in an API key — and then it wired that key into the code. Reasonable enough. What's not obvious, if you don't code, is that "wired into the code" sometimes means "wired into the code your users' browsers download." Not stolen. Not breached. Just sitting in plain text, one right-click away.

This is the check I keep coming back to when I look at an AI-built app, because it's fast, it's zero-code, and it's the one where "I didn't touch anything" isn't actually the reassurance it sounds like.

<div class="article-toc">
<div class="article-toc-label">What this article covers</div>

1. [What is it?](#s1)
2. [Why should you care if you don't code?](#s2)
3. [What does good look like?](#s3)
4. [What can you safely ask your AI agent?](#s4)

</div>

<h2 id="s1"><span class="section-no">01 —</span> What is it?</h2>

An API key is a password, except it's not for you — it's for one piece of software talking to another. When your app needs to charge a card through Stripe, or call OpenAI to generate some text, it has to prove it's allowed to make that request. The API key is that proof: a long string your app sends along with every request, the same way you'd type a password to prove it's really you logging in. It's a handshake between two systems, not between a person and a system.

That framing — machine password — covers most of it, but the details of *what kind* of key matter. A **house key** is the simplest case: whoever holds it gets in, full stop. Some API keys work exactly like that. Others behave more like a **library card**: it gets you into the building and lets you borrow books, but not into the manager's office or the cash register — the access is scoped to specific things. Good key design uses library-card keys wherever possible. An AI-generated integration may use a broader credential than the task actually requires, and a founder reviewing the result rarely has a way to tell which one it is.

Here's the part that actually differentiates this from "don't commit your `.env` file" advice you may have already heard. A particularly easy leak to create in an AI-built frontend is a key built directly into the code your users' browsers run, not into a file that got pushed to GitHub by accident. Frameworks like Next.js and Vite have a specific naming convention for this: any environment variable prefixed `NEXT_PUBLIC_` or `VITE_` gets bundled into the public JavaScript your site ships, by design — that's [documented, intended behavior](https://nextjs.org/docs/app/building-your-application/configuring/environment-variables), not a bug. It exists because some values genuinely need to reach the browser. The problem is an AI agent reaching for that prefix out of habit, for a value that was never meant to leave your server, because it's the fastest way to make an error go away.

<h2 id="s2"><span class="section-no">02 —</span> Why should you care if you don't code?</h2>

Because the consequence isn't abstract — it's a bill, or it's your users' data. If your OpenAI key is sitting in your public JavaScript, anyone who opens dev tools can copy it and start making calls on your account, and you find out when the invoice arrives, not before. If it's a Stripe key with the wrong scope, someone can potentially read payment data that belongs to your users, not you. This isn't an edge case reserved for large companies. It's also one of the easiest findings to make concrete, because an exposed value can sometimes be shown directly in the browser.

There's a precision worth getting exactly right, because the intuitive version of it is wrong: **an API key doesn't behave like an interactive password.** You'd notice if someone logged into your email — there's a session, a device, maybe a "new sign-in" email. API-key use usually doesn't produce the same user-facing sign-in signals as an interactive login. Unlike an interactive password, an API key may stay usable until it expires *or* someone revokes or rotates it — so a leaked key can keep working quietly, for weeks, without anything that looks like "someone logging in." Nobody has to breach anything. The key was already sitting there, working exactly as designed, for whoever found it.

This pattern is easy to create when an agent is optimizing for a demo that works. That makes it worth checking explicitly rather than assuming a working integration kept every credential in the right place.

<h2 id="s3"><span class="section-no">03 —</span> What does good look like?</h2>

You don't need to read code to get most of the way here. Look for:

- **Secrets live server-side only** — API keys for Stripe, OpenAI, or any paid service should never be readable from a file your browser downloads.
- **Sensitive credentials stay on your server, not on the user's device.** If your app needs to talk to a paid or private API (payments, LLMs, email, storage), the credential should live on your server. Your app calls your server; your server calls the paid API on the app's behalf and passes back only the result. This applies to browser code, mobile-app bundles, and desktop-app code alike — anything running on someone else's machine is extractable, regardless of framework or file naming. `NEXT_PUBLIC_` / `VITE_` prefixes are one common way this rule gets accidentally broken, but they're not the only one.
- **Keys are scoped, not universal** — a library-card key where a library-card key is available, rather than a house key handed out by default.
- **There's a plan for revoking or rotating a key**, not just for issuing one — since a key can keep working long after it should have stopped.

And here's the zero-code first check, the one you can run yourself in under a minute: open your live site, right-click anywhere, choose "View Page Source" (or press F12 to open developer tools and look at the page's source), and search — Ctrl+F or Cmd+F — for `key`, `sk-`, and `token`. If any of those turn up a long string that looks like a real credential sitting in plain text, that's worth investigating immediately.

I want to be precise about what that check actually tells you, because it's easy to over-read: **this is a first check, not proof that nothing is exposed.** A clean result means the easy, obvious version of this problem isn't visible on the one page you searched — not that no secret anywhere in your app is exposed. Keys can be built into JavaScript files loaded from other pages, other bundles, or behind a login you didn't check from. Treat a clean F12 search as "nothing jumped out," not as a certificate.

<p class="refs"><strong>References:</strong> Fencer's "How to Secure Your Vibe Coded App" (fencer.dev) walks through this exact self-test for AI-built apps. On the framework side, see the official <a href="https://nextjs.org/docs/app/building-your-application/configuring/environment-variables">Next.js environment variables docs</a> and the <a href="https://vitejs.dev/guide/env-and-mode">Vite env & mode guide</a> for how each framework decides what gets bundled into public code. For the broader discipline, OWASP's <a href="https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html">Secrets Management Cheat Sheet</a> and GitHub's <a href="https://docs.github.com/en/code-security/secret-scanning/introduction/about-push-protection">push protection documentation</a> are both worth a read if you want the fuller picture.</p>

<h2 id="s4"><span class="section-no">04 —</span> What can you safely ask your AI agent?</h2>

You don't have to run the search yourself — you can ask your AI coding tool to do it, with wording that keeps the same "not found ≠ doesn't exist" honesty as the F12 check above. Paste this in as-is:

<div class="check-callout">
<div class="check-callout-label">The prompt to paste into your AI tool</div>

> Search this project for hard-coded secrets or API keys — including any bundled into the browser/frontend (e.g. `NEXT_PUBLIC_`/`VITE_` variables). Show me where each is and whether it ships to users. If your search finds nothing, say the search found nothing — don't conclude the app is clean. Don't change anything.

</div>

Read what it finds before acting on it. If it lists secrets that ship to the browser, that's your priority list — but this article stops at "here's what I found," not "here's how to fix it," on purpose. Moving a key server-side, reissuing it, and revoking the exposed one is real work worth getting right, and it's a conversation for whoever's technically responsible for this project, not a follow-up prompt you paste in on your own. [Anthropic's own guidance on API key best practices](https://support.anthropic.com/en/articles/9767949-api-key-best-practices-keeping-your-keys-safe-and-secure) is a good primer if the key in question is for an AI provider specifically.

<aside class="lr-callout">
  <div class="lr-callout-icon" aria-hidden="true"></div>
  <div class="lr-callout-body">
    <p class="lr-callout-eyebrow">Automated first read</p>
    <p class="lr-callout-text">Before requesting a CTO review, you can start with the <a href="https://mikbry.com/launch-readiness/">Launch Readiness Scan</a>. It gives you an automated first read, but it doesn't see your production environment or business context.</p>
  </div>
</aside>

This is one article in the Launch Readiness KB series, which walks through what a Trust Report scan actually looks for before you launch, hire, or raise on an AI-built app — one concept per article, always ending in something safe to ask your agent, never something that changes your code.
