Supported topologies: where each detection layer is actually active

Quadrant: Reference. Audience: integrators and platform engineers deciding where in their stack to place humanymous, and evaluators who need to know which detections will and will not fire in their topology before they benchmark.

This page is limitations-first. The single most important fact about deploying humanymous is that its detection layers are not all active in every topology — some depend on the process terminating raw TLS on its own accept loop, and one of the two shipped binaries does not capture them at all. If you benchmark the rich demo engine and then deploy the reverse proxy, or you place either behind a TLS-terminating CDN, you inherit a materially weaker detector and nothing in the request path will tell you. This page maps exactly what is active where, so you size expectations to your real topology, not the demo.

This repository is a reference implementation, not a production-hardened build. Where a capability is deferred to production, this page says so and links production vs reference.

The one fact to take away first

The seven detection layers split into two groups by where the evidence comes from:

  • L1–L4 (client plane) — collected in the browser by JS + WASM and beaconed to the server under a rotating integrity token. Active wherever the detection bundle can run and reach the control plane.
  • L5–L6 (network plane) — JA3/JA4 TLS fingerprint, the HTTP/2 fingerprint, header order, and the UA-vs-TLS/UA-vs-H2 cross-checks. These are read from the raw TLS connection itself. They exist only in a process that terminates raw TLS on its own accept loop and extracts the ClientHello.

If anything between the client and the humanymous process re-terminates TLS — a CDN, a WAF appliance, an L7 load balancer, a corporate TLS-inspecting proxy — the ClientHello humanymous sees is that intermediary’s, not the browser’s, and the entire network plane is inert. It does not error; it simply produces no L5/L6 signals, and detection silently collapses to the client plane (the most spoofable layer) plus header and IP-intel heuristics.

The two shipped surfaces are not equivalent detectors

There are two binaries, and they do not have the same detection coverage. This is the gap most likely to surprise you.

Surface Binary Client plane (L1–L4) Network plane (L5/L6 TLS/H2) Notes
Core detection engine cmd/server (:8443) Yes Yes — captures the raw ClientHello + H2 frames on its own accept loop The rich demo/reference surface. What most people benchmark.
Gate reverse proxy cmd/gate (:8444) Yes No — the gate does not currently extract the ClientHello The component you deploy in front of an origin. JA3/JA4/H2 and their cross-checks (HR-2/HR-5/HR-11/HR-14) do not fire here. prod-delta.

The consequence, stated plainly: an integrator who evaluates the Core engine and then deploys the Gate loses the network plane. At the gate, detection is client JS/WASM + header cross-checks + behavior + IP-intel only. The gate logs a startup NOTE saying so, and the ClientHello-capture path is a documented prod-delta (see production vs reference). If you need TLS fingerprinting in production, either wire raw ClientHello + H2 capture into the gate accept loop (the captureListener pattern already exists in cmd/server) or deploy the Core engine directly as the TLS terminator.

Topology matrix: what fires where

Read your intended topology across the row. “Client” = L1–L4, “Network” = L5/L6 TLS/H2, “Headers/IP” = header-order + IP-intel heuristics + behavioral rate/correlation.

Topology Who terminates the browser’s TLS Client (L1–L4) Network (L5/L6) Headers / IP / behavior Verdict quality
Core engine, direct-facing The Core itself Active Active Active Full — the design’s intended shape
Gate proxy, direct-facing The Gate itself Active Inactive (gate does not capture ClientHello) Active Reduced — no TLS/H2 plane
Core/Gate behind an L4 / TCP-passthrough LB (PROXY protocol) The humanymous process Active Active on Core / inactive on Gate Active (real client IP recovered via PROXY v2) As direct-facing; IP-keyed state stays correct
Core/Gate behind a CDN or L7 LB that re-terminates TLS The CDN / L7 LB Active Inactive — silently Active, but IP is the CDN’s unless XFF is trust-configured Weakest — client plane + headers only
Behind a corporate TLS-inspecting proxy (client side) The inspection proxy Active Active but mismatched — the JA4 is the proxy’s, not the user’s Active Risk of false-positive on HR-2 (UA-vs-TLS); see limitations

Two rows deserve emphasis:

  • Behind a CDN/L7-LB, the network plane is inert and silent. This is the dominant production topology, and it disables exactly the defenses that catch uTLS/curl_cffi TLS parrots and Chromium TLS mimics. If your CDN can export the client’s JA3/JA4 as a header (some can), that is the supported way to recover the signal — ingest it out-of-band rather than expecting humanymous to capture it. Until then, do not claim network-layer coverage in this topology.
  • The L4-passthrough row is the way to keep the network plane on the Core while still load-balancing. Terminate TLS at the Core, put an L4 (TCP) balancer in front with PROXY protocol v2 so the real client IP is recovered, and do not put a re-terminating CDN ahead of it. See the -trusted-proxies flag in CLI, config & per-route policy.
flowchart TB
  subgraph GOOD["Network plane ACTIVE"]
    direction LR
    B1["Browser"] -->|"raw TLS"| L4["L4 / TCP-passthrough LB<br/>(PROXY v2, optional)"] -->|"raw TLS"| C1["humanymous Core<br/>terminates TLS · reads ClientHello"]
  end
  subgraph BAD["Network plane INERT (silent)"]
    direction LR
    B2["Browser"] -->|"raw TLS"| CDN["CDN / WAF / L7 LB<br/>re-terminates TLS"] -->|"new TLS"| C2["humanymous<br/>sees the CDN's ClientHello"]
  end

The datacenter-ASN signal needs a dataset (it now fails open)

l5.ip.datacenter_asn — the signal that flags a request coming from cloud/hosting IP space — depends on a real datacenter/ASN CIDR dataset that the reference does not ship. With no dataset wired, it fails open: it fires for no one. This is deliberate. An earlier build used a placeholder that treated every public IP as a datacenter IP, which — combined with hard rule HR-11 — hard-CHALLENGEd 100% of real human traffic on any non-loopback deployment. Missing a datacenter bot is far cheaper than challenging every human, so the signal is silent until you provide real data via SetDatacenterCIDRs. Plan to wire an ASN/cloud-CIDR feed if datacenter egress is part of your threat model.

Single-node by default; what scales and what does not

humanymous defaults to single-node, in-process state. Only three kinds of shared state have a distribution seam today (an experimental, off-by-default -redis backend): bans, sticky verdicts, and the rate limiter. Everything else is per-process in memory:

  • Cross-session correlation (residential-proxy-rotation catch, HR-19)
  • The recon sweep detector (HR-30)
  • The nonce caches (anti-replay)
  • The Core’s RIT counter and PoW solve-state

The consequence for a multi-node deployment behind a load balancer: correlation, sweep, and nonce anti-replay weaken as node count rises, and replicating the Core desyncs RIT counters (spurious HR-17 friction) and loses PoW state (re-challenging users who already solved). Until those seams exist, the supported scale-out shape is: cap the Core to a single vertically-scaled node, or accept single-node correlation limits and pin verdict/token continuity with a shared token key (HMN_TOKEN_KEY) and the -redis ban/verdict backend. See CLI flags and production vs reference.

Clock dependence

Three mechanisms derive a value from wall-clock time buckets and therefore assume loosely-synchronized clocks (NTP) across any fleet: the RIT time bucket, the origin-cloaking epoch, and the verdict-token epoch. Each tolerates roughly ±1 bucket of skew as a grace window. A node whose clock drifts more than a bucket past its peers will see spurious RIT/token friction. Run NTP; do not deploy a fleet with unsynchronized clocks.

The attested preset adds an attestation floor on operator-marked high-value routes: a scoring-ALLOW there is priced to CHALLENGE → Pass unless the session presents possession or a step-up proof. That step-up proof depends on a topology prerequisite — it belongs alongside the port and co-location facts in install requirements, because it is a placement constraint, not a runtime tunable.

The mechanism: on a verified Pass solve the Core mints a session-bound receipt; the Gate redeems it at POST /__hmn/stepup and issues the hmn_su proof. The receipt is bound only to the hsid session id. So the Core-Pass front-end and the Gate must serve hsid in the same cookie jar, or the id the Gate reads will not match the id the receipt was minted for. Note this is not a same-origin requirement:

Placement of Core-Pass relative to the Gate Shared hsid? Result
Same host, different ports Yes — cookies are not port-scoped Works
Pass proxied through the Gate Yes — one origin Works
Split-domain / cross-subdomain Pass, no shared cookie Domain on hsid No — hsid diverges verifyStepUpReceipt returns a sid mismatch and 403s indefinitely; hmn_su is never minted; a real human loops on the route

The failure mode is bounded but permanent until fixed: a diverged cookie jar is never a DENY and never clears itself — the human keeps solving the Pass without ever getting the fast-path. Because a valid-signature/wrong-sid receipt is a cross-plane misconfiguration (not automated traffic), the Gate logs it with an actionable reason rather than a blanket block.

To deploy attested routes correctly:

  • Co-serve the Core-Pass front-end through the Gate, or set a shared cookie Domain on hsid so it is the same value on both planes.
  • Set a shared HMN_TOKEN_KEY across the Core and the Gate — the receipt is signed with it and verified with it; without a shared key the receipt cannot verify at all.

See Configure attested routes for the route-marking and end-to-end redemption walkthrough.

Checklist: pick your topology deliberately

  • If you want the full seven-layer verdict: deploy the Core engine as the raw-TLS terminator, direct-facing or behind an L4-passthrough LB — with no re-terminating CDN/L7-LB in front.
  • If you must sit behind a CDN/L7-LB: expect the network plane to be inert; size detection to client + headers + IP-intel, and ingest JA3/JA4 from the CDN out-of-band if it can export it.
  • If you deploy the Gate proxy: know that it captures no TLS fingerprints today; it is the enforcement/injection/audit surface, and its detection is client + headers + behavior + IP-intel.
  • If you scale out: keep the Core single-node, or accept the documented multi-node correlation/nonce/RIT limits; enable the -redis ban/verdict backend and a shared HMN_TOKEN_KEY.
  • If you run attested routes: co-serve Core-Pass through the Gate (or share a cookie Domain on hsid) so hsid does not diverge, and set a shared HMN_TOKEN_KEY on both planes — otherwise the step-up receipt never verifies and humans loop on the route.
  • Run NTP on every node.