Inspecting Claude Code’s Network Traffic with Linux Namespaces and MITM Proxying (Part 5)

Classifying all of Claude’s network connections

So far we’ve examined individual connections. We looked at a single WebFetch and a single decrypted API call. To build a complete picture of Claude’s network behavior, we need to exercise all of its network tools in a single session and categorize every connection it makes. This produces a full traffic matrix: which destinations Claude contacts, what each connection carries, and whether each request originates from your machine or routes through Anthropic. That matrix is the basis for the security analysis that follows.

Setting up the full test

Run a prompt that exercises multiple network tools:

./mitm-capture.sh "First, use WebFetch to fetch https://example.com and tell me the page title. Then use WebSearch to search for 'linux network namespaces tutorial' and summarize the top result."

Analyzing by category

WebFetch traffic:

We can pull specific get requests by parsing the mitm.txt file generated by the mitm-capture.sh script

grep -A 5 '>>> GET https://example.com' mitm.txt

The command above will provide you with the results of a direct GET request to the target URL from the local machine. The request includes the Claude-User User-Agent. The response body contains the full page content.

WebSearch traffic:

grep -A 5 '>>> .*search' mitm.txt   # Look for search-related requests

Observe whether search queries go directly to a search engine or route through the Anthropic API. Run this yourself. The behavior may depend on your Claude Code version and configuration. Document what you find. The traffic capture tools give you the means to answer this question empirically rather than guessing.

API traffic:

grep '>>> POST.*api.anthropic.com' mitm.txt

These are the model inference calls. Your prompt goes to Anthropic, the model reasons about tool use and the response comes back with tool invocations and final answers.

Telemetry traffic:

grep '>>> .*datadoghq' mitm.txt

Usage logging and metrics sent to DataDog’s intake endpoint.

MCP traffic:

grep '>>> .*mcp\|>>> .*cloudflare' mitm.txt

If you have MCP servers configured, claude contacts them at startup.

The traffic matrix

Traffic TypeDestinationOriginEncryptedContains
WebFetchTarget URL directlyYour machineYes (TLS)Full HTTP GET with Claude-User UA
WebSearchObserve and documentObserveYes (TLS)Search queries and results
Model APIapi.anthropic.comYour machineYes (TLS)Prompts, tool calls, model responses, API key
Telemetry*.datadoghq.comYour machineYes (TLS)Usage metrics
MCPConfigured serversYour machineVariesMCP protocol messages

The key architectural finding is WebFetch is a local operation. When claude uses the WebFetch tool, the Claude CLI running on your machine makes a direct HTTP(S) connection to the target URL. The request originates from your IP address, goes directly to the target server, and the response comes back to your machine. The Anthropic API is not in the middle.

These are the Immediate implications:

  • Claude can fetch from any URL your machine can reach
  • Internal/LAN services (http://192.168.x.x/...http://localhost:...) are accessible
  • The target server logs YOUR IP address as the requester
  • Network restrictions on your machine (firewall rules, VPN routing) apply to these fetches

Security implications of local fetch for your network

LAN accessibility by Anthropic

If WebFetch connects directly from your machine, then claude can be prompted to reach internal services:

"Use WebFetch to fetch http://192.168.1.1/ and tell me what you see."

By default, Claude Code shows each tool invocation to the user for approval before executing it. If a user approves that WebFetch call and the target is reachable (say, a router admin page), claude will fetch it. The same applies to:

  • Internal wikis and documentation servers
  • Development servers running on localhost
  • Cloud metadata endpoints (http://169.254.169.254/... on cloud VMs)
  • Internal APIs and admin interfaces

This isn’t a vulnerability in Claude Code. This is the expected behavior of a local fetch tool. The per-call approval prompt is a first layer of defense: you see the target URL before the request fires… but approval prompts are easy to click through, especially during long sessions. If you configure auto-approve policies, you’ll bypass them entirely. Network-level controls provide defense-in-depth beyond the approval model.

Internet-accessible vs. LAN-only targets

Understanding this distinction is critical:

Internet-accessible site (e.g., https://example.com):

Your machine ──── Internet ──── example.com
     │
     └── claude's WebFetch goes this way
         Target sees YOUR public IP

LAN-only service (e.g., http://192.168.1.100:8080):

Your machine ──── LAN ──── internal-server:8080
     │
     └── claude's WebFetch goes this way too
         No internet traversal needed
         Internal server sees your LAN IP

This distinction matters because Anthropic’s infrastructure can’t reach your LAN. If WebFetch were server-side, internal services would be safe by network topology. But because WebFetch is local, it can access anything your host is approved to access.

Using namespaces for defense, not just observation

You can use claude-sandbox.sh

Earlier we used network namespaces to observe Claude’s traffic. The same technique can also block Claude from reaching internal services while still allowing it to access the internet APIs it needs to function. This can turn the observation tool into a security control.

The same namespace technique from the isolation section can restrict claude’s network access:

# After creating the namespace, add restrictive iptables rules INSIDE it:
sudo ip netns exec claudesbx iptables -A OUTPUT -d 10.0.0.0/8 -j DROP      # Block RFC1918
sudo ip netns exec claudesbx iptables -A OUTPUT -d 172.16.0.0/12 -j DROP   # Block RFC1918
sudo ip netns exec claudesbx iptables -A OUTPUT -d 192.168.0.0/16 -j DROP  # Block RFC1918
sudo ip netns exec claudesbx iptables -A OUTPUT -d 169.254.0.0/16 -j DROP  # Block link-local

Now claude has internet access (via NAT) but cannot reach any internal/LAN address. You get both isolation for observation and restriction for defense.

Defense-in-depth considerations

For production use of AI agents with network tools:

  1. Network namespace isolation: Run the agent in a namespace with controlled egress. Capture and audit all traffic.
  2. Egress filtering: Allowlist specific destinations rather than blocklisting RFC1918 ranges. If the agent only needs to reach specific APIs, restrict it to those.
  3. DNS-based control: Use a controlled DNS resolver in the namespace that only resolves permitted domains.
  4. Proxy-based inspection: Route all agent traffic through a forward proxy with URL allowlisting. This gives you both filtering and a complete audit log.
  5. Credential scoping: The API key used by the agent should have minimal permissions. If your Anthropic key is stolen (e.g., from a decrypted capture), the blast radius should be limited.

That covers it. You now have sufficient skill to be able to wrap a binary in a private namespace, implement packet inspection & MITM teardown of TLS Sessions for claude so you can observe it’s requests out to claude API endpoints and any assets on the internet for scraping. We covered a ton of ground. Circle back to part 1 or head over to the github repository for this project.

Inspecting Claude Code’s Network Traffic with Linux Namespaces and MITM Proxying (Part 3)

Extracting hostnames from encrypted traffic

In the last post, we explored isolating traffic using namespaces. Now, we’ll explore pulling hostnames from an encrypted session.

The capture file contains Claude’s isolated traffic, but all of it is encrypted with TLS. We can’t read the contents of any request or response. However, we don’t need decryption to determine if WebFetch connects directly to target websites or routes through Anthropic. If example.com appears as a direct connection from the namespace, WebFetch is local. If only api.anthropic.com appears, it’s proxied.

TLS leaks destination hostnames in two places, and we can extract them from the encrypted capture.

Open the capture:

tcpdump -nr claude.pcap | head -30

You’ll see TCP handshakes, data transfers, and teardowns. But the payloads are encrypted. TLS protects them. You can’t see the URLs being fetched, the headers, or the response bodies because they’re encrypted in HTTPS transactions.

What TLS leaks: SNI and DNS

Even though the payload is encrypted, two pieces of metadata remain visible in plaintext:

  1. Server Name Indication (SNI): During the TLS handshake, the client sends the server hostname in cleartext as part of the ClientHello message. This is necessary because multiple HTTPS sites can share one IP address (virtual hosting), and the server needs to know which certificate to present before encryption begins.
  2. DNS queries: Before connecting to any hostname, the process resolves it via DNS. Standard DNS (UDP port 53) is unencrypted.

Example analogy:

TLS is like sending a letter in a sealed, opaque envelope. Nobody en route can read the contents. But the envelope still has the destination address written on the outside- the postal service needs it to deliver the letter. In TLS, the SNI field is equivalent to the delivery address. DNS queries are the phone book lookup you make before writing the address. DNS queries are also visible to anyone watching.

Where this analogy breaks down: With physical mail, you can send letters without a return address. With TLS, the client’s IP address is always visible (it has to be, for TCP to work), and the SNI hostname is structurally required in most configurations. There’s no “anonymous envelope” option in standard TLS. The Encrypted Client Hello (ECH) extension aims to seal the SNI, but it requires server-side support and isn’t widely deployed.

Together, these two metadata leaks tell you where claude connects, even though you can’t see what it sends.

Mental Model Check: “HTTPS hides everything.”

This is the most common misconception about TLS, and it’s important to correct because it affects how you reason about privacy and surveillance. TLS protects the contents of communication. This includes the URL path, headers, body, and cookies. But it does not protect the metadata: which server you’re connecting to (SNI), the IP addresses of both ends, the timing and volume of data transferred, and the DNS lookups that preceded the connection. A network observer who can’t read your HTTPS traffic can still build a detailed profile of which services you use, when, and how much data you exchange.

Extracting hostnames with sni.py

The repository includes sni.py, a pure-Python pcap parser that extracts SNI values from TLS ClientHello messages:

python3 sni.py claude.pcap

Output from a real capture (claude fetching example.com):

  8  api.anthropic.com                  -> 160.79.104.10
  3  docs.mcp.cloudflare.com            -> 104.18.24.159
  1  example.com                        -> 104.20.23.154
  1  http-intake.logs.us5.datadoghq.com -> 34.149.66.137

What this tells us

The output reveals four distinct destinations, each serving a different role:

SNI HostnamePurposeCount
api.anthropic.comModel API: inference requests and responses8
docs.mcp.cloudflare.comA configured MCP server, contacted at startup3
example.comThe WebFetch target: a direct connection1
http-intake.logs.us5.datadoghq.comTelemetry / usage logging1

Look at the third row. example.com appears as a direct TLS connection from the namespace. This means WebFetch initiated a TCP connection to example.com’s IP address from the local machine. The request was not proxied through api.anthropic.com. We can see this from metadata without decrypting a single byte of content.

WebFetch connects directly from your machine. Everything that follows confirms and enriches this finding, but the SNI evidence is sufficient on its own.

You can also examine DNS queries to confirm:

tcpdump -nr claude.pcap 'udp port 53'

This shows DNS lookups for each of those hostnames, confirming the process resolved them locally.

The partial finding

We now know WHERE claude’s traffic goes. We can see it contacts the target URL directly. But we still can’t see the HTTP request content . For that, we need to decrypt the TLS.

In the part 4, we’ll dig deeper into the details by decrypting HTTPS with MITM proxy.