System prompt concatenates unbounded user content
ID |
unbounded-user-content-in-system-prompt |
Severity |
high |
Remediation Complexity |
medium |
Remediation Risk |
medium |
Remediation Effort |
medium |
OWASP LLM |
LLM01:2025 — Prompt Injection |
Family |
LLM01 — Prompt Injection |
Asset kind |
ai_prompt |
Red-team vectors |
Prompt Injection, Jailbreaks |
Tags |
ai_security |
Description
A role=system prompt that string-concatenates a user-controlled variable directly into its body — with no role separation, delimiter, or input guardrail — lets attacker-supplied text override the system instructions. This is the canonical prompt-injection exposure (OWASP LLM01).
This detector raises a finding when a system-role message interpolates a user-controlled variable (an f-string / format placeholder such as {user_input}, or a "…" + user_input concatenation) within the same construction window.
This is the deterministic FilePattern half of the detection. The AST half (true f-string / template analysis) and the "no outgoing edge to an ai_guardrail`" cross-asset condition land with the AST framework and the in-memory asset graph in later slices; together they remove the residual false positives this content heuristic accepts. A template-escaped placeholder ({{user_input}}`) is deliberately not matched.
|
Examples
messages = [
{"role": "system", "content": f"You are a helpful assistant. User says: {user_input}"}, (1)
]
openai.chat.completions.create(model="gpt-4o", messages=messages)
| 1 | User-controlled {user_input} is concatenated straight into the system role, so a hostile input can rewrite the agent’s instructions. |
Mitigation / Fix
messages = [
{"role": "system", "content": "You are a helpful assistant."}, (1)
{"role": "user", "content": user_input}, (2)
]
guarded = llm_guard.scan_prompt(user_input) (3)
| 1 | Keep the system role free of user input. |
| 2 | Pass user content as a separate user-role message (role separation). |
| 3 | Add an input guardrail before the model call to detect a hostile prompt. |