feat: distinguish Guardian denials and soft denials#26231
Conversation
There was a problem hiding this comment.
💡 Codex Review
codex/codex-rs/core/src/session/handlers.rs
Line 928 in 4252b2d
When a user approves a soft-denied action whose command/action payload is large, this injects the serialized event.action back into model context as an unrestricted developer message. The stored action comes from the original tool request rather than the truncated Guardian prompt, so a long command can create a new >1k or even >10K-token context item; the repository's Model visible context rules require hard caps and ContextualUserFragment-backed injected fragments (AGENTS.md lines 87-90).
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| super::GuardianAssessmentOutcome::Deny => Some(parsed_payload.denial_kind.unwrap_or( | ||
| if risk_level == GuardianRiskLevel::Critical { | ||
| GuardianDenialKind::Denial | ||
| } else { | ||
| GuardianDenialKind::Soft | ||
| }, |
There was a problem hiding this comment.
Fail closed when denial kind is omitted
If the reviewer returns a terse deny payload such as {"outcome":"deny"} for a critical action, this fallback first defaults the missing risk to High and then marks the denial as Soft, making it eligible for /approve even though critical/secret-exfiltration denials are supposed to be non-overridable. Since the schema only requires outcome, missing denial_kind on a deny should either be rejected/failed closed or default to a hard denial rather than becoming user-overridable.
Useful? React with 👍 / 👎.
| if guardian_assessment_allows_session_reuse(&assessment) { | ||
| ReviewDecision::ApprovedForSession |
There was a problem hiding this comment.
Keep one-retry approvals out of the session cache
When a user approves a soft denial, the retry authorization is only represented as transcript context and the UI/injected text promise a one-time retry; if Guardian then returns a low-risk authorized allow, this branch turns it into ApprovedForSession, and the new shell/unified_exec with_cached_approval wrapper stores that decision for all future identical commands in the session. That silently expands a one-use override into a session-wide approval, so explicit-denial retries should return Approved or otherwise bypass approval-cache writes.
Useful? React with 👍 / 👎.
| #[serde(rename = "user")] | ||
| User, | ||
| #[serde(rename = "guardian_subagent", alias = "auto_review")] | ||
| #[serde(rename = "auto_review", alias = "guardian_subagent")] |
There was a problem hiding this comment.
Preserve serialized approvals reviewer compatibility
This changes app-server v2 responses that serialize ApprovalsReviewer::AutoReview from the existing "guardian_subagent" wire value to "auto_review"; inputs remain backward-compatible via the alias, but older integrations that compare or deserialize the previous response literal from config/thread settings APIs will now see a different value. The repo review rules explicitly call out app-server API breaking changes (AGENTS.md lines 92-99), so keep output compatibility or version/gate the new canonical serialization.
Useful? React with 👍 / 👎.
| return with_cached_approval( | ||
| &session.services, | ||
| "shell", | ||
| keys, |
There was a problem hiding this comment.
Re-review sandbox escapes instead of using cached approval
For strict auto-review, the orchestrator explicitly says the first Guardian approval covers only the sandboxed attempt and that retrying without the sandbox requires a fresh Guardian review; however this cache check runs before invoking Guardian, so an earlier low-risk ApprovedForSession for the same command causes the unsandboxed retry approval path to return from the cache with no fresh review. This lets a command that was only approved under sandboxed permissions escape the sandbox after a denial without evaluating the retry reason or unsandboxed risk.
Useful? React with 👍 / 👎.
Co-authored-by: Codex noreply@openai.com
4252b2d to
3bd85a2
Compare
## Why Auto Review should remain the effective approval reviewer when settings cross runtime boundaries. A config or app-server round trip must not change the reviewer identity, and delegated work must not silently fall back to user review. This requires both a stable canonical serialized value and propagation of the effective setting. `auto_review` is the canonical value across protocol and app-server output, while `guardian_subagent` remains accepted as backward-compatible input. ## What changed - serialize `ApprovalsReviewer::AutoReview` consistently as `auto_review` across core protocol and app-server v2 - continue accepting `guardian_subagent` when reading existing config or client requests - carry the active turn's approval reviewer into spawned agents - update config/debug expectations and add delegated-task regression coverage ## Scope This does not change Guardian policy or remove compatibility with existing `guardian_subagent` inputs. It preserves the selected reviewer across serialization, config reloads, app-server settings, and delegated task setup. Related Guardian changes are split independently: - #26231 adds denials and soft denials - #26334 retries transient reviewer failures - #26333 reuses narrowly scoped low-risk approvals - #26232 adds TUI denial recovery ## Validation - `just test -p codex-app-server-protocol` (224 passed) - regression coverage for delegated task reviewer propagation - serialization coverage for canonical `auto_review` output and legacy `guardian_subagent` input --------- Co-authored-by: saud-oai <saud@openai.com>
## Why Auto Review should remain the effective approval reviewer when settings cross runtime boundaries. A config or app-server round trip must not change the reviewer identity, and delegated work must not silently fall back to user review. This requires both a stable canonical serialized value and propagation of the effective setting. `auto_review` is the canonical value across protocol and app-server output, while `guardian_subagent` remains accepted as backward-compatible input. ## What changed - serialize `ApprovalsReviewer::AutoReview` consistently as `auto_review` across core protocol and app-server v2 - continue accepting `guardian_subagent` when reading existing config or client requests - carry the active turn's approval reviewer into spawned agents - update config/debug expectations and add delegated-task regression coverage ## Scope This does not change Guardian policy or remove compatibility with existing `guardian_subagent` inputs. It preserves the selected reviewer across serialization, config reloads, app-server settings, and delegated task setup. Related Guardian changes are split independently: - openai#26231 adds denials and soft denials - openai#26334 retries transient reviewer failures - openai#26333 reuses narrowly scoped low-risk approvals - openai#26232 adds TUI denial recovery ## Validation - `just test -p codex-app-server-protocol` (224 passed) - regression coverage for delegated task reviewer propagation - serialization coverage for canonical `auto_review` output and legacy `guardian_subagent` input --------- Co-authored-by: saud-oai <saud@openai.com>
## Why Auto Review should remain the effective approval reviewer when settings cross runtime boundaries. A config or app-server round trip must not change the reviewer identity, and delegated work must not silently fall back to user review. This requires both a stable canonical serialized value and propagation of the effective setting. `auto_review` is the canonical value across protocol and app-server output, while `guardian_subagent` remains accepted as backward-compatible input. ## What changed - serialize `ApprovalsReviewer::AutoReview` consistently as `auto_review` across core protocol and app-server v2 - continue accepting `guardian_subagent` when reading existing config or client requests - carry the active turn's approval reviewer into spawned agents - update config/debug expectations and add delegated-task regression coverage ## Scope This does not change Guardian policy or remove compatibility with existing `guardian_subagent` inputs. It preserves the selected reviewer across serialization, config reloads, app-server settings, and delegated task setup. Related Guardian changes are split independently: - openai#26231 adds denials and soft denials - openai#26334 retries transient reviewer failures - openai#26333 reuses narrowly scoped low-risk approvals - openai#26232 adds TUI denial recovery ## Validation - `just test -p codex-app-server-protocol` (224 passed) - regression coverage for delegated task reviewer propagation - serialization coverage for canonical `auto_review` output and legacy `guardian_subagent` input --------- Co-authored-by: saud-oai <saud@openai.com>
## Why Auto Review should remain the effective approval reviewer when settings cross runtime boundaries. A config or app-server round trip must not change the reviewer identity, and delegated work must not silently fall back to user review. This requires both a stable canonical serialized value and propagation of the effective setting. `auto_review` is the canonical value across protocol and app-server output, while `guardian_subagent` remains accepted as backward-compatible input. ## What changed - serialize `ApprovalsReviewer::AutoReview` consistently as `auto_review` across core protocol and app-server v2 - continue accepting `guardian_subagent` when reading existing config or client requests - carry the active turn's approval reviewer into spawned agents - update config/debug expectations and add delegated-task regression coverage ## Scope This does not change Guardian policy or remove compatibility with existing `guardian_subagent` inputs. It preserves the selected reviewer across serialization, config reloads, app-server settings, and delegated task setup. Related Guardian changes are split independently: - openai#26231 adds denials and soft denials - openai#26334 retries transient reviewer failures - openai#26333 reuses narrowly scoped low-risk approvals - openai#26232 adds TUI denial recovery ## Validation - `just test -p codex-app-server-protocol` (224 passed) - regression coverage for delegated task reviewer propagation - serialization coverage for canonical `auto_review` output and legacy `guardian_subagent` input --------- Co-authored-by: saud-oai <saud@openai.com>
## Why Auto Review should remain the effective approval reviewer when settings cross runtime boundaries. A config or app-server round trip must not change the reviewer identity, and delegated work must not silently fall back to user review. This requires both a stable canonical serialized value and propagation of the effective setting. `auto_review` is the canonical value across protocol and app-server output, while `guardian_subagent` remains accepted as backward-compatible input. ## What changed - serialize `ApprovalsReviewer::AutoReview` consistently as `auto_review` across core protocol and app-server v2 - continue accepting `guardian_subagent` when reading existing config or client requests - carry the active turn's approval reviewer into spawned agents - update config/debug expectations and add delegated-task regression coverage ## Scope This does not change Guardian policy or remove compatibility with existing `guardian_subagent` inputs. It preserves the selected reviewer across serialization, config reloads, app-server settings, and delegated task setup. Related Guardian changes are split independently: - openai#26231 adds denials and soft denials - openai#26334 retries transient reviewer failures - openai#26333 reuses narrowly scoped low-risk approvals - openai#26232 adds TUI denial recovery ## Validation - `just test -p codex-app-server-protocol` (224 passed) - regression coverage for delegated task reviewer propagation - serialization coverage for canonical `auto_review` output and legacy `guardian_subagent` input --------- Co-authored-by: saud-oai <saud@openai.com>
## Why Auto Review should remain the effective approval reviewer when settings cross runtime boundaries. A config or app-server round trip must not change the reviewer identity, and delegated work must not silently fall back to user review. This requires both a stable canonical serialized value and propagation of the effective setting. `auto_review` is the canonical value across protocol and app-server output, while `guardian_subagent` remains accepted as backward-compatible input. ## What changed - serialize `ApprovalsReviewer::AutoReview` consistently as `auto_review` across core protocol and app-server v2 - continue accepting `guardian_subagent` when reading existing config or client requests - carry the active turn's approval reviewer into spawned agents - update config/debug expectations and add delegated-task regression coverage ## Scope This does not change Guardian policy or remove compatibility with existing `guardian_subagent` inputs. It preserves the selected reviewer across serialization, config reloads, app-server settings, and delegated task setup. Related Guardian changes are split independently: - openai#26231 adds denials and soft denials - openai#26334 retries transient reviewer failures - openai#26333 reuses narrowly scoped low-risk approvals - openai#26232 adds TUI denial recovery ## Validation - `just test -p codex-app-server-protocol` (224 passed) - regression coverage for delegated task reviewer propagation - serialization coverage for canonical `auto_review` output and legacy `guardian_subagent` input --------- Co-authored-by: saud-oai <saud@openai.com>
## Why Auto Review should remain the effective approval reviewer when settings cross runtime boundaries. A config or app-server round trip must not change the reviewer identity, and delegated work must not silently fall back to user review. This requires both a stable canonical serialized value and propagation of the effective setting. `auto_review` is the canonical value across protocol and app-server output, while `guardian_subagent` remains accepted as backward-compatible input. ## What changed - serialize `ApprovalsReviewer::AutoReview` consistently as `auto_review` across core protocol and app-server v2 - continue accepting `guardian_subagent` when reading existing config or client requests - carry the active turn's approval reviewer into spawned agents - update config/debug expectations and add delegated-task regression coverage ## Scope This does not change Guardian policy or remove compatibility with existing `guardian_subagent` inputs. It preserves the selected reviewer across serialization, config reloads, app-server settings, and delegated task setup. Related Guardian changes are split independently: - openai#26231 adds denials and soft denials - openai#26334 retries transient reviewer failures - openai#26333 reuses narrowly scoped low-risk approvals - openai#26232 adds TUI denial recovery ## Validation - `just test -p codex-app-server-protocol` (224 passed) - regression coverage for delegated task reviewer propagation - serialization coverage for canonical `auto_review` output and legacy `guardian_subagent` input --------- Co-authored-by: saud-oai <saud@openai.com>
## Why Auto Review should remain the effective approval reviewer when settings cross runtime boundaries. A config or app-server round trip must not change the reviewer identity, and delegated work must not silently fall back to user review. This requires both a stable canonical serialized value and propagation of the effective setting. `auto_review` is the canonical value across protocol and app-server output, while `guardian_subagent` remains accepted as backward-compatible input. ## What changed - serialize `ApprovalsReviewer::AutoReview` consistently as `auto_review` across core protocol and app-server v2 - continue accepting `guardian_subagent` when reading existing config or client requests - carry the active turn's approval reviewer into spawned agents - update config/debug expectations and add delegated-task regression coverage ## Scope This does not change Guardian policy or remove compatibility with existing `guardian_subagent` inputs. It preserves the selected reviewer across serialization, config reloads, app-server settings, and delegated task setup. Related Guardian changes are split independently: - openai#26231 adds denials and soft denials - openai#26334 retries transient reviewer failures - openai#26333 reuses narrowly scoped low-risk approvals - openai#26232 adds TUI denial recovery ## Validation - `just test -p codex-app-server-protocol` (224 passed) - regression coverage for delegated task reviewer propagation - serialization coverage for canonical `auto_review` output and legacy `guardian_subagent` input --------- Co-authored-by: saud-oai <saud@openai.com>
## Why Auto Review should remain the effective approval reviewer when settings cross runtime boundaries. A config or app-server round trip must not change the reviewer identity, and delegated work must not silently fall back to user review. This requires both a stable canonical serialized value and propagation of the effective setting. `auto_review` is the canonical value across protocol and app-server output, while `guardian_subagent` remains accepted as backward-compatible input. ## What changed - serialize `ApprovalsReviewer::AutoReview` consistently as `auto_review` across core protocol and app-server v2 - continue accepting `guardian_subagent` when reading existing config or client requests - carry the active turn's approval reviewer into spawned agents - update config/debug expectations and add delegated-task regression coverage ## Scope This does not change Guardian policy or remove compatibility with existing `guardian_subagent` inputs. It preserves the selected reviewer across serialization, config reloads, app-server settings, and delegated task setup. Related Guardian changes are split independently: - openai#26231 adds denials and soft denials - openai#26334 retries transient reviewer failures - openai#26333 reuses narrowly scoped low-risk approvals - openai#26232 adds TUI denial recovery ## Validation - `just test -p codex-app-server-protocol` (224 passed) - regression coverage for delegated task reviewer propagation - serialization coverage for canonical `auto_review` output and legacy `guardian_subagent` input --------- Co-authored-by: saud-oai <saud@openai.com>
## Why Auto Review should remain the effective approval reviewer when settings cross runtime boundaries. A config or app-server round trip must not change the reviewer identity, and delegated work must not silently fall back to user review. This requires both a stable canonical serialized value and propagation of the effective setting. `auto_review` is the canonical value across protocol and app-server output, while `guardian_subagent` remains accepted as backward-compatible input. ## What changed - serialize `ApprovalsReviewer::AutoReview` consistently as `auto_review` across core protocol and app-server v2 - continue accepting `guardian_subagent` when reading existing config or client requests - carry the active turn's approval reviewer into spawned agents - update config/debug expectations and add delegated-task regression coverage ## Scope This does not change Guardian policy or remove compatibility with existing `guardian_subagent` inputs. It preserves the selected reviewer across serialization, config reloads, app-server settings, and delegated task setup. Related Guardian changes are split independently: - openai#26231 adds denials and soft denials - openai#26334 retries transient reviewer failures - openai#26333 reuses narrowly scoped low-risk approvals - openai#26232 adds TUI denial recovery ## Validation - `just test -p codex-app-server-protocol` (224 passed) - regression coverage for delegated task reviewer propagation - serialization coverage for canonical `auto_review` output and legacy `guardian_subagent` input --------- Co-authored-by: saud-oai <saud@openai.com>
Why
Guardian currently emits one denied outcome for two materially different decisions:
Without a stable distinction, clients must either offer recovery too broadly, which can bypass policy, or never offer recovery, which strands legitimate actions that only need explicit confirmation.
The same boundary must hold for delegated Tasks and connector or MCP calls. Delegation carries the user's existing intent, but it does not authorize new data export, unrelated connector access, destructive side effects, or persistent policy changes.
Behavior
softdenialAn absent
denialKindmeans the review did not produce a policy-denial classification, such as an approval, an in-progress or aborted review, or an operational review failure.What changed
GuardianDenialKindwithsoftanddenialwire values.denialfor critical risk andsoftotherwise.denialKindthrough core events, rollout and thread history, app-server v2 notifications, generated JSON schemas, and TypeScript bindings.Scope
This PR provides the policy and protocol foundation. The surrounding product work remains independently reviewable:
Validation
Coverage verifies:
denial