Skip to main content Signal blog Official Microsoft Blog Command Line Microsoft On The Issues Asia Canada Europe, Middle East and Africa Latin America The Code of Us What's new AI Innovation Digital Transformation Sustainability Security Work & Life Diversity & Inclusion Unlocked Microsoft 365 Azure Copilot Windows Surface XBOX Deals Small Business Support Windows Apps Outlook OneDrive Microsoft Teams OneNote Microsoft Edge Moving from Skype to Teams Computers Shop XBOX Accessories VR & mixed reality Certified Refurbished Trade-in for cash XBOX Game Pass Ultimate PC Game Pass XBOX games PC games Microsoft AI Microsoft Security Dynamics 365 Microsoft 365 for business Microsoft Power Platform Windows 365 Small Business Digital Sovereignty Azure Microsoft Developer Microsoft Learn Support for AI marketplace apps Microsoft Tech Community Microsoft Marketplace Software companies Visual Studio Microsoft Rewards Free downloads & security Education Gift cards Licensing Unlocked stories View Sitemap

By builders, for builders.

A Microsoft publication

Stop restricting the agent. Start restricting its environment.

Human review improves safety but limits autonomy. Standing credentials preserve autonomy but increase risk. With Azure SRE Agent, we found a safer middle by moving control out of the model and into the runtime around it.

Azure SRE Agent gives an LLM tools, a code execution environment, and access to production resources. The first question most people ask is: โ€œHow is that safe?โ€ 

The instinctive answer is to restrict the agent. Least-privileged scopes. Short-lived credentials. A human approval gate in front of anything that mutates state. All of that helps, and we do all of it. 

But after a year in production, we learned that restriction is only half the answer. A useful agent needs the capability to reason, the authority to act, and the agency to carry work through to completion. It must gather evidence, choose between tools, and act on what it finds. The same authority that makes an agent useful is also what makes it risky. 

Human review is the obvious mitigation, and it remains the right boundary for irreversible, high-consequence actions. But if every meaningful action requires approval, the human is still operating the system one click at a time. The agent hasnโ€™t removed the operational burden; it has only changed the interface. Rather than simply restricting the agent, the design problem is figuring out how to make a much larger class of actions safe enough to execute autonomously. 

So, we start from a harder assumption: the agent will eventually do the wrong thingโ€”whether itโ€™s talked into it by a poisoned log line or simply going wrong on its own. A prompt can tell the agent what it should do, but it can’t guarantee what the agent will do. The same is true of controls implemented inside the environment the agent can inspect or influence. To the agent, a control within reach is just one tool call away from being bypassed. 

The enterprise version of this problem is harder, because a shared agent serves readers, operators, and admins at once. โ€œCan the agent do this?โ€ splits into multiple questions: Who is asking? What authority do they carry? What can the execution environment reach? Where do the credentials live while it runs? 

But the safer platform isnโ€™t the one with the most approval gates. To maximize safety, you need to move the controls outside the agentโ€™s reach. Inside its execution environment, the agent stays fully capable. Outside it, the enforcement layer decides what the environment can reach, what authority each operation carries, and when a human enters the loop. Authority is issued per task and expires with it. Prohibited behavior isnโ€™t discouraged; it fails to execute. 

We rebuilt Azure SRE Agent around this model. What follows traces each boundary we introduced, the gap it exposed, and how moving enforcement out of the agent let us increase autonomy without treating safety as a matter of trust.

Right intentions, unsafe outcomes

Letโ€™s start with where we got it wrong. The failures that changed our architecture werenโ€™t clever attacks. They were normal agent behavior pointed at an environment that allowed the wrong outcome. 

None of these needed an adversaryโ€”thatโ€™s the point. An adversary just makes it all worse for free: every channel the agent reads can be written to by someone you donโ€™t trust, and at the execution layer, a hallucinated command and an injected one are the same command. The recent public disclosure of a coding agent steered into reading `/proc/self/environ` and finding a live API key is just the OCR story with malice added. 

If you strip away the good intentions, there are three classes of attacks: 

Underneath all four incidents is the same interaction pattern: the agent sits between things it reads and things it can act on. Every inbound channel can carry untrusted instructions. Every outbound channel can leak sensitive data or change production.

That forced the shift: If the environment permits it, the agent will eventually do itโ€”intentionally, maliciously, or by accident. The environment is the policy.

If the environment permits it, the agent will eventually do itโ€”intentionally, maliciously, or by accident. The environment is the policy.

So we moved the policy boundary outside the agentโ€™s reach, converging on four enforcement layers that close the gaps.

1. Sandboxing: Get execution out of the trust boundary

Like many agents, our first design ran the harness itself, model-authored code, tools, and credentials together on the same machineโ€”a pattern inherited from coding assistants. The harness is the control plane: it drives the loop, enforces policy, registers tools, and fetches credentials. Every path from the agent to the rest of the platform runs through it. That works better when thereโ€™s a human in the loop. Autonomous agents keep the layout but lose that immediate oversight, leaving model-authored code with the hostโ€™s network, filesystem, and identity. 

The GitHub incident was possible because the harness sat on a filesystem the agent could read: when the agentโ€™s token expired, it pulled the OAuth flow out of the harnessโ€™s own source and ran it itself. Better in-process checks wouldnโ€™t have closed the gap: a policy hook can inspect a command before it runs, but the agent can inspect the hook right back – modify it, kill it, route around it. The code being governed can interfere with the machinery governing it. 

Co-residency cut the other way, too: model-authored code had the hostโ€™s network. The OCR incident was possible because nothing stood between the agent deciding to send customer data and the request leaving the machine. The prompt said not to. The network still allowed it. The same co-residency also puts platform secrets within reach, often one file read away in places like /proc/self/environ from model-authored code, injected or not.

So we split the system into two. Agent reasoning and orchestration stay in a trusted runtime. Model-authored code and tools run in a per-agent microVM, connected back to the runtime over a narrow API surface. Inside the VM, the agent keeps full control: inspect files, launch processes, install packages. The agent canโ€™t touch the machinery governing itโ€”provisioning, tool mounting, policy, credential flowsโ€”none of which shares its filesystem. Platform secrets stay outside it, and egress is default-deny at a boundary the model canโ€™t modify. The agent may still attempt the OCR call; it simply canโ€™t leave. 

We chose microVMsโ€”built on ACA Sandboxesโ€”over containers because containers share the host kernel. For arbitrary model-authored code, we wanted each agent to have its own kernel behind a hardware-virtualized boundary without sacrificing interactive startup times. 

But isolation leaves a gap: tools still need to authenticate. Put credentials inside a microVM, and they become accessible to everything running there, including model-authored code, dependencies, and local MCP servers. 

The sandbox needs to use credentials without possessing them.

2. Nothing worth stealing

Isolation moved platform secrets out of the runtime into the tool execution sandbox. But tools still need to authenticate. az needs an Azure token. git needs repository access. kubectl, MCP servers, and package registries need credentials of their own. 

Put those credentials inside the microVM, and itโ€™s still a credential-bearing environment. Every tool, generated script, dependency, and local MCP server runs beside the agentโ€™s working identity. 

Egress allowlists arenโ€™t enough. The destinations a stolen credential would be sent to are often the same ones the agent must reach. GitHub, Azure, Kubernetes, and registries must all remain accessible. 

So we adopted a stronger rule: The sandbox can use credentials, but it canโ€™t possess them.

The sandbox can use credentials, but it canโ€™t possess them.

We did not rewrite git, az, kubectl, or existing SDKs. They still request authentication through their normal paths but receive an opaque handle instead of a real credential. The handle is useless outside the egress proxy that transforms it. 

The proxy does the real exchange at the boundary. Each handle is valid for exactly one tool call, one destination, one operation, and one use: 

Only after those checks pass does the proxy inject a short-lived credential into the outbound request. The credential never enters the VMโ€™s filesystem, environment, process memory, tool output, model context, logs, or memory. 

This closes the failure mode from our GitHub incident. An expired token no longer gives the agent an opportunity to reconstruct OAuth, capture refresh credentials, and turn its sandbox into a credential store. Credential issuance and refresh terminate outside the VM.

But authentication credentials are only one class of sensitive data. A legitimate tool call can return secrets as data: az command can retrieve a key or connection string, just as a repository, configuration file, or log can contain passwords, tokens, or keys as ordinary text. In those cases, the secret can still enter the sandbox as part of the requested operation. 

That was our memorized secret incident. The agent found a customer credential during an investigation, quoted it in its findings, and saved it to memory with a note never to use it. But the ordering was already wrong: once the value had entered model context, a note not to use it couldn’t undo the exposure. The secret had already propagated into memory, sub-agents, and investigation notes. 

This requires a second boundary, which we are piloting internally: inspecting and scrubbing sensitive tool output before it enters model context. 

The rules are simple: Real credentials never enter the sandbox. Raw secrets never enter the model.

Real credentials never enter the sandbox. Raw secrets never enter the model.

At this point, the agent can authenticate without acquiring durable credentials and investigate without ingesting recognized secrets. But neither guarantee prevents an authorized action from being wrong.

3. Authority without blanket approval

Secretless authentication determines how the agent reaches production systemsโ€”but not which production effects may proceed unattended. 

The VM incident exposed that gap. The agent didnโ€™t steal a token, bypass egress, or leak data. It used a valid path to take a production action, but the action was wrong. When its safety checks became unavailable mid-run, it should have stopped and escalated. Instead, it matched the situation to a past trajectory and deallocated the VMโ€”through a path the approval policy never intercepted. 

Thatโ€™s the other half of agent safety: not whether the agent can perform an operation, but whether it should perform this operation, now, against this target, given this evidence. 

Our current production boundary is simple: every mutation requires human approval. Reads stay autonomous, writes wait for approval, deletes are blocked. Itโ€™s safe, but it treats every change alike. The hard cases sit in between – restart this instance, scale this service, drain this node, deallocate this VM. No policy can classify these from the command alone. The same operation is routine or catastrophic depending on three inputs: 

Anthropicโ€™s Claude Code auto mode and Metaโ€™s agent guardrails point in the same direction: classify each action before letting it run unattended. So, we treat approval as a risk-classification problem rather than a permission check. Before execution, an independent guard – outside the agent’s reasoning loop – scores the proposed action against all three inputs: what it does, what it touches, and whether the evidence behind it is current and corroborated. Low-risk actions with current evidence proceed. Critical targets, or actions with insufficient evidence, stop for review.  

Weโ€™re still building this layer out, and itโ€™s where our design is least settled. But it already unlocks event-driven operation: an incident, a failed deployment, or a scheduled task can start an investigation with no human in the chat. The agent gathers evidence, takes the actions classified as low-risk, and pauses exactly where the remaining authority requires a person. The unit of approval is not the command. Itโ€™s the operation, its target, and its evidence.

The unit of approval is not the command. Itโ€™s the operation, its target, and its evidence.

Everything above assumes the agent is acting autonomously. But when a human enters the loop, it acts on behalf of that personโ€”and with the agent being a shared team resource, the question shifts from, โ€œIs this action safe?โ€ to, โ€œIs this user allowed to cause this action?โ€ Thatโ€™s the next boundary.

4. Nothing to borrow

The previous layer decides whether an action is safe enough for the agent to perform unattended. A shared agent canโ€™t answer that question with one sandbox, one tool set, one memory, and one identity for everyone. Doing so creates a confused deputy: a low-privilege user can borrow capabilities they donโ€™t hold directly or modify shared state that influences a more privileged session later. 

Shared memory makes the problem concrete. A user can teach the agent behavior that persists beyond that userโ€™s authority. The same path exists through connectors, skills, hooks, and other shared configurations. The agent canโ€™t be expected to remember which parts each user may influence. 

The callerโ€™s role must shape the environment before reasoning begins. Readers can observe but not drive the agent. Users can chat without modifying shared behavior. Operators can manage shared surfaces without approving high-privilege actions. Administrators can explicitly approve or delegate that authority. 

These roles arenโ€™t prompt instructions. They determine which tools and MCP servers are mounted, which resources the sandbox can reach, which memory is visible or writable, which credentials may be injected, and which actions require approval. 

The rule is monotonic: the callerโ€™s authority may be narrowed by the environment, but it must never be widened by the agent. A low-privilege request canโ€™t be laundered through shared memory, a shared connector, an alternate tool path, or a high-privilege service identity. 

The agent has nothing to borrow because there is no ambient authority outside the callerโ€™s delegation chain. Rather than something the model remembers, policy is the environment instant for that user.

Autonomy through constraint 

Model guardrails matter, but production safety canโ€™t depend on them working every time. We already accept this with people: no one hands an operator root and promises to be careful. We give them scoped identities, just-in-time access, network boundaries, change control, and audit trails. Judgment is the first line of defenseโ€”never the only one. 

Agents need the same backstops at a different cadence. An agent can make hundreds of tools calls in a single incident, replan between any two of them, and reach the same effect through three different tools. Approve every step and autonomy disappears; approve only the plan and everything after it runs unchecked. So, the question was never whether to keep policy gates. Instead, the question was where to put them: at runtime, as close as possible to each production effect, with human review reserved for the consequences the system canโ€™t bound on its own. 

That’s what the four layers are: one move, repeated. We opened with the questions a shared agent forces: Who is asking? What authority do they carry? What can the environment reach? Where do the credentials live? Each layer answers one of those questions in the runtime instead of the prompt. 

Across the four layers, the design principles are the same:  

Where it still breaks 

The system isn’t complete, and we still discover gaps in our enforcement layers. Examples of gaps we closed recently: an action blocked through one tool could still be reached through a different execution channel that bypassed hooks. In another case, an MCP server could silently widen its contract after onboarding, and the protocol had no mechanism to detect the change. 

As these gaps surface, we improve our implementation. But our security principles stay invariant:

Better models will make mistakes rarer. They wonโ€™t shrink the blast radius when a mistake still happens. A smarter model shifts where the line falls between autonomous action and human reviewโ€”more actions cleared as low-risk, more investigations that run start to finish without a human in the chat. But that line is drawn by the controls, not by the model. What microVM can reach, where credentials live, whose authority a session carries.

Five questions for agent platform builders 

The four incidents ultimately changed the questions we asked in review: 

If the answer to any of those questions was โ€œno,โ€ we werenโ€™t running a guarded agent. These are questions worth asking of any agent platform, including our own.ย 

Thatโ€™s what we mean when we say: The environment is the policy.


We also thank Zhenquan Xu, Hong Wang, Yefu Wang, and Eben Carek for their contributions to this work.