In part 3, we explored pulling hostnames from an encrypted session. In this post, we’ll crack some TLS.
Decrypting HTTPS with a MITM proxy
The hostname extraction helped us discover where scraping is launching from. WebFetch connects directly to target websites from your machine. But hostnames alone don’t reveal what Claude sends in those requests: the HTTP headers, the User-Agent string, the exact URL paths, or the response bodies it receives. To see the full content of Claude’s network traffic and understand exactly what target servers receive and what data flows back, we need to decrypt the TLS.
How MITM proxying works
We use a Man-in-the-Middle (MITM) proxy to read the plaintext content of claude’s HTTPS requests and responses. The proxy sits between claude and the target server, terminating TLS on both sides so it can read the traffic in between:
claude ──TLS──► mitmproxy ──TLS──► example.com
▲ │
│ reads plaintext
│ request & response
│
trusts proxy's CA
(NODE_EXTRA_CA_CERTS)
- Claude connects to
mitmproxy(thinking it’s the real server) - mitmproxy presents a certificate it generated on-the-fly, signed by its own CA
- Claude accepts this certificate because we told Node.js to trust that CA
- mitmproxy reads the plaintext request
- mitmproxy connects to the real server and forwards the request
- The real server responds; mitmproxy reads the response
- mitmproxy forwards the response back to claude
mitmproxy now has a complete plaintext transcript of every HTTP request and response.
Example analogy:
A MITM proxy is like a bilingual translator at a diplomatic meeting. Both sides think they’re talking directly to each other, but the translator sits in the middle, receiving every message, reading it, and forwarding it. The key requirement: each side must trust the translator’s credentials. If either side refuses to accept the translator, the communication fails.
Where this analogy breaks down: A real translator can only work if both parties agree to use one. In MITM proxying, the server doesn’t agree to anything. It sees a normal TLS connection from the proxy’s IP address. Only the client must be tricked (or, in our case, explicitly configured) into accepting the proxy’s certificates, so you only need to modify the client’s trust configuration, not the server’s.
Mental Model Check: “MITM is an attack technique.”
MITM interception on a network you don’t control is an attack. MITM proxying on your own machine, for a process you launched, with a CA you generated is a debugging technique. The mechanism is identical for both attacks and debugging. The only difference is authorization context. Burp Suite (penetration testing), Charles Proxy (mobile app debugging), Fiddler (Windows HTTP debugging), and corporate TLS inspection appliances all use the same approach. The
claude-mitm.shscript is doing exactly what these tools do, scoped to one process.
Scoping the trust to one process
We scope the trust to one process. We don’t install the mitmproxy CA system-wide. Instead, we set three environment variables only for the claude process:
export HTTPS_PROXY=”http://127.0.0.1:8080″ # Route HTTPS through the proxy
export HTTP_PROXY=”http://127.0.0.1:8080″ # Route HTTP through the proxy
export NODE_EXTRA_CA_CERTS=”$HOME/.mitmproxy/mitmproxy-ca-cert.pem” # Trust the proxy’s CA

Node.js honors both HTTPS_PROXY (route traffic through a proxy) and NODE_EXTRA_CA_CERTS (trust additional CA certificates). These are set only in the shell environment of the claude process. No other process on the system is affected.
We can scope the trust this way because Node.js reads CA certificates from environment variables at startup. That means we can modify the trust configuration of one Node.js process without affecting any other process on the system. If claude were written in Go or Rust, we’d need a different approach. Those runtimes use the system certificate store and don’t honor NODE_EXTRA_CA_CERTS. The environment-variable approach works here specifically because claude is a Node.js application.
Running the MITM capture

Interactive mode (two terminals):
Terminal 1:
./claude-mitm.sh proxy # Starts mitmweb with web UI at http://127.0.0.1:8081
Terminal 2:
./claude-mitm.sh run # Launches claude routed through the proxy
Browse to http://127.0.0.1:8081 to see decrypted traffic in real time.
One-shot scripted mode (single command):
./mitm-capture.sh "Use the WebFetch tool to fetch https://example.com and report the title."
This starts mitmdump (the CLI version of mitmproxy) with the mitm_logger.py addon, runs one claude prompt through it, and writes a plaintext transcript to mitm.txt.
Reading the decrypted transcript
grep '^>>> ' mitm.txt # Every outbound request
less mitm.txt # Full headers and bodies
Here’s a real captured WebFetch request :
>>> GET https://example.com/
Accept: text/markdown, text/html, */*
Accept-Encoding: gzip, compress, deflate, br
User-Agent: Claude-User (claude-code/2.1.168; +https://support.anthropic.com/)
Host: example.com
<<< 200 OK (example.com)
Content-Type: text/html
...
--- response body ---
<!doctype html><html lang="en"><head><title>Example Domain</title>...
What the decrypted traffic reveals
With the TLS removed, we can now see exactly what Claude sends and receives. A few things stand out:
- User-Agent: WebFetch identifies itself as
Claude-User (claude-code/<version>; +https://support.anthropic.com/). Target servers can identify these requests. - Content preference: The
Acceptheader liststext/markdownfirst. claude asks the server for markdown before HTML because markdown is more useful to a language model than raw HTML. - Direct connection: The request goes directly to
example.com, confirming the SNI evidence from the encrypted traffic analysis. There is no intermediate Anthropic proxy. - API calls: You’ll also see
POST https://api.anthropic.com/v1/messages. These are the model inference calls containing your prompt, tool results, and the model’s responses. - Telemetry: Requests to
http-intake.logs.us5.datadoghq.comcarrying usage metrics.
Security warning.
mitm.txtcontains your decrypted API authorization token in plaintext. Look for theAuthorization: Bearer sk-ant-...header in everyPOSTtoapi.anthropic.com. This is your Anthropic API key. It can be used by anyone who obtains it to make API calls billed to your account. The mitm.txt file is.gitignore‘d in the project. Delete it when you’re done:rm mitm.txt. Treat it with the same care as a credential dump.

