Lessons · LLM01: Prompt Injection · warning

The request body goes to the model unchecked

Anything a client sends, of any shape or size, lands in your model call.

Caught by request-body-to-llm-without-schema · CWE-20, CWE-77

What is wrong

Your route reads the request body and passes it to the model without checking what it is.

Your own frontend sends a nice short question. An attacker does not use your frontend. They send whatever they want straight to the endpoint: a 200,000 character message, extra fields, a system role in the messages array, a different model name.

Validation at the edge is the one place you decide what your app accepts. Without it, the model call accepts everything.

How it is used against you

Your chat route takes { messages } from the body and passes it straight to the model.

export async function POST(req: Request) {
  const { messages } = await req.json();
  return streamText({ model, messages }).toTextStreamResponse();
}
  1. An attacker opens the browser network tab and copies the request your frontend makes.
  2. They replay it with their own messages array, starting with a system message that replaces your instructions.
  3. They also pad the request to the model's maximum length and send it in a loop.
  4. Your model now follows their rules, and every request costs you the maximum tokens.

The cost: Prompt injection with no effort, plus a direct way to run up your bill.

How to spot it

  • await req.json(), request.formData(), or req.body used on the next line with no schema.
  • A messages array from the client passed through as is.
  • No maximum length on free-text fields.

The fix

Paste this into Claude Code, Cursor, or whatever wrote the code. When llm-audit finds the mistake in your project, npx llm-audit prompt gives you the same prompt with every file and line filled in.

Fix this in my project: the request body is passed to the model without validation.

Please:
1. Parse the body with an explicit schema (zod or valibot) before using it.
2. Set a maximum length on every free-text field and a maximum number of messages.
3. Only accept "user" and "assistant" roles from the client. Add the system prompt on the server.
4. Reject the request with a 400 if validation fails.
5. Add rate limiting to this endpoint.

Show me the diff and the schema.

Check the rest

A prompt that looks for the same mistake everywhere else, without changing anything yet.

Check this project's API routes that call an AI model, before I ship it.

Look for:
- Request bodies, form data, or query parameters passed to a model call without schema validation.
- Message arrays from the client accepted as is, including system messages.
- Free-text fields with no maximum length.
- Model endpoints with no rate limiting.

For each one, give me the file and line and explain what a request sent directly to the endpoint, not through my UI, could do. Do not change any code yet.

Find it in your code

npx llm-audit scan

Runs on your machine with Semgrep. Nothing is uploaded.