Skip to main content
interlace
Plugin: anthropic-securityRules

no-untrusted-content-in-prompt

Disallow untrusted content built into the Anthropic system prompt

Disallow untrusted content built into the Anthropic system prompt.

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 const holding 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 every return is 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 typed number / 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 }) and streamText({ system }) belong to vercel-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
.oxlintrc.json
{  "jsPlugins": ["oxlint-plugin-anthropic-security"],  "rules": {    "anthropic-security/no-untrusted-content-in-prompt": "error"  }}
vite.config.ts
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.