Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions python/packages/hyperlight/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,23 @@

Hyperlight-backed CodeAct integrations for Microsoft Agent Framework.

## FIDES compatibility

> [!WARNING]
> **FIDES integration is not supported with CodeAct providers.** Host-tool calls
> made inside generated code bypass the agent's function middleware, so FIDES
> policy enforcement and label tracking do not apply to those nested calls or
> intermediate code values. Checks on the outer `execute_code` invocation do not
> enforce the policies of individual tools called inside it.
>
> Registering tools with recognized FIDES `additional_properties` logs a warning,
> but does not block execution. This is a best-effort diagnostic: FIDES also uses
> defaults for unannotated tools, so no warning does not imply support or safety.
> Keep tools that require FIDES as direct agent tools, not Hyperlight-managed tools,
> and enforce authorization and destination restrictions inside host functions.
> Sandbox isolation does not supply FIDES enforcement.
> See the [FIDES guide](../../samples/02-agents/security/FIDES_DEVELOPER_GUIDE.md).

## Installation

```bash
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import asyncio
import contextvars
import logging
import mimetypes
import os
import shutil
Expand Down Expand Up @@ -32,9 +33,21 @@
from ._instructions import build_codeact_instructions, build_execute_code_description
from ._types import AllowedDomain, AllowedDomainInput, FileMount, FileMountHostPath, FileMountInput

logger = logging.getLogger("agent_framework")

DEFAULT_HYPERLIGHT_BACKEND = "wasm"
DEFAULT_HYPERLIGHT_MODULE = "python_guest.path"
EXECUTE_CODE_TOOL_DESCRIPTION = "Execute Python in an isolated Hyperlight sandbox."
_FIDES_TOOL_PROPERTY_KEYS = frozenset({
"source_integrity",
"confidentiality",
"max_allowed_confidentiality",
"accepts_untrusted",
"standing_guidance",
"agent_framework.security.principals",
"_agent_framework_internal_security_tool",
"_mcp_trust_server_ifc",
})
OUTPUT_FILE_RETRY_ATTEMPTS = 10
OUTPUT_FILE_RETRY_DELAY_SECONDS = 0.1
DEFAULT_MAX_OUTPUT_FILES = 20
Expand Down Expand Up @@ -1609,6 +1622,16 @@ def add_tools(
with self._state_lock:
combined_tools = _collect_tools(self._managed_tools, tools)
self._managed_tools = combined_tools
for tool_obj in self._managed_tools:
if tool_obj.additional_properties and not _FIDES_TOOL_PROPERTY_KEYS.isdisjoint(
tool_obj.additional_properties
):
logger.warning(
"FIDES is not supported with CodeAct providers. Metadata on tool '%s' will not be enforced "
"through function middleware for calls made inside generated code. "
"Keep FIDES-dependent tools as direct agent tools.",
tool_obj.name,
)
self._refresh_approval_mode()
Comment thread
eavanvalkenburg marked this conversation as resolved.
Resolved

def get_tools(self) -> list[FunctionTool]:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,83 @@ def test_execute_code_tool_updates_approval_with_managed_tools() -> None:
assert execute_code.approval_mode == "always_require"


@pytest.mark.parametrize("entry_point", [HyperlightExecuteCodeTool, HyperlightCodeActProvider])
@pytest.mark.parametrize("register_later", [False, True])
@pytest.mark.parametrize(
"properties",
[
{"source_integrity": "untrusted"},
{"confidentiality": "private"},
{"max_allowed_confidentiality": "public"},
{"max_allowed_confidentiality": None},
{"accepts_untrusted": False},
{"standing_guidance": []},
{"agent_framework.security.principals": []},
{"_agent_framework_internal_security_tool": object()},
{"_mcp_trust_server_ifc": False},
],
)
def test_codeact_warns_for_fides_tool_metadata(
entry_point: type[HyperlightExecuteCodeTool] | type[HyperlightCodeActProvider],
register_later: bool,
properties: dict[str, Any],
caplog: pytest.LogCaptureFixture,
) -> None:
@tool(additional_properties=properties)
def annotated_tool(value: int) -> int:
return value

with caplog.at_level("WARNING", logger="agent_framework"):
codeact = entry_point(tools=None if register_later else annotated_tool, _registry=_FakeRuntime())
if register_later:
codeact.add_tools(annotated_tool)

records = [
record for record in caplog.records if "FIDES is not supported with CodeAct providers." in record.message
]
assert len(records) == 1
assert records[0].levelname == "WARNING"
assert records[0].args == ("annotated_tool",)
assert codeact.get_tools()[0] is annotated_tool
assert annotated_tool.additional_properties == properties
assert annotated_tool.invocation_count == annotated_tool.invocation_exception_count == 0


@pytest.mark.parametrize("entry_point", [HyperlightExecuteCodeTool, HyperlightCodeActProvider])
@pytest.mark.parametrize("properties", [None, {}, {"custom_property": False}])
def test_codeact_does_not_warn_for_unannotated_tools(
entry_point: type[HyperlightExecuteCodeTool] | type[HyperlightCodeActProvider],
properties: dict[str, Any] | None,
caplog: pytest.LogCaptureFixture,
) -> None:
@tool(additional_properties=properties)
def plain_tool(value: int) -> int:
return value

with caplog.at_level("WARNING", logger="agent_framework"):
codeact = entry_point(tools=plain_tool, _registry=_FakeRuntime())

assert "FIDES" not in caplog.text
assert codeact.get_tools()[0] is plain_tool


def test_run_snapshot_warns_for_fides_metadata_added_after_registration(caplog: pytest.LogCaptureFixture) -> None:
@tool
def annotated_tool(value: int) -> int:
return value

codeact = HyperlightExecuteCodeTool(tools=annotated_tool, _registry=_FakeRuntime())
assert annotated_tool.additional_properties is not None
annotated_tool.additional_properties["accepts_untrusted"] = False

with caplog.at_level("WARNING", logger="agent_framework"):
run_tool = codeact.create_run_tool()

assert "FIDES is not supported with CodeAct providers." in caplog.text
assert run_tool.get_tools()[0] is annotated_tool
assert annotated_tool.invocation_count == annotated_tool.invocation_exception_count == 0


def test_execute_code_tool_replaces_tools_with_the_same_name() -> None:
execute_code = HyperlightExecuteCodeTool(tools=[compute], _registry=_FakeRuntime())

Expand Down
16 changes: 16 additions & 0 deletions python/packages/monty/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,22 @@ Monty-backed CodeAct integrations for Microsoft Agent Framework.
> This package is in **beta**. APIs may change before its stable release. It is
> included in `agent-framework[all]`.

## FIDES compatibility

> [!WARNING]
> **FIDES integration is not supported with CodeAct providers.** Host-tool calls
> made inside generated code bypass the agent's function middleware, so FIDES
> policy enforcement and label tracking do not apply to those nested calls or
> intermediate code values. Checks on the outer `execute_code` invocation do not
> enforce the policies of individual tools called inside it.
>
> Registering tools with recognized FIDES `additional_properties` logs a warning,
> but does not block execution. This is a best-effort diagnostic: FIDES also uses
> defaults for unannotated tools, so no warning does not imply support or safety.
> Keep tools that require FIDES as direct agent tools, not Monty-managed tools, and
> enforce authorization and destination restrictions inside host functions.
> See the [FIDES guide](../../samples/02-agents/security/FIDES_DEVELOPER_GUIDE.md).

## Installation

```bash
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from __future__ import annotations

import json
import logging
import mimetypes
from collections.abc import Callable, Iterator, Sequence
from functools import partial
Expand All @@ -32,9 +33,22 @@
from ._monty_bridge import InlineCodeBridge, generate_type_stubs
from ._types import FileMount, FileMountInput

logger = logging.getLogger("agent_framework")

EXECUTE_CODE_TOOL_NAME = "execute_code"
EXECUTE_CODE_TOOL_DESCRIPTION = "Execute Python in a Monty interpreter."

_FIDES_TOOL_PROPERTY_KEYS = frozenset({
"source_integrity",
"confidentiality",
"max_allowed_confidentiality",
"accepts_untrusted",
"standing_guidance",
"agent_framework.security.principals",
"_agent_framework_internal_security_tool",
"_mcp_trust_server_ifc",
})

#: Virtual path that the optional ``workspace_root`` directory is mounted at,
#: matching the Hyperlight default. Use ``file_mounts`` for any other path.
WORKSPACE_MOUNT_PATH = "/input"
Expand Down Expand Up @@ -259,6 +273,16 @@ def add_tools(
) -> None:
"""Add Monty-side tools to this execute_code surface."""
self._managed_tools = _collect_tools(self._managed_tools, tools)
for tool_obj in self._managed_tools:
if tool_obj.additional_properties and not _FIDES_TOOL_PROPERTY_KEYS.isdisjoint(
tool_obj.additional_properties
):
logger.warning(
"FIDES is not supported with CodeAct providers. Metadata on tool '%s' will not be enforced "
"through function middleware for calls made inside generated code. "
"Keep FIDES-dependent tools as direct agent tools.",
tool_obj.name,
)
self._refresh_approval_mode()
Comment thread
eavanvalkenburg marked this conversation as resolved.
Resolved

def get_tools(self) -> list[FunctionTool]:
Expand Down
77 changes: 77 additions & 0 deletions python/packages/monty/tests/monty/test_monty_codeact.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,83 @@ def test_tool_construction_defaults() -> None:
assert monty_tool.get_tools() == []


@pytest.mark.parametrize("entry_point", [MontyExecuteCodeTool, MontyCodeActProvider])
@pytest.mark.parametrize("register_later", [False, True])
@pytest.mark.parametrize(
"properties",
[
{"source_integrity": "untrusted"},
{"confidentiality": "private"},
{"max_allowed_confidentiality": "public"},
{"max_allowed_confidentiality": None},
{"accepts_untrusted": False},
{"standing_guidance": []},
{"agent_framework.security.principals": []},
{"_agent_framework_internal_security_tool": object()},
{"_mcp_trust_server_ifc": False},
],
)
def test_codeact_warns_for_fides_tool_metadata(
entry_point: type[MontyExecuteCodeTool] | type[MontyCodeActProvider],
register_later: bool,
properties: dict[str, Any],
caplog: pytest.LogCaptureFixture,
) -> None:
@tool(additional_properties=properties)
def annotated_tool(value: int) -> int:
return value

with caplog.at_level("WARNING", logger="agent_framework"):
codeact = entry_point(tools=None if register_later else annotated_tool)
if register_later:
codeact.add_tools(annotated_tool)

records = [
record for record in caplog.records if "FIDES is not supported with CodeAct providers." in record.message
]
assert len(records) == 1
assert records[0].levelname == "WARNING"
assert records[0].args == ("annotated_tool",)
assert codeact.get_tools()[0] is annotated_tool
assert annotated_tool.additional_properties == properties
assert annotated_tool.invocation_count == annotated_tool.invocation_exception_count == 0


@pytest.mark.parametrize("entry_point", [MontyExecuteCodeTool, MontyCodeActProvider])
@pytest.mark.parametrize("properties", [None, {}, {"custom_property": False}])
def test_codeact_does_not_warn_for_unannotated_tools(
entry_point: type[MontyExecuteCodeTool] | type[MontyCodeActProvider],
properties: dict[str, Any] | None,
caplog: pytest.LogCaptureFixture,
) -> None:
@tool(additional_properties=properties)
def plain_tool(value: int) -> int:
return value

with caplog.at_level("WARNING", logger="agent_framework"):
codeact = entry_point(tools=plain_tool)

assert "FIDES" not in caplog.text
assert codeact.get_tools()[0] is plain_tool


def test_run_snapshot_warns_for_fides_metadata_added_after_registration(caplog: pytest.LogCaptureFixture) -> None:
@tool
def annotated_tool(value: int) -> int:
return value

codeact = MontyExecuteCodeTool(tools=annotated_tool)
assert annotated_tool.additional_properties is not None
annotated_tool.additional_properties["accepts_untrusted"] = False

with caplog.at_level("WARNING", logger="agent_framework"):
run_tool = codeact.create_run_tool()

assert "FIDES is not supported with CodeAct providers." in caplog.text
assert run_tool.get_tools()[0] is annotated_tool
assert annotated_tool.invocation_count == annotated_tool.invocation_exception_count == 0


def test_add_remove_clear_tools_round_trip() -> None:
monty_tool = MontyExecuteCodeTool()

Expand Down
17 changes: 17 additions & 0 deletions python/samples/02-agents/security/FIDES_DEVELOPER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,23 @@

**FIDES** is a comprehensive security system for AI agents. This developer guide describes the deterministic prompt injection defense system implemented in the agent framework. The system provides label-based security mechanisms to defend against prompt injection attacks by tracking integrity and confidentiality of content throughout agent execution.

> [!WARNING]
> **Combining FIDES with CodeAct providers is currently unsupported**, including
> [Monty](../../../packages/monty/README.md) and
> [Hyperlight](../../../packages/hyperlight/README.md). FIDES's per-tool guarantees
> apply to invocations routed through its function middleware. Host-tool calls
> inside generated code bypass that middleware; nested calls and intermediate
> code values do not receive its policy enforcement or label tracking. Checking
> the outer `execute_code` invocation does not enforce the policies of individual
> tools called inside it, and sandbox isolation does not supply FIDES enforcement.
>
> CodeAct logs a warning for recognized FIDES tool metadata, but this is diagnostic
> only: execution is not blocked, and defaults can apply to unannotated tools.
> No warning does not imply support or safety. Keep tools that require FIDES as
> direct agent tools rather than CodeAct-managed tools, or disable CodeAct for
> workflows requiring those guarantees. Host functions must independently enforce
> authorization and destination restrictions.

## Context Provider Pattern with SecureAgentConfig!

**`SecureAgentConfig` is now a `ContextProvider`** — add it to any agent with a single `context_providers=[config]` line. It automatically injects security tools, instructions, and middleware via the `before_run()` hook. No security knowledge required from developers.
Expand Down
Loading