How it fits together
- 01XDP prefilter
- 02TCP SYN
- 03TLS · JA4
- 04HTTP/2 · QUIC
- 05Score
- 06Verdict
Why build it
Bots lie about who they are, and the commercial answer is a black box sitting in front of all your traffic. I wanted the same depth of inspection on my own servers, with rules I can read and data that stays with me.
Every layer, one question
Each connection is fingerprinted on every layer it passes through, and the fingerprints are checked against each other: does this stack match the browser the client claims to be?
- TCP SYN: window size, option order and TTL, before a single byte of TLS.
- TLS: JA4, JA4T and JA4H, read straight from the ClientHello.
- HTTP/2 and QUIC: SETTINGS frames, pseudo-header order, transport parameters.
- A consistency score sorts every client into
human,suspicious,botorverified_bot.
Proxies without proxy lists
A proxy adds a hop, and a hop shows up as time. LarpGuard compares the round trip of the TCP handshake with the round trip of the TLS 1.3 handshake; when they disagree, someone is in the middle. No IP lists to buy or keep fresh.
The fast path lives in the kernel
Known-bad traffic never reaches userspace: an XDP/eBPF prefilter with its own loader (no libbpf) drops it at the network card. Honeypot paths, a tarpit and decoy responses waste the time of whoever is left.
It learns from its own traffic
Every decision is an event in ClickHouse. A gradient-boosting model is trained on those events and runs inside the C++ gateway in microseconds. The grey zone gets a JS challenge with proof of work and my own CAPTCHA; real crawlers are verified by DNS and IP ranges, and Web Bot Auth signatures (Ed25519).
Rules people can read
Policies are written in a small rule language, and rate limits use GCRA kept in sync across instances through Valkey.
path ~ "/admin*" and class != "human" => challenge
ja4 in known_bad => drop
rate(ip, 100/min) => tarpitStack
C++20, BoringSSL, HTTP/3 on ngtcp2 and nghttp3, XDP/BPF, ClickHouse, Valkey, behind Traefik.