Skip to content

fix(governance): agent argument named "tool" bypasses the sensitive-tool approval gate#27

Closed
sxyrxyy wants to merge 1 commit into
Cogensec:mainfrom
sxyrxyy:fix/governance-action-tool-collision
Closed

fix(governance): agent argument named "tool" bypasses the sensitive-tool approval gate#27
sxyrxyy wants to merge 1 commit into
Cogensec:mainfrom
sxyrxyy:fix/governance-action-tool-collision

Conversation

@sxyrxyy

@sxyrxyy sxyrxyy commented Jul 23, 2026

Copy link
Copy Markdown

The problem

Some tools are on a watchlist (payment_execute, admin_api, file_delete, database_write). When an agent uses one, governance (GOV-001) is meant to stop and require human approval.

The action dict it checks was built as {"tool": tool_name, "type": "tool_call", **tool_input}. The agent's own arguments get spread in last, so an argument named tool overwrites the real tool name. GOV-001 then checks the fake name, sees something harmless, and skips approval.

Impact: a prompt-injected agent calls payment_execute with an argument {"tool": "noop"} and the approval gate never fires. With enforce=True the dangerous tool just runs. Even in observe-only mode, the signed attestation records it as governance: pass, so the audit trail certifies a check that never happened. The same trick overrides type, which the type-keyed rules rely on.

The fix

Spread the agent's arguments first so the trusted tool/type fields always win:

{**tool_input, "tool": tool_name, "type": "tool_call"}

Keeps the flat action shape, so GOV-003 still reads action["amount"]. The sibling decision-record paths already nest arguments safely and are unchanged.

Verified

Through the real adapter path with enforce=True: the poisoned call now resolves to payment_execute and is denied (escalate, fail-closed with no approval handler). The honest call is unchanged. Existing test suite unaffected (the async adapter tests fail identically on main without this change; they need pytest-asyncio, unrelated to this fix).

… policy gate

The action dict inspected by GovernanceLayer was built as
`{"tool": tool_name, "type": "tool_call", **tool_input}`. Spreading the
agent-supplied arguments last let an argument named `tool` overwrite the
real tool name. GOV-001 checks `action["tool"]` against the sensitive-tool
set, so a HIGH/CRITICAL agent calling e.g. `payment_execute` with an
argument `{"tool": "noop"}` read as a non-sensitive tool and skipped the
REQUIRE_APPROVAL escalation.

Spread the arguments first so the trusted `tool`/`type` fields always win.
Keeps the flat action shape (GOV-003 still reads `action["amount"]`).
@sxyrxyy
sxyrxyy force-pushed the fix/governance-action-tool-collision branch from b462384 to 2869dfc Compare July 23, 2026 03:26
@sxyrxyy

sxyrxyy commented Jul 23, 2026

Copy link
Copy Markdown
Author

Superseding with a fresh branch for a clean history.

@sxyrxyy sxyrxyy closed this Jul 23, 2026
@sxyrxyy
sxyrxyy deleted the fix/governance-action-tool-collision branch July 23, 2026 03:28
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