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 |
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.
When a recognized input guardrail — llm_guard / scan_prompt, a NeMo Guardrails LLMRails / RailsConfig wrapper, input_rail, sanitize_input — is used near the flagged code, the finding is still reported but with confidence lowered to low, and the guardrail_hint property names the guardrail found. A guardrail nearby does not guarantee that the user content reaching this system prompt is actually protected, so review these low-confidence findings and mute them once you confirm the guardrail covers the input path. Guardrail names that only appear in comments or string literals do not lower the confidence.
A template-escaped placeholder ({{user_input}}, rendered through a template with role separation) is considered safe and is not flagged.
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
sanitized = llm_guard.scan_prompt(user_input) (1)
messages = [
{"role": "system", "content": "You are a helpful assistant."}, (2)
{"role": "user", "content": sanitized}, (3)
]
| 1 | Run an input guardrail on the user content and use its sanitized output — scanning without using the result protects nothing. |
| 2 | Keep the system role free of user input. |
| 3 | Pass the guarded user content as a separate user-role message (role separation). |