no-untrusted-content-in-prompt
Disallow untrusted content built into the Anthropic system prompt
Disallow untrusted content built into the Anthropic 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 it reads
system as a string or as an array of text blocks (the prompt-caching form, [{ type: 'text', text, cache_control }]), and a role: 'system' turn in messages. Request paths: messages.create, messages.stream, messages.parse and the beta messages.toolRunner, plus each params body of a messages.batches.create request list (an array literal or an items.map(β¦)).
What counts as static
Text this file fixes is static, and is followed within the file (bounded, never across files):
- a string literal, an interpolation-free template or tagged template (
dedent`β¦`), and concatenations,[β¦].join()and string methods over those; - a
constholding any of the above, a member of a same-file object literal of prompts (PROMPTS[kind]), a ternary between static branches, and a same-file helper whose everyreturnis static; - an imported constant (
RECOMMENDED_PROMPT_PREFIX) and an environment read β module code and operator configuration, not request data; - a value that can only print digits or a fixed format: a date (
new Date().toISOString()), a count (.length), arithmetic, a comparison, or a parameter typednumber/boolean.
A whole-value identifier the file cannot see into still counts as static β a parameter (system: systemPrompt inside a wrapper) or a global. Inside a template or a concatenation the same value is the dynamic half, and that is what gets reported, at the place it is spliced in. A request object or a message list held in a const is read the same way as one written inline.
Not this rule
- The Vercel AI SDK.
generateText({ system })andstreamText({ system })belong tovercel-ai-security/no-dynamic-system-prompt. This rule gates on Anthropic'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 Anthropic SDK is imported, any call whose member path matches a request path is inspected, unless this file shows the receiver is something else (an object literal, or a
new X()of a class that does not extend the SDK). A receiver that is a parameter, an import or a call result is inspected β clients are passed around, and resolving every one back to its construction would take cross-file analysis.
Incorrect
await client.messages.create({
system: `You are an assistant for ${tenantName}.`,
messages,
});Correct
await client.messages.create({
system: 'You are a helpful assistant.',
messages: [{ role: 'user', content: `Tenant: ${tenantName}` }],
});Use this rule
npm install -D oxlint-plugin-anthropic-security{ "jsPlugins": ["oxlint-plugin-anthropic-security"], "rules": { "anthropic-security/no-untrusted-content-in-prompt": "error" }}import { defineConfig } from 'vite-plus';export default defineConfig({ lint: { jsPlugins: ['oxlint-plugin-anthropic-security'], rules: { 'anthropic-security/no-untrusted-content-in-prompt': 'error', }, },});oxlint-plugin-anthropic-security re-exports eslint-plugin-anthropic-security/oxlint: the same rules under the same IDs, and no ESLint install. Why oxlint.
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.