An agent is most of the way through a task when it tries to reach a destination its egress proxy does not allow. What comes back is an opaque failure: a connection reset, a timeout, a 403 with no body. So the agent does what a language model does with an unexplained failure. It retries. It reformulates the request. Eventually it gives up, or it improvises around the wall, which is worse. Every one of those moves is a guess, because the block carries none of what a right next move would need. The agent cannot tell whether the destination is wrong, down, malicious, or simply not approved yet. It cannot tell whether the block is requestable, who could approve it, how long approval would take, or whether retrying later would matter. There is no machine-actionable way for it to ask for access, and no human standing by at the moment of the block.

That is the wrong shape. The block is not just a network failure. It is an authorization denial at the egress boundary, and for many agent tasks that denial should be requestable. This is the egress version of a problem I wrote about for policy decisions in Authorization Denied Is No Longer Enough. A deny is increasingly not a full stop. It is the beginning of an escalation. At the network egress boundary the same thing is true, and the enforcement point is the proxy.

Not every wall works that way, and it helps to say so up front. Some blocks are appealable and some are architectural. Security and governance blocks can be requestable, because some authority can say yes. A platform block, where the runtime cannot route outside its own vendor’s ecosystem, leaves no policy decision to appeal. This post is about the first kind, and comes back to the difference at the end.

Egress Allowlists Cannot Always Be Enumerated Upfront

Agent runtimes commonly route outbound traffic through an egress proxy that enforces a destination allowlist, and this is already standard practice rather than a hypothetical. Anthropic’s sandbox-runtime puts a proxy outside the sandbox that allows no domains by default and permits only the hostnames you configure, and the same allowlist model sits behind Claude Code’s managed sandbox (secure deployment guidance).

Open-source agent egress proxies like Brex’s CrabTrap sit between the agent and external APIs and evaluate every outbound request against allow and deny policies, returning a 403 on a block, and current guidance for running autonomous agents treats a network egress allowlist as a baseline control. They share one behavior: when a destination is not on the list, the agent gets a failure, not a way forward. That model assumes you can enumerate the destinations ahead of time. Agents increasingly do not work that way.

As I described in OAuth for Open-World Ecosystems, modern agents discover APIs dynamically, interact with previously unknown systems, and expand workflows during execution. A renewal assistant that started with a known set of endpoints discovers mid-task that the answer lives behind a partner API it has never called. A host-level allowlist provisioned at startup cannot accommodate that. The deployment is left with two bad options: over-provision egress so broadly that the proxy stops being a control, or block the destination and let the task stall. Bounded, high-assurance workloads can and should still enumerate egress ahead of time. The claim here is narrower. Dynamic, open-world agent tasks cannot always name every legitimate destination before execution, and those are exactly the tasks agents are being hired for.

The block is the honest behavior. The problem is what the block does not carry.

The Block Is a Requestable Denial, and the Proxy Is a PEP

Reframe the egress proxy as a policy enforcement point. The dropped connection is a decision, and that decision could be a requestable denial: access is currently denied, but an escalation path is available. The agent could ask for the destination, a governance event could occur, and the proxy could re-evaluate and let the traffic through.

What is missing is not the decision. It is the transport. A headless agent has no browser, no consent screen, and no human in the loop at the moment of the block. So how does it discover that a block is requestable, learn where to ask, and find out when it has been let through?

When the call crosses a tool boundary instead of a raw socket, this problem already has an answer at that layer. MCP’s proposed structured authorization denials carry exactly this shape, a machine-actionable denial with remediation hints returned to the agent’s orchestration loop, covered in the least-privilege series. The tool boundary is the better escalation surface where it exists, because it sees operations rather than bytes. The egress profile below is for everything that boundary cannot see: HTTP the agent’s code makes directly, destinations discovered outside any tool wrapper, and the floor beneath a tool layer that is misconfigured or bypassed. The two are siblings with the same discipline, and the rest of this post is about the floor.

The Carrier Depends on Where the Block Lands

The floor is not one surface. An egress denial can land at an explicit HTTP proxy that can answer with a real response, inside an authorization-aware exchange, or as a bare socket reset in an opaque tunnel, and each position has a natural carrier that already exists.

Where the block landsNatural denial carrier
Explicit HTTP forward proxyAn HTTP response with Proxy-Status (RFC 9209) naming the error and a Problem Details (RFC 9457) body carrying the recovery document
AAuth-aware boundaryThe protocol’s own challenge, in-band
MCP tool boundaryThe structured authorization denial above
Opaque tunnel, firewall drop, or socket resetAn authenticated side-channel status API, the case the rest of this post profiles
The whole network sessionRFC 8908 as designed

Where an HTTP response exists, use it. Proxy-Status was designed for intermediaries to describe the errors they generate, and a Problem Details body can carry the same structured recovery members this post defines. The hard case is the one with no response surface at all, a reset inside a tunnel the proxy cannot speak into. That case needs a side channel, and the side channel needs a state machine.

RFC 8908 Supplies the Recovery State Machine

The captive portal is not the proposal. The proposal is that a blocked agent needs a recovery state machine, and RFC 8908 demonstrates one that already shipped.

RFC 8908, the Captive Portal API, is the JSON API behind the coffee-shop and hotel wifi experience. A client queries it and learns whether it is captive, where the portal is (user-portal-url), and how long any granted access lasts (seconds-remaining). It was designed so a device could discover its network restriction without scraping an HTML interstitial.

A blocked agent can be modeled as a captive client. It is on a network, behind an enforcement point, denied egress, and it needs a machine-readable answer to “why am I blocked and how do I get out.” That is the state machine the Captive Portal API demonstrates: restricted, remediation available, re-query after completion. What it does not supply is the rest. A requestable-egress profile still needs its own scoping, discovery, authentication, and extension contract, and RFC 8908’s registry rules mean the members this post proposes are extensions to define and register, not fields the spec already carries.

That is the move both designs in this post make. This is not a claim that per-destination agent egress is what RFC 8908 originally standardized. It is a profile of the same recovery shape: a blocked client queries a known API, receives captive: true, and learns the machine-actionable path to remediation. In this profile, the egress proxy is both the enforcement point and the captive-state authority for the blocked destination, and the blocked response carries the recovery context alongside captive: true: a proposed access-request member at the destination altitude, or an opaque denial-bound resource-token at the operation altitude.

Repurposing a captive-network protocol for headless agents has three costs, and they are worth naming up front because they are the price of the insight, not an afterthought.

Granularity mismatch. RFC 8908 signals captivity at the network level: you are on this network, you are captive or you are not. Egress control is per-destination: this agent may reach destination A but not destination B. Both designs resolve this by scoping the response, not the network. The runtime queries its one configured captive endpoint with the blocked destination, and the answer is a per-denial document carrying that destination’s requestable denial rather than a network-wide state. RFC 8908 defines a GET of one provisioned URI and no destination parameter, so the scoped query is itself part of what this profile has to define. The document is not a bearer capability. The authority lives in the denial binding inside it, authenticated by the proxy and scoped to the subject, destination or operation, proxy identity, policy context, and expiry. A copied response must not open another destination, and the endpoint must not let an unauthenticated caller enumerate what the agent tried to reach.

Discovery into TLS tunnels. Direct configuration is the safe baseline: the agent runtime starts with the egress proxy’s captive API endpoint and queries it when the proxy reports a blocked destination or operation. RFC 8910 provisioning and HTTP 511 help in managed environments, but a proxy cannot inject a 511 into an opaque TLS tunnel without terminating it. Fully tunneled HTTPS egress therefore cannot rely on network injection as the discovery mechanism.

Untrusted URLs. The user-portal-url and venue-info-url in a captive portal response are human-facing and attacker-influenceable. An agent must not navigate them or, worse, feed them to an LLM as instructions. The machine-actionable path is the structured member the response carries, the access-request member or the resource-token, not the portal URL. This matters more for agents than for humans, because the client here is a model that will tend to act on text it reads.

The Minimum Shape

A useful profile does not need to standardize the whole approval system behind the proxy. The minimum flow is short:

  1. The agent or runtime attempts egress.
  2. The proxy blocks the destination or operation.
  3. The runtime queries the configured captive API endpoint for that denial.
  4. The captive response says captive: true and carries a structured recovery member.
  5. The runtime, agent, or proxy submits the requestable denial to the approval service the member names.
  6. On completion, the agent retries.
  7. The proxy re-evaluates before allowing the traffic.

Drawn as the state machine it is, with the failure branches the list leaves implicit:

flowchart TD A[Agent attempts egress] --> B{Proxy evaluates
current policy} B -->|allow| C[Request proceeds] B -->|deny| D[Runtime queries its
configured captive endpoint] D --> E{captive?} E -->|false| F[Real failure,
surfaced as one] E -->|true| G[Denial document with
recovery member and binding] G --> H[Requestable denial submitted
to the approval service] H --> I[Held work with a durable
handle, the turn ends] I -->|denied| J[Denial surfaces
to the task] I -->|approved| K[Orchestrator re-invokes
the work with the handle] K --> A

What the profile has to define is the recovery contract between the blocked agent and the enforcement point:

  • how the agent discovers or is configured with the captive API endpoint
  • how the proxy scopes a captive response to the blocked destination or operation
  • which response member carries the machine-actionable recovery path
  • how the denial is cryptographically bound to the thing that was blocked
  • how long the denial binding and any resulting approval remain valid
  • how the agent polls or is notified that the request has completed
  • how the proxy re-evaluates before allowing egress
  • what the agent must ignore, especially human-facing portal URLs and untrusted text

On that first point, the safe baseline is direct configuration: the agent runtime is launched knowing its egress proxy’s captive API endpoint and queries it when a connect attempt is blocked. HTTP 511 and RFC 8910 provisioning help in managed environments, but they are not the baseline for fully tunneled HTTPS, where the proxy cannot inject a signal into the tunnel. The minimum profile assumes the runtime already knows its proxy and standardizes what comes back when a destination or operation is captive.

This also answers a question the tunnel otherwise makes unanswerable, whether a reset was the proxy’s policy or the network’s weather. The runtime does not need to tell them apart at the socket. On any suspicious failure it asks its configured captive endpoint about that destination. captive: true means a policy block with a recovery path. captive: false means the failure is real and should surface as one. The query is cheap, authenticated, and turns every ambiguous drop into a classified one.

Everything else can be deployment-specific: approver routing, ticketing systems, user interface, risk scoring, and the internal policy language. The standardization point is the blocked-client handshake, not the governance workflow behind it.

The path to that handshake is incremental, which is how enforcement seams actually ship. The first phase needs one operator and no counterparty. An egress proxy starts returning recovery metadata instead of a bare 403 or a silent reset, using whichever carrier its position allows. The second phase wires that metadata to an approval service and a policy re-evaluation, still entirely inside one organization. Only the third phase, binding approvals to mission state at operation grain, waits on the authorization substrate. Each phase pays for itself before the next one exists.

Who Runs the Recovery

The recovery actor does not have to be the model, and that matters for adoption. “The client” in this design is really two things: the agent, meaning the model and its tool code, and the runtime’s egress layer underneath it. The recovery can live in either, or in the proxy itself, which changes how much has to be modified.

Transparent hold-and-forward. A TLS-terminating proxy can hold the blocked request open, run the recovery itself, and forward the call on approval or fail it on denial. The agent sees one slow request that succeeds or fails, and nothing about it changes. This is the server-brokered shape, the same move as parking a denied tool call as held work, applied at the egress hop. Its limit is the clock. It works when the decision fits inside the client’s request timeout, which covers automated policy re-evaluation and a fast human tap, but not an approval that takes minutes. Past that window the connection times out and something has to retry.

A captive-aware egress client in the runtime. The layer that retries can be the runtime’s outbound stack rather than the agent. A generic captive-aware egress client detects the block, runs the recovery handshake, waits, and retries on behalf of every agent the runtime hosts. Write it once and no individual agent changes. This is the agent-egress analogue of the Captive Network Assistant a laptop already runs, the component that handles captivity for every app without each app knowing about it. It is also what “direct configuration is the baseline” really means. The thing configured, and modified, is the runtime’s egress layer, not the agent.

The agent itself. At the far end, the agent parses the machine-actionable member in the captive response and drives the recovery, or it speaks the authorization protocol natively and skips the captive handshake entirely, as the next section takes up. This is the most capable path, but it is not the only one, and it is not required for an agent that cannot be changed.

So the cost of adopting this is not “rewrite every agent.” A transparent proxy covers fast decisions with no client change at all. A captive-aware egress client in the runtime covers slow approvals for every agent it hosts, still without touching agent code. Parsing the member or speaking the protocol natively buys the agent direct control when that is worth having, and only then.

The wait itself has a shape, and it is not an open connection or a spinning loop. A recovery that outlives the request timeout becomes held work, and holding it is two actors’ jobs. The egress layer’s part ends at raising the held state and its durable handle up the stack, because a network sandbox cannot serialize an agent’s cognitive state. The orchestrator above it captures the handle, checkpoints the run, and lets the agent’s turn end rather than idling a context window against a human’s calendar. Approval re-invokes the work with the handle, and the retry happens in a fresh turn. An agent that spin-waits on an approval is burning its own working memory to simulate a queue the runtime should own.

Two Ways to Carry the Recovery

There are two ways to carry the recovery inside the captive response, and they sit at different altitudes on different authorization substrates. They are siblings, not a stack. AuthZEN carries a destination-level requestable denial, AAuth carries operation-level authorization, and neither requires the other. The interesting axis between them is how fine-grained the control is and how much human judgment each block deserves. And a client that already speaks AAuth needs no captive handshake at all, which the last part of this section takes up.

Destination-Level, on AuthZEN

Destination-level recovery is a proposed extension of the AuthZEN Access Request and Approval Profile (ARAP) from the requestable-denial work. The egress proxy is both an AuthZEN policy enforcement point and a Captive Portal API server. The agent is the AuthZEN Subject, the blocked destination is the Resource, and connect is the Action. When the proxy blocks, the captive portal response carries the requestable denial in a proposed access-request member. The denied subject, resource, and action are reconstructable from the denial binding, so the member can carry just the context:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
{
  "captive": true,
  "user-portal-url": "https://proxy.example.com/portal/req_01J8Z3",
  "access-request": {
    "context": {
      "evaluation_id": "eval_01K2Q4DP3K",
      "reason": "egress_not_allowed",
      "access_request": {
        "endpoint": "https://pdp.example.com/access/v1/requests",
        "template": "egress_destination",
        "expires_at": "2026-07-31T20:25:00Z",
        "binding_token": "eyJhbGciOiJFUzI1NiIsImtpZCI6InBkcC0xIn0...",
        "display": {
          "title": "Egress to api.partner.example is blocked",
          "description": "Approval is required before this agent may call api.partner.example."
        }
      }
    }
  }
}

In the base profile the PEP owns this machinery. It submits the access request with the denied Subject, Resource, and Action, holds the Task Handle, and re-evaluates on completion. This design keeps that default. The proxy submits, and the captive endpoint exposes only safe status for the runtime to poll, so the agent never touches the task machinery. Letting the runtime submit directly is a proposed extension, not base behavior: the Access Request Service must authenticate the caller and authorize it for the bound denial, the submission presents the denial-binding material (evaluation_id or binding_token) rather than restated context, and this profile additionally requires that submission to be sender-constrained, so a leaked identifier cannot be redeemed by another party. Either way, the agent retries only after the proxy re-evaluates against current policy using the base profile’s reevaluate completion mode, and only then does the destination become reachable. Approval resolution is server-side by default. The proxy reads the approval from PDP state at re-evaluation rather than trusting an approval the agent injects into a header. When the connection is allowed, a re-query returns captive: false with seconds-remaining derived from the approval’s expiry, and at expiry the destination returns to captive.

This is the policy-decision shape of the problem. What gets approved is “may this subject connect to this destination,” and the PDP stays authoritative end to end. The response itself stays RFC 8908-shaped, served as application/captive+json with the requestable denial inside it.

Operation-Level, on AAuth and Mission

Operation-level recovery sits at a finer altitude. It builds on Dick Hardt’s AAuth protocol (draft-hardt-oauth-aauth-protocol) and its Rich Resource Requests (R3) companion (draft-hardt-aauth-r3), with the Mission layer I have written about in Mission Architecture on AAuth and AAuth Now Has a Mission Layer. Control is at the operation level, not the host level. The question is not “may I reach this host” but “may this mission perform this operation, with these parameters.”

Provenance matters here, because four sources are composing. The signed exchanges, resource tokens, and challenges come from the AAuth core draft. The r3_granted, r3_conditional, r3_uri, and r3_s256 fields come from the R3 companion, a living editor’s copy rather than a submitted draft. Mission is the governance layer I have argued for on top of both. And the captive-portal carriage of any of it is this essay’s proposal, not theirs.

The proxy is not only deciding whether the agent may reach api.partner.example. It is deciding whether the current Mission permits a specific operation exposed by that API. Routine operations can be pre-authorized in the token. Risky operations can be marked conditional, so the proxy stops the call and asks the Person Server for a fresh decision before the side effect leaves the network.

Operation-level recovery is only as good as the proxy’s visibility. Behind an opaque CONNECT tunnel the proxy can only govern the host or connection. A TLS-terminating proxy can match on HTTP method and URL. An API-aware gateway can govern named operations like invoice.read, deriving an R3 document from a published API description (OpenAPI, MCP, gRPC) when the destination does not publish one itself. That derived document bounds what the enterprise lets the agent attempt. It does not manufacture the third party’s authorization semantics or grant anything at the resource, which still applies its own policy to whatever arrives. The finer the visibility, the finer the grant, and fully tunneled egress falls back to host-level rules.

Where the proxy can see operations, the Mission-bound token carries a two-tier model: r3_granted operations flow through the proxy immediately, the fast path where authority travels with the agent, while r3_conditional operations are authorized in principle but validated against the Person Server per call, so the expensive evaluation happens only where it earns its cost. The agent never holds the R3 document that defines those operations. It carries only r3_s256, the SHA-256 hash of the document at r3_uri, and the Person Server, not the agent, fetches and evaluates the semantics, so prompt injection cannot rewrite the operation set through the agent.

So when the renewal agent tries to issue a refund through the same partner API, the block is not “you may not reach this host.” It is “this mission is not yet authorized for payment.refund on this destination.” For a client that reaches the proxy over plain HTTP and does not speak AAuth, the proxy carries the recovery in the captive portal response. The response hands back an opaque resource-token: a denial-bound recovery artifact, not a credential and not authority. It captures the blocked call, the proxy, the subject, the destination, the destination’s R3 document hash, and an expiry, so it can be redeemed only as input to the Person Server’s evaluation. The response also carries a user-portal-url pointing at the Person Server for the human-facing review:

1
2
3
4
5
{
  "captive": true,
  "user-portal-url": "https://person-server.example.com/consent/req_01K2R7",
  "resource-token": "eyJhbGciOiJFUzI1NiIsImtpZCI6InByb3h5LTEifQ..."
}

The agent presents that resource-token to its Person Server in a request that names the R3 operations it needs, here payment.refund. The Person Server verifies the token, fetches and validates the R3 document for the destination (served only to signed, authorization-side requests), and routes the parameter-sensitive parts to the human, who approves against the R3 display text, its summary, implications, data_accessed, and an irreversible marker, rather than the agent’s own rationale. On approval, the Person Server issues a refreshed Mission-bound token whose r3_granted and r3_conditional sets reflect the new authority.

The agent presents that token to the egress proxy and retries. invoice.read flows straight through. payment.refund is now authorized, but as a conditional operation it is challenged on every call: the proxy returns an AAuth challenge carrying the concrete parameters, the agent round-trips to the Person Server, and the Person Server issues a per-call token or denies before the refund leaves the network.

When the Client Speaks AAuth

The captive portal is a bridge. It exists because the blocked client is usually a generic HTTP agent that has no idea what AAuth is, so the proxy translates a Mission-bound refusal into an RFC 8908 response the agent can parse without speaking the authorization protocol underneath.

A client that speaks AAuth does not need the bridge. AAuth carries authorization in the HTTP exchange itself, in signed requests and in the challenges a resource returns through the core draft’s AAuth-Requirement header, which names what the exchange still needs, so the refusal and the re-authorization happen in-band at the same request boundary the egress proxy already enforces. There is no separate captive handshake to discover. The block is an AAuth challenge, and the agent satisfies it the way it satisfies any conditional operation, by round-tripping to its Person Server before the proxy re-evaluates.

That collapses two cases into one. Over the captive portal, the initial question of whether the mission may reach the destination at all and the per-call question of whether it may perform payment.refund look like different events, one a captive response and one a later challenge. Natively, they are the same mechanism at different moments. The first blocked destination is just the first r3_conditional challenge, and every later one takes the same path.

So the two altitudes above are really about the client, not the protocol. Destination-level on AuthZEN and operation-level over the captive portal are how a blocked HTTP agent gets a machine-actionable way out. AAuth-native operation governance is a possible native path. The captive portal matters because today’s agents mostly do not speak AAuth: they speak HTTP, fail on opaque network errors, and need a compatibility recovery channel in the meantime.

Same Transport, Different Altitude

AuthZEN Access Request and Approval ProfileAAuth and R3 Mission egress
GranularityDestination (host)Operation (or host, by proxy visibility)
What is approvedMay this subject connect to this destinationMay this mission perform this operation, with these parameters
Consent and approvalPolicy re-evaluation at the PDP, human approval optionalHuman approval at the Person Server against resource-authored display
SubstrateAuthZEN Authorization API (PDP and PEP)Dick Hardt’s AAuth and R3 drafts, plus the Mission layer
Enforcement-time checkRe-evaluate against current policyr3_conditional validated per call
Captive portalRequired, the recovery transportBridge for HTTP-only clients, skipped when the client speaks AAuth

Both use RFC 8908 when the client speaks only HTTP, though they carry the recovery differently. The AuthZEN design puts the requestable denial in an access-request member. The AAuth design returns an opaque resource-token that the agent takes to its Person Server, and it drops the captive portal entirely once the client speaks AAuth. The choice between them is altitude, how fine-grained the control needs to be and how much human judgment a given block deserves. A coarse “may I reach this partner API” is well served by destination-level policy re-evaluation. A consequential, parameter-shaped action like a refund, which a person should look at before it happens, belongs at the operation level with curated consent. They are tiers for different risk, not competitors.

The Principles That Keep This Honest

Reusing the captive portal API is the easy insight. The discipline is in what the proxy is allowed to conclude from a block.

Approval is not a grant. A captive: true response is a requestable denial, not a grant, whether it carries an access-request member or a resource-token. The proxy keeps the destination blocked until re-evaluation, or a re-projected token, actually permits it. This is the same invariant as the policy-decision case: the enforcement layer stays authoritative at the moment of access, and the workflow that produced the approval does not get to declare the connection open. An approval signed earlier is an input to that decision, not a substitute for it. It is the same reason a session is not a mission and the reason governing the stay matters more than governing the entry.

Binding prevents confused-deputy egress. The denial binding ties the approval to the exact thing denied: an evaluation_id or binding_token for the destination in the AuthZEN design, the r3_uri and r3_s256 for the operation set in the AAuth design. An approval for one destination cannot be replayed to open another. Without that binding, the egress proxy becomes a confused deputy.

Egress approval permits the attempt. Resource authorization permits the operation. Everything the proxy grants is about what may leave the network, at whatever granularity it can see. The destination still authenticates, authorizes, and may refuse the call on its own terms. No captive response, access request, or refreshed token binds the resource, which is the same final-word boundary the control-points series draws.

Consent is curated, not narrated. When a human is in the loop, they approve against resource-authored, structured descriptions of impact, and the agent’s own rationale is untrusted and sanitized before display. The person approving an agent’s refund needs to see what the operation will actually do, not what the agent says it will do. This is the credential-side argument from From Passports to Power of Attorney and the Power of Attorney series, applied at the network boundary: authority is granted against the work, not against the asker’s self-description.

Failure Modes to Design Against

The profile has to be strict about failure because the blocked client is an agent, not a person reading a hotel wifi page.

Malicious portal content. The agent must treat user-portal-url, venue-info-url, and any human-readable display as untrusted. It can surface them to a human or governance system, but it must not browse them, summarize them into its plan, or follow instructions found there.

Approval replay. An approval for one destination or operation must not open another. The denial binding has to cover subject, destination, operation where applicable, proxy identity, expiry, and the policy decision context.

Stale approval. Approval is time-bounded. The proxy must re-evaluate at the moment of egress and must return to captive: true when the approval expires, the Mission changes, or the policy state changes.

DNS and wildcard ambiguity. Destination-level approval needs precise matching rules. api.partner.example, *.partner.example, IP literals, CNAME chains, SNI names, and CONNECT targets are not interchangeable unless policy explicitly says they are. The binding also has to survive time and indirection. Approval binds to what was actually approved, scheme, hostname, port, resolved address class, and TLS server identity, and neither DNS rebinding between approval and retry nor a redirect chain from an approved destination may carry the request somewhere unapproved under the original approval.

Non-idempotent replay. A held or retried request is not automatically safe to send twice. Transparent hold-and-forward must never replay unsafe methods on its own initiative. A POST, a payment, or a refund needs an idempotency key the destination honors, or it must be reconstructed and re-issued explicitly by the agent after approval rather than replayed from a buffer.

Held-request contents. A request parked for approval can contain credentials, tokens, and sensitive bodies. Whatever holds it becomes a custodian: the held material needs encryption at rest, a hard expiry aligned with the denial binding, and redaction from approval surfaces and logs.

The status API as an oracle. A queryable captive endpoint that answers questions about arbitrary destinations is a policy oracle and an SSRF-shaped probe. It must authenticate its callers, answer only for the runtime’s own blocked attempts, rate-limit, and avoid disclosing policy structure in its answers. The correlation state behind those answers is itself a target. Answering a socket-reset query means keeping a recent registry of blocked attempts per client, and that registry must be bounded, a ring buffer with a hard TTL aligned to the denial binding, so a looping agent or induced failures degrade classification rather than exhaust the proxy.

TLS termination’s own cost. Operation-level visibility prices in interception. A terminating proxy holds plaintext, credentials, and session material for every governed call, which concentrates privacy and key-custody risk at the proxy and makes the proxy itself a high-value target. Deployments that cannot accept that concentration fall back to host-level control, and should say so rather than terminate quietly.

Proxy impersonation. The agent must know which egress proxy and captive API it is allowed to trust. A random captive response from the network is not enough. Direct configuration, mTLS, signed metadata, or an equivalent trust mechanism is required.

Privacy leakage. A requestable egress denial reveals intent: where the agent wanted to go, what operation it wanted to perform, and sometimes why it was blocked. The proxy, PDP, AS, and approval workflow all become observers of task state. The response should minimize what is carried in the captive payload and keep sensitive prompt or plan evidence out of the agent-visible fields.

Not Every Block Is Requestable

The recovery above assumes a block that someone can say yes to. It helps to separate three kinds, because only two of them are requestable.

A security block says “I do not trust this destination.” The policy behind the proxy has decided the destination is unsafe or out of bounds. An approver can still be asked, so the block is requestable, and the answer may legitimately stay no.

A governance block says “this mission may not use this destination.” The destination is fine in general, but the current task was not approved for it. This is the operation-level case above. Recovery is a mission decision, and widening the mission is exactly the escalation the captive response carries.

A platform block says “the enterprise would allow this, but the platform cannot.” The agent runtime only permits the destinations its own vendor’s ecosystem exposes, so an agent that discovers it needs a system outside that ecosystem has nowhere to appeal. There is no approver who can grant it, because the constraint is not a policy the proxy can re-evaluate. It is the architecture of the runtime.

That third case is the one that turns a blocked agent into a captive one, and it is worth noticing that the word has changed meaning. Until now captive meant restricted under a policy with a way out. Here it means structurally confined, and no recovery contract reopens architecture. Picture an enterprise whose agent is approved to work across its stack. The CRM lives at one vendor, contracts in Box, signatures in DocuSign, procurement in Coupa, and HR in Workday. If the agent’s runtime egress only permits one vendor’s endpoints, the agent cannot reach any of the others, whatever the enterprise approved. The block is not security and not governance. It is reachability, and reachability is becoming a perimeter of its own. An agent is only as autonomous as the destinations it is allowed to discover.

That is a different problem from the one this post solves, and a larger one. The recovery mechanism here makes security and governance blocks requestable. It does nothing for a platform that will not route beyond its own ecosystem, because there is no denial to appeal. OAuth deliberately let a client talk to many resource servers, and no one expected “this client may only call APIs owned by one vendor.” Runtime connectivity controls can quietly produce exactly that. Whether agent ecosystems stay open is a portability question, not a recovery question, and a larger one than this post can carry.

The Wall Is a State, Not a Dead End

The egress proxy was always a policy enforcement point. We just treated its block as a failure instead of a state.

For agents, that is no longer enough. A blocked destination may be unsafe, outside the mission, or merely not approved yet. The client needs to know which kind of wall it hit and whether there is a way through.

The pieces compose rather than compete. RFC 8908 contributes the recovery state machine: discover captivity, learn the remediation path, and retry after policy changes. Proxy-Status and Problem Details carry the denial where an HTTP response exists, and an authenticated side channel carries it where one does not. AuthZEN evaluates whether this subject may reach this destination. AAuth carries the operation-level ask in-band, skipping the captive-portal bridge when the client speaks it, and Mission decides whether widening the work is on the table at all.

A blocked agent behind a policy is not stuck. It is captive in the recoverable sense, and that captivity is a state with a defined way out.