Rules / CFG063
CFG063 Codex CLI removes the human from the approval loop
Severity: error (approval_policy: never) · warn (approval_policy: on-failure, approvals_reviewer: auto_review)
OWASP: LLM03:2026 – Excessive Agency
Two keys, two halves of the same loop
approval_policy decides how often Codex asks. approvals_reviewer decides who answers the prompts that are still raised: user (the default) asks the person, while auto_review routes escalated requests — sandbox escapes, blocked network access, MCP prompts, ARC escalations — to a reviewer subagent. A committed file can therefore leave approval_policy looking perfectly safe and still have nobody watching.
Codex accepts guardian_subagent as a legacy alias for auto_review (codex-rs/protocol/src/config_types.rs), so cfgaudit matches both spellings; a file using the old name would otherwise slip through.
approvals_reviewer is warn rather than error. Codex's own field documentation says it "does not disable separate safety checks such as ARC", and the subagent applies a risk-based framework rather than blanket-approving, so it is a weaker claim than approval_policy: never. Both findings can fire on one file; that combination is the interesting one, and each names its own key.
What cfgaudit checks
OpenAI Codex CLI's approval_policy (in .codex/config.toml) controls when the agent asks before running commands — the analog of Claude Code's defaultMode. Two values remove the human-in-the-loop:
| Value | Meaning | Severity |
|---|---|---|
never |
Never ask; all commands are auto-approved (non-interactive) — the Codex equivalent of defaultMode: bypassPermissions |
error |
on-failure |
DEPRECATED — all commands are auto-approved and only escalated to the user on failure | warn |
The safe value is on-request.
untrusted used to be the second one. Codex retired it on 2026-08-19 ("Remove untrusted from the CLI, configuration schema, and MCP tool interface. Explicit approval_policy = "untrusted" settings now fail with an actionable error"), and the same change removed the known-safe command allowlist behind it ("Projects marked untrusted now request approval for every command unless an explicit exec policy rule allows it"). cfgaudit therefore recommends only on-request, which is accepted before and after that change. A file that still sets untrusted is not reported: on an older Codex it is a safe value, and on a current one the CLI raises its own error.
Where cfgaudit looks
Codex config is project-merged, not user-global. Besides the user file it loads a repo-local .codex/config.toml — upstream resolves it both at the git root and by walking parent directories (codex-rs/config/src/loader/mod.rs, layer order cwd → tree → repo).
cfgaudit therefore scans:
| Path | Scope | Flag |
|---|---|---|
<repo>/.codex/config.toml |
project | always |
~/.codex/config.toml ($CODEX_HOME) |
user | --user |
A committed .codex/config.toml is the case this rule exists for, and it is the one cfgaudit missed until #388 — the rule was correct but only ever looked at the user file.
The bare ${PWD}/config.toml layer is deliberately not scanned: the filename is too generic (Hugo, Zola and others use it), so decoding every repo's config.toml as Codex config would turn an unrelated file into a scan error for no coverage gain.
Keys Codex refuses from a project layer
Upstream guards a subset against repo contents (PROJECT_LOCAL_CONFIG_DENYLIST), commented "Project-local config comes from repository contents, so it should not get to choose where a user's credentials are sent or which local commands are run." It covers openai_base_url, chatgpt_base_url, apps_mcp_product_sku, model_provider(s), notify, profile(s), the two experimental realtime URLs, and otel.
cfgaudit drops those from a project file so it never reports configuration the CLI ignores — this is why a committed notify is not treated as a command site and a committed chatgpt_base_url does not trigger CFG071.
approval_policy, sandbox_mode and mcp_servers are not on that denylist, which is exactly why they are worth flagging in a committed file.
Trust gate
Project layers are "loaded but disabled when the directory is untrusted". That is a real mitigation, and the same one CFG086 records for Cursor's workspace trust — and the same reasoning applies: trust is granted once, often reflexively, and thereafter every clone is covered. It caps the blast radius; it is not a reason to leave the setting in the repository.
Trigger example
approval_policy = "never"
→ error.
Safe alternative
approval_policy = "on-request"
Keep approval interactive; reserve never for sandboxed, non-interactive CI runs where you also constrain sandbox_mode (see CFG064).
References
- CFG004 (Claude Code
defaultMode: bypassPermissions/auto), CFG060 (GeminidefaultApprovalMode) — equivalent rules for other agents - CFG064 (Codex
sandbox_mode: danger-full-access) — the sandbox half of the same risk - OpenAI Codex CLI
The same key again under [apps]
approvals_reviewer is spelled three more times inside the [apps] table, where it overrides the thread default for one connector rather than for the session:
| Position | What it covers |
|---|---|
[apps._default] approvals_reviewer |
approval prompts from every connector |
[apps.<id>] approvals_reviewer |
one connector's prompts |
[apps.<id>.links.<link>] approvals_reviewer |
one connected account's prompts |
mcp_approvals_reviewer_from_layers (codex-rs/core/src/connectors.rs) resolves them link first, then app, then _default, and falls back to the thread default only when none is set. A managed requirements layer can still veto the value (requirements.approvals_reviewer.can_set), which is why these stay warn like the root key.
The practical consequence is that the root key alone does not tell you whether prompts are human-answered: a file can leave approvals_reviewer unset at the top and still route a connector's escalations to the subagent. cfgaudit reports each position separately, and a file that sets both the root key and an apps position produces both findings, because they name different blast radiuses.
Documentation for cfgaudit 1.18.1. Source of this page · Rule implementation