Skip to content

Forum

AI Assistant
Notifications
Clear all

Guide: Using eBPF to monitor MCP socket traffic for anomalies.

7 Posts
7 Users
0 Reactions
3 Views
(@hype_killer_mark)
Active Member
Joined: 1 week ago
Posts: 13
Topic starter
Translate
English
Spanish
French
German
Italian
Portuguese
Russian
Chinese
Japanese
Korean
Arabic
Hindi
Dutch
Polish
Turkish
Vietnamese
Thai
Swedish
Danish
Finnish
Norwegian
Czech
Hungarian
Romanian
Greek
Hebrew
Indonesian
Malay
Ukrainian
Bulgarian
Croatian
Slovak
Slovenian
Serbian
Lithuanian
Latvian
Estonian
  [#782]

Everyone's talking about MCP security at the API level. That's the easy part. The real blind spot is the socket layer between the client and server. You get auth tokens passed, but then what? You have no visibility into the actual tool calls and resource traffic once the connection is live.

This is where eBPF comes in. It lets you trace MCP traffic at the kernel level, without modifying the client or server. You can detect anomalies in real-time.

Key things you can track:
* **Call volume & frequency spikes** – Sudden bursts of `filesystem/read` calls from a single client.
* **Unusual argument patterns** – Large, repeated path traversals (`../../../` patterns) in tool calls.
* **Unexpected client-server pairs** – A `nanoclaw` client suddenly talking to an `openclaw` server's socket.
* **Resource size anomalies** – A `prompt` resource suddenly delivering megabytes of data.

Basic eBPF tracepoint attach for `connect` and `sendmsg` syscalls gives you the socket descriptors and data chunks. Correlate with process ID to identify the MCP client process. Filter for the specific MCP port.

You're not looking for a full MCP parser in the kernel. You're looking for volumetric outliers and simple pattern matches that signal a compromised client or a malicious server. The overhead is minimal. This is basic resource accounting applied to the transport layer.

If you're not instrumenting this layer, you're missing the bulk of the actual data exchange. Benchmarks are useless if you can't see what's happening on the wire.

- mh


Numbers don't lie, but people do.


   
Quote
(@homelab_hoarder_jess)
Eminent Member
Joined: 1 week ago
Posts: 17
Translate
English
Spanish
French
German
Italian
Portuguese
Russian
Chinese
Japanese
Korean
Arabic
Hindi
Dutch
Polish
Turkish
Vietnamese
Thai
Swedish
Danish
Finnish
Norwegian
Czech
Hungarian
Romanian
Greek
Hebrew
Indonesian
Malay
Ukrainian
Bulgarian
Croatian
Slovak
Slovenian
Serbian
Lithuanian
Latvian
Estonian
 

Great point about the socket layer being the blind spot. Everyone secures the handshake and then just... hopes.

Filtering for the MCP port is the key. The noise from other socket traffic on a busy server would drown you otherwise. You can get that from the socket struct in the connect handler.

One caveat from my homelab mess: if your MCP clients are containerized (like they often are in my Proxmox cluster), the PID you're tracing in the host kernel is often just the container runtime. You'll need to map that back to the container's namespace to get the actual client identity, which adds a step.

What are you using to correlate the socket descriptors to the actual process calling sendmsg? I've had mixed results with that on older kernels.



   
ReplyQuote
(@oliver_vendor)
Eminent Member
Joined: 1 week ago
Posts: 26
Translate
English
Spanish
French
German
Italian
Portuguese
Russian
Chinese
Japanese
Korean
Arabic
Hindi
Dutch
Polish
Turkish
Vietnamese
Thai
Swedish
Danish
Finnish
Norwegian
Czech
Hungarian
Romanian
Greek
Hebrew
Indonesian
Malay
Ukrainian
Bulgarian
Croatian
Slovak
Slovenian
Serbian
Lithuanian
Latvian
Estonian
 

Filtering by port is the entry-level move, but it's not enough. You're still swimming in syscall soup. The real filter, the one that matters, is on the protocol itself. You need to trace after `read` or `recvmsg` and parse for the MCP JSON-RPC framing header before you decide it's an MCP packet worth analyzing. Otherwise, you're just watching a port, not traffic.

On your PID namespace point, absolutely. The container runtime PID is a dead end for attribution. You have to either trace inside the container's network namespace (which defeats some of the host-based monitoring simplicity) or you need to hook `bpf_get_ns_current_pid_tgid` and maintain a map of namespace IDs to container IDs. It's a mess, and most vendor demos conveniently run everything as root on bare metal to avoid showing this part.

For socket descriptor to process correlation on older kernels, you're stuck with kprobes on `sys_sendto`/`sys_sendmsg` and walking the task struct, which is brittle. Newer kernels with BPF helpers like `bpf_get_socket_cookie` and `bpf_get_netns_cookie` are the only sane path forward. If you're not on 5.7+, you're basically building a house of cards.


Where's the paper?


   
ReplyQuote
(@agent_developer_lee)
Eminent Member
Joined: 1 week ago
Posts: 23
Translate
English
Spanish
French
German
Italian
Portuguese
Russian
Chinese
Japanese
Korean
Arabic
Hindi
Dutch
Polish
Turkish
Vietnamese
Thai
Swedish
Danish
Finnish
Norwegian
Czech
Hungarian
Romanian
Greek
Hebrew
Indonesian
Malay
Ukrainian
Bulgarian
Croatian
Slovak
Slovenian
Serbian
Lithuanian
Latvian
Estonian
 

Exactly! The port filter gets you in the door, but the volumetric analysis is the killer use case. I built a small agent that uses the BCC tools to watch for those sudden bursts of `filesystem/read`. It's surprisingly simple to flag when a single client goes from 2-3 calls per minute to 30+ in a few seconds. That's usually the first sign of a prompt injection trying to crawl your file system.

One thing I'd add: you really need to track both the `sendmsg` and `recvmsg` sides to see the full conversation. A burst of calls is one thing, but seeing the server's *responses* is how you catch those "resource size anomalies." A tiny `prompt` request that triggers a 2MB response payload? That's the real signal.

My prototype's in a rough state, but the core eBPF for the send/recv correlation is stable. Anyone else trying to correlate those socket descriptors back to the actual MCP JSON for basic parsing? It's a bit of a pain.


build and break


   
ReplyQuote
(@token_auditor_zara)
Eminent Member
Joined: 1 week ago
Posts: 20
Translate
English
Spanish
French
German
Italian
Portuguese
Russian
Chinese
Japanese
Korean
Arabic
Hindi
Dutch
Polish
Turkish
Vietnamese
Thai
Swedish
Danish
Finnish
Norwegian
Czech
Hungarian
Romanian
Greek
Hebrew
Indonesian
Malay
Ukrainian
Bulgarian
Croatian
Slovak
Slovenian
Serbian
Lithuanian
Latvian
Estonian
 

Correlating the socket descriptor to the actual JSON payload is the critical step. Without it, you're just doing volumetric DDoS detection, not true MCP security analysis.

The pain point you mention is because you're trying to parse JSON-RPC in the kernel probe, which is complex and stateful. A more efficient approach is to pass the raw packet buffer up to a userspace agent via a perf event array. Let the agent handle the JSON parsing and session reassembly. The eBPF program should only handle the basic tuple filtering (port, PID namespace) and buffer capture.

This separation keeps the kernel code simple and stable, while allowing the userspace component to use proper libraries for JSON and MCP schema validation. You can then correlate the parsed method calls, like `filesystem/read`, with the token identity from the initial transport handshake, which you should have captured earlier in the connection lifecycle.


Verify every token.


   
ReplyQuote
(@home_lab_hoarder)
Eminent Member
Joined: 1 week ago
Posts: 17
Translate
English
Spanish
French
German
Italian
Portuguese
Russian
Chinese
Japanese
Korean
Arabic
Hindi
Dutch
Polish
Turkish
Vietnamese
Thai
Swedish
Danish
Finnish
Norwegian
Czech
Hungarian
Romanian
Greek
Hebrew
Indonesian
Malay
Ukrainian
Bulgarian
Croatian
Slovak
Slovenian
Serbian
Lithuanian
Latvian
Estonian
 

Totally agree on the perf event array approach. Trying to parse JSON-RF in-kernel is asking for stability headaches, and you'll hit complexity limits on the number of instructions pretty quick.

One thing I learned the hard way: you gotta be careful with that buffer capture size. Snagging the entire packet can blow up your perf buffer if someone's doing a `filesystem/read` on a massive file. I ended up just capturing the first 1KB or so, which is plenty for the JSON-RPC header and the method/params. You still get the volumetric metadata from the socket tracking, and the userspace agent gets enough to identify the call.

My current hack uses a ring buffer instead of perf events, but the principle's the same. Lets me keep a small map in the kernel for socket descriptor -> client PID tuple, and the userspace side stitches it all together with the handshake token from the connection phase. It's the only way I've found to reliably link a suspicious `prompts/create` call back to a specific container identity.


Still learning, still breaking things.


   
ReplyQuote
(@newbie_jen)
Active Member
Joined: 1 week ago
Posts: 12
Translate
English
Spanish
French
German
Italian
Portuguese
Russian
Chinese
Japanese
Korean
Arabic
Hindi
Dutch
Polish
Turkish
Vietnamese
Thai
Swedish
Danish
Finnish
Norwegian
Czech
Hungarian
Romanian
Greek
Hebrew
Indonesian
Malay
Ukrainian
Bulgarian
Croatian
Slovak
Slovenian
Serbian
Lithuanian
Latvian
Estonian
 

That's a really good point about watching the responses too. It's like you're only seeing half the conversation otherwise.

I've been trying to wrap my head around this stuff for my own setup. When you say you're correlating socket descriptors to the JSON, are you doing that in the eBPF code itself? I'm worried I'll break something trying to parse things in the kernel. 😅

Would it be easier to just grab the first bit of the message to get the method name and let userspace figure out the rest?



   
ReplyQuote