AI001 · STOA-LLM01-PROMPT-EXPOSURE
Untrusted input observed flowing into prompt construction.
- OWASP: LLM01 Prompt Injection (v1.1 and 2025).
- Severity: high · Gates: no (opt in via
[gate].additional_rules). - Kind: data-flow (needs the AST layer;
--no-astdisables it).
Detection
A taint chain from an untrusted source to a prompt sink:
- Sources: request surfaces (
request.get_json(),request.args,
req.body/query/params), input()/sys.stdin, open(...).read(), and retrieval results (retriever.invoke, similarity_search, index.query) — the indirect-injection surface.
- Sinks: the message/prompt argument of a model call
(client.messages.create, chat.completions.create, llm.invoke, generateText, …), or a prompt template that reaches such a call.
- System-role placement (tainted value in
system=or arole: "system"
message) escalates confidence one tier and tags system_role_interpolation.
Vulnerable → remediated
# VULNERABLE — request value interpolated into system instructions
topic = request.get_json()["topic"]
prompt = f"You are a support bot. Answer about {topic}."
client.messages.create(system=prompt, messages=[{"role": "user", "content": "help"}])
# REMEDIATED — untrusted content isolated in the user role, instructions static
client.messages.create(
system=SYSTEM_PROMPT, # static constant
messages=[{"role": "user", "content": f"<topic>{escape_xml(topic)}</topic>"}])
Finding message
A request-derived value flows into prompt construction reaching a model call. No boundary construct was observed on this flow. Content that reaches instruction text can override agent behavior (OWASP LLM01). Consider moving untrusted content into a delimited user-role message and keeping instruction text static. Analysis is intra-file; flows through other files are not visible.
Suppress: # stoa: ignore[AI001] reason