Skip to content

Python: Sample for using WorkIQ MCP server using a gateway for labelling and IFC policy evaluation #6860

Open
shrutitople wants to merge 29 commits into
microsoft:mainfrom
shrutitople:add/gateway-integration-for-workiq
Open

Python: Sample for using WorkIQ MCP server using a gateway for labelling and IFC policy evaluation #6860
shrutitople wants to merge 29 commits into
microsoft:mainfrom
shrutitople:add/gateway-integration-for-workiq

Conversation

@shrutitople

Copy link
Copy Markdown
Contributor

Motivation & Context

FIDES enforces information-flow-control policy locally today. This change lets a FIDES-secured agent delegate policy decisions to an external FIDES Gateway fronting its MCP servers, while still tracking labels and surfacing approvals locally. It also adds a runnable WorkIQ email/teams sample as a reference.

Description & Review Guide

SecureMCPToolProxy(gateway_policy=True) delegates per-call policy checks to the gateway's eval_policy (allow/deny/ask).
ConfidentialityLabel gains a readers lattice [PRIVATE] with a readers frozenset, intersected on combine via ConfidentialityLabel.combine, and parses gateway _meta IFC labels including the top-level "$" scope.
New [workiq-email-example.py] sample ([--cli] using ToolApprovalMiddleware, plus README and tests.

shrutitople and others added 27 commits May 22, 2026 13:48
…ecurity, and change context label only using the labels of unhidden result from tools
… instead of /insiders

- Switch MCP_URL from /mcp/insiders to /mcp/ in github_mcp_example.py
- Add MCP_HEADERS constant with X-MCP-Features: ifc_labels to opt-in to
  server-side IFC label emission in _meta payloads
- Fix SecureMCPToolProxy to pass headers via httpx.AsyncClient so they are
  included on session.initialize(), not just on tool calls (was causing 401
  to silently surface as anyio cancel-scope CancelledError)
- Update README, FIDES_DEVELOPER_GUIDE, FIDES_IMPLEMENTATION_SUMMARY, and
  0024-prompt-injection-defense.md to remove all /insiders references
    1. Can connect to workiq via gateway
    2. Can read the top-level label specified under "$", {"ifc": {"$" : {...}}}
    3. ConfidentialityLabel supports readers list. Context aggreation happens correctly on readers (only checked for basic-ifc).

TODO:
    1. Add support for policy enforcement on the gateway.
    2. Add / Confirm that FIDES works with the readers
Copilot AI review requested due to automatic review settings July 1, 2026 14:17
@giles17 giles17 added documentation Usage: [Issues, PRs], Target: documentation in the code base and learn docs python Usage: [Issues, PRs], Target: Python labels Jul 1, 2026
@github-actions github-actions Bot changed the title Sample for using WorkIQ MCP server using a gateway for labelling and IFC policy evaluation Python: Sample for using WorkIQ MCP server using a gateway for labelling and IFC policy evaluation Jul 1, 2026
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Python Test Coverage

Python Test Coverage Report •
FileStmtsMissCoverMissing
packages/core/agent_framework
   security.py110132370%176, 180, 200–203, 252, 582–583, 601, 604–605, 683–684, 686, 734, 746, 762, 779, 956, 972–975, 1033, 1037, 1040–1042, 1050–1054, 1059–1062, 1064, 1079–1083, 1088–1092, 1096–1099, 1101, 1113–1114, 1117–1118, 1194, 1200–1201, 1206, 1208–1209, 1239–1240, 1268–1276, 1299–1302, 1412–1413, 1444–1445, 1464–1465, 1469–1470, 1515–1516, 1603–1604, 1833, 1837, 1841, 1944, 1949–1950, 1954, 1958–1960, 1971–1972, 2016, 2028, 2030, 2047, 2068, 2079, 2105, 2140–2141, 2169, 2172–2175, 2177, 2180, 2182–2183, 2185–2186, 2188, 2190, 2192, 2196–2197, 2199–2202, 2205, 2213, 2215–2217, 2224–2225, 2227, 2237, 2240–2241, 2247–2248, 2250, 2270–2271, 2274–2277, 2284, 2286–2287, 2294–2295, 2308–2310, 2315, 2318–2320, 2328, 2330–2331, 2338–2339, 2352–2354, 2360, 2362, 2385, 2495, 2523–2525, 2560–2562, 2570, 2578, 2850–2851, 2853, 2855–2857, 2860, 2872, 2878–2879, 2881, 2912, 2916–2919, 2921, 3071–3072, 3085, 3108, 3175, 3185–3186, 3347–3348, 3352–3354, 3359, 3361–3369, 3374–3375, 3377–3381, 3383–3385, 3388–3390, 3392–3393, 3398, 3401–3402, 3406, 3408, 3444–3446, 3482, 3510–3511, 3514–3515, 3549, 3555–3557, 3559, 3561, 3563, 3570, 3575, 3580, 3593, 3598–3599, 3604–3605, 3607–3621, 3623, 3625, 3727–3730, 3732–3733, 3735, 3737, 3743, 3752, 3761–3765, 3771–3773, 3777, 3783–3784, 3788, 3797–3799, 3806, 3811, 3816, 3821, 3826, 3831, 3843–3844, 3851–3853, 3855–3858
TOTAL43399525787% 

Python Unit Test Overview

Tests Skipped Failures Errors Time
8532 37 💤 0 ❌ 0 🔥 2m 8s ⏱️

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

This PR extends the Python FIDES security layer to support a “PRIVATE with readers” confidentiality lattice and adds gateway-driven policy enforcement for MCP tools (via a Fides Gateway eval_policy call). It also introduces a runnable WorkIQ email/teams sample and updates the security sample README and core security tests accordingly.

Changes:

  • Add ConfidentialityLabel reader restrictions (list/frozenset readers) and update label combine/serialization and policy checks accordingly.
  • Add optional gateway policy enforcement (SecureMCPToolProxy(gateway_policy=True)) that attaches a per-tool _gateway_policy_fn and routes enforcement through eval_policy.
  • Add a WorkIQ gateway sample script plus README entry and update unit tests for the new confidentiality semantics.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

File Description
python/samples/02-agents/security/workiq-email-example.py New runnable sample demonstrating WorkIQ MCP usage via a local Fides Gateway plus approval flow handling.
python/samples/02-agents/security/README.md Documents the new WorkIQ gateway sample, setup steps, and expected behavior.
python/packages/core/tests/test_security.py Updates unit tests to reflect the new “PRIVATE with readers” confidentiality model.
python/packages/core/agent_framework/security.py Implements reader-aware confidentiality labels and gateway-driven policy enforcement hooks for MCP tools.

Comment thread python/packages/core/agent_framework/security.py
Comment thread python/packages/core/agent_framework/security.py
Comment thread python/packages/core/agent_framework/security.py
Comment thread python/packages/core/agent_framework/security.py
Comment thread python/samples/02-agents/security/workiq-email-example.py Outdated
Comment thread python/packages/core/agent_framework/security.py Outdated
PUBLIC: ConfidentialityLabel # set after class body
PRIVATE: ConfidentialityLabel # set after class body

def __init__(self, value: str | list[str] = "public", *, readers: frozenset[str] | None = None) -> None:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The literal will tell users that they made a mistake (misspelling or a non-list name) and the Sequence is to allow covariant typing.

Suggested change
def __init__(self, value: str | list[str] = "public", *, readers: frozenset[str] | None = None) -> None:
def __init__(self, value: Literal['public' | 'private'] | Sequence[str] = "public", *, readers: frozenset[str] | None = None) -> None:

ValueError: If ``value`` is not ``"public"``, ``"private"``, or a list.
"""
if isinstance(value, list):
self._level = "private"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
self._level = "private"
self._level: Literal['public' | 'private'] = "private"

client=main_client,
name="WorkIQSecureAgent",
instructions=AGENT_INSTRUCTIONS,
tools=all_tools,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

it is not necessary to unpack the tools list for both, you can just pass the two MCP servers in:

Suggested change
tools=all_tools,
tools=[secure_mcp_1, secure_mcp_2],

Comment on lines +250 to +253
if mode == "cli":
asyncio.run(run_cli_async(debug=debug, gateway_port=gateway_port, auto_approve=auto_approve))
else:
asyncio.run(run_devui_async(debug=debug, gateway_port=gateway_port))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

again, this complicates showing the exact setup for this feature, and adds a lot of overhead on the because of the two interfaces, remember that samples are not for dev purposes, it is to show how something works, less is more!

print(f"[auto-approve] gateway policy flagged '{tool_name}' -> approving")
else:
prompt = f"Approve tool '{tool_name}' flagged by gateway policy? [y/N] "
answer = (await asyncio.to_thread(input, prompt)).strip().lower()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this is also not needed for a sample, just use answer = input(prompt)

)


async def run_cli_async(*, debug: bool = False, gateway_port: int = 9090, auto_approve: bool = False) -> None:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

wiring the auto-approval should a rule in the ToolApprovalMiddleware, that makes the other code much simpler as well


@experimental(feature_id=ExperimentalFeature.FIDES)
class ConfidentialityLabel(str, Enum):
class ConfidentialityLabel:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm never a big fan of this kind of enum-looking but not quite it features... and I'm thinking if we maybe could do Literal['public' | 'private'] | Sequence[str] as the value for a confidentiality param, with helpers functions for combining and comparison, that might simplify things a bit and it saves a additonial class concept that users then do not need to know

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.

4 participants