Skip to main content
All writing

Security5 min read

The Agent Can Use Your Stripe Key. It Should Never Be Able to Read It.

Pasting an API key into an AI chat is the fastest way to get an integration working, and the fastest way to get it leaked. There is a better shape for this.

Sooner or later every app needs a key. Payments need a Stripe key, email needs a sending key, a map needs a maps key. In an AI builder the obvious move is to paste it into the chat: here is my key, wire it up. The agent will happily oblige, and the integration will work on the first try.

That key is now in the conversation history, in whatever the model wrote to disk, possibly in a commit, and possibly in the JavaScript bundle every visitor downloads. None of those are places you chose. You wanted the app to be able to use the key. You did not need the agent to know it.

Using a secret and reading a secret are different permissions. Most tools hand out both at once.

Why the model should never see the value

A model that can read a secret can be talked into writing it somewhere. It does not take malice: an instruction hidden in a web page it read, a README in a dependency, or a plain misunderstanding of a request is enough to get a key printed into a log file or a config the build ships. The model has no reliable sense of which strings are dangerous. The only robust defence is that it never holds the value in the first place.

So in Vibez.now the agent can ask which secrets exist, and whether each is meant for the server or the browser, but it cannot read a value or set one. When it needs a key that is not there, it asks for it and stops, rather than inventing a placeholder and building on top of it. You add the value yourself, in a form the model never sees, and the code reads it at runtime as an environment variable.

The agent can

List the names of the project's secrets, see whether each is server-only or browser-visible, and write code that reads process.env.NAME.

The agent cannot

Read a secret's value, set one, or paste one into a file. It never has the string to leak.

When a key is missing

It asks you for the secret by name and stops. A placeholder that looks real is how a broken integration ships.

At rest

Values are encrypted with AES-256-GCM, so a copy of the database alone does not hand anyone your keys.

Server keys and browser keys are different things

Most providers give you two kinds of key, and mixing them up is the single most common way an AI-built app leaks one. A publishable key is meant to be public. It goes in the browser and can only do harmless things. A secret key can move money, send email as you or read your customer list, and must only ever run on a server.

Publishable keySecret key
ExampleStripe pk_live_…, a maps browser keyStripe sk_live_…, an email API key
Where it can liveIn the page code, visible to anyoneOnly in a server function
If it leaksLittle to nothingCharges, spam sent as you, data exposure
In Vibez.nowExposure: build (inlined into the bundle)Exposure: server (api/ functions only)

The choice is made per secret, when you add it, not inferred from a naming convention. Frameworks often expose any variable that starts with a particular prefix to the browser. That rule is invisible in the code that uses it, and an agent renaming a variable to get something working can move a secret key into the bundle without a single line that looks wrong.

Scan what ships, not just what you wrote

Checking the source code for keys is necessary and not sufficient. A build tool can inline a value into the output even when no source file contains it. The bundle is what the world receives, so the bundle is what has to be checked.

Vibez.now runs a security scan before every publish, over the source and over the built output, looking for the specific shapes of real credentials: Stripe live keys, cloud access keys, AI provider keys, GitHub tokens, private key blocks. Findings block by severity, not by count. One live secret in the bundle stops the publish. A dozen style warnings do not, because a scanner that cries wolf trains people to ignore the time it is right.

If you already pasted a key into a chat

Rotate it. Removing the message does not remove copies in logs, history or commits. Generate a new key at the provider, add it as a secret, and revoke the old one. It takes five minutes, and it is the only fix that actually works.

A short checklist for any AI-built app

  1. Search the chat history for anything that looks like a key. Rotate what you find.
  2. Open the deployed page, view the source and the loaded scripts, and search for sk_, secret and key. A secret key there is a live incident.
  3. Check that every endpoint that uses a secret also checks who is calling it. A hidden key behind an open endpoint is still an open endpoint.
  4. Give each app its own key, restricted to what it needs. One shared key across five projects means one leak is five incidents.
  5. Set spending or rate limits at the provider, so the worst case of a leak has a ceiling you chose.
Isn't it simpler to just give the agent the key?

It is simpler for the first five minutes. After that you have a credential in places you cannot list, and the only way to be sure it is safe is to rotate it. Adding a secret through a form costs the same five minutes once.

Can a browser key ever be dangerous?

Yes, if it is unrestricted. A maps or analytics key with no domain restriction can be used by anyone to run up your bill. Restrict browser keys to your domain at the provider.

What does the scanner do with a false positive?

It reports the file and line, so you can see exactly what it matched. Patterns are anchored to provider-specific prefixes rather than "any long random string", which keeps false positives rare.

The aim is an app that can use every key it needs while the agent that built it never knew any of them. That is not a limitation on the agent. It is what makes it reasonable to hand one the keys at all.

Keep reading