Direct concatenation of user input into system prompt strings is the root cause of most real-world prompt injection attacks, classified as OWASP LLM01:2025 and tracked under MITRE ATLAS AML.T0051. When a user message is spliced into the same string as your instructions, an attacker can craft input that terminates your instructions and appends new ones—overriding safety constraints, extracting the system prompt, or pivoting to unintended behavior. CWE-1427 captures this structural failure precisely: the trust boundary between instructions and data collapses when they share the same string. Any application handling sensitive user data, performing agentic actions, or operating in a regulated environment (NIST AI RMF MEASURE 2.6) is exposed to full instruction override through nothing more than a well-crafted chat message.
Critical because successful injection via concatenation hands the attacker complete control over the model's instruction set, enabling immediate system prompt override, data exfiltration, or safety bypass without any additional exploit steps.
Place user content exclusively in a role: "user" message object—never inside the system message string. The structural separation is the defense; delimiters and quoting inside a concatenated string are not sufficient because they can be escaped.
// Correct: role separation enforces the instruction/data boundary
const response = await openai.chat.completions.create({
model: "gpt-4o",
messages: [
{ role: "system", content: FIXED_SYSTEM_PROMPT },
{ role: "user", content: userMessage } // never concatenated into system
]
})
Audit every file that constructs AI messages (look for template literals referencing req.body, params, or session values inside system-role strings) and refactor each call site to use the two-message pattern. The parameterized-templates check in this audit verifies that prompt construction is centralized so this fix holds across the codebase.
ID: ai-prompt-injection.input-sanitization.no-direct-concatenation
Severity: critical
What to look for: Enumerate every location where user input is passed to an LLM API call. For each location, classify whether user input is directly concatenated into the prompt string or separated via parameterized templates. examine all code that builds prompt messages sent to the AI provider. Look for string concatenation or template literals that embed raw user input directly into the system or user message string without any intermediary sanitization step. Parameterized message construction — where user content is placed in a dedicated role: "user" message object rather than spliced into the system message — is the correct pattern.
Pass criteria: User-supplied values are placed exclusively in role: "user" message objects in the messages array, and are never concatenated into role: "system" messages. Template literals that only embed pre-defined, developer-controlled strings in the system message are acceptable — 100% of LLM call sites must avoid direct string concatenation with user input. Report the count: "X LLM call sites found, all Y use parameterized templates."
Fail criteria: Raw user input (from request body, query params, or user session) is concatenated or interpolated directly into the system prompt string, or is mixed with system instructions in a single string without structural separation.
Skip (N/A) when: No AI provider integration detected — no AI SDK packages in package.json and no AI API calls found.
Cross-reference: The parameterized-templates check verifies the template structure itself is secure.
Do NOT pass when: User input is wrapped in quotes or delimiters within a concatenated string — this is not true parameterization and can be escaped.
Detail on fail: Quote the actual concatenation code pattern found. Example: "lib/chat.ts buildSystemPrompt() interpolates req.body.userMessage directly into the system role string using a template literal"
Remediation: Direct string concatenation of user input into system prompts is the root cause of most prompt injection vulnerabilities. The user's message should always travel as a separate, clearly-labeled message in the conversation array — never embedded inside the system prompt.
Structure your AI calls with strict role separation:
// Correct: user content is structurally separate
const response = await openai.chat.completions.create({
model: "gpt-4o",
messages: [
{ role: "system", content: FIXED_SYSTEM_PROMPT },
{ role: "user", content: userMessage } // user content isolated here
]
})
// Wrong: user content is merged into system instructions
// (described without literal dangerous code)
// Avoid building a single string that mixes instructions and user input
For a deeper analysis of transport and header security for your AI endpoints, the Security Headers & Basics Audit covers HTTP-level protections.