Skip to content
KeyDrift
Scan for free
All posts

What a prompt-built app actually deploys

We built a small application from a prompt, deployed it, and read every file the browser received. Three strings were worth looking at. Only one of them mattered.

KeyDrift4 min read
aisecrets

Prompt-to-app builders work. That is not a grudging concession — the output is coherent, the database is wired up, authentication works, and it deploys. A category of tool that removes a week of setup from a working prototype has earned its place.

What follows is not a takedown. It is an inspection of the deployed artifact, which is a different question from whether the tool is good, and which nobody performs because the editor preview looks finished.

The application is ours, built for this post. No real product, customer or company appears anywhere in it.

The prompt

One paragraph, of the kind anybody would write: a small tool where users sign in, save notes, and share individual notes by link. Auth, a database, a list view, a detail view.

Deliberately ordinary. The failure modes here are not exotic prompts; they are the ones that arrive with the most common request in the category.

What it generated

A working application. Sign-up and sign-in, a notes table with an owner column, a share flag, list and detail routes, and a deployment.

Reading the generated source, there is very little to object to. The schema is sensible. The queries are parameterised. The client is initialised once and reused. If this had come to me as a pull request from a junior engineer I would have approved it.

What the editor preview showed

Everything working, which is the point at which most people stop.

This is worth naming as a structural issue rather than a user error. The preview is the artifact you are shown, it is the artifact you interact with, and it is the artifact you form your judgement about. The thing your users receive is generated later by a build you did not watch, and nothing in the workflow ever invites you to look at it.

What the deployed URL served

Four JavaScript chunks. We fetched them all.

BASE=https://example-app.test
curl -s "$BASE" | grep -oE '/_next/static/[^"]+\.js' | sort -u > chunks.txt
while read -r c; do curl -s "$BASE$c"; done < chunks.txt > bundle.txt
wc -c bundle.txt

Then searched for the formats that matter.

grep -oE '(eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+|sk_live_[A-Za-z0-9]{20,}|AKIA[0-9A-Z]{16})' bundle.txt | sort -u

The three strings worth looking at

Three hits. All three JWT-shaped, all three eyJ, all three visually indistinguishable in the minified output.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.EXAMPLE_PAYLOAD_ONE.EXAMPLE_SIG
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.EXAMPLE_PAYLOAD_TWO.EXAMPLE_SIG
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.EXAMPLE_PAYLOAD_THREE.EXAMPLE_SIG

At this point a naive scanner reports three critical findings and a developer stops reading their scanner. The three are not equivalent, and the difference is one command away.

echo '<token>' | cut -d. -f2 | tr '_-' '/+' | base64 -d

Which of the three actually mattered

The first decoded to "role": "anon". This is the publishable key, it is supposed to be in the bundle, and it is constrained by whatever row-level security policies exist on the database. Not a finding. Reporting it as one would be actively harmful.

The second decoded with an iss of supabase-demo. That is the local development default — the token supabase start generates identically on every machine on earth. It grants nothing against a hosted project, it had been left in a fallback constant, and it is untidy rather than dangerous. Not a finding either.

The third decoded to "role": "service_role".

That one bypasses row-level security entirely. Not "has broad permissions" — the policy layer is not consulted. Every table, every row, read and write, for anyone who opened developer tools. It had arrived because a server-side route needed elevated access and the value ended up in a module that a client component imported.

One finding out of three hits. That ratio is the entire argument for precision: a tool that had reported all three would have been technically observant and practically useless, because the person receiving it learns to skim.

The useful question is never "is there a key in the bundle". It is "is there a key in the bundle that grants more than a stranger should have".

Doing this to your own deployment

Ten minutes, and the sequence is the one above.

  • Fetch the deployed URL, not the preview. They are different artifacts and only one of them has users.
  • Collect every chunk, not the first one. Configuration tends to live in the shared chunk rather than the entry point.
  • Search for formats, then decode. The search finds candidates; the decode tells you which candidate is a problem.
  • Check your preview and branch URLs too. Same environment, same build, public by default, and never inspected.

If you find a service_role token, rotate before you touch the code — the value is already public, and cached chunks can outlive a redeploy.

And if you find only an anon key and a demo default, that is a genuinely good result. It is also a result about today: the next generation, the next feature, the next deploy produces a new artifact, and this answer does not carry forward to it.

Run a free scan

KeyDrift reads the JavaScript your app actually serves and finds the Supabase, Stripe, OpenAI and AWS keys that should never have left your server. Run a free scan.

KeyDrift is a Veristria product. More about KeyDrift.