fix(approvals): fail closed plugin tool gates#103932
Conversation
|
Codex review: needs maintainer review before merge. Reviewed July 11, 2026, 10:31 PM ET / July 12, 2026, 02:31 UTC. Summary PR surface: Source +43, Tests +409, Docs +8, Generated 0. Total +460 across 16 files. Reproducibility: yes. at source level: current main honors timeout allow, leaves sibling waiter results unbound to their originating approval ID, and returns node-policy allow-once without consuming it. Review metrics: 1 noteworthy metric.
Root-cause cluster Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Risk before merge
Maintainer options:
Next step before merge
Maintainer decision needed
Security Review detailsBest possible solution: Keep the strict fail-closed implementation, retain the legacy field only for one documented deprecation train, and land after explicit owner acceptance and green exact-stack gates. Do we have a high-confidence way to reproduce the issue? Yes at source level: current main honors timeout allow, leaves sibling waiter results unbound to their originating approval ID, and returns node-policy allow-once without consuming it. Is this the best way to solve the issue? Yes technically: the patch enforces strict verdict parsing at final gate boundaries and consumes one-shot authority where it is handed to plugin policy code; the release cutover remains a human compatibility and security choice. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 01fc1ed02a50. Label changesLabel justifications:
Evidence reviewedPR surface: Source +43, Tests +409, Docs +8, Generated 0. Total +460 across 16 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
Review history (4 earlier review cycles)
|
0077d9e to
1ec7715
Compare
5acd4de to
7e84b46
Compare
1ec7715 to
593e3ab
Compare
7e84b46 to
1053f33
Compare
|
Land-ready update: rebased through the reconciled stack; slice review clean ("consistently fails closed for missing, malformed, mismatched, timed-out, and disallowed plugin approval decisions", 0.95). Additions since the last head: Validation: relay-binding, embedded-mode, and gateway matrix suites green on Testbox ( |
593e3ab to
be22413
Compare
1053f33 to
b9780e2
Compare
9e904ef to
678f238
Compare
Squash-rebased #103932 segment onto the ancestor-propagation tip on current main. Plugin node.invoke approvals claim a one-shot allow-once decision before handing execution authority to the policy, so observation or retry cannot replay a consumed approval; sibling tool gates bind approval ids to their originating reviewer identity. (cherry picked from commit 122df0d75281f572c012b6484ed5daf085d1d577) (cherry picked from commit f23d8ac240a8dcf2a42c4daaae962263975a0ae8) (cherry picked from commit 8632fb6436a224dac7a9a9ef0216884530de2c24) (cherry picked from commit d9fe2dd5c53665e5732f5f3da5ed1907a686ade8) (cherry picked from commit bb139f2c8aa36ddad70413e1ef79ff491a6f2ecd) (cherry picked from commit 9b3e056b68ea515c7d4baef269289b6670570480) (cherry picked from commit b11e66b59a7af1f3b08712de06864aed64d349e4) (cherry picked from commit a12916ee592d03dfa35e86a2aaede4476a5d4e5d) (cherry picked from commit d699de840fc8346892b9ae244fe3c3e8c4413032) (cherry picked from commit 9b7e5a9) (cherry picked from commit 4b507593f1b2f10b4496984c84a2803b853e5c5e) (cherry picked from commit 56408a186733c4eeb5aa76bd5907cde7b0cd3d5b) (cherry picked from commit a4051d6d8353d39d6fa0b5d69c36e06c3cd3e796) (cherry picked from commit 7682980)
7682980 to
a8b493f
Compare
678f238 to
72842e5
Compare
Squash-rebased #103932 segment onto the ancestor-propagation tip on current main. Plugin node.invoke approvals claim a one-shot allow-once decision before handing execution authority to the policy, so observation or retry cannot replay a consumed approval; sibling tool gates bind approval ids to their originating reviewer identity. (cherry picked from commit 122df0d75281f572c012b6484ed5daf085d1d577) (cherry picked from commit f23d8ac240a8dcf2a42c4daaae962263975a0ae8) (cherry picked from commit 8632fb6436a224dac7a9a9ef0216884530de2c24) (cherry picked from commit d9fe2dd5c53665e5732f5f3da5ed1907a686ade8) (cherry picked from commit bb139f2c8aa36ddad70413e1ef79ff491a6f2ecd) (cherry picked from commit 9b3e056b68ea515c7d4baef269289b6670570480) (cherry picked from commit b11e66b59a7af1f3b08712de06864aed64d349e4) (cherry picked from commit a12916ee592d03dfa35e86a2aaede4476a5d4e5d) (cherry picked from commit d699de840fc8346892b9ae244fe3c3e8c4413032) (cherry picked from commit 9b7e5a9) (cherry picked from commit 4b507593f1b2f10b4496984c84a2803b853e5c5e) (cherry picked from commit 56408a186733c4eeb5aa76bd5907cde7b0cd3d5b) (cherry picked from commit a4051d6d8353d39d6fa0b5d69c36e06c3cd3e796) (cherry picked from commit 7682980) (cherry picked from commit a8b493f)
a8b493f to
aceb990
Compare
Squash-rebased #103932 segment onto the ancestor-propagation tip on current main. Plugin node.invoke approvals claim a one-shot allow-once decision before handing execution authority to the policy, so observation or retry cannot replay a consumed approval; sibling tool gates bind approval ids to their originating reviewer identity. (cherry picked from commit 122df0d75281f572c012b6484ed5daf085d1d577) (cherry picked from commit f23d8ac240a8dcf2a42c4daaae962263975a0ae8) (cherry picked from commit 8632fb6436a224dac7a9a9ef0216884530de2c24) (cherry picked from commit d9fe2dd5c53665e5732f5f3da5ed1907a686ade8) (cherry picked from commit bb139f2c8aa36ddad70413e1ef79ff491a6f2ecd) (cherry picked from commit 9b3e056b68ea515c7d4baef269289b6670570480) (cherry picked from commit b11e66b59a7af1f3b08712de06864aed64d349e4) (cherry picked from commit a12916ee592d03dfa35e86a2aaede4476a5d4e5d) (cherry picked from commit d699de840fc8346892b9ae244fe3c3e8c4413032) (cherry picked from commit 9b7e5a9) (cherry picked from commit 4b507593f1b2f10b4496984c84a2803b853e5c5e) (cherry picked from commit 56408a186733c4eeb5aa76bd5907cde7b0cd3d5b) (cherry picked from commit a4051d6d8353d39d6fa0b5d69c36e06c3cd3e796) (cherry picked from commit 7682980) (cherry picked from commit a8b493f) (cherry picked from commit aceb990)
72842e5 to
be74c25
Compare
aceb990 to
a29b0e7
Compare
Squash-rebased #103932 segment onto the ancestor-propagation tip on current main. Plugin node.invoke approvals claim a one-shot allow-once decision before handing execution authority to the policy, so observation or retry cannot replay a consumed approval; sibling tool gates bind approval ids to their originating reviewer identity. (cherry picked from commit 122df0d75281f572c012b6484ed5daf085d1d577) (cherry picked from commit f23d8ac240a8dcf2a42c4daaae962263975a0ae8) (cherry picked from commit 8632fb6436a224dac7a9a9ef0216884530de2c24) (cherry picked from commit d9fe2dd5c53665e5732f5f3da5ed1907a686ade8) (cherry picked from commit bb139f2c8aa36ddad70413e1ef79ff491a6f2ecd) (cherry picked from commit 9b3e056b68ea515c7d4baef269289b6670570480) (cherry picked from commit b11e66b59a7af1f3b08712de06864aed64d349e4) (cherry picked from commit a12916ee592d03dfa35e86a2aaede4476a5d4e5d) (cherry picked from commit d699de840fc8346892b9ae244fe3c3e8c4413032) (cherry picked from commit 9b7e5a9) (cherry picked from commit 4b507593f1b2f10b4496984c84a2803b853e5c5e) (cherry picked from commit 56408a186733c4eeb5aa76bd5907cde7b0cd3d5b) (cherry picked from commit a4051d6d8353d39d6fa0b5d69c36e06c3cd3e796) (cherry picked from commit 7682980) (cherry picked from commit a8b493f) (cherry picked from commit aceb990) (cherry picked from commit a29b0e7)
be74c25 to
93ba8c4
Compare
a29b0e7 to
2e3be08
Compare
2e3be08
into
codex/durable-approval-registry
Squash-rebased openclaw#103932 segment onto the ancestor-propagation tip on current main. Plugin node.invoke approvals claim a one-shot allow-once decision before handing execution authority to the policy, so observation or retry cannot replay a consumed approval; sibling tool gates bind approval ids to their originating reviewer identity. (cherry picked from commit 122df0d75281f572c012b6484ed5daf085d1d577) (cherry picked from commit f23d8ac240a8dcf2a42c4daaae962263975a0ae8) (cherry picked from commit 8632fb6436a224dac7a9a9ef0216884530de2c24) (cherry picked from commit d9fe2dd5c53665e5732f5f3da5ed1907a686ade8) (cherry picked from commit bb139f2c8aa36ddad70413e1ef79ff491a6f2ecd) (cherry picked from commit 9b3e056b68ea515c7d4baef269289b6670570480) (cherry picked from commit b11e66b59a7af1f3b08712de06864aed64d349e4) (cherry picked from commit a12916ee592d03dfa35e86a2aaede4476a5d4e5d) (cherry picked from commit d699de840fc8346892b9ae244fe3c3e8c4413032) (cherry picked from commit 9b7e5a9) (cherry picked from commit 4b507593f1b2f10b4496984c84a2803b853e5c5e) (cherry picked from commit 56408a186733c4eeb5aa76bd5907cde7b0cd3d5b) (cherry picked from commit a4051d6d8353d39d6fa0b5d69c36e06c3cd3e796) (cherry picked from commit 7682980) (cherry picked from commit a8b493f) (cherry picked from commit aceb990) (cherry picked from commit a29b0e7) (cherry picked from commit 2e3be08)
… gates Squash of the remaining stack (#103912 native clients, #103921 ancestor propagation, #103932 fail-closed gates — merged into this branch) plus the main-gate repairs (hermetic model discovery via the provider-runtime boundary, usage-cost settle-budget contract assertion, control-ui raw-copy baseline, docs map, native i18n inventory, session.approval protocol coverage allowlist, plugin-sdk DTS budget for approval wire types, Android createdAtMs capture for canonical approval readback).
… gates Squash of the remaining stack (#103912 native clients, #103921 ancestor propagation, #103932 fail-closed gates — merged into this branch) plus the main-gate repairs (hermetic model discovery via the provider-runtime boundary, usage-cost settle-budget contract assertion, control-ui raw-copy baseline, docs map, native i18n inventory, session.approval protocol coverage allowlist, plugin-sdk DTS budget for approval wire types, Android createdAtMs capture for canonical approval readback).
… gates Squash of the remaining stack (#103912 native clients, #103921 ancestor propagation, #103932 fail-closed gates — merged into this branch) plus the main-gate repairs (hermetic model discovery via the provider-runtime boundary, usage-cost settle-budget contract assertion, control-ui raw-copy baseline, docs map, native i18n inventory, session.approval protocol coverage allowlist, plugin-sdk DTS budget for approval wire types, Android createdAtMs capture for canonical approval readback).
Squash-rebased #103932 segment onto the ancestor-propagation tip on current main. Plugin node.invoke approvals claim a one-shot allow-once decision before handing execution authority to the policy, so observation or retry cannot replay a consumed approval; sibling tool gates bind approval ids to their originating reviewer identity. (cherry picked from commit 122df0d75281f572c012b6484ed5daf085d1d577) (cherry picked from commit f23d8ac240a8dcf2a42c4daaae962263975a0ae8) (cherry picked from commit 8632fb6436a224dac7a9a9ef0216884530de2c24) (cherry picked from commit d9fe2dd5c53665e5732f5f3da5ed1907a686ade8) (cherry picked from commit bb139f2c8aa36ddad70413e1ef79ff491a6f2ecd) (cherry picked from commit 9b3e056b68ea515c7d4baef269289b6670570480) (cherry picked from commit b11e66b59a7af1f3b08712de06864aed64d349e4) (cherry picked from commit a12916ee592d03dfa35e86a2aaede4476a5d4e5d) (cherry picked from commit d699de840fc8346892b9ae244fe3c3e8c4413032) (cherry picked from commit 9b7e5a9) (cherry picked from commit 4b507593f1b2f10b4496984c84a2803b853e5c5e) (cherry picked from commit 56408a186733c4eeb5aa76bd5907cde7b0cd3d5b) (cherry picked from commit a4051d6d8353d39d6fa0b5d69c36e06c3cd3e796) (cherry picked from commit 7682980) (cherry picked from commit a8b493f) (cherry picked from commit aceb990) (cherry picked from commit a29b0e7) (cherry picked from commit 2e3be08) (cherry picked from commit c9ae3d7)
Squash-rebased #103932 segment onto the ancestor-propagation tip on current main. Plugin node.invoke approvals claim a one-shot allow-once decision before handing execution authority to the policy, so observation or retry cannot replay a consumed approval; sibling tool gates bind approval ids to their originating reviewer identity. (cherry picked from commit 122df0d75281f572c012b6484ed5daf085d1d577) (cherry picked from commit f23d8ac240a8dcf2a42c4daaae962263975a0ae8) (cherry picked from commit 8632fb6436a224dac7a9a9ef0216884530de2c24) (cherry picked from commit d9fe2dd5c53665e5732f5f3da5ed1907a686ade8) (cherry picked from commit bb139f2c8aa36ddad70413e1ef79ff491a6f2ecd) (cherry picked from commit 9b3e056b68ea515c7d4baef269289b6670570480) (cherry picked from commit b11e66b59a7af1f3b08712de06864aed64d349e4) (cherry picked from commit a12916ee592d03dfa35e86a2aaede4476a5d4e5d) (cherry picked from commit d699de840fc8346892b9ae244fe3c3e8c4413032) (cherry picked from commit 9b7e5a9) (cherry picked from commit 4b507593f1b2f10b4496984c84a2803b853e5c5e) (cherry picked from commit 56408a186733c4eeb5aa76bd5907cde7b0cd3d5b) (cherry picked from commit a4051d6d8353d39d6fa0b5d69c36e06c3cd3e796) (cherry picked from commit 7682980) (cherry picked from commit a8b493f) (cherry picked from commit aceb990) (cherry picked from commit a29b0e7) (cherry picked from commit 2e3be08) (cherry picked from commit c9ae3d7)
Squash-rebased #103932 segment onto the ancestor-propagation tip on current main. Plugin node.invoke approvals claim a one-shot allow-once decision before handing execution authority to the policy, so observation or retry cannot replay a consumed approval; sibling tool gates bind approval ids to their originating reviewer identity. (cherry picked from commit 122df0d75281f572c012b6484ed5daf085d1d577) (cherry picked from commit f23d8ac240a8dcf2a42c4daaae962263975a0ae8) (cherry picked from commit 8632fb6436a224dac7a9a9ef0216884530de2c24) (cherry picked from commit d9fe2dd5c53665e5732f5f3da5ed1907a686ade8) (cherry picked from commit bb139f2c8aa36ddad70413e1ef79ff491a6f2ecd) (cherry picked from commit 9b3e056b68ea515c7d4baef269289b6670570480) (cherry picked from commit b11e66b59a7af1f3b08712de06864aed64d349e4) (cherry picked from commit a12916ee592d03dfa35e86a2aaede4476a5d4e5d) (cherry picked from commit d699de840fc8346892b9ae244fe3c3e8c4413032) (cherry picked from commit 9b7e5a9) (cherry picked from commit 4b507593f1b2f10b4496984c84a2803b853e5c5e) (cherry picked from commit 56408a186733c4eeb5aa76bd5907cde7b0cd3d5b) (cherry picked from commit a4051d6d8353d39d6fa0b5d69c36e06c3cd3e796) (cherry picked from commit 7682980) (cherry picked from commit a8b493f) (cherry picked from commit aceb990) (cherry picked from commit a29b0e7) (cherry picked from commit 2e3be08) (cherry picked from commit c9ae3d7)
Squash-rebased #103932 segment onto the ancestor-propagation tip on current main. Plugin node.invoke approvals claim a one-shot allow-once decision before handing execution authority to the policy, so observation or retry cannot replay a consumed approval; sibling tool gates bind approval ids to their originating reviewer identity. (cherry picked from commit 122df0d75281f572c012b6484ed5daf085d1d577) (cherry picked from commit f23d8ac240a8dcf2a42c4daaae962263975a0ae8) (cherry picked from commit 8632fb6436a224dac7a9a9ef0216884530de2c24) (cherry picked from commit d9fe2dd5c53665e5732f5f3da5ed1907a686ade8) (cherry picked from commit bb139f2c8aa36ddad70413e1ef79ff491a6f2ecd) (cherry picked from commit 9b3e056b68ea515c7d4baef269289b6670570480) (cherry picked from commit b11e66b59a7af1f3b08712de06864aed64d349e4) (cherry picked from commit a12916ee592d03dfa35e86a2aaede4476a5d4e5d) (cherry picked from commit d699de840fc8346892b9ae244fe3c3e8c4413032) (cherry picked from commit 9b7e5a9) (cherry picked from commit 4b507593f1b2f10b4496984c84a2803b853e5c5e) (cherry picked from commit 56408a186733c4eeb5aa76bd5907cde7b0cd3d5b) (cherry picked from commit a4051d6d8353d39d6fa0b5d69c36e06c3cd3e796) (cherry picked from commit 7682980) (cherry picked from commit a8b493f) (cherry picked from commit aceb990) (cherry picked from commit a29b0e7) (cherry picked from commit 2e3be08) (cherry picked from commit c9ae3d7)
Squash-rebased #103932 segment onto the ancestor-propagation tip on current main. Plugin node.invoke approvals claim a one-shot allow-once decision before handing execution authority to the policy, so observation or retry cannot replay a consumed approval; sibling tool gates bind approval ids to their originating reviewer identity. (cherry picked from commit 122df0d75281f572c012b6484ed5daf085d1d577) (cherry picked from commit f23d8ac240a8dcf2a42c4daaae962263975a0ae8) (cherry picked from commit 8632fb6436a224dac7a9a9ef0216884530de2c24) (cherry picked from commit d9fe2dd5c53665e5732f5f3da5ed1907a686ade8) (cherry picked from commit bb139f2c8aa36ddad70413e1ef79ff491a6f2ecd) (cherry picked from commit 9b3e056b68ea515c7d4baef269289b6670570480) (cherry picked from commit b11e66b59a7af1f3b08712de06864aed64d349e4) (cherry picked from commit a12916ee592d03dfa35e86a2aaede4476a5d4e5d) (cherry picked from commit d699de840fc8346892b9ae244fe3c3e8c4413032) (cherry picked from commit 9b7e5a9) (cherry picked from commit 4b507593f1b2f10b4496984c84a2803b853e5c5e) (cherry picked from commit 56408a186733c4eeb5aa76bd5907cde7b0cd3d5b) (cherry picked from commit a4051d6d8353d39d6fa0b5d69c36e06c3cd3e796) (cherry picked from commit 7682980) (cherry picked from commit a8b493f) (cherry picked from commit aceb990) (cherry picked from commit a29b0e7) (cherry picked from commit 2e3be08) (cherry picked from commit c9ae3d7)
Related to #103505.
Stacked on #103921; review only the final commit in this PR.
What Problem This Solves
Plugin tool gates still had three ways to hand out execution authority without a valid, bound approval:
timeoutBehavior: "allow"option could execute after a timeout or unavailable reviewer;allow-oncewithout atomically consuming that one-shot authority.The file-transfer plugin also treated approval results too loosely at its final dispatch boundary.
Why This Change Was Made
The durable approval lifecycle needs a strict final invariant: only an exact
allow-onceorallow-alwaysverdict, permitted by the request and bound to the parked approval ID, may release a tool gate.This change therefore:
timeoutBehavioras a deprecated plugin API field but ignores it at runtime;plugin.approval.waitDecisionresults to the requested approval ID;allow-oncebefore returning execution authority;The exec-host
askFallbackcutover is intentionally not included. PR #103515 is concurrently refactoring that authorization transaction boundary; the strict exec delta should follow after that work lands rather than duplicate or conflict with it.User Impact
Unanswered, unavailable, malformed, stale, or mismatched plugin approvals no longer execute tools. Existing plugins may continue sending the legacy
timeoutBehaviorfield, but setting it toallowno longer grants authority after timeout. Explicit permitted approvals continue to work, and file-transferallow-alwayspersistence is unchanged.Evidence
tbx_01kx6r6jmg877rbq9wmyhmdfn7:corepack pnpm test src/agents/agent-tools.before-tool-call.e2e.test.ts src/agents/agent-tools.before-tool-call.embedded-mode.test.ts src/gateway/node-invoke-plugin-policy.test.ts extensions/file-transfer/src/shared/node-invoke-policy.test.tscorepack pnpm tsgo:corecorepack pnpm tsgo:test:srccorepack pnpm docs:check-mdx— 705 files passed.git diff --checkpassed.codex/approval-ancestor-propagation: no actionable findings, confidence 0.86.No screenshots or transcript logs attached.