Codex is an agent with access to local data and tools, and this sets its threat model. Any untrusted text may try to change the agent's plan: a web page, an issue, a package README, a generated file, a test fixture, a log, an MCP server's response or an instruction from a foreign branch of the repository. Prompt injection is an attempt to make the agent treat such text as a controlling command. The key understanding of the whole chapter: untrusted content is data, not commands.
The danger of injection is not constant but depends on what the agent has at hand. Injection becomes far more dangerous when the agent simultaneously has secrets, write access and the network. Separately each of these factors limits the damage: without the network the stolen has nowhere to go, without write access it cannot persist, without secrets there is nothing to steal. Together they add up to a real risk, so they are separated: the simultaneous presence of all three is what is avoided in an untrusted context.
From this follows the main thesis about the security boundary. The real boundary runs not in the system prompt, which injection can bypass, but in permissions, the sandbox and execpolicy. Asking the model "do not yield to harmful instructions" is not protection: one successful wording is enough to bypass the request. But a technical boundary - the network off, a deny on secrets, a forbidden on a dangerous command - holds regardless of what is written in the untrusted text. A mechanism protects, not an admonition.
| Source of untrusted text | Why it is dangerous |
|---|---|
| Web page, package README | May carry instructions disguised as content |
| Issue, foreign branch, repo instruction | Written by someone else, outside your control |
| Generated file, test fixture, log | Easy to miss as "yours", though the content is external |
| MCP response | An external server's reply is also untrusted data |
Secrets obey a simple rule: they must not be where the model will read them. Do not paste keys into a prompt, instructions, a skill, an agent definition, committed settings, a test snapshot or a log. Use a secret store and injection from the environment, and put a deny on sensitive paths in the profile. Deny-read for .env and keys is a useful baseline but not the only protection: a secret that is not in the context cannot be carried off by injection, even if it succeeded.
Third-party code needs the same wariness as any untrusted input. Hooks run real local commands with the rights of the Codex process - these are not harmless notes but executable code. Non-managed hooks require a review of the exact definition hash, and after any change they become untrusted again until reviewed. This is protection from a quiet substitution: a changed hook does not start executing silently but requires new explicit trust in its new content.
Installing a plugin is not automatic trust in its contents. A plugin can bring skills, agents, MCP and hooks at once, and the presence of a plugin in a source does not mean someone checked the security of its bundled hooks. Their trust is assessed separately, like any executable code. Hence the discipline: pin and review the source, study what exactly the plugin brings, start in a test environment and not confuse "it installed" with "checked and safe".
The practical conclusion is to move repeatable decisions from random prompts into the proper layer. What you explain to the agent by hand every time is better expressed once in config, instructions, a skill, a custom agent, a plugin, a hook or MCP - where it will be visible, checkable and outlive the session. A random prompt is easy to lose and impossible to review; a policy in the proper layer, on the contrary, is read, tested and explained. Security is an architecture of layers, not a lucky wording.
The typical failures around trust are predictable. Relying on a request to the model instead of a technical boundary in permissions and the sandbox. Keeping secrets, write access and the network simultaneously in an untrusted context. Pasting a key where the model will read it. Taking an installed plugin or a changed hook for trusted without a review. And keeping policy in random prompts instead of a layer. Treat untrusted content as data, keep the boundary in mechanisms, separate secrets, write access and the network and move decisions into the proper layer.