Skip to main content

Network inspection behind a VPN

In this option, agents need no gateway settings: each laptop only has to trust the company's inspection certificate. Each laptop runs your company VPN to a gateway host you run. There, a TLS inspection proxy and Shield check every response from the model provider before it goes back down the tunnel. Once the VPN is connected, enforcement is automatic for an agent the gateway covers: today Claude Code, on Anthropic's API (see which agents it covers). Such an agent keeps its default provider URL. Setting NO_PROXY, or pointing the agent at another hostname or a bare IP on Anthropic's address, does not get around the gateway: the API is inspected and anything else there is refused, apart from Anthropic's named websites. Which other traffic goes through the VPN depends on the routing design.

The design fits any VPN and inspection proxy that meet the requirements below. Our reference deployment is WireGuard, an iptables firewall and mitmproxy on a Linux gateway host, and it is the only one we have tested. The steps, the identity table and the test results on this page are that deployment's.

Pilot

Tested on the reference deployment, with Claude Code 2.1.296, mitmproxy 12.2.3 and WireGuard, against a mock of the Anthropic Messages API. Linux containers stood in for the Mac and the gateway host. macOS-specific behaviour, the live provider and your own network, VPN and proxy are what a pilot validates. Read what changes for browsing before you plan a rollout.

Container diagram: a coding agent on the Mac sends provider HTTPS through the VPN client; on the company gateway the tunnel and firewall redirect it to the inspection proxy, which asks the Shield server about each tool call and looks up the enrolled user for the tunnel address, and the audit log records the decision. The reference deployment shown is WireGuard and mitmproxy, with Claude Code

Zoomable version: open the interactive gateway architecture.

How it works​

  1. Each Mac has its own VPN credential and a tunnel address bound to it, registered by an administrator with the user and device it belongs to. In the reference deployment the credential is a WireGuard key, and a key the gateway does not know cannot complete a handshake.
  2. The gateway firewall accepts provider traffic only. Connections to approved provider addresses on port 443 are redirected into the inspection proxy. Other forwarding is rejected, and so are other ports, UDP 443 and IPv6.
  3. The proxy terminates TLS with a company inspection certificate that the agents trust, and opens its own verified TLS connection to the provider. Which connections it decrypts depends on the routing design. The full tunnel decrypts every hostname on the provider's address and refuses all but the API. The daily-use design passes Anthropic's named websites through undecrypted, decrypts the API, and decrypts and refuses anything else.
  4. The proxy holds the provider's whole response and asks Shield about every tool call in it, with the same mapping as the AgentShield hook. It releases the original response only if every call is approved. Otherwise it returns an HTTP 400 that names the rule.
  5. Shield records who sent it. The proxy looks up the enrolled user and device for the tunnel address the request arrived on. The tunnel already authenticated that address with the device's VPN credential. Shield writes the decision, with that identity, to a hash-chained audit log on the gateway. In the pilot that write is best-effort: if the log cannot be written, Shield still answers, so monitor it.

Only the provider's Messages endpoints pass. The proxy forwards POST /v1/messages and its token-count endpoint. It refuses every other request a client makes to the provider. Our tests ran Claude Code non-interactively, with its non-essential traffic turned off. How a default interactive session behaves behind the proxy is part of what a pilot validates.

An error fails closed. The proxy puts a rejection in place before it parses anything, and restores the provider's response only after every check passes. An unexpected error therefore cannot let a tool call through. We tested that by forcing one.

Observe mode works here too. With SHIELD_GATEWAY_MODE=observe on the gateway and Shield in audit-only mode, the proxy releases responses and records them, as described in observe mode. Released responses carry an observed marker instead of approved. On this path, observe mode is covered by unit tests of the proxy, not by the network lab.

What the VPN and proxy need​

The guarantees on this page rest on the properties below. Each one says how the reference deployment provides it, and where it falls short: keeping the VPN connected is left to device management and is not tested on macOS, and in the daily-use design a passed-through website is decided on its TLS hostname alone. Keeping the routed provider addresses current is pilot work (see limits). When you evaluate your own VPN or proxy, check each one.

The VPN

  • It sends the provider's addresses, IPv4 and IPv6, to the gateway. In the daily-use design it sends only those; in the full tunnel, everything. Provider traffic that takes another path is not inspected.

    Reference: WireGuard routes. For the daily-use design they list Anthropic's addresses.

  • It stays connected, and fails closed. If a developer can disconnect it, or the client sends provider traffic direct when the gateway is unreachable, agent traffic leaves the gateway's path. Keeping it connected is a device-management control.

    Reference: WireGuard's routes do not depend on the gateway being reachable, so with the gateway down, provider traffic has no direct path (tested on Linux). Keeping the tunnel up on macOS is not tested.

  • It binds each device to the source address the proxy sees. Each record's user and device rest on this, so nothing may translate the address on the way to the proxy. A VPN that hands out addresses per session must also give the gateway its live record of which device holds which address. Without that, the gateway can still enforce the policy, but its records cannot name the user.

    Reference: WireGuard binds each device key to one tunnel address. The gateway refuses an address it has no enrollment for.

The gateway host

  • Provider traffic leaves only through the proxy, and in the full tunnel nothing else leaves. Other ports, UDP 443 (the QUIC port) and IPv6 to the provider are refused or steered into the proxy. Otherwise an agent can take a path the proxy never sees. In the full tunnel, every other destination is refused too: that is what stops an agent pointed at a relay outside the provider's addresses. A full-tunnel gateway that forwards other traffic to the internet loses that.

    Reference: an iptables firewall drops forwarding by default, redirects port 443 to the provider into the proxy, and rejects the rest.

The inspection proxy

  • It decrypts with a company certificate that only agents trust. Each agent needs a way to load it. Claude Code reads it from NODE_EXTRA_CA_CERTS.

  • It holds the whole response before releasing any of it. A proxy that streams responses through delivers a tool call before Shield has answered.

    Reference: mitmproxy can stream responses; the reference turns that off explicitly.

  • A failure in its Shield integration withholds the response.

    Reference: mitmproxy logs an add-on's error and continues delivering. So the reference add-on puts a rejection in place before it parses anything, and restores the provider's response only after every check passes. A forced error is tested.

  • It decides on the TLS hostname and the HTTP host together. It forwards only the provider's Messages endpoints on the API hostname, and refuses other hostnames and bare IPs. In the daily-use design it also passes named websites through undecrypted.

    Reference: a Shield add-on for mitmproxy, with mitmproxy's list of passed-through hostnames. A passed-through website is decided on its TLS hostname alone. So in the daily-use design, a client that sends a website's hostname and asks for the API passes uninspected: a known gap, pinned by a check (see browsing).

Another proxy fits if it has these properties and can call the Shield server for each tool call. That includes one your company already runs for TLS inspection. Its Shield integration is an adapter, which a pilot builds and tests; the mitmproxy add-on is the only one built so far. An agent other than Claude Code needs the additions in which agents it covers, including a way to route its provider's traffic.

Who each record names​

Every decision record carries:

FieldComes from
User and display nameThe enrollment registry, looked up by the tunnel address that the device's VPN credential is bound to, never by a header or name the client sends
Device and tenantThe same enrollment entry
Device credentialIn the reference deployment, the public half of the device's WireGuard key; the private key stays on the Mac
Request and tool-call idsThe gateway's own request id, and the provider's id for each tool call
AgentInferred from the request's User-Agent, and labelled as inferred. A network gateway cannot verify which program sent a request.

A VPN credential identifies an enrolled device, not who is at the keyboard of a shared machine.

What we tested (reference deployment)​

These are the full-tunnel labs, built with iptables and mitmproxy. The identity lab (24 checks) runs over WireGuard. The routing and inspection lab (25 checks) carries the laptop's traffic in a lab-only GRE tunnel, standing in for the VPN. The daily-use design's checks, over WireGuard, are below. All on 14 October 2026, against Shield main plus pilot branch PR #4354. "Real Claude Code" means the CLI ran end to end, as an unprivileged user. Results are judged by files on disk and what reached the provider, not by logs.

CaseResult
Default provider URL, approved commandReal Claude Code runs it
NO_PROXY=* and no proxy settings, blocked commandStill blocked: the rule is named, nothing runs
Agent pointed at a different approved provider URLStill inspected and blocked
Agent pointed at an unapproved relayConnection refused; nothing runs
Unapproved hostname on an approved address, literal IP, other port, UDP 443, IPv6No tool call delivered
Inspection certificate not trustedTLS fails; no fallback to a direct connection
Two tool calls in one response, one blockedNeither runs
Claude Code's own Read tool, approved and blockedReleased, or never read
Shield stopped, or the whole gateway stoppedNothing runs; no direct path to the provider
Unexpected error inside the proxyHTTP 400; no tool call
An unprivileged user on the client (a Linux stand-in for the Mac) tries to change its routeNot permitted
Two enrolled usersEach action recorded with the right user, device, request and tool-call id
Forged identity headers, or a forged tunnel source addressCannot claim another user's identity
Unknown key, or a revoked oneNo access; the other user keeps working
Shield outageThe rejection is recorded with the original user

That is 49 checks across two lab setups: 25 for routing and inspection, 24 for identity. Another 28 unit tests cover the proxy's release rule in both modes and its destination rule. They include the cases the lab does not reach: degraded, audit-only and approval verdicts, a provider-run tool, an undeclared tool, a truncated or oversized response, and the non-streamed path.

What changes for browsing​

There are two routing designs, and both are tested. The choice decides what the network alone can guarantee.

Daily use (split tunnel, recommended)Restricted pilot (full tunnel)
Sent to the gatewayOnly Anthropic's addresses (IPv4 and IPv6)All of the laptop's traffic
Ordinary browsingGoes direct, with the site's own certificate. Shield never decrypts, logs or slows it, and a gateway outage does not affect itUnavailable while connected, including Git hosting, package downloads and network calls from MCP servers
Anthropic's own websitesNamed websites pass through the gateway undecrypted: they share the API's address (see below). Any other hostname on that address is refused, including platform.claude.com, where the Console and docs now live, because it also serves the APIDecrypted at the gateway, then refused
Agent pointed at a relay outside Anthropic's addresses (for example Amazon Bedrock or Google Vertex AI, one setting away)Goes direct, uninspected; the AgentShield hook on the laptop still sees its actionsRefused at the gateway
Tested26 checks49 checks (above)

Why Anthropic's websites ride the tunnel. On 2026-10-14, api.anthropic.com, claude.ai, console.anthropic.com, docs.anthropic.com and www.anthropic.com all resolved to the same address, inside a block registered to Anthropic. Routing the API's address therefore routes those sites too, and no unrelated site. The gateway passes a named list of Anthropic websites through untouched, by their TLS hostname. Shield keeps no record of them, but the gateway host still sees their connection metadata (address, hostname, volume). If the gateway is down, those websites are unreachable until it is back.

Any other hostname, or a bare IP, on that address is refused, because it could be another way to reach the API. An Anthropic website missing from the list therefore fails in a browser until it is added. Install the inspection certificate for agents only (Claude Code reads NODE_EXTRA_CA_CERTS), not system-wide: a browser that trusted it would let the gateway decrypt such a site before refusing it.

The design assumes Anthropic serves its API on none of the passed-through hostnames. On 15 October 2026, an unauthenticated API request got these replies:

HostnameReply
platform.claude.comThe API's own reply, as from api.anthropic.com
console.anthropic.com, docs.anthropic.comA redirect to platform.claude.com, where the Console and docs now live
www.anthropic.com404
claude.aiA browser challenge

So platform.claude.com is never passed through. It is refused like any other hostname, and the Console and docs do not load in a browser while connected. A pilot re-checks the list against the live service.

What we tested in the daily-use design, with real Claude Code and Linux containers standing in for the Mac and the gateway host:

CaseResult
Ordinary HTTPS over IPv4 and IPv6, HTTP, UDP on port 443 (the QUIC port)Direct, with the site's own certificate. The gateway's counters and both audit logs are unchanged
Claude Code on its default URL, and on a changed approved URLInspected and attributed to the enrolled user; a blocked call never runs
An Anthropic website on the API's addressPassed through without decryption: the site's own certificate, and no Shield or gateway record
Another hostname, or a bare-IP URL, on that addressRefused by the gateway as an unapproved destination; Claude Code pointed at another hostname runs nothing
IPv6 to the APISteered into the tunnel and refused there. Claude Code tried IPv6 first, then fell back to IPv4, where it was inspected
Shield down, or the gateway downThe agent gets no response it can act on, and has no direct fallback; browsing keeps working
Unregistered VPN keyNo access to the API; browsing works (revocation is tested in the full-tunnel identity lab above)
VPN disconnected, or the agent pointed at a relay outside Anthropic's addressesLeaves on the direct path, uninspected.
A client that sends a website's hostname in TLS but asks for the APIPasses through uninspected. Only decrypting every website closes this. The full tunnel does: its proxy refuses a request whose TLS and HTTP hostnames differ (unit-tested)

The last two rows are the design's three known gaps. The lab pins each one with a check, so closing a gap has to be deliberate.

Run both. In the daily-use design the network inspects the agent lane while it stays in the lane. The AgentShield hook on the laptop sees the actions of an agent that leaves it.

Limits to plan for​

  • The VPN must stay on. Someone who controls their own Mac can disconnect a VPN, and in either design agent traffic then goes direct, uninspected. Keeping the tunnel up is a device-management control you configure. We have not tested it on macOS.
  • Daily use: routed addresses must stay current, and drift fails open. The daily-use design routes Anthropic's published address ranges. If the provider starts using an address you don't route, agent traffic to it goes direct, uninspected, with no alert. The lab used static addresses; keeping the ranges current, and monitoring them, is pilot work.
  • Agents must trust the inspection certificate. Claude Code does, through NODE_EXTRA_CA_CERTS; check how another agent's runtime loads an extra certificate. A client that pins its certificates will fail rather than be inspected.
  • Developers keep their own provider credentials. The gateway passes them through unchanged. The pilot tested API keys; subscription sign-in has not been tested. Claude Code 2.1.296 signs in and refreshes its token at platform.claude.com, which the reference proxy refuses, so expect sign-in to fail while connected until a pilot designs for it.
  • Enrollment is an administrator step in the reference deployment. Self-service enrollment with your company sign-in, and a prompt that tells an unenrolled developer how to enroll, are in design.
  • Full tunnel: provider addresses are fixed in the lab. The lab used static provider addresses. Keeping the firewall's approved addresses current as the provider's DNS changes, while failing closed rather than open, is pilot work.
  • Records stay on the gateway. Decisions are in Shield's hash-chained audit log and the gateway's delivery log, written best-effort. Neither is sent to AI Agent Lens Cloud in the pilot.