> ## Documentation Index
> Fetch the complete documentation index at: https://docs.agentvault.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Egress and tunnelling

> Route the proxy browser's traffic out through your own IP address, and choose what happens when the tunnel is unavailable.

Send the proxy browser's traffic out through your own IP address, so merchants
see your connection instead of a datacenter's.

## Overview

The proxy runs the browser in the cloud. Without a tunnel, merchants see the
proxy's datacenter address, which many sites block or challenge. A checkout that
works from your laptop can hit a bot wall from the proxy.

With a tunnel, the SDK opens a reverse SOCKS5 tunnel over its existing proxy
connection. The browser's traffic is routed back through your machine and exits
from **your** address.

* **On by default, as a preference.** The SDK asks for a tunnel and falls back
  to the proxy's address, or the deployment's
  [residential egress](#residential-egress) where one is configured, with a
  warning, if one cannot be set up.
* **You relay only encrypted bytes.** Substitution happens inside the proxy's
  browser, above the transport you relay, so you never see card data, passwords
  or any other real value.
* **Your network stays private.** The tunnel refuses to reach private and LAN
  addresses, so a merchant page cannot use it to probe your network.

## Choose a mode

| Setting | `chromium.launch` | Environment | Behaviour |
| - | - | - | - |
| Preferred (default) | `tunnel` omitted | `SELF_AGENT_PAY_TUNNEL` unset | Uses the tunnel when it can, otherwise exits from the proxy and logs a warning |
| Required | `{ tunnel: true }` | `SELF_AGENT_PAY_TUNNEL=1` | Fail closed: refuses to launch without a live tunnel |
| Off | `{ tunnel: false }` | `SELF_AGENT_PAY_TUNNEL=0` | Always exits from the proxy |

The launch option overrides the environment variable. The MCP server reads the
same variable.

"Exits from the proxy" means the deployment's own egress: the proxy's address,
or its [residential egress](#residential-egress) where one is configured.

<CodeGroup>
  ```ts SDK theme={null}
  const browser = await chromium.launch({ tunnel: true });
  ```

  ```bash Environment theme={null}
  export SELF_AGENT_PAY_TUNNEL=1
  ```
</CodeGroup>

### When to require the tunnel

Require it when exiting from your address is a promise you have made, for
example to a merchant that blocks datacenter traffic. A required tunnel never
falls back silently: an agent told it is exiting from your IP, quietly exiting
from a datacenter IP instead, would be a promise broken without a signal.

A required launch against a proxy that does not establish the tunnel throws
before any browser starts, and the message says to set `SELF_AGENT_PAY_TUNNEL=0`
if direct egress is acceptable.

## Plaintext is never relayed through you

In a tunnelled session the proxy splits traffic by scheme:

| Scheme | Route |
| - | - |
| `https` | Through your tunnel, out from your address |
| `http` | Directly out of the proxy, never through your machine |

Without a tunnel, whether because it is off or because a preferred tunnel fell
back, there is no split. Everything leaves from the session's single egress:
the proxy's own address, or the deployment's residential egress when one is
configured. Residential egress carries HTTPS only, so a plain `http` request
never leaves at all (see [HTTPS only](#https-only)).

<Warning>
  Do not read a tunnel-less checkout as preserving your address. Without a tunnel,
  the merchant sees the proxy's address, or the deployment's residential exit,
  never yours.
</Warning>

Token fills on plaintext pages are refused regardless, with `insecure_page`,
except on a loopback address such as `http://localhost` used for a local demo
(see [URL locking](/security/url-locking#plaintext-pages-are-refused)). On
plaintext, anyone on the network path between the proxy and the merchant could
read a card number, password or authenticator code typed into the page.

## Residential egress

On a deployment with a residential provider, a session without a tunnel through
your machine leaves through that provider instead of the proxy's own address.
That covers `tunnel: false`, a preferred tunnel that fell back, and every
session on the [hosted MCP server](/mcp/tool-reference#on-the-hosted-server).
Sites see a home address in one country, chosen as described in
[Residential egress country](#residential-egress-country).

* **Used, or required.** Where the deployment has a provider, a session without
  a tunnel uses it with nothing to set. To refuse to start without it, launch
  with `residentialEgress: true` or set `SELF_AGENT_PAY_RESIDENTIAL_EGRESS=1`.
  `launch()` then fails with `egress_unavailable` and a `retryAfterSeconds`
  when the deployment has no provider, or the provider does not answer for your
  country. A working tunnel through your machine satisfies the requirement too.
* **Never a silent fallback.** If the proxy cannot set up the residential route
  for a session, `launch()` fails with "residential egress setup failed" rather
  than sending the session out from the proxy's address.
* **Lost mid-session.** If the provider stops carrying the session, every later
  call except `outcome()` and the close calls fails with `egress_lost`. Nothing
  leaves from any other address. Close the session and launch a new one.

### HTTPS only

A residential egress session never sends anything over plain `http`.

| What the page asks for | What happens |
| - | - |
| An `http://` page, from `goto()`, a link, a form or a popup | Loaded as `https://` on the same host and path, and on the same port when the address names one. A form posted to an `http://` address is sent to the `https://` one with the same method and body |
| An `http://` image, script, stylesheet or request from the page | Blocked. The page loads without it |
| A plain `ws://` socket or any other unencrypted connection | Refused before it leaves the proxy |
| An `https://` page whose server redirects to an `http://` address | The redirect is not followed |

When the HTTPS attempt fails (no TLS on the host, a certificate the browser will
not accept, no answer) or the server redirects to `http://`, a `goto()` fails
with `insecure_site`, and the tab stays on the page it was on. Nothing was sent
over plain `http`, and retrying the same address fails the same way, so choose
another site. A `click()` or `press()` whose navigation is stopped this way gets
the browser's own navigation error instead. See
[`insecure_site`](/sdk/error-codes#page-and-fill).

### Which exit a session used

When a residential egress session ends, Agent Vault records one `egress_used`
event for the account holder. It shows on [**Activity**](/dashboard/activity) as
**Residential exit**, with the title "Residential exit used", and says:

* The country the exit was in.
* How much the session carried: bytes sent and bytes received.
* How many [attested checkouts](/security/attested-detokenization) went out
  through the same exit, when there were any.
* Whether the provider stopped carrying the session before it ended.

It never names an IP address. The provider picks the exit inside its own network,
so Agent Vault never learns it. There is at most one event per proxy session,
and none for a session that never reached a site. It is best effort, like the
rest of **Activity**. The event is always recorded. Email and desktop alerts for
it are off by default: the account holder turns them on with **Which residential
exit my agent's session used** in
[Settings](/dashboard/approvals-and-notifications#events).

### Residential egress country

Without a tunnel, a deployment with a residential provider sends each session
out from a home address in one country. Card issuers compare where a purchase
comes from with the card's billing address, so Agent Vault picks that country
from your account when the session starts, in this order:

1. The billing country of the cards your API key may use, if they all agree on
   one. If the key may use more than 20 cards, this step is skipped.
2. Otherwise, the country of your saved addresses, if they all agree on one. If
   you have more than 20 saved addresses, this step is skipped.
3. Otherwise, the fallback country the deployment is configured with.

A disagreement never picks a winner. A key with a US card and a Canadian card
moves on to your addresses, and if those disagree too, the session leaves from
the fallback country, which may not be yours. To keep sessions in your own
country, give a key only cards billed in that country.

## When traffic did not use the tunnel

If any of a session's traffic did not go out through your tunnel, Agent Vault
tries to notify the account owner with an `untunneled_egress` event (best
effort). It names the reason: the
tunnel was unavailable (including a preferred tunnel that fell back), or the
traffic was plaintext and took the direct route by design.

Plaintext exposure is reported only when it was observed. When a preferred
tunnel was unavailable, the event says the traffic was readable on the way only
if the proxy saw plain `http` traffic in the session. A session the proxy saw
use only `https` stayed encrypted, and the event says so. Traffic the proxy
cannot watch, such as an attested checkout's own browser, gets neither claim. The direct-route case is always
plain `http`, so it is always reported as readable.

The event is reported once, when the session ends, so there is at most one per
proxy session.

For plain `http` traffic, the event names the first five sites the session
reached that way and counts the distinct sites, up to 100. Past 100 it stops
counting and does not say that more were left out, so a count of 100 is a lower
bound. The alert text names the first three sites and counts the rest. When the
tunnel was unavailable, the event names no plain `http` sites, because the whole
session went out another way. Merchants an attested checkout reached are named
and counted separately, with the same limits, and there the alert says "at
least" once it stops counting. See [Ceilings](/reference/limits#ceilings).

## Tunnelling and attested checkout

The [attested checkout](/security/attested-detokenization) runs its own browser
inside a confidential-compute enclave.

* Where the deployment relays the enclave's traffic, an attested checkout in a
  tunnelled session goes out through your tunnel too.
* Where it cannot, a session that **required** the tunnel is refused up front
  with `attested_checkout_unavailable`, and nothing is recorded. Open a session
  without `tunnel: true`, or fill the card token on your own page.
* In a session that only **preferred** the tunnel, an unrelayed attested
  checkout exits from the enclave's address, and the owner is told through
  `untunneled_egress`.
* In a [residential egress](#residential-egress) session, a relayed attested
  checkout goes out through the residential exit and is counted in the
  `egress_used` event. An unrelayed one exits from the enclave's address, and
  the owner is told through `untunneled_egress`, whatever the session asked for.

If a required tunnel drops while the owner is deciding a purchase approval, the
attested checkout is refused with `invalid_plan`, before any transaction is
recorded. The plan is fine; the transport went away. Relaunch with
`tunnel: true` and run the same plan again.

## Related

<CardGroup cols={2}>
  <Card title="Environment variables" icon="sliders" href="/reference/environment-variables">
    `SELF_AGENT_PAY_TUNNEL` and the other settings.
  </Card>

  <Card title="Error codes" icon="triangle-exclamation" href="/sdk/error-codes">
    `insecure_page`, `insecure_site`, `egress_lost`, `invalid_plan` and the attested codes.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.