# Supported topologies: where each detection layer is actually active Source: https://humanymous.net/reference/supported-topologies.html Project: humanymous — defensive anti-bot / browser-automation detection (Apache-2.0). --- # 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](./production-vs-reference.md). ## 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](./production-vs-reference.md)). 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](./cli-config-policy.md). ```mermaid flowchart TB subgraph GOOD["Network plane ACTIVE"] direction LR B1["Browser"] -->|"raw TLS"| L4["L4 / TCP-passthrough LB
(PROXY v2, optional)"] -->|"raw TLS"| C1["humanymous Core
terminates TLS · reads ClientHello"] end subgraph BAD["Network plane INERT (silent)"] direction LR B2["Browser"] -->|"raw TLS"| CDN["CDN / WAF / L7 LB
re-terminates TLS"] -->|"new TLS"| C2["humanymous
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](./cli-config-policy.md) and [production vs reference](./production-vs-reference.md). ## 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. ## Attested routes need one shared cookie jar (a topology prerequisite) 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](./install-requirements.md#ports), 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](../how-to/configure-attested-routes.md) 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. ## Related pages - [Where Gate fits: vs WAF, CDN bot manager, and CAPTCHA](../explanation/where-gate-fits.md) — the threat-tier (T0–T4) model and when Gate is *not* the right tool. - [Which piece am I using?](../explanation/which-piece-am-i-using.md) — the three surfaces (Core `:8443`, Gate `:8444`, Observatory) so you never confuse two binaries. - [Production vs reference](./production-vs-reference.md) — the canonical prod-delta list, including ClientHello capture at the gate. - [CLI, config & per-route policy](./cli-config-policy.md) — `-trusted-proxies` (PROXY protocol), `-redis`, `HMN_TOKEN_KEY`, and the route model. - [Will this break my app?](../explanation/will-this-break-my-app.md) — fail-open/closed behavior and monitor-first rollout.