eBPF + MCP.
Giving your agent a wiretap on itself.
Your agent just spent forty minutes doing something. What did it actually send over HTTPs? Not what the logs say — what left the machine.
Your agent ran for 40 minutes. What did it actually send?
The distinction between what the agent said it did and what actually left the machine is the whole talk.
Every existing approach has a fatal caveat.
Helps when you own the app.
Fails for third-party tools, browsers, CLIs. Instrumentation is uneven. Bodies get redacted.
Helps for browser-only flows.
Fails per-browser: extension limits, misses non-browser calls, breaks when the agent shells out.
Helps when you can install a CA on the client.
Fails on cert pinning, TLS fingerprint changes, env-var CA that some SDKs ignore. Terminates TLS and opens a new connection.
There is a moment when the plaintext exists in memory. Read it there.
Before SSL_write encrypts outbound bytes, the plaintext exists in memory.
After SSL_read decrypts inbound bytes, the plaintext exists in memory.
This does not break TLS. It observes the program at the point where the program itself already has plaintext.
// libssl.so.3 int SSL_write(SSL *ssl, const void *buf, int num); // ^^^^^^^^^^ // plaintext, pre-encryption int SSL_read(SSL *ssl, void *buf, int num); // ^^^^^^^^^^ // plaintext, post-decryption
Attach to SSL_write and SSL_read without touching the app.
agent / browser / CLI
↓
plaintext request
↓
OpenSSL SSL_write ←←← eBPF uprobe (captures outbound plaintext)
↓
encrypted TLS
↓
network
↓
encrypted TLS
↓
OpenSSL SSL_read ←←← eBPF uprobe (captures inbound plaintext)
↓
plaintext response
↓
application
No CA. No proxy. Cert pinning irrelevant. Cross-process, cross-language, host-wide.
Agent → MCP → capture daemon → ecapture → OpenSSL uprobes.
agent | MCP tool calls: list_domains, get_pairs, search ↓ MCP server (my Python layer) | domain filter, request/response pairing, full-text index ↓ capture daemon (subscribes to interesting events) | reads flat stream ↓ gojue/ecapture v0.8.12 (the eBPF harness) | attaches uprobes ↓ OpenSSL · GnuTLS · BoringSSL · rustls
ecapture does the eBPF part. My layer does the useful abstraction — and MCP-wraps it so the agent can call it.
Capture gives you bytes. The abstraction layer gives you answers.
Raw ecapture output: chunks, streams, timestamps. Not useful to a human. Not useful to an agent.
My layer is ~600 lines of Python. That's the useful part.
Three questions an operator asks — three tools.
The whole MCP server is ~40 lines of Python once the capture layer exists.
Two agents. One target. One observer.
A Claude Code session with a small task — e.g. “find the top 3 US tech news headlines and summarize each in 50 words.”
Uses its browser tool + search tool. Real HTTPs traffic to real sites.
A Claude Code session with the ebpf-mcp server connected.
Task: “audit what agent A just did — which sites did it hit, did it send anything unexpected?”
Every live moment has a canned counterpart. Screen recording ready if wifi or the MCP handshake dies.
Agent B calls list_domains. Here is what agent A actually hit.
domain requests last_seen api.openai.com 17 12s ago www.techcrunch.com 4 48s ago arstechnica.com 3 1m 12s ago news.ycombinator.com 2 1m 44s ago google.com 2 2m 03s ago stats.tracker.dev 6 4m 22s ago ← unexpected
The unexpected domain jumps out. Now agent B calls get_pairs(“stats.tracker.dev”).
The plaintext body on stage. No proxy involved.
POST https://api.openai.com/v1/chat/completions Authorization: Bearer sk-***REDACTED*** Content-Type: application/json { “model”: “gpt-4o”, “messages”: [ {“role”: “system”, “content”: “You are a helpful assistant.”}, {“role”: “user”, “content”: “Summarize this article in 50 words: ...”} ] } 200 OK · 1.4s · 823 bytes {“choices”: [{“message”: {“content”: “OpenAI announced ...”}}]}
This is the request body the agent actually sent. Not what the trace said. Not what the model summarized. The bytes before encryption.
Capture host-wide. Persist selectively.
Host-wide — any process on the box, no client-side cooperation required.
The uprobes fire on every SSL_write / SSL_read. That's the correct default.
Selective — ring buffer in RAM by default, disk overflow only for watched domains or on explicit save.
The capture stream contains secrets. Treat the persistence layer like a secrets store: short retention, allowlists, no casual sharing.
Honest limits. This makes the talk credible.
crypto/tls is separate, BoringSSL / Chrome may be statically linked or stripped.This is sensitive infrastructure. Treat it that way.
The eBPF+MCP combination is a category, not a one-off.
sha256(binary) → offsets.execve, open, socket — a full agent audit surface.redactions_check MCP tool — auto-flag outbound bodies that look like secrets.MCP tools usually give the agent capability.
This one inspects the agent's capability.
2026's agent-observability question is not «did the tool call succeed?»
It is «what did the tool call actually send, and did the response actually match the model's claim about it?»
eBPF gives us the ground-truth layer. MCP gives the agent a way to reason over that layer during its own session.
Together: the agent audits itself, in real time, without a human in the loop.