A system prompt in the bundle is readable by every visitor
A system prompt leak in a client-side AI app is concrete and easy to confirm: the instructions, persona, and guardrails you wrote for the model are sitting in the JavaScript that ships to the browser, where any visitor can read them word for word. If the prompt is in the bundle, it is not secret. Open DevTools, search the loaded scripts, and the text is right there.
Two things are worth separating here. What is observable is the prompt text itself: a string that begins with something like "You are a helpful assistant for..." plus whatever rules follow it. That is a fact you can verify by reading the file. What is only possible is the downstream effect: someone reading the prompt can craft inputs that step around its rules, learn what tools or data it references, or rebuild your app's behavior. The leak is the observable part. The bypass is what the leak enables.
This matters because builders often treat the prompt as the thing that keeps the AI in line. If the rule is "never reveal pricing logic" or "only answer questions about our product," a reader who can see that rule knows exactly what boundary to push on. Prompt secrecy was doing work it cannot actually do once the text is public.
Why AI builders leave the prompt client-side
The fast path to a working AI feature is to call the model directly from the front end. The generated code defines the system prompt as a string in a component or a config file, then sends it with each request. The app works on the first try, the assistant behaves, and nothing visibly signals that the prompt is now public.
A few patterns push the prompt into the client by default:
The skipped step is the review where someone asks where the prompt lives at runtime. From inside the app the assistant follows its instructions perfectly, which makes the design look finished. The fact that those instructions also shipped to every visitor is invisible from the inside, and the public URL exists before anyone checks. If your source maps are also published, the prompt is even easier to recover in readable form; that is a related exposure worth checking alongside this one.
- The prompt is declared inline next to the chat UI, so it bundles with everything else the browser downloads.
- It is stored in a build-time variable that the bundler inlines as a plain string, the same way a client-readable VITE_ or NEXT_PUBLIC_ prefixed value gets baked in.
- Guardrails are written as prompt rules ("refuse X, only do Y") rather than enforced on a server, so the entire control sits in text the client can see.
Confirm whether your prompt is exposed
You can confirm this on your own app without attacking anything. Load your deployed site, open DevTools, and look in two places.
In the Network tab, watch the request your app sends to the model or to your AI proxy. If the request payload going out from the browser contains the full system prompt as plain text, the prompt is being assembled client-side and is exposed to anyone watching their own network traffic. If the outgoing request carries only the user's message and your server attaches the system prompt out of view, that is the safer result.
In the Sources tab, use the search-in-all-files function and look for distinctive phrases from your prompt: the opening line, a rule you know you wrote, the assistant's name, or any tool or data reference. A match in a loaded .js file means the prompt is in the bundle. No match across the client scripts, with the prompt only appearing in server-side requests you cannot see from the browser, is the safer result.
- Suspicious result: the prompt text appears in a downloaded script, or rides along in the outgoing request payload from the browser.
- Safer result: the browser sends only user input, and the prompt is added server-side where the client never sees it.
- Check related strings too. While searching, look for secret-shaped values near the prompt, like an sk- or sk-ant- prefixed key. A leaked prompt and a leaked API key often ship from the same client-side shortcut.
Move the prompt server-side, and know what that does not cover
The fix is to stop sending the prompt from the browser. Put a small server endpoint between your client and the model. The browser sends the user's message to your endpoint; your server attaches the system prompt and forwards the request to the model with your API key. The prompt and the key both stay off the client. This is the same boundary that keeps your model credentials out of the bundle, so the two fixes tend to land together.
What moving the prompt server-side does not cover is just as important. It hides the text, but secrecy is not a security control on its own. A determined user can still probe the live assistant and infer much of its behavior from its responses, and prompt injection can coax a model into ignoring its instructions whether or not the prompt is visible. Treat a hidden prompt as removing an easy disclosure, not as making the app safe to misuse. For the input side of that problem, see the note on prompt injection in AI-built apps.
- Assemble the system prompt on the server, not in client code or in a build-time variable that the bundler inlines.
- Keep the model API key server-side as well; a client-side prompt and a client-side key usually travel together.
- Enforce real guardrails where they can be enforced. Input validation, output checks, allowed-tool lists, and authorization belong on the server, not in a sentence the model is asked to obey.
Hiding the prompt fixes disclosure, not control. If your app depends on the model never doing something, that rule has to be enforced in code on a trusted server, not stated in a prompt the model can be talked out of following.
Where VibeCodeGuard fits, and what it does not prove
VibeCodeGuard's Launch Check inspects your public bundle for prompt-shaped strings and configuration that was left client-side. If a recognizable system prompt or guardrail block is sitting in the shipped JavaScript, that is a public-surface signal you can act on before launch, reported with severity and fix direction. It looks at what an outside visitor can actually download and read.
What it does not do is judge whether your prompt is the only sensitive asset, or whether your server-side controls are correct. The scan reads the public surface; it does not review your code, confirm that guardrails are enforced on the server, or test whether the live assistant can be manipulated. A clean result means the obvious client-side disclosure was not found, not that the app is safe to misuse.
Next step: load your deployed app, search the client scripts and the outgoing model request for your prompt text, and if it is there, move both the prompt and the API key behind a server endpoint. Run a focused Launch Check against the public URL before sharing it, so an exposed prompt shows up in your scan and not in someone else's reading of your bundle.
A clean Launch Check does not prove your AI feature is locked down. It checks what ships to the browser, not whether your server enforces the rules your prompt only describes. Verifying enforcement and resistance to injection needs code review and manual testing.
> launch check
Scan the public surface before launch.
Get severity, evidence, and practical fix guidance for the checks VibeCodeGuard can run from the outside.