//nefariousplan

Content Is Command

External content feeds an interpreter that treats it as instructions. Prompt injection is one instance.

There is a line in every system between what is content and what is command. Content gets stored, displayed, parsed, analyzed. Command gets executed, trusted, acted upon. The security of the system depends on the line holding.

Content-is-command is what happens when the line does not hold. An input channel the system treated as passive data turns out to be the same channel it reads instructions from. The attacker's job is not to bypass a check. The attacker's job is to write content that the interpreter reads as command, and then let the interpreter do the rest.

Mechanism

The pattern shows up wherever an interpreter reads a stream that mixes sources. A shell executes what its user types. A template engine substitutes variables and also evaluates expressions. A SQL query combines structure and values. A PDF renderer follows document instructions and user preferences. An LLM reads its prompt and also any text that happens to be in the prompt.

Each of these systems assumes a boundary: the command part is trusted, the content part is data. Attacks on this pattern do not break the interpreter. They exploit the ambiguity at the boundary. The attacker embeds instructions inside content the interpreter was supposed to treat as inert, and the interpreter, doing exactly what its spec says, does the wrong thing.

What distinguishes content-is-command from ordinary injection is the CONTENT source. Classical injection comes from an input channel the designer already knew was hostile: HTTP parameters, form fields, URL query strings. The defense is input validation at the boundary. Content-is-command comes from channels the designer treated as content: documents, emails, webpages, PDFs, support tickets, database fields populated long ago by someone else. The defense is harder because there is no clean boundary to validate. The content and the command arrive on the same wire, in the same format, routed by the same parser.

Exhibits

CVE-2025-27407: graphql-ruby Loaded the Schema by Compiling It. graphql-ruby's Schema::Loader.load is content-is-command at a layer below the application. The content channel is a JSON introspection document describing types; the interpreter is the Ruby compiler; the bridge is class_eval <<-RUBY ... def #{method_name}; self[#{method_name.inspect}]; end RUBY, where method_name is whatever string came out of the JSON's name field. The loader's documented purpose is to ingest external schemas, so the content source is wide by design. The DSL the loader feeds was written for author-typed identifiers, so the interpreter at the bottom of the call stack does not distinguish caller-source from wire-source. The PoC's payload is a four-line method-definition wedged into a name field; once the HEREDOC interpolates it, system(...) runs at class-definition time. The fix removes the eval (define_method with a captured closure) and validates names against /^[_a-zA-Z][_a-zA-Z0-9]*$/, naming both halves of the boundary the original code did not.

CVE-2025-23211: bleach Is An HTML Sanitizer. Jinja2 Does Not Read HTML.. The Tandoor case is content-is-command reduced to its smallest reproducible form: a single text column in a database, compiled every time the record is read, by an interpreter that was never given a sandbox. The pattern's mechanism_md says "a template engine substitutes variables and also evaluates expressions"; Tandoor's pipeline is that sentence turned into Python. The exhibit adds a specific teaching about adjacency: a sanitizer for one grammar running in front of an interpreter for another is not a layered defense. It is two unrelated checks at the same call site. The sanitizer sees HTML; the interpreter sees Python. They share a variable name and nothing else.

CVE-2026-40261: The Injection Is in syncCodeBase, Not generateP4Command. Repository configuration values in composer.json and package source metadata from a Packagist-compatible registry are treated as passive connection parameters: port, user, depot, branch reference. The Perforce driver interpolates them directly into shell command strings. A developer who never authored a shell command has, through their dependency declarations, provided input to a shell interpreter. The registry delivery path makes the content-is-command shape explicit: attacker-controlled metadata from a registry becomes a shell command on the developer's machine without any file the developer explicitly ran.

Prompt Injection Is a Supply Chain Attack. An LLM reads its prompt. The prompt contains the user's question, the system's instructions, and any other content the integration has assembled: document excerpts, email bodies, retrieval-augmented context. To the model, these are all text in the same context window. An attacker who controls a piece of text that reaches the prompt reaches the instruction stream. The agent doing the summarization reads the attacker's words as a directive, then calls tools with the attacker's intent. The attack is not a bug in the model. It is the model doing its job against an input whose provenance the prompt could not encode.

CVE-2026-33937: Handlebars Trusts Its Own AST. The wire-side shape: an express.json() middleware that deserializes whatever shape the body carries, a Handlebars.compile overload that dispatches on input.type === 'Program' to skip the parser, and a route handler whose call site cannot tell which path it took. The field is named editorTemplateData and the surrounding documentation calls it a template; the interpreter accepts either a template (passive content) or a parsed AST (an instruction stream that the codegen pastes into generated JavaScript). The pattern's mechanism_md says "the interpreter, doing exactly what its spec says, does the wrong thing"; Handlebars spec'd both inputs as legitimate fourteen years ago. The boundary is at the JSON shape, not at the field name. No single component owns the boundary; the composition produces a wire-to-JavaScript pipeline no individual contract names.

CVE-2026-34621: Adobe Acrobat's Privilege Gate Inherits What It Checks. A PDF is a container for declarative rendering instructions. Acrobat honors those instructions. When a document's declared renderer or embedded action is malicious, the trust gate around privileged actions inherits the privileges of the thing it is checking, and the document becomes a command carrier. The defender sees a file. The parser sees a script. The write happens in the space where those two views diverge.

Boundaries

Not every injection. Classical SQL injection, command injection, and XSS are injection at a known hostile boundary. The defense is established (parameterized queries, escaping, CSP), and the failure mode is usually misuse of known-good primitives rather than a deep design problem. Content-is-command is structurally different: the attacker's content travels through channels the system was not designed to treat as hostile.

Not every interpreter. If your system has a documented interpreter for user input and the user supplied malicious input, that is ordinary. Content-is-command describes the case where the content channel becomes an interpreter by accident of shared tokenization, shared context, shared parser. The channel was not meant to accept instructions. The interpreter chose not to distinguish.

Not every supply-chain poisoning. A malicious package is malicious code executing as code. Content-is-command is inert-looking data, parsed by a component that should not have been an interpreter, producing command-like effects. A compromised package does not need this pattern to hurt you. The pattern explains why passive-looking inputs hurt you anyway.

Defender playbook

Enumerate your interpreters. For every parser in your stack, ask: what does it do with well-formed input, and what does it do with surprising input that also happens to be well-formed? An LLM, a PDF reader, a template engine, a markdown renderer, a YAML loader, an XML parser with DTDs, each of these is an interpreter. Each has a content-is-command shape when its content source is wider than its designer expected.

Tag content by provenance, not by format. The dangerous shift is when content from untrusted sources enters a context that was trusted. A Slack message repeated into an LLM prompt changes meaning. A PDF from email embedded into a workflow engine changes meaning. Provenance tracking lets you enforce that untrusted content does not reach privileged interpreters.

Constrain what interpreters can do. An LLM with tool access is a much larger attack surface than an LLM that only produces text. A PDF renderer with JavaScript enabled is a much larger surface than one without. The smaller the interpreter's action set, the smaller the content-is-command blast radius.

Treat retrieval as a trust boundary. Any system that pulls content into an interpreter's context (search indexes, embedding stores, RAG pipelines, email summarizers) is defining a new trust boundary. Log what crosses the boundary. Consider rate limits, source allowlists, or out-of-band confirmation for actions triggered by retrieved content.

Audit the parsers you did not write. If your stack includes a document format parser (PDF, DOCX, XLSX, SVG, ZIP, RTF) from a third party, it has known and unknown ambiguity seams. You will not patch those. You can sandbox them. Containment is the primary defense.

Kinship

Trust Inversion. Content-is-command is trust inversion at the input channel. The channel you trusted as content is now authorizing commands. Every content-is-command vulnerability is a trust-inversion instance with the inverted authorizer being an interpreter.

The Detector Is The Target. Sandboxes and scanners often BECOME content-is-command surfaces. A sandbox that parses a file to decide if it is malicious is an interpreter reading attacker-controlled content. If the parser's interpretation diverges from the defender's, the detector misclassifies and the attacker gets the benefit of the ambiguity.

Security Tool As Primitive. When an interpreter lives inside a security tool (a YARA engine, an AV signature matcher, an EDR script interpreter), content-is-command against that interpreter produces a security-tool-as-primitive outcome. The tool reads attacker content, interprets it as command, executes with the tool's privilege.

The question is not whether your system has interpreters. Every system does. The question is whether any of them read inputs from sources you would call untrusted if you thought about it for thirty seconds.