Meta Description: A deep technical walkthrough of Microsoft Foundry's network egress controls for hosted agents — how the RAI egress policy engine enforces Allow, Deny, Transform, and Rewrite rules, how Audit vs Enforced mode actually behaves, and how to test the boundary instead of trusting the agent's answer.
The question nobody puts in the design doc
You give an agent two tools: "look up a vendor" and "check a payment record." Code review is clean. Unit tests pass. Then a document the agent reads contains an unfamiliar upload link, or a dependency you didn't audit quietly follows a redirect, and the process makes an HTTP call to a host nobody approved.
The failure here isn't a bad tool. It's that "which tools did I give the agent" and "where can this process send a request" are two completely different questions, and most teams only answer the first one. A tool is a function signature with your name on it. The runtime process behind that tool can, in principle, open a socket to anywhere the sandbox's network stack allows — including destinations no tool definition ever mentions.
Microsoft Foundry's answer to this, shipped as a preview capability on Foundry Hosted Agents, is a declarative, policy-engine-enforced network egress control layer that sits between your agent's compute and the internet. It isn't a code pattern you implement inside an HTTP wrapper. It's a reviewable, versioned artifact attached to the agent definition itself, evaluated by a proxy the application code never sees or controls. This article is a full technical walkthrough of how that layer is built, how it behaves at runtime, how to test it properly, and where its boundaries actually are.
Status check: Network egress controls for hosted agents are in preview as of this writing — no preview SLA, not intended for production workloads yet. Everything below is accurate to the documented behavior, but validate current GA status before you build on it. (verify current GA status before publishing to production)
Table of Contents
- Why a tool list is not a network boundary
- Where egress policy lives in the Foundry architecture
- Anatomy of an egress policy
- Rule actions: Allow, Deny, Transform, Rewrite
- First-match evaluation semantics
- Audit mode vs Enforced mode — the behavior that trips people up
- Building and attaching a policy, end to end
- Testing the boundary, not the agent's answer
- A real-world developer scenario: the invoice agent
- How this relates to VNet injection and identity
- Production considerations
- Security considerations
- Performance and scale considerations
- Common mistakes and pitfalls
- Alternatives and trade-offs
- Practical recommendations
- Conclusion
- References
Why a tool list is not a network boundary
Most agent security reviews stop at the tool registry. "This agent can call get_vendor_record, check_payment_status, and code_interpreter. Therefore its blast radius is these three functions." That reasoning has a hole in it: a tool function is just a wrapper around an HTTP client, and an HTTP client will happily talk to any host that resolves and accepts a TCP connection, unless something outside your code stops it.
There are at least three realistic ways a hosted agent ends up calling a destination nobody signed off on:
- Prompt-injected URLs. A document, email, or web page the agent reads contains a link, and a tool or library the agent uses follows it.
- Dependency drift. A helper library gets upgraded and starts phoning home to a telemetry endpoint, or a transitive dependency makes a request you never audited.
- Model-generated destinations. With code interpreter or dynamically constructed requests, the model itself can compose a URL that was never in your original design.
A rule written inside a single HTTP wrapper class only helps for calls that go through that exact wrapper. It does nothing for the other two paths. Microsoft Foundry's egress control model deliberately moves the destination decision off the application layer and onto the hosted-agent definition, so the policy is enforced regardless of which code path inside the container tries to make the call.
The distinction matters for how you reason about the agent's blast radius: a tool list tells you what the agent was designed to do; an egress policy tells you what the agent's process is physically able to reach, independent of design intent.
Where egress policy lives in the Foundry architecture
A Foundry Hosted Agent runs as a managed container behind one of the supported protocols (Responses or Invocations — see Day 2 of this series for that distinction). Outbound HTTP/HTTPS traffic from that container does not go directly to the public internet. It is routed through a managed egress proxy that Foundry operates as part of the hosted-agent runtime.
The proxy's behavior is governed by a Responsible AI (RAI) policy resource — the same control-plane object family Foundry already uses for content-safety filtering — extended with an egressPolicy property. This is an important architectural choice: network egress isn't a separate product surface bolted on later, it's a property of the same policy object that governs model content filtering. One resource, two concerns:
-
properties.mode— the content-safety setting (Blocking, etc.) — unrelated to networking. -
properties.egressPolicy.mode— the network enforcement setting (AuditorEnforced).
These are easy to confuse in code review because they're sibling properties on the same JSON body with similar-sounding names. Treat them as orthogonal: one governs what the model is allowed to generate, the other governs what the process is allowed to reach.
The policy resource lives under the Cognitive Services / Azure AI Services account as a raiPolicies child resource, managed through the Azure Resource Manager control plane (Microsoft.CognitiveServices/accounts/raiPolicies, API version 2026-05-15-preview at time of writing). An agent version references exactly one RAI policy via rai_config.rai_policy_name. That single policy can carry many egressPolicy.rules, so you compose everything a given agent needs — multiple allowed hosts, header transforms, rewrites — inside one policy object rather than attaching a list of policies.
This "one policy, many rules" shape has a practical consequence: if you want different agents to have different egress footprints (which you almost always do), you provision a catalog of policies and bind each agent version to the one appropriate for it, rather than trying to share a single permissive policy across every agent in the project.
Anatomy of an egress policy
Here is a minimal policy body, the kind you'd PUT to the raiPolicies control-plane endpoint:
{
"properties": {
"basePolicyName": "Microsoft.DefaultV2",
"mode": "Blocking",
"egressPolicy": {
"mode": "Audit",
"defaultAction": "Deny",
"rules": [
{
"name": "allow-finance",
"ruleType": "Fqdn",
"match": { "host": "finance.contoso.example" },
"action": { "actionType": "Allow" }
},
{
"name": "allow-vendors",
"ruleType": "Fqdn",
"match": { "host": "vendors.contoso.example" },
"action": { "actionType": "Allow" }
}
]
}
}
}
Breaking down the fields that matter:
| Field | Purpose |
|---|---|
basePolicyName |
The underlying content-safety base policy this egress policy extends. |
properties.mode |
Content-safety blocking behavior — not the network setting. |
egressPolicy.mode |
Audit (log, don't block) or Enforced (actually block). |
egressPolicy.defaultAction |
What happens when no rule matches — typically Deny. |
rules[] |
An ordered list evaluated first-match wins. |
rules[].ruleType |
Currently Fqdn — match by hostname (exact or wildcard, e.g. *.org). |
rules[].match |
The host (and optionally path) a request must hit to trigger this rule. |
rules[].action |
Allow, Deny, Transform, or Rewrite. |
You create and update this resource with az rest, because it's a preview ARM template shape not yet wrapped by a dedicated az subcommand:
SUBSCRIPTION_ID="<your-subscription-id>"
RESOURCE_GROUP="<your-resource-group>"
ACCOUNT_NAME="<your-foundry-account>"
az login
az account set --subscription "$SUBSCRIPTION_ID"
ACCOUNT_ID="/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${RESOURCE_GROUP}"
ACCOUNT_ID="${ACCOUNT_ID}/providers/Microsoft.CognitiveServices/accounts/${ACCOUNT_NAME}"
export RAI_POLICY_ID="${ACCOUNT_ID}/raiPolicies/invoice-egress-audit"
POLICY_URL="https://management.azure.com${RAI_POLICY_ID}?api-version=2026-05-15-preview"
az rest --method put --url "$POLICY_URL" \
--headers "Content-Type=application/json" \
--body "@invoice-egress.json"
Always read the policy back before wiring it to a live agent version — a GET tells you the configuration was stored correctly, but it tells you nothing about runtime enforcement. That's a separate verification step, covered below.
az rest --method get --url "$POLICY_URL" \
--query "{id:id,egressPolicy:properties.egressPolicy}" \
--output json
Rule actions: Allow, Deny, Transform, Rewrite
This is where the feature goes beyond a basic allow-list firewall. There are four action types, and the last two are easy to overlook if you think of egress control as a binary permit/block decision.
Allow — forward the request unmodified to its original destination.
Deny — return a 403 to the caller; the request never reaches the destination (in Enforced mode).
Transform — forward the request, but mutate headers first. Three operations: Insert (add only if absent), Set (always overwrite), Remove (strip a header). Useful for injecting a non-secret workload tag the destination requires, or stripping a header the agent runtime sets by default but you don't want to leak.
{
"name": "insert-custom-header",
"ruleType": "Fqdn",
"match": { "host": "httpbin.org" },
"action": {
"actionType": "Transform",
"headers": [
{ "name": "X-Custom-Tag", "value": "my-value", "operation": "Insert" }
]
}
}
Rewrite — redirect the request to a different host and/or path than the one the agent asked for. This is a deliberate substitution, not a redirect response — the proxy itself sends the request to the new destination.
{
"name": "rewrite-host",
"ruleType": "Fqdn",
"match": { "host": "www.google.com" },
"action": {
"actionType": "Rewrite",
"rewrite": { "scheme": "https", "host": "www.bing.com" }
}
}
Rewrite is particularly useful when an approved backend service moves behind a new endpoint and you don't want to redeploy every agent that calls it — you update one rule in the policy instead of chasing down hardcoded URLs across containers.
First-match evaluation semantics
The proxy evaluates rules[] in array order and stops at the first rule whose match matches the request. If nothing matches, defaultAction applies. This has a non-obvious implication worth internalizing: rule order is part of your security posture, not just a convenience.
Consider two rules targeting the same host, where specificity doesn't save you from ordering mistakes:
{
"rules": [
{
"name": "deny-httpbin-ip",
"ruleType": "Fqdn",
"match": { "host": "httpbin.org", "path": "/ip" },
"action": { "actionType": "Deny" }
},
{
"name": "allow-httpbin-all",
"ruleType": "Fqdn",
"match": { "host": "httpbin.org" },
"action": { "actionType": "Allow" }
}
]
}
With this order: GET /get matches nothing in rule 1, falls to rule 2, is allowed. GET /ip matches rule 1 first and is denied — even though rule 2 would also match it. Swap the order and the deny rule becomes unreachable dead code, silently. There's no "longest match wins" or "most specific wins" semantics here — it's pure array order. Code review for an egress policy has to read it top to bottom like a firewall ruleset, not scan it like an unordered set of permissions.
The same first-match logic applies across action types, not just Allow/Deny. If an Allow rule for a host appears before a Transform rule for the same host, the Transform never executes — the Allow rule already terminated the lookup. If you need both forwarding and header mutation for the same destination, express it as a single Transform rule (Transform implicitly allows the request through after mutating it) rather than two separate rules.
Audit mode vs Enforced mode
This is the single most important operational distinction in the whole feature, and it is not a blanket "dry run flag." Audit mode behaves differently depending on which action type is involved:
| Action | Audit mode | Enforced mode |
|---|---|---|
| Allow | Forwards, logs decision | Forwards, logs decision |
| Deny | Forwards anyway, logs a would-deny event | Blocks with 403, request never leaves the proxy |
| Transform | Applies the transform, forwards | Applies the transform, forwards |
| Rewrite | Applies the rewrite, forwards | Applies the rewrite, forwards |
Read that Deny row again: in Audit mode, a Deny rule does not deny anything. It's purely observability — the proxy logs that it would have blocked the call, but the request still goes out. Transform and Rewrite, by contrast, are not gated by egressPolicy.mode at all — they execute in both modes, because they're not "enforcement" actions in the blocking sense, they're active request modification. If your mental model of Audit is "nothing happens to traffic, we just watch," you will ship a Transform or Rewrite rule believing it's inert in Audit mode, and it won't be.
The practical workflow this enables: deploy a policy in Audit mode first, point real (or representative synthetic) traffic at it, and inspect the logged would-deny decisions — via the invocation's network egress decision spans or the project's Application Insights records — before you ever flip to Enforced. This gives you a data-driven way to build your allow-list instead of guessing every hostname the agent might legitimately need, which is exactly how most real egress policies should be developed: observe first, enforce second.
One more trap: a missing deny event in your logs is not proof a call was allowed. Always correlate the policy's logged decision with an independent signal — the destination's own receipt log, or an explicit probe response — before concluding "this was allowed because I didn't see a deny."
Building and attaching a policy, end to end
Attachment happens at agent version creation time, via the azure-ai-projects Python SDK (>=2.2.0) and a RaiConfig object that references the policy's full ARM resource ID — not just its short name.
import os
from azure.identity import DefaultAzureCredential
from azure.ai.projects import AIProjectClient
from azure.ai.projects.models import (
AgentEndpointProtocol, ContainerConfiguration,
HostedAgentDefinition, ProtocolVersionRecord, RaiConfig,
)
with AIProjectClient(
endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
credential=DefaultAzureCredential(),
allow_preview=True,
) as project:
version = project.agents.create_version(
agent_name="invoice-agent",
definition=HostedAgentDefinition(
cpu="1",
memory="2Gi",
container_configuration=ContainerConfiguration(
image=os.environ["AGENT_IMAGE"],
),
protocol_versions=[
ProtocolVersionRecord(
protocol=AgentEndpointProtocol.RESPONSES,
version="2.0.0",
)
],
rai_config=RaiConfig(
rai_policy_name=os.environ["RAI_POLICY_ID"],
),
),
)
print(f"Created {version.name}, version {version.version}")
Two details that will save you a debugging session:
-
An agent binds to a single RAI policy, not a list. If you need multiple guardrail "patterns" available, provision a catalog of policies up front (e.g.
allow-httpbin,transform-insert-header,rewrite-host,deny-all,wildcard-host) and pick which one a given agent version'srai_configpoints to. You don't compose by attaching several policies; you compose by writing more rules into one. -
Changing a policy's rules does not retroactively affect already-pinned agent versions in flight. Create a new agent version when you change egress behavior, direct test traffic to that specific version, and confirm its
activestate before trusting results. Don't assume editing the policy JSON and re-reading it means every running session now behaves differently — version pinning is explicit.
For infrastructure-as-code deployments, the pattern is to provision the Foundry project/account layer first, then a dependent Bicep layer that stands up the raiPolicies catalog and exports the chosen policy's ARM ID as an output consumed by the agent's deployment definition — keeping the guardrail as a first-class, reviewable infrastructure artifact rather than an imperative post-deploy script.
Testing the boundary, not the agent's answer
The single biggest methodology mistake here is treating "the agent gave me a sensible-looking final answer" as evidence the network boundary works. It isn't. You need an explicit diagnostic path that proves the request behaved as expected, independent of what the LLM decided to say about it.
Build a dedicated probe tool into your test agent image:
import os
import requests
def run_egress_probe() -> dict[str, int]:
targets = {
"allowed": os.environ["INVOICE_ALLOWED_PROBE_URL"],
"blocked": os.environ["INVOICE_BLOCKED_PROBE_URL"],
}
ca_bundle = os.environ["REQUESTS_CA_BUNDLE"]
results = {}
for label, url in targets.items():
response = requests.get(
url,
verify=ca_bundle,
timeout=10,
allow_redirects=False,
)
results[label] = response.status_code
return results
Key details embedded in that twelve-line function matter more than they look:
-
verify=ca_bundleuses the runtime-provided CA bundle rather than disabling TLS verification. If the egress proxy operates in full TLS-inspection mode, it terminates TLS with its own certificate, and the agent needs to trust that certificate chain to get a clean signal — don't paper over this by disabling verification, since that would also hide a misconfigured proxy. -
allow_redirects=Falseremoves ambiguity about which destination is actually "under test" — a redirect chain can obscure whether the policy evaluated the host you intended. - Running this same function on your laptop tells you nothing. It has to execute inside the deployed hosted-agent container, invoked through the agent's actual Responses/Invocations endpoint, because the egress proxy is a property of that runtime path, not of your network.
Then run the comparison table that actually validates the feature:
| Probe | Audit trial | Enforced trial |
|---|---|---|
| Approved finance endpoint |
200 — request reaches the endpoint |
200 — request reaches the endpoint |
| Unapproved test endpoint |
200 — but logged as a would-deny event |
403 from the proxy — request never reaches the endpoint |
A few failure modes to watch for while reading these results: a destination returning its own 403 is not the same as the proxy denying the request — you need to confirm the request never arrived at the destination's receipt log. Similarly, a DNS failure or TLS handshake failure is not a successful policy denial; it might just mean the destination is unreachable for unrelated reasons. Don't declare the test passed until you've correlated the proxy's decision with the destination's own logs.
A real-world developer scenario: the invoice agent
Pull the pieces together with the running example: an agent that looks up vendor records and checks payment status, with exactly two legitimate destinations.
-
Author the policy in Audit mode with
defaultAction: Denyand twoAllowrules forfinance.contoso.exampleandvendors.contoso.example. - Deploy a test agent version with that policy attached, and run real (or representative) traffic through it for a representative window — a day of QA traffic, a batch of recorded production-like requests, whatever reflects actual usage.
- Pull the Audit logs. Anything that shows up as a would-deny event is either a bug (an endpoint you forgot to allow) or a finding (traffic going somewhere it genuinely shouldn't).
-
Resolve every would-deny event — either add a deliberate
Allow/Transform/Rewriterule for a legitimate destination, or treat an unexplained destination as an incident to investigate before going further. -
Clone the policy to
Enforcedmode, attach it to a new agent version, re-run the same traffic sample, and confirm the approved calls still succeed while everything else now returns403at the proxy instead of reaching the network. - Cut production traffic over to the enforced version only after the comparison table above matches expectations.
Note what this workflow deliberately does not claim to solve: the network policy answers "is this the right destination?" It says nothing about whether the vendor API call is authenticated correctly, or whether the invoice amount the agent is sending is the right one. Those are identity and application-logic questions, handled by separate controls — the policy engine is not a substitute for authentication, authorization, or business validation. An approved destination can still reject an unauthorized caller; an authorized caller can still send the wrong payload to an approved destination. Keep the three questions — right destination, authorized caller, correct data — separate, because a single control answering "yes" to one of them tells you nothing about the other two.
How this relates to VNet injection and identity
Day 11 of this series covered Foundry Agent Service's private networking model — VNet injection, subnet IP capacity planning, and private endpoints for inbound connectivity to the Foundry control and data planes. Egress policy is a different axis entirely: it governs outbound calls the hosted agent's own process initiates, independent of whether the agent's inbound surface is public or privately networked.
You can — and in regulated environments typically should — combine both: a VNet-injected Foundry resource with private endpoints limiting who can reach your project, and an egress policy limiting what the agent's runtime can reach outward. Neither replaces the other. VNet injection is about the network path into Foundry; egress policy is about the network path out of a specific hosted agent's compute.
Similarly, the Toolbox/MCP governance model covered earlier in this series (Day 6) answers "who does the agent act as when it calls a tool" — an identity and authorization question. Egress policy answers "where can the agent's process connect" — a network question. A toolbox can restrict which MCP tools are callable and under which identity; it says nothing about whether the container process itself, outside the toolbox's mediation, could open a socket somewhere else. Treat these as complementary layers in a defense-in-depth stack, not substitutes for each other.
Production considerations
- New resource, not a retrofit. Don't bolt egress rules onto an existing mixed content-safety/network policy you rely on elsewhere — create a dedicated policy resource for this purpose so a misconfiguration doesn't also break unrelated content filtering.
- Version pinning discipline. Every policy change should correspond to a new agent version, tested independently, before production traffic is redirected to it. Treat egress policy changes with the same rigor as a code deployment — because functionally, that's what they are.
- Managed identity in Transform rules is supported for value references (with appropriate RBAC on the target resource), but secret references are not supported during preview — don't try to inject credentials through header Transform rules while this limitation holds.
-
Platform connectivity is allowed separately. A
defaultAction: Denypolicy is not a claim that literally every runtime connection is blocked — Foundry's own required platform connectivity (telemetry, control-plane calls the runtime itself needs) is permitted outside the scope of youregressPolicy.rules. Don't interpret "deny all" as "the agent container has zero network access"; it specifically governs the application-traffic path you defined the policy for.
Security considerations
- Scope the guarantee honestly. This walkthrough, and the public sample it's based on, scopes the control to the documented hosted-agent HTTP/HTTPS path. Don't extrapolate "egress is controlled" to arbitrary protocols or other Foundry agent hosting models without verifying the specific surface you're using actually routes through this proxy.
- Destination approval is not destination authentication. Allowing a host does not grant the agent any credentials or authorization at that host — your application must still handle authentication and authorization to the finance and vendor APIs independently. A network Allow rule is a necessary condition for legitimate traffic, never a sufficient one for security.
-
Audit Deny is observability, not a safety net. Don't run a sensitive workload against a policy you believe is restrictive while it's still in
Auditmode — a Deny rule in that mode does not stop anything from reaching its destination. -
Wildcard hosts are a blast-radius decision.
*.orgmatches every subdomain under every.orgregistration, not just the ones you intend. Prefer exact FQDNs unless you have a specific, bounded reason to use a wildcard, and document that reason in the rule'sname.
Performance and scalability considerations
Every outbound call from a hosted agent now incurs a proxy hop for rule evaluation. For latency-sensitive agents making many small outbound calls per turn (e.g., an agent that calls multiple grounding APIs per response), measure the added round-trip rather than assuming it's negligible — first-match evaluation over a short, well-ordered rule list should add low single-digit milliseconds, but a long, poorly ordered rule list is a different story, and this isn't publicly benchmarked at time of writing (verify this latency characteristic with your own measurements before capacity planning around it). As with any policy engine, keep rule lists as short and as precisely ordered as the scenario allows — both for auditability and for the proxy's evaluation cost.
Cost considerations
The egress policy feature itself, as a property of an existing RAI policy resource, doesn't introduce a separate line-item cost in the way a dedicated Azure Firewall or NAT Gateway resource would — but if you combine it with VNet injection and private endpoints (the complementary inbound control from Day 11), you're paying for that networking infrastructure regardless. Budget for the full private-networking stack if your compliance posture requires both inbound and outbound controls, not just this one layer. (verify current pricing model before budgeting, since preview features commonly change their cost structure at GA)
Common mistakes and pitfalls
-
Confusing
properties.modewithproperties.egressPolicy.mode. They're sibling fields with similar names governing completely different things — one is content safety, one is network enforcement. A reviewer skimming the JSON can easily assume they're linked. - Assuming Audit mode is a universal dry run. It only neutralizes Deny. Transform and Rewrite rules execute for real in Audit mode — if you're testing a Rewrite rule in Audit before going to Enforced, understand that it's already actively redirecting traffic, not simulating a redirect.
- Treating rule order as cosmetic. First-match semantics mean a more specific Deny placed after a broader Allow for the same host is dead code. Review egress policies top-to-bottom like a firewall ruleset.
- Testing on a workstation instead of the deployed container. The egress proxy only sits in the path of the hosted-agent runtime. Running your diagnostic function locally proves nothing about the deployed boundary.
- Treating a missing log entry as proof of denial, or a destination-side 403 as proof the proxy worked. Both require correlating the proxy's own decision record with an independent signal — the destination's receipt log or an explicit probe response.
- Forgetting that an agent binds to exactly one policy. Trying to "attach multiple policies" for layered rules is not the supported shape — consolidate all the rules an agent needs into the one policy it references.
- Disabling TLS verification to make a probe "pass." If the proxy does TLS inspection, the correct fix is trusting the runtime-provided CA bundle, not disabling verification — disabling verification can mask a genuinely misconfigured proxy.
Alternatives and trade-offs
| Approach | What it controls | Where it's enforced | Trade-off |
|---|---|---|---|
| Foundry egress policy (this article) | Outbound FQDN-level allow/deny/transform/rewrite for hosted-agent traffic | Managed proxy in the hosted-agent runtime path | Preview-only, hosted-agent-scoped, declarative but limited rule types (FQDN matching only) |
| VNet injection + NSGs/Azure Firewall | Full network-layer control, inbound and outbound, across all resources in a subnet | Azure networking layer | Broader scope and maturity, but heavier infrastructure and not specific to agent semantics |
| Application-layer allow-lists (in code) | Whatever the specific HTTP client wrapper checks | Inside your container's code | Easiest to implement, weakest guarantee — bypassed by any code path that doesn't go through the wrapper |
| Private endpoints for destination APIs | Restricts who can reach the destination, not what the agent can call | Destination-side networking | Good complementary control, doesn't stop the agent from attempting unrelated destinations |
These aren't mutually exclusive. A mature production posture layers application-level input validation, Foundry's egress policy for agent-specific outbound governance, and broader Azure network controls (VNet, private endpoints, Azure Firewall) for defense in depth — each catching failure modes the others don't.
Practical recommendations
- Start every egress policy in
Auditmode, run representative traffic, and build your allow-list from observed legitimate calls rather than guessing upfront. - Keep rule lists short, specific (exact FQDNs over wildcards), and ordered deliberately — document the reasoning for ordering in rule names or an adjacent comment in your IaC.
- Build the probe-tool pattern into every hosted agent you deploy with an egress policy, and run it as a standard part of your deployment pipeline, not a one-off manual check.
- Never let an egress policy change go live without creating a new agent version, confirming its
activestate, and re-running the probe comparison table against it. - Pair this with the VNet/private networking controls from Day 11 and the Toolbox/MCP identity governance from Day 6 — none of the three replaces the other two.
Conclusion
Network egress control reframes a question most agent teams never ask explicitly: not "what did I design this agent to do," but "what can this agent's process actually reach." Microsoft Foundry answers it by moving the destination decision out of application code and into a reviewable, versioned policy object — evaluated by a proxy the agent's code never touches — with first-match Allow/Deny/Transform/Rewrite rules and a genuinely useful Audit-before-Enforce workflow.
The feature is still preview, the rule model is intentionally narrow (FQDN matching, not arbitrary protocols), and it answers exactly one of the three questions that matter for a tool call — destination, authorization, and data correctness — leaving the other two to your existing identity and application controls. Used for what it's scoped to do, it closes a real gap: the difference between a tool list you can audit in a pull request and a network boundary you can actually test.
If you're running hosted agents today, the actionable next step isn't "turn on Enforced mode everywhere." It's "deploy one Audit-mode policy on your highest-risk agent, look at what it actually calls, and see if the picture matches what you assumed."
References
- Add guardrails to a hosted agent — network egress controls (Microsoft Learn)
Microsoft.CognitiveServices/accounts/raiPoliciesARM template referenceRaiConfig— azure-ai-projects Python SDK reference- Egress Control Test Agent sample (foundry-samples, GitHub)
- Building agents that act on your behalf with Toolboxes in Foundry (Microsoft Foundry Blog)
- Deploy a hosted agent (Microsoft Learn)
- Keep destination rules outside your agent code: egress controls walkthrough (Microsoft Foundry Blog)
This is Day 20 of the Microsoft Foundry 100 Days / 100 Blogs series — one deep technical article a day covering the breadth of Microsoft Foundry for developers, AI engineers, and architects.


Top comments (0)