Python: fix FIDES session isolation and runtime integration gaps - #7528
Python: fix FIDES session isolation and runtime integration gaps#7528Oscar Lobato Ruiz (lerelerele) wants to merge 4 commits into
Conversation
FIDES kept conversation scoped security state on shared middleware instances, so a single Agent or middleware instance serving more than one conversation could leak context labels, audit records and pending approvals across conversation boundaries. A blocked tool call also terminated the invocation loop without a visible result, which could reach the user as an empty response. Move conversation scoped state into AgentSession.state and address the A1 to C3 items from microsoft#7455: - A1: context label, audit log, pending approvals and counters live in AgentSession.state; variable stores are keyed per session. - A2: a blocked call returns a correlated function_result instead of terminating with no content, so the model can explain the refusal or choose another action. Approval requests still pause for the user. - A3: audit records carry a per run turn and a per call call_index instead of the constant -1. - B1: new tool_labels configuration for tools the application did not construct, such as harness and MCP tools. - B2: approval request additional_properties propagate into the reconstructed function call. - B3: standing approval rules and auto approval callbacks can no longer approve a FIDES policy violation. - B4: new public build_function_call_content() extension point; the former private method remains as an alias. - B5: new deny_untrusted_tools; deny takes precedence over allow. - C1: new enable_quarantine=False so labels and policy enforcement can be used without quarantine tooling. - C2: the quarantine client is bound to the current async context instead of a process global slot. - C3: MCP read only tools receive a PUBLIC confidentiality cap, since their arguments still leave the process, with a granular mark_read_tools_as_sinks opt out. Also hardens durable state: label metadata is sanitized before it reaches session storage, and the audit log is capped per session with a configurable limit. Middleware order is unchanged, and direct middleware calls made without an AgentSession keep working through explicit fallbacks. Adds 21 regression tests in tests/test_fides_7455_regressions.py.
|
@microsoft-github-policy-service agree |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.
Suppressed comments (2)
python/packages/core/agent_framework/security.py:2425
PolicyEnforcementFunctionMiddleware.clear_audit_log()has the same session-resolution issue asget_audit_log(): when called without an explicitsession, it usesget_current_session()which may beNoneafter middleware execution. Fall back to the remembered active session so the public API behaves consistently.
def clear_audit_log(self, session: Any = None) -> None:
"""Clear the audit log."""
self._resolve_audit_log(session or get_current_session()).clear()
python/packages/core/agent_framework/security.py:2421
PolicyEnforcementFunctionMiddleware.get_audit_log()only falls back toget_current_session(), which is cleared when the label-tracking middleware exits. This means callingget_audit_log()without explicitly passingsessioncan incorrectly return the no-session fallback log even though a session was previously active in the current async context (contrary to the module’s new “remember last session” behavior). Consider also falling back to the weakly remembered_fides_active_session.
This issue also appears on line 2423 of the same file.
List of violation records.
"""
return self._resolve_audit_log(session or get_current_session()).copy()
|
It's a bit confusing why the PR body links to an issue that says that this PR doesn't solve. Can you please link to the correct issue? |
- Reuse make_json_safe for ContentLabel Enum and set serialization. - Keep middleware state in plain ContextVars and route FIDES state access through shared helpers. - Move regression coverage into existing test suites. - Resolve the remembered session for audit-log accessors after run teardown.
|
You’re right. I updated the PR description to reference the correct issue, #7455, using I also addressed the other review suggestions:
Removed
Removed the local
One intentional consequence is that any FIDES session-state access materializes the complete schema, including empty audit, approval, and counter fields. This keeps the durable contract owned by one accessor. While validating the changes, I also found and fixed a separate audit-log issue: after a run completed, Focused FIDES tests, the full core suite excluding integration tests, Ruff, and Pyright all pass. |
• Closes #7455
This PR implements the A1 - C3 changes described in #7455.
The main architectural change is to make FIDES runtime state conversation-scoped. Context labels, audit records, pending approvals, counters, and variable stores are now associated with
AgentSession.stateinstead of shared middleware or agent instances. This prevents state leaking between concurrent conversations that reuse the same agent or middleware.What changed
AgentSession.state; variable stores are isolated per session.function_resultinstead of terminating the loop with empty output, allowing the model to explain the refusal or choose another action. Approval requests still pause for the user.turnand per-callcall_index.tool_labelsconfiguration for externally-created tools, including harness and MCP tools.additional_propertieswhen reconstructing function calls.build_function_call_content()extension point while retaining the former private method as an alias.deny_untrusted_tools, with deny rules taking precedence over allow rules.enable_quarantine=False, allowing labels and policy enforcement without quarantine tooling.PUBLICconfidentiality cap to read-only MCP tools, withmark_read_tools_as_sinksas an opt-out.The durable
session.state["_fides"]schema is owned by_fides_session_state(). It creates the mapping and initializes the context label, audit log, pending approvals, and turn/call counters in one place.before_run(), the labelaccessors, and the policy-enforcement accessors all route through it; their per-key initialization has been removed.
_current_middlewarenow uses a plain async-safeContextVar, replacing the previousthreading.local()storage. No-session fallbacks remain local, and direct middleware calls still return turn1on their first call.max_audit_log_entries, which defaults to1000.Additional API surface
This PR also includes two additions that were not explicitly listed in #7455:
max_audit_log_entries, defaulting to1000and acceptingNonefor an unlimited log.begin_turn(session), which provides the run boundary needed to populate meaningful audit turn numbers.These additions are called out explicitly for maintainer review.
Scope
This PR is scoped to the Python core FIDES integration work described in #7455.
Tests and verification
Regression coverage was added to the existing test suites:
python/packages/core/tests/test_security.pypython/packages/core/tests/core/test_harness_tool_approval.pyThe standalone regression test module was removed.
pytest packages/core/tests -m "not integration" -q