Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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 @@ -1607,8 +1620,19 @@ def add_tools(
) -> None:
"""Add sandbox-managed tools to this execute_code surface."""
with self._state_lock:
combined_tools = _collect_tools(self._managed_tools, tools)
new_tools = _collect_tools(tools)
combined_tools = _collect_tools(self._managed_tools, new_tools)
self._managed_tools = combined_tools
for tool_obj in new_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()

def get_tools(self) -> list[FunctionTool]:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,130 @@ 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


@pytest.mark.parametrize("entry_point", [HyperlightExecuteCodeTool, HyperlightCodeActProvider])
def test_codeact_does_not_repeat_fides_warning_for_unrelated_registration(
entry_point: type[HyperlightExecuteCodeTool] | type[HyperlightCodeActProvider],
caplog: pytest.LogCaptureFixture,
) -> None:
@tool(additional_properties={"source_integrity": "untrusted"})
def annotated_tool(value: int) -> int:
return value

@tool
def plain_tool(value: int) -> int:
return value

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

assert "FIDES" not in caplog.text
assert codeact.get_tools() == [annotated_tool, plain_tool]


@pytest.mark.parametrize("entry_point", [HyperlightExecuteCodeTool, HyperlightCodeActProvider])
def test_codeact_warns_once_for_annotated_tool_replacement(
entry_point: type[HyperlightExecuteCodeTool] | type[HyperlightCodeActProvider],
caplog: pytest.LogCaptureFixture,
) -> None:
@tool(name="shared")
def original_tool(value: int) -> int:
return value

@tool(name="shared", additional_properties={"accepts_untrusted": False})
def replacement_tool(value: int) -> int:
return value

codeact = entry_point(tools=original_tool, _registry=_FakeRuntime())
with caplog.at_level("WARNING", logger="agent_framework"):
codeact.add_tools(replacement_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].args == ("shared",)
assert codeact.get_tools() == [replacement_tool]


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 @@ -258,7 +272,18 @@ def add_tools(
tools: FunctionTool | Callable[..., Any] | Sequence[FunctionTool | Callable[..., Any]],
) -> None:
"""Add Monty-side tools to this execute_code surface."""
self._managed_tools = _collect_tools(self._managed_tools, tools)
new_tools = _collect_tools(tools)
self._managed_tools = _collect_tools(self._managed_tools, new_tools)
for tool_obj in new_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()

def get_tools(self) -> list[FunctionTool]:
Expand Down
Loading
Loading