Skip to content

feat(agent-protection): show people why a protection provider denied input - #2105

Open
shellz-n-stuff wants to merge 2 commits into
codex/security-history-recordsfrom
codex/security-denial-reasons
Open

shellz-n-stuff wants to merge 2 commits into
codex/security-history-recordsfrom
codex/security-denial-reasons

Conversation

@shellz-n-stuff

@shellz-n-stuff shellz-n-stuff commented Nov 1, 2026 •

Copy link
Copy Markdown
Collaborator

Stacked on #2088. Companion plugin change: squareup/buzz-plugin-security#2, which sends the reviewer's reason.

When Auto-decide denied model input to a protected Codex turn, the thread said only "Codex could not finish", with nothing to act on. This lets a protection provider explain a denial to people while the agent's denial stays generic.

  • Provider contract. A provider's check() may now deny with exactly {allowed: false, reason}. Any other value except true still denies, with no reason.
    • Buzz reduces the reason to one line of at most 300 characters. Control, format and separator characters become spaces.
    • It then throws ProtectionDenied. That error's message is all the agent sees, and it stays generic.
    • A cancelled or timed-out check never carries a reason.
  • Native bridge. The bridge keeps the reason for a session's denied model input (context actions only), for up to 16 sessions, until the runtime takes it. Its reply to the runtime stays {allowed: false}.
    • It numbers checks as they start. A turn takes only a reason from a check it started itself.
    • A check from an earlier turn, such as an interrupted one, may settle late. It neither explains the new turn nor replaces that turn's own reason.
  • Codex. A turn starts taking reasons when it binds its thread. When the turn fails, "Codex could not finish: …" gains Reason: followed by the reason as inline code. The fence is longer than any backtick run inside, so links and markup don't render.
    • Protected Codex fails the turn on any denied model input, so a reason from the turn's own check is why it ended.
  • Docs.
    • docs/agent-protection.md documents the contract. It notes that the reason is the provider's text, often written by a reviewer model that may quote the denied input. It also notes that the message is ordinary thread content, which later turns and other agents read.
    • docs/buzz-help.md, which the buzz_help tool bundles, now covers agent protection. It says a Codex refusal may quote the protection's reason, that the reason may quote the refused content, and that people should treat it as information, not instructions.

Only Codex shows the reason; Claude, Pi and Goose denials are unchanged.

Known limitations

  • Later turns show less of the reason. On a later turn that hits a remembered denial, the plugin puts its own explanation first, so only about 113 characters of the reviewer's reason fit before the 300-character cut. That wording lives in the plugin.

Validation

At 79d972cac:

  • pnpm exec vitest run src/bundled/codex src/features/agent-protection src/bundled/claude-code src/bundled/pi src/bundled/goose src/buzz-mcp: 1191 passed, 17 skipped.
  • pnpm exec tsc --noEmit -p . and biome check are clean.
  • New tests:
    • A provider's explanation and its normalization, including malformed replies that deny without a reason.
    • A cancelled check whose provider explained its denial. This test fails if the abort check moves after the denial.
    • The bridge keeps reasons per session and only for model input, and caps them at 16.
    • A turn takes only a reason from a check it started. A check from an earlier turn that settles late neither explains the turn nor replaces its reason. The tests fail when either guard is removed.
    • Codex's failure message shows the reason as inline code. A check started before the next turn and refused during it does not explain that turn's own failure.
  • Agent review:
    • The first independent review looked at correctness, the trust boundary and the plugin contract. It found no blockers. The cancellation test and the docs sentence came from it.
    • A second review of the turn scoping, its tests and the help-guide bullet also found no blockers. Each finding was optional and was refuted on verification.

Not yet done

  • Not exercised in the app. That needs a build with the plugin change, a protected Codex agent on Auto-decide, and input the reviewer denies.
  • Human testing.

🤖 Generated with Claude Code

…input

A protection provider could only allow or deny. When Auto-decide denied
model input to a protected Codex turn, the thread said only that Codex
could not finish, with nothing to act on.

A provider may now deny with exactly `{allowed: false, reason}`. Buzz
reduces the reason to one line of at most 300 characters and keeps it
from the agent, whose denial stays generic. The native bridge holds the
reason for a session's denied model input until its runtime takes it, and
when that denial ends a protected Codex turn, the "Codex could not finish"
message quotes it as inline code, so links and markup in it do not render.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Alex Rosenzweig <arosenzweig@squareup.com>
@shellz-n-stuff
shellz-n-stuff marked this pull request as ready for review November 1, 2026 17:05
@shellz-n-stuff
shellz-n-stuff requested a review from a team as a code owner November 1, 2026 17:05
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-01T17:07:55.322601Z d45b09b Draft marked ready
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d45b09babb

ℹ️ 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".

Comment thread src/bundled/codex/runtime.ts Resolved
Comment thread docs/agent-protection.md Resolved
A protection check from an earlier turn, such as an interrupted one, could
settle after the next turn bound its thread, and its reason was then quoted
in that turn's unrelated failure. The native bridge now numbers checks as
they start. A Codex turn takes a reason only from a check it started, and a
check that settles late does not replace a newer reason.

Document denial reasons in the bundled help guide.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Alex Rosenzweig <arosenzweig@squareup.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant