BoundBench

Microsoft Agent Framework

Microsoft's multi-language (Python/.NET) framework for building AI agents and multi-agent workflows.

github.com/microsoft/agent-framework · 2026-10-04 · e9857bd

Defense-in-depth score

3.4 / 10

Minimal

Microsoft Agent Framework ships well-engineered safety primitives (exact-call approvals bound to the session, a hardened Docker shell, a Hyperlight sandbox, and an information-flow-control layer against prompt injection), but almost all are opt-in. With constructor defaults, registered and MCP tools run without approval, tool results are not treated as untrusted, and tools use the developer's ambient credentials. The bundled local shell tool is the exception: it requires approval by default, but runs on the host with the full process environment.

Key gaps (1)

  1. In the default configuration nothing distinguishes untrusted tool/MCP results from instructions, and registered tools run without approval, so an injected agent holding the developer's credentials can both exfiltrate and act unattended. C5 · Untrusted input blast radius

Criteria

C1 Identity & least privilege

Minimal 0.05 / 1.00

The framework runs every tool with whatever credentials the developer hands the chat client or the tool, and offers no primitive to scope, downscope, or check authority per request in the default path. The bundled LocalShellTool inherits the full parent process environment by default, so cloud and API credentials in the environment are reachable from shell commands. An opt-in experimental information-flow layer can tag content with tenant/user principals, but that is not an authorization layer and is off by default. The main independent safeguard is that the shell tool requires human approval by default.

C2 Approval gates

Minimal 0.30 / 1.00

The framework has a solid human-approval mechanism: a tool marked always_require pauses the run and returns an approval request carrying the exact function call and arguments, and by default an approval response only counts if it matches a request the framework recorded in the session, so replayed or fabricated approvals in message history cannot authorize execution. But the default for any registered function tool, MCP tool, and agent-as-tool is never_require, so approval is opt-in per tool. The bundled shell tools do default to approval (and refuse to turn it off without an explicit acknowledge_unsafe flag), while bundled memory-write tools never require it. There are no checkpoints or rollback for tool side effects.

C3 Tool & action scoping

Moderate 0.50 / 1.00

Every function tool built from a Python signature or Pydantic model gets typed, recursive argument validation before execution, and MCP calls forward only the parameter names the server declared. The bundled file store resolves paths against its root, rejects symlinks and opens with O_NOFOLLOW. Beyond types, the framework has no central allowlist for paths, hosts, or quantities, and the shipped shell tools accept any command string (their regex policy is explicitly documented as not a security feature). A plain Agent starts with no tools, so least agency is the default.

C4 Code-execution isolation

Moderate 0.50 / 1.00

Code execution is an add-on: the core runs Python tool functions in-process, and the tools package ships a LocalShellTool that runs commands directly on the host with the parent's environment, protected only by its default per-command approval. A well-hardened DockerShellTool (no network, non-root nobody user, read-only root, all capabilities dropped, no-new-privileges, memory/pid limits, and rejection of extra Docker flags that would undo them) and a Hyperlight WebAssembly sandbox are available, but only if the developer chooses them. Other paths such as skill script runners and stdio MCP servers run on the host.

C5 Untrusted input blast radius

Moderate 0.50 / 1.00

By default, tool and MCP results enter the conversation with no taint tracking, and since registered tools default to no approval, a prompt-injected agent can combine untrusted input, the developer's data and credentials, and outbound tools without a human. The framework ships an unusually strong opt-in defense, FIDES: content labels propagate through the run, untrusted results can be hidden behind variable references and processed by a quarantined LLM, and a policy middleware blocks (or escalates to approval) any tool not marked as accepting untrusted input once the context is tainted. It is experimental and off unless the developer installs SecureAgentConfig.

C6 Memory, context & configuration integrity

Minimal 0.10 / 1.00

The framework does not auto-load workspace instruction files or a .env from the current directory; .env files are read only when the developer passes a path. Its memory subsystem, however, lets the model write durable memories without approval or validation, and the experimental MemoryContextProvider automatically extracts facts from transcripts (including tool results) and reinjects them into later sessions. Memory stores are namespaced per session or owner in storage paths, which limits cross-user spread.

C7 Third-party extensions

Minimal 0.28 / 1.00

Third-party code enters mainly through MCP servers and skills that the developer configures in code; nothing is enabled by default and the workspace cannot add servers. There is no version pinning or integrity check, and when a server announces a changed tool list the client reloads it silently, so a server update can change tool definitions without re-approval. Stdio servers run as separate processes; when no env is given the MCP SDK's default restricted environment applies.

C8 Secrets & sensitive-data protection

Minimal 0.40 / 1.00

API keys are wrapped in a SecretString with a masked repr, tool exceptions are redacted before being returned to the model by default, and OpenTelemetry sensitive data (prompts, arguments, results) is off by default. Gaps: a version/feature header is sent on by default, tool exceptions are logged in full, and the bundled shell tool hands the whole process environment, including secrets, to model-chosen commands.

C9 Audit & traceability

Minimal 0.35 / 1.00

Each tool invocation runs inside an OpenTelemetry execute_tool span with tool name, call id, timing and error status, and arguments/results are added when sensitive-data capture is enabled. Instrumentation code is on by default, but nothing is recorded anywhere unless the developer configures an exporter, and there is no tamper-evident storage or approver attribution.

C10 Limits & kill switch

Minimal 0.38 / 1.00

The tool loop is capped at 40 model round-trips by default, and the bundled shell tools have a 30-second per-command timeout that kills the whole process group. A total function-call cap and a wall-clock budget exist but default to unlimited, and there is no token or cost cap. Sub-agents and background agents run with their own fresh budgets as asyncio tasks.