Skip to content

Python: bind and recover FIDES policy approvals - #8142

Open
Eduard van Valkenburg (eavanvalkenburg) wants to merge 3 commits into
python-fides-label-correctnessfrom
python-fides-approval-binding
Open

Python: bind and recover FIDES policy approvals#8142
Eduard van Valkenburg (eavanvalkenburg) wants to merge 3 commits into
python-fides-label-correctnessfrom
python-fides-approval-binding

Conversation

@eavanvalkenburg

@eavanvalkenburg Eduard van Valkenburg (eavanvalkenburg) commented Sep 8, 2026

Copy link
Copy Markdown
Member

Motivation & Context

A FIDES policy approval must authorize exactly one reviewed invocation across interruption, persistence, and resume. Reused provider call IDs, stale or interleaved decisions, abandoned approval state, and expired or evicted authority can otherwise bind approval to the wrong occurrence, leak pending records, or allow an old serialized grant to authorize a replacement request.

Description & Review Guide

  • What are the major changes? Adds occurrence-aware approval identity and exact resolved invocation binding; bounds session-scoped pending policy approvals with FIFO capacity and TTL expiry; rotates a persisted request-generation identity whenever policy authority is replaced; and routes only lifecycle-authenticated core and AG-UI rejection/cancellation decisions through a private middleware observer capability.
  • What is the impact of these changes? Policy grants are consumed once, survive legitimate restore/resume paths, reject stale generations and mismatched invocations, clean only the matching occurrence in the owning session, and cannot grow without bound. Expired or evicted grants surface a caller-visible replacement request that requires a fresh second approval in streaming, non-streaming, and AG-UI flows.
  • What do you want reviewers to focus on? Please focus on occurrence identity versus request-generation identity, fixed provider-scope cleanup, authenticated AG-UI non-grant handling, replacement persistence across restore, and exactly-once call/result history.

Related Issue

Fixes #7455 and #7890. Depends on #8141 and is the top layer of the four-PR FIDES hardening stack.

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. If it is a breaking change, add the breaking change label (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.

Comment thread python/packages/core/tests/core/test_harness_tool_approval.py Fixed
Comment thread python/packages/core/tests/core/test_harness_tool_approval.py Fixed

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.

🟡 Changes recommended

Provider-scoped and AG-UI rejection cleanup is incomplete, and capacity validation can be bypassed with NaN.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Hardens Python FIDES approvals across persistence and resume.

Changes:

  • Adds occurrence-bound, FIFO/TTL-limited policy approvals.
  • Preserves replacement approval requests and balanced history.
  • Adds rejection/cancellation cleanup with expanded tests.
File summaries
File Description
agent_framework/security.py Adds approval lifecycle and cleanup logic.
agent_framework/_tools.py Handles replacement requests and non-grants.
agent_framework/_sessions.py Preserves unresolved approval controls.
agent_framework/_middleware.py Adds the non-grant notification hook.
tests/test_security.py Tests bounds, expiry, and cleanup.
tests/core/test_harness_tool_approval.py Tests end-to-end reapproval flows.
tests/core/test_function_invocation_logic.py Tests replacement request collection.
docs/specs/004-python-function-calling-loop.md Documents new approval semantics.
Review details

Suppressed comments (1)

python/packages/core/agent_framework/security.py:2712

  • This duplicate validation also accepts float("nan"), after which the policy middleware's capacity comparison can never evict records. Validate that the public configuration value is an actual integer so the advertised bound cannot be disabled by an untyped runtime value.
        if isinstance(max_pending_approvals, bool) or max_pending_approvals < 1:
            raise ValueError("max_pending_approvals must be at least 1.")
  • Files reviewed: 8/8 changed files
  • Comments generated: 3
  • Review effort level: Balanced

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +3025 to +3026
if middleware_pipeline is not None and responses_not_granted:
middleware_pipeline.notify_approval_responses(responses_not_granted, session=invocation_session)
Comment on lines +2060 to +2061
if isinstance(max_pending_approvals, bool) or max_pending_approvals < 1:
raise ValueError("max_pending_approvals must be at least 1.")
Comment on lines +2267 to +2268
scope = self._scope_for_session(session)
self._prune_pending_approvals(scope)

@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.

MAF Automated Review — Iteration 1

Result: Findings reported
Scope: full PR (1 commit(s)): c7b777531e77
Model: gpt-5.6-sol-fast

Overview

The PR strengthens FIDES approvals with occurrence-bound identities, durable binding signatures, FIFO/TTL limits, fail-closed reapproval, and streaming/non-streaming coverage. The replacement-request and transcript-normalization paths have concrete guards against stale or replayed authority. One provider-path scope mismatch remains: rejection and cancellation notifications inspect standalone middleware state instead of the fixed SecureAgentConfig scope, so the promised prompt cleanup does not occur.

Reviewed the supplied pull-request change set across correctness, security/reliability, architecture, and failure behavior.
1 verified finding remained after source verification (1 medium) across 1 file. Details are attached to the affected lines below.

Affected areas: python/packages/core/agent_framework/security.py

session: AgentSession | None,
) -> None:
"""Discard authenticated non-grants from only their owning security scope."""
scope = self._scope_for_session(session)

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.

When SecureAgentConfig clones this middleware, pending approvals live in its fixed provider scope (session.state[source_id]), but this lookup uses the inherited standalone state key. An authenticated rejection or cancellation therefore inspects an empty scope and leaves the matching record until TTL pruning or FIFO eviction, contrary to the new cleanup contract. For fixed-scope clones, use the fixed scope here (mirroring _activate_security_scope) while retaining session lookup for reusable standalone middleware.

Comment on lines +2256 to 2258
return current_binding.binding_key() == pending.binding_key() and self._response_matches_pending(
approval_response, approval_id, call_id, pending.body_signature
)

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.

Should a replacement approval carry a fresh generation token? After TTL expiry or FIFO eviction, _request_policy_violation_approval stores a new record under the same occurrence ID, but this check excludes created_at; because the call body and IDs are also unchanged, replaying the original serialized grant satisfies the replacement and executes without a second user decision. Could we rotate a request-generation nonce for each replacement and require it in _response_matches_pending?

Comment on lines +759 to +764
def on_approval_responses(
self,
responses: Sequence[Content],
*,
session: AgentSession | None,
) -> None:

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.

Could we keep this approval-lifecycle callback behind a private observer protocol instead of adding it to public FunctionMiddleware? Callers of on_approval_responses must know that responses were rebound to the active session, authenticated against the pending snapshot, and filtered to decisions that will not execute, but the only implementation is experimental PolicyEnforcementFunctionMiddleware, and callable middleware cannot participate. A private capability implemented by the policy middleware and dispatched by FunctionMiddlewarePipeline would keep those rules in the approval subsystem without expanding the stable middleware API.

@github-code-quality

github-code-quality Bot commented Sep 8, 2026

Copy link
Copy Markdown

Code Coverage Overview

Languages: Python

Python / code-coverage/python

The overall line coverage in commit 656da37 in the python-fides-approva... branch is 91%. Line coverage data for the python-fides-label-c... branch is not yet available.

Show a line coverage summary of the most covered files.
File python-fides-label-c... python-fides-approva... 656da37 +/-
packages/core/a...work/_skills.py 95%
packages/core/a...ework/_tools.py 94%
packages/core/a.../_compaction.py 94%
packages/core/a...rk/_sessions.py 94%
packages/core/a...ework/_types.py 93%
packages/core/a...bservability.py 93%
packages/openai..._chat_client.py 92%
packages/core/a...amework/_mcp.py 91%
packages/ag-ui/...i/_agent_run.py 90%
packages/core/a...ork/security.py 85%

Updated September 09, 2026 16:50 UTC

@moonbox3
Evan Mattson (moonbox3) force-pushed the python-fides-approval-binding branch from 268d0b8 to 5e42eb2 Compare September 8, 2026 21:50
Bound FIDES policy approvals per session with FIFO and TTL expiry, clean authenticated non-grants by occurrence, and persist visible replacement approvals so stale grants require a safe second approval.\n\nCo-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Harden request-generation authority, keep lifecycle observation private, and notify fixed-scope FIDES policy middleware from authenticated AG-UI rejection and cancellation paths.\n\nCo-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Comment thread python/packages/core/tests/core/test_harness_tool_approval.py Fixed
Comment thread python/packages/ag-ui/agent_framework_ag_ui/_agent_run.py Fixed
Preserve immutable function occurrences while rotating approval generations, route authenticated AG-UI non-grants through private observers, and cover canonical, legacy, mixed-cancellation, and restored-session flows.\n\nCo-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Usage: [Issues, PRs], Target: documentation in the code base and learn docs python Usage: [Issues, PRs], Target: Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Python: [Feature]: FIDES integration: improvement requests from a production deployment

3 participants