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.