`auto_review.policy` silently replaces the catalog security policy despite being described as additional instructions

Open 💬 1 comment Opened Aug 13, 2026 by Frefreak

What version of Codex CLI is running?

codex-cli 0.147.0

Source reproduction: commit e9be887f57c8e2d4f606c36890b692d03f328726.

What subscription do you have?

Pro Lite (5x)

Which model were you using?

gpt-5.6-sol; approval review uses the codex-auto-review catalog entry.

What platform is your computer?

Darwin 25.6.0 arm64 arm

What terminal emulator and version are you using (if applicable)?

Ghostty through tmux 3.6a.

Codex doctor report

{
  "codexVersion": "0.147.0",
  "relevantChecks": {
    "config.load": "ok",
    "sandbox.helpers": "ok"
  },
  "note": "Full report omitted because the reproduction is independent of auth, network, and local state."
}

What issue are you seeing?

The Rust config type and generated JSON schema describe local [auto_review].policy as:

Additional policy instructions inserted into the guardian prompt.

The public configuration reference calls it “Local Markdown policy instructions for automatic review,” but does not warn that setting it removes the model catalog policy.

The implementation treats any nonblank local policy as a complete replacement:

  1. ConfigToml.auto_review.policy is normalized into Config.guardian_policy_config.
  2. Guardian prompt construction selects that value or the catalog policy:
let tenant_policy_config = parent_config
    .guardian_policy_config
    .as_deref()
    .or_else(|| catalog_auto_review.and_then(|messages| messages.policy.as_deref()))
    .unwrap_or(BUNDLED_GUARDIAN_POLICY);

Consequently, adding one narrow local rule can silently remove the catalog policy's rules for sensitive-data egress, credential probing, persistent security weakening, and destructive actions. The policy template still defaults low- and medium-risk actions to allow, so an attempted tightening can weaken unrelated review categories.

Managed guardian_policy_config may intentionally be a complete organization-policy override. The issue is that local [auto_review].policy is collapsed into the same override field, losing the distinction between managed replacement and user-supplied “additional” instructions.

What steps can reproduce the bug?

From the codex-rs workspace at the commit above:

  1. Confirm the schema wording:
$ rg -n -C 2 'Additional policy instructions' codex-rs/core/config.schema.json
347-      "properties": {
348-        "policy": {
349:          "description": "Additional policy instructions inserted into the guardian prompt.",
350-          "type": "string"
  1. Confirm local policy is loaded into the complete-override field:
$ cargo test -p codex-core load_config_uses_auto_review_guardian_policy_config -- --nocapture
test config::tests::load_config_uses_auto_review_guardian_policy_config ... ok
  1. Confirm that field displaces the catalog policy in the final prompt:
$ cargo test -p codex-core guardian_review_session_config_prefers_managed_policy_and_uses_catalog_template -- --nocapture
test guardian::review_session::tests::guardian_review_session_config_prefers_managed_policy_and_uses_catalog_template ... ok

Together, these existing tests deterministically prove replacement. A direct regression test can inject LOCAL_POLICY_SENTINEL and CATALOG_POLICY_SENTINEL and assert that both appear in base_instructions; the catalog assertion fails today.

What is the expected behavior?

Local customization should not silently remove the catalog security policy.

Preferably:

  • managed guardian_policy_config remains an explicit complete override;
  • local [auto_review].policy is appended to the effective catalog/bundled policy;
  • prompt construction preserves whether a policy came from managed requirements or local additions;
  • a regression test asserts that both local and catalog sentinels appear.

If complete replacement is intended locally, the field and documentation should say so explicitly, for example policy_override, with a prominent warning that it removes catalog safeguards. A separate safe additional_policy setting would still be useful.

Additional information

I searched open/closed issues and pull requests for auto_review.policy, guardian_policy_config, “Additional policy instructions,” custom/catalog Guardian policy, and Guardian prompt overrides. No existing issue covers this contract.

Related but distinct:

  • #30598: per-prefix human reviewer routing under global auto-review.
  • #37930: Guardian treating user transcript messages as untrusted.
  • #25570: canonical reviewer-name serialization.
  • PR #18959: introduced user-customizable auto-review policy config.
  • PR #32875: preserved guardian_policy_config precedence when adding catalog policies, but did not address collapsing local policy into that complete-override field.

Official configuration reference: https://developers.openai.com/codex/config-reference

View original on GitHub ↗

1 Comment

jdcodes1 · 10 days ago

Confirmed still present on main @ 1f41cc5d92 — the selection is now factored into Config::resolve_guardian_policy, same replacement semantics:

https://github.com/openai/codex/blob/1f41cc5d92722748e45cae9cecc6d883a4e7cbb1/codex-rs/core/src/config/mod.rs#L1454-L1466

guardian_policy_config → catalog policy → bundled policy is a first-match chain, so any nonblank local [auto_review].policy fully shadows the catalog's sensitive-egress/credential/destructive-action rules — while the schema doc string promises "additional policy instructions." Your key point stands: this makes an attempted tightening a silent loosening of everything the catalog covers, which is the worst failure direction for a security reviewer config.

Fix shape: keep two distinct inputs — managed guardian_policy_config stays a full override (that's its contract), but local [auto_review].policy should be appended to whichever base wins the managed/catalog/bundled selection, matching its documented semantics. One function change plus a doc line; a test asserting the catalog rules survive alongside a local addition would pin it. Same docs-say-safe-but-isn't family as #39085, for whoever triages both.