SafetyCard Request a review

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

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.

Safest setup

Agent Framework starts every agent with no tools, validates tool arguments against their types, and asks before each command its bundled shell tools run. Most of its stronger protections, including per-call approval for your own tools, a hardened container shell and untrusted-content tracking, are there to turn on, so the safest setup enables them and adds containment around the process.

SetupHowWhat it gives you
Scoped, low-limit keysGive the process only the model and service credentials this agent needs, each scoped or read-only with a spending limit set at the providerWhatever a tool does, it reaches only the systems and spend you chose, not everything your environment holds C1
Per-call approvalapproval_mode="always_require" on each function tool, MCP tool and as_tool sub-agent that writes, sends or spendsThe run pauses and shows a person the exact tool and arguments before each consequential call C2
Untrusted-content policyAdd SecureAgentConfig to the agent, keeping the default block_on_violation=True and a short allow_untrusted_tools listOnce untrusted tool results enter the context, tools not cleared for them are blocked C5
A container shellUse DockerShellTool for model-run commands, keeping its default network and mount_readonly=TrueCommands run in a locked-down container with no network and a read-only workspace C4
A clean local shellIf you use LocalShellTool, set clean_env=True and keep the default approval_mode="always_require"Host commands see only the variables you pass, and a person approves each one C1
Call and time budgetsSet max_function_calls and max_duration_seconds in function_invocation_configuration, and keep max_iterations at 40 or lowerEach run ends after a fixed number of tool calls and a fixed amount of time C10
Its own container or VMRun the agent process in a container or VM whose network reaches only the hosts the task needsAn outer boundary around in-process tools and MCP servers, and a narrow path for data to leave C5
An exported traceCall configure_otel_providers with an otlp_endpoint that stores spans outside the agent's reachA timed record of every tool call that outlives the process C9

Approving with confidence

  • Read each prompt and approve calls one at a time; each request shows the exact tool and its arguments.
  • The bundled memory tools save without a prompt, and saved memories return in later sessions, so review what is stored.
  • MCP servers can change their tool list during a session without a new prompt; connect servers you have reviewed, at a fixed version.

Handling data

  • Keep enable_sensitive_data off unless trace storage is protected like the data; with it on, spans carry prompts, arguments and results.
  • A content-free usage header is sent by default; AGENT_FRAMEWORK_USER_AGENT_DISABLED=true turns it off.

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

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

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

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

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

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

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

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

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

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

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.