no-untrusted-content-in-prompt
Disallow untrusted content built into the Gemini system prompt
Disallow untrusted content built into the Gemini system prompt.
- CWE: CWE-1427 โ Improper Neutralization of Input Used for LLM Prompting
- OWASP: A03:2021 โ Injection ยท LLM Top 10: LLM01 Prompt Injection
- CVSS: 8.1 (High) ยท Preset:
strictonly
Why
A system prompt is instruction text. Whatever is spliced into it is read by the model as instructions, not as data โ so anyone who controls that value controls the agent's rules. The model has no way to tell "this part of my instructions came from a stranger" once the string is assembled.
This is the same argument as mcp-sdk-security/no-tool-description-injection, one layer down: tool descriptions and system prompts are both model-facing instruction surfaces, and both must be static.
What counts as static
A string literal, a template with no interpolations, or a concatenation of those. A bare identifier also counts โ systemInstruction: SYSTEM_PROMPT is the correct pattern and by far the most common one, and following it is the data-flow analysis this rule deliberately avoids. The interpolation is where the injection is visible in one place, and that is what gets reported.
Not this rule
- The Vercel AI SDK.
generateText({ system })andstreamText({ system })belong tovercel-ai-security/no-dynamic-system-prompt. This rule gates on Gemini's own request paths, so no line is ever reported by both. - User turns.
{ role: 'user', content: `${input}` }is the remediation, not the bug โ a user message is where runtime values belong, because the model reads them as data.
Known limitations
- The gate is file-level: once the Gemini SDK is imported anywhere in the file,
any call whose member path matches a request path is inspected. An unrelated
object that happens to expose the same path โ
ai.models.generateContenton something that is not a Gemini client โ is a false positive. Resolving the callee back to the import would need the cross-scope data-flow analysis these rules deliberately avoid; the trade is a rule that stays fast and predictable, and it is why this rule ships instrictonly until the corpus run measures the real rate. - A system prompt assembled before the call (
const p = base + role;thensystemInstruction: p) is a false negative, for the same reason a bare identifier counts as static.
Incorrect
await ai.models.generateContent({ systemInstruction: `Answer as ${persona}.`, contents });Correct
await ai.models.generateContent({
systemInstruction: 'You are a helpful assistant.',
contents: [{ role: 'user', parts: [{ text: `Persona: ${persona}` }] }],
});Did this rule catch something? Star the repo to get new CWE coverage as we ship it โ or follow the AI-code-security benchmarks behind these rules.