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 tagssystem_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