What Gate is (and is not): a technical overview

Diátaxis quadrant: Explanation. Audience: engineering leaders, architects, and first-pass evaluators deciding whether humanymous Gate belongs in their stack.

This page is written engineer-to-engineer. It leads with the limits, because that is the honest way to evaluate a bot-mitigation layer: what a control does not do is as load-bearing as what it does. Read this before you read anything that sounds like a feature list.

This repository is a reference implementation, not a production-hardened build. Where a capability is deferred to production (a production responsibility), this page says so.

Start with the limits

Two boundaries matter before anything else.

Coherent browser or human-assisted traffic is a design boundary, not a solved problem. Anti-detect browser stacks driven by real humans are hard to separate from legitimate users because much of what they emit is genuine human behavior on a genuine engine. Gate mitigates this boundary with rate limiting and reputation, never with detection alone. On routes using the attested preset, an attestation floor also prices an otherwise allowed request: without a possession credential or a valid step-up proof, the request is challenged instead of fast-pathed. This raises the cost of access to selected routes; it does not make the traffic detectable.

The safe-GET fail-open residual is deliberate. On a balanced route, when the verdict is still Unknown (no evidence collected yet) and the request is a safe method (GET or HEAD), Gate fails open — it passes the request to the origin rather than blocking it. This is an accepted residual: it protects real first-time visitors and crawlers you want, at the cost of letting a not-yet-scored safe read through. That window is covered meanwhile by fingerprint- and subnet-level rate metering, and you can close it entirely on sensitive routes by choosing the strict preset, which fails closed (Unknown → challenge). Unsafe methods (POST/PUT/PATCH/DELETE) always fail closed regardless of preset.

Everything below should be read against those two limits.

The problem, stated mechanically

Four automated abuse patterns motivate this layer:

  • Credential stuffing — replaying breached username/password pairs against a login endpoint at machine speed.
  • Scraping — bulk extraction of catalog, pricing, or content that a human would never page through by hand.
  • Inventory sniping — automated carts and checkouts that clear limited stock before humans can transact.
  • Fake account creation — programmatic signups that seed spam, fraud, or later abuse.

The common thread is not malice — it is mechanism. Each pattern is an HTTP client that is not a human driving a browser, and the goal of Gate is to tell those apart with evidence, then let you decide what to do. We describe blocked traffic as “automated” or “not verified as human.” We do not describe your own users as attackers.

The mechanism: seven named stages and a score, not a flag

Gate is a reverse proxy that terminates TLS in front of your origin. When a request arrives on a route configured to inject, Gate streams its current collection loader into the HTML response. The browser returns evidence to the control plane (/__hmn/collect). Gate reuses Core’s scorer, but it does not currently collect Core’s complete browser report or extract the client’s full ClientHello and HTTP/2 evidence. Do not treat a Core measurement as a Gate measurement.

Detection is organized as seven stages:

  • Static client inspection — browser automation and headless-environment evidence.
  • Client fingerprinting — graphics, audio, display, and hardware consistency.
  • Client integrity — modified native functions, runtime hooks, and proxy evidence.
  • Interaction analysis — event trust, pointer, keyboard, and scrolling observations.
  • Network and protocol inspection — encrypted-connection negotiation, HTTP/2 behavior, and header order.
  • Consistency checks — comparisons between browser claims and independently observed behavior.
  • Scoring and policy — one combined risk score followed by ordered enforcement rules.

The scoring-and-policy stage produces a risk score from 0 to 100 (one decimal), not a binary bot/human flag. Three principles shape how the score is produced:

  • Defense-in-depth. No single signal produces a verdict. A lone “botty” reading is weak evidence.
  • Cross-check-first. Inter-layer disagreement — a UA that claims Chrome while the TLS and HTTP/2 fingerprints resolve to a different engine — outweighs any single botness heuristic. Consistency is harder to fake than any one attribute.
  • Low false-positive. Privacy browsers, blocking extensions, and old devices must not be scored as bots. Low-confidence signals get weak weighting, not penalties. The same input always produces the same score (deterministic).

On top of the score sit enforcement rules, which can raise a score-based verdict when a high-confidence condition is met. Examples include the hard automation artifact rule and the browser-claim-without-execution-evidence rule. A completed proof-of-work challenge can upgrade a score-based CHALLENGE to ALLOW, but it never overrides a rule-promoted verdict: computation does not prove that a person is present.

The enforcement model: three actions at the edge

The engine emits one of three verdicts, and Gate enforces it at the edge before your origin is involved:

  • ALLOW → the request passes to the origin.
  • CHALLENGE → Gate withholds the request from the origin and asks for additional verification. The reference includes challenge components, but not a complete usable recovery path for every challenged visitor.
  • DENY → the request is blocked. The origin is never contacted.

Thresholds under the shipped policy: risk 0–29 → ALLOW, 30–69 → CHALLENGE, 70–100 → DENY — with a fired enforcement rule able to promote the verdict regardless of the score:

\[\text{verdict} = \begin{cases}\text{DENY} & \text{denial rule matched, or risk}\ge 70\\ \text{CHALLENGE} & \text{challenge rule matched, or }30\le\text{risk}<70\\ \text{ALLOW} & \text{risk}<30\end{cases}\]

The bundled challenge is self-hosted and does not put a third-party CAPTCHA vendor in the path. It is still reference code: do not describe CHALLENGE as recoverable until your deployment has wired and tested an accessible end-to-end verification flow. When a valid, fingerprint-bound verdict trust token is present, an ALLOW can take a fast path with no re-scoring.

Per-route policy is chosen from five presets: off (no injection, no enforcement), monitor (inject, score, log, enforce nothing), balanced (the default for any unmatched route — inject, enforce, safe-GET fail-open), strict (inject, enforce, fail-closed, synchronous re-score), and attested (strict plus an attestation floor for operator-marked high-value routes — a scoring-ALLOW is priced up to a Pass challenge unless it presents possession or an hmn_su step-up; CHALLENGE→Pass, never DENY; refused on catch-all routes). A node-local kill switch or the global -monitor flag can demote enforcement to monitor everywhere, so you can turn detection into observation without redeploying.

Note: Route matching is longest-prefix-wins. The default table sends /login, /checkout, and /admin to strict, /health to off, and everything else to balanced.

The audit posture: tamper-evident, and honest about the residual

Every enforcement decision is written to an append-only audit log before it takes effect — an audit-or-panic sink. The log is a hash chain with a per-record HMAC, an Ed25519 Signed Tree Head (STH) every 32 records, and an independent local witness that co-signs each STH. A writer that tries to rewrite history cannot obtain a witness co-sign, and an offline verifier can check the whole chain without trusting the operator.

This is tamper-evident, not tamper-proof — and the difference is a real residual, stated plainly: records written after the last signed checkpoint remain re-writable by the writer until the next checkpoint or anchor. That unanchored in-window residual is the honest scope of the guarantee. Tampering with anything already checkpointed is detectable; the most recent unanchored records are not yet protected.

Identifiers in the log (IP, JA4, HTTP/2 fingerprint, UA, SNI, device fingerprint) are stored only as per-subject pseudonyms (scrypt-stretched 64-hex), never raw. This is pseudonymous, not anonymous — re-identification is possible offline for anyone who holds the keystore and HMN_UNSEAL (Gate exposes no re-identification API; dual-control does not gate this). The audit subject is a session, not a person: each visit gets its own unlinkable subject key, so Art. 17 erasure of one session does not automatically erase a person’s other visits. Right-to-erasure is implemented as cryptographic erasure (crypto-shred): the per-subject linkage key is destroyed while the chain and its Merkle anchors stay intact and verifiable.

Warning: Cryptographic erasure is irreversible. Destroying the linkage key cannot be undone; the records become permanently unlinkable to the subject. A cancellable hold window precedes commit, but once committed there is no recovery.

What Gate is not

  • Not a finished challenge product. Gate does not depend on a third-party CAPTCHA service, but the reference challenge components are not a complete visitor-recovery experience. Operators must supply and test the accessible flow they intend to run.
  • Not a web application firewall replacement. Gate assigns an automation-risk verdict from its available evidence. It does not inspect payloads for Structured Query Language injection, cross-site scripting, or application-layer exploits. It is complementary to a web application firewall, not a substitute.
  • Not a guarantee. Gate reduces automated abuse and raises its cost. It does not block all bots, and — per the limits section — it does not resolve human-assisted coherent browser or human-assisted automation traffic. Treat it as one control in a layered posture.

Topology

flowchart TD
  B["End-user browser"] -- "HTTPS" --> G
  subgraph G["humanymous Gate"]
    direction TB
    T["Terminate encrypted connection · stream browser collection bundle into HTML"]
    S["Score inline (0–100) · apply enforcement rules"]
    A["Write decision to tamper-evident audit log"]
    E{"Enforce verdict at edge"}
    T --> S --> A --> E
  end
  E -- "ALLOW" --> O["Operator origin app · (Gate fronts it; does not control it)"]
  E -- "CHALLENGE" --> Verify["Verification interstitial · (origin never contacted)"]
  E -- "DENY" --> X["Blocked · (origin never contacted)"]

Gate sits in the request path and owns the verdict; your origin app stays as it is behind it. On CHALLENGE or DENY, the origin is never reached.

Where to go next

  • Will this break my app? — the safety model, fail-open behavior, and a staged rollout from monitor to enforce.
  • How Gate sees a request — the concepts and glossary behind the stages, verdicts, and signal identifiers.
  • Where Gate fits — a side-by-side on where Gate complements an existing WAF, CDN bot manager, or CAPTCHA, with the threat model and honest limitations.