The concrete launch risk: untrusted text steering your model
Prompt injection is the AI app risk where text that your model reads gets treated as instructions instead of data. If your app takes content you do not control, a user message, an uploaded document, a web page your agent fetches, an email it summarizes, and passes that content to a model, anyone who controls that content can try to override what you told the model to do. The model has no reliable way to tell your instructions apart from instructions buried inside the data it was handed.
Direct prompt injection is the obvious version: a user types "ignore your previous instructions and ..." into a chat box. The version that bites AI-built apps at launch is indirect prompt injection. Here the malicious instruction is not typed by your user at all. It rides inside content your app pulls in on its own, a product review, a support ticket, a PDF, a page your agent scrapes, and the model reads it as a command. This maps to the OWASP LLM Top 10 category for prompt injection (LLM01); the name is just taxonomy, not proof your app is affected.
What the injection can actually do depends entirely on what your model is wired to. Separate that cleanly:
- If the model only produces text shown back to the user, the worst case is wrong, off-policy, or embarrassing output, plus a possible leak of whatever else was in the prompt.
- If the model can call tools, hit APIs, query a database, send email, or drive an MCP server, the injected instruction can try to make it take those actions. That is the launch-blocking case.
- The blast radius is the model's reach, not the cleverness of the prompt. A model with no powers is a content problem; a model holding credentials and tools is an authorization problem wearing a language problem's clothes.
Why AI-built apps walk into this
The fast path from an AI builder to a working app is to wire the model directly to the interesting capabilities. "Build me an agent that reads my support inbox and drafts replies" produces code where the same model both reads untrusted email and holds the ability to send. That is exactly the shape prompt injection targets, and it ships because it demos perfectly: with your own benign test content, nothing goes wrong.
The generated default rarely includes a trust boundary. An AI-generated agent tends to pass fetched or uploaded content straight into the prompt alongside the system instructions, with no separation between "this is what you must do" and "this is data to consider." It often grants the model broad tool access because narrowing permissions is extra work the prompt did not ask for. And the review loop that would catch this, someone tracing where untrusted input flows and what the model is allowed to do with it, is the step that gets skipped when the app already works on the first try.
- Untrusted content and trusted instructions get concatenated into one prompt, so the model cannot structurally tell them apart.
- The model is handed more capability than the feature needs, because least privilege is a deliberate decision, not a default.
- Fast deploy means the agent is live and reachable before anyone maps its data flow. This is a generated-insecure-default pattern; see the note on AI-generated insecure defaults.
What to check on your own app before launch
You do not need an attack to assess your exposure. The questions are about data flow and capability, and you can answer them by reading your own app from the outside in. Do this only against your own app.
Trace the untrusted input. List every place content you do not fully control reaches the model: the chat box, file uploads, anything your agent fetches by URL, any external API, email, or database field a user can write. For each one, follow it in your code or in DevTools and confirm whether it lands in the same prompt as your system instructions.
Then inventory the model's reach. Write down every tool, API call, database operation, and outbound action the model can trigger. If your app exposes an agent or MCP endpoint, check whether it answers requests without authentication, an unauthenticated tool endpoint is reachable by anyone, not just your users (see the note on MCP server exposure when you deploy an AI agent backend). Finally, check what happens to the model's output: if responses render as HTML or markdown-with-HTML without sanitization, injection can chain into a script-injection bug (see the note on insecure output handling in LLM apps).
- Suspicious result: untrusted content is string-concatenated into the same prompt as your instructions, with the model holding tools or credentials. That is the high-risk combination.
- Safer result: untrusted content is clearly fenced as data, the model that reads it has no ability to act, and any action requires a separate step you control.
- Suspicious result: the agent or proxy route responds to an unauthenticated request, or model output is rendered as raw HTML.
- Safer result: actions sit behind authentication and authorization checks the model cannot talk its way past, and output is escaped or sanitized before it renders.
How to reduce it, and what the fix does not cover
There is no input filter that fully solves prompt injection, and you should not ship as if there were. Models do not reliably separate instructions from data, so "we tell the model to ignore malicious instructions" is not a control. The durable mitigations are about constraining what an injected instruction can reach, not about detecting the injection itself.
What these controls do not cover: they do not make the model immune to being steered, and they do not validate the meaning of what flows through. A model can still produce wrong or off-policy text within its allowed scope. The point is that when injection happens, the model cannot exceed the permissions you enforced around it. Prompt injection is a risk you bound, not one you close.
- Treat the model as an untrusted caller. Put real authentication and authorization in front of every action, enforced in code outside the model, so it does not matter what the model was convinced to request. Distinguish the two: authentication is who is calling; authorization is what that caller is allowed to do. The model passing your auth does not mean the action it requested is permitted.
- Apply least privilege to the model's tools. Give it only the capabilities the feature needs, scope them tightly, and require a human confirmation step for anything destructive or irreversible.
- Keep untrusted content out of the instruction channel where you can, and mark it clearly as data. This reduces the easy cases; it does not eliminate the risk.
- Sanitize and escape model output before rendering, and constrain it where you can, so an injection cannot pivot into script injection in the browser.
Authentication and authorization checks enforced outside the model are the load-bearing control here. Prompt-level instructions ("do not follow injected commands") are defense in depth at best, and a single string in your code can quietly turn a content bug into a privileged action.
Where VibeCodeGuard fits, and what it does not prove
VibeCodeGuard's Launch Check looks at your public surface, the security headers, exposed build and deploy artifacts, secret-shaped strings in the client bundle, publicly readable endpoints, and open CORS, and reports those with severity and fix guidance. Some of those findings touch this risk at the edges: an exposed provider key or an AI proxy route that answers unauthenticated requests on the public surface is the kind of signal it can flag, and that route is often the same one an injection would try to abuse.
What it does not do is detect prompt injection or prove your app is safe from it. Prompt injection lives in your data flow, where untrusted content meets the model, and in your authorization logic, what the model is allowed to do. Confirming those is manual code and data-flow review, not something a public-surface scan can verify. A clean Launch Check tells you the outside-facing basics are in order; it says nothing about whether your agent treats untrusted input as data or hands it straight to a model holding tools.
Next step: pick your single most powerful AI feature, the one where the model can take an action, and trace one untrusted input from where it enters to what the model can do with it. If you cannot point to an authorization check enforced outside the model on that path, that is the thing to fix before you share the URL. Run a focused Launch Check on the public surface in parallel, so the outside-facing signals are covered while you do the data-flow work only you can do.
A clean scan is not a clean bill of health for prompt injection. The scan sees what is reachable from outside; it does not read how your prompt is assembled or what your tools can do. Treat the scan as one input and treat your data-flow review as the real check.
> launch check
Scan the public surface before launch.
Get severity, evidence, and practical fix guidance for the checks VibeCodeGuard can run from the outside.