Security for Codex

Shell commands, patches and MCP tools in Codex pass the adapter before they run, and its rules hold even with hooks off.

A denial in Codex

The denial, in context
The hook answers deny with the policy and the reason. Codex shows it and the model reads it, so the agent moves on to the rest of the task.
The escalation id
Every denial carries its id, and the person asks for an exception from the record. A retry before approval is denied again.
The rules file
The org’s deny rules also compile to a Codex rules file, so the plainest denials hold with no hook running at all.

One command on each machine

Run it once per machine. It enrols the person, starts the adapter, writes hooks.json beside Codex’s config and generates the rules file. Codex asks the person to trust the new hook once; the install prints exactly what to trust.

$ specify adapter install codex
  • PreToolUse on shell commands, apply_patch and every MCP tool: the decision
  • PermissionRequest: a deny wins before Codex asks; an allow skips the prompt
  • PostToolUse: the input scanner over what the agent read
  • rules/specify.rules: the org’s deny rules as prefix rules, checked with codex execpolicy check
# ~/.codex/hooks.json
{ "hooks": { "PreToolUse": [{
    "matcher": "Bash|Edit|Write|mcp__.*",
    "hooks": [{ "type": "command", "command": "specify-hook" }]
}] } }

What is checked in Codex

Shell commandsChecked
Shell, including the unified exec_command.
File edits and writesChecked
apply_patch, matched as Edit or Write.
File readsChecked
Through the shell commands that read them, and the input scanner on what came back.
MCP tool callsChecked
Every call to every server, by server and tool name.
Web search and other hosted toolsNot checked
Codex runs web search as a hosted tool with no hook, so it is not checked. The org can turn it off or restrict it in the managed requirements.
SubagentsChecked
spawn_agent calls are matched as Agent and carry onto the record.
IdentitySigns in once
The person signs in once on the machine; every event names them from then on.

What the org can pin

Codex reads a managed requirements file that a person cannot override. It carries the hook, keeps hooks on, and pins the restrictive rules.

# requirements.toml
allow_managed_hooks_only = true

[features]
hooks = true

[hooks]
managed_dir = "/Library/Managed/specify"

[rules]
prefix_rules = [
  { pattern = ["vercel", "deploy", "--prod"], decision = "forbidden",
    justification = "Ask your org admin for an exception" },
]
  • Codex does not distribute the hook scripts. The managed directory is delivered by the org’s device management, and the install prints the path.
  • Managed rules match a command prefix and may only be prompt or forbidden, which is what a pinned denial needs.

When something is missing

If the hook does not answer in time
Codex treats a failing hook as non-blocking unless it exits with code 2. That is Codex’s design, and it is why the adapter decides from the cached bundle on the machine and never waits on the network to answer.
If the adapter is not running
The shim answers from the last signed bundle: the org’s flagged classes are denied, everything else is allowed, and an adapter-down event is logged. The rules file holds on its own.
If the person removes the hook entry
The rules file still applies, and a managed requirements file keeps both in place. Inventory shows hooks removed and when the adapter was last seen.