Skip to content

Python: bind policy-enforcement approvals to a single tool invocation - #6966

Merged
Roger Barreto (rogerbarreto) merged 5 commits into
microsoft:mainfrom
rogerbarreto:rogerbarreto/approval-callid-consistency-118671
Jul 9, 2026
Merged

Python: bind policy-enforcement approvals to a single tool invocation#6966
Roger Barreto (rogerbarreto) merged 5 commits into
microsoft:mainfrom
rogerbarreto:rogerbarreto/approval-callid-consistency-118671

Conversation

@rogerbarreto

Copy link
Copy Markdown
Member

Motivation & Context

Hardens the FIDES PolicyEnforcementFunctionMiddleware approval flow (approval_on_violation=True). A granted approval was tracked only by call_id in a set that was never cleared, so a reused call_id could satisfy a later or different policy-violating call, and an approved response was accepted without checking its own id / embedded function_call.

Description & Review Guide

  • Major changes: an approval now binds to the exact invocation shown for review — call_id + function name + arguments + security label + session — the response id and embedded function_call are validated, and the approval is consumed on first use. Any reused call_id, different function, changed arguments, escalated label, different session, or mismatched response requires a fresh approval.
  • Impact: Python agent-framework-core only; behaviour-preserving for legitimate approve→execute flows. No .NET change (the concept is Python-only; the hosted .NET approval path reconstructs calls from server-recorded state and is not affected).
  • Focus: PolicyEnforcementFunctionMiddleware binding/consume-once logic and the added regression tests.

Related Issue

N/A

Contribution Checklist

  • The code builds clean without any errors or warnings
  • All unit tests pass, and I have added new tests where possible
  • The PR follows the Contribution Guidelines
  • This PR is linked to an issue and there is no other open PR for this issue (see Related Issue above).
  • This is not a breaking change.

PolicyEnforcementFunctionMiddleware retained approved call_ids in a set
that was never cleared, so a reused call_id could re-authorize a later
or different tool call without a fresh approval. It also accepted an
approved response as long as the invocation metadata carried a pending
call_id, without checking the response id or embedded function_call.

Bind each approval to the exact invocation shown for review: call_id,
function name, arguments, the security label (integrity/confidentiality),
and the session. Validate that the approval response itself names the
pending request (its id and embedded function_call), and consume the
approval on first use. A reused call_id, a different function, changed
arguments, an escalated label, a different session, or a mismatched
approved response now all require a fresh approval. Adds regression tests
covering each of those cases plus legitimate re-approval.
Copilot AI review requested due to automatic review settings July 7, 2026 14:50
@giles17 Giles Odigwe (giles17) added the python Usage: [Issues, PRs], Target: Python label Jul 7, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Hardens the Python FIDES policy-enforcement approval flow so a granted approval is bound to a single, exact tool invocation and cannot be replayed to authorize a different call.

Changes:

  • Replace call-id-only tracking with a per-call_id pending-approval record that binds function name+args, context label, and session, and consume the approval on first successful use.
  • Validate the approval response body (embedded function_call) against the pending request before allowing execution.
  • Add regression tests covering replay attempts across repeated calls, different functions, changed arguments, escalated labels, and different sessions.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
python/packages/core/agent_framework/security.py Implements bound pending-approval records, response validation, and consume-once behavior for policy-violation approvals.
python/packages/core/tests/test_security.py Adds regression tests exercising approval binding, replay prevention, and session/label/argument constraints.

Comment thread python/packages/core/agent_framework/security.py Outdated
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Automated Code Review

Reviewers: 5 | Confidence: 87% | Result: All clear

Reviewed: Correctness, Security Reliability, Test Coverage, Failure Modes, Design Approach


Automated review by rogerbarreto's agents

Make the policy-enforcement approval-response check reject a response
that omits its id or embedded function_call.call_id: both must now be
present and equal to the pending call_id, closing a None-identifier
bypass. Adds a regression test.
Comment thread python/packages/core/agent_framework/security.py Outdated
PolicyEnforcementFunctionMiddleware computed the approval decision once
and reused it across the integrity and confidentiality checks, so a call
that violated both policies produced an approval request describing only
the untrusted-context violation and then silently waved the undisclosed
confidentiality violation on replay.

Detect every applicable violation up front and surface them together in a
single approval request, so a granted approval waves only what it
disclosed. The binding (call_id, function, arguments, security label,
session) and consume-once behavior are unchanged. Adds a regression test
covering a combined untrusted-context and confidentiality violation.
Comment thread python/packages/core/agent_framework/security.py
A pending policy approval was bound to the call body, security label, and
session but not to the violations it disclosed. Because the violation set
depends on the tool's policy metadata (max_allowed_confidentiality,
accepts_untrusted), a replay could compute a different or larger set after
that metadata changed and execute it under the old approval even though the
user never reviewed that risk.

Record the canonical disclosed violation fingerprint (type plus reason) in
the pending record and require the replay to trip the same set, otherwise
re-request approval disclosing the new set. Also require the approval
response's approved flag to be a strict boolean True so a truthy non-boolean
value is not treated as approval. Adds regression tests for a new violation
appearing on replay, a same-type violation whose disclosed risk worsened,
and a non-boolean approved flag.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

python Usage: [Issues, PRs], Target: Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants