Deployment & policy operations
Diátaxis quadrant: How-to. Audience: operators and integrators deploying humanymous Gate (“Gate” after first mention) beyond a first local run.
This page is a shelf of independent, goal-titled recipes. Each follows the same template — a goal, numbered steps, and a way to verify — so you can jump to the one task you need and stop. Concepts and full option tables are linked, not repeated here: for the mechanism behind a request see How Gate sees a request, and for every flag, preset, and threshold see the CLI, config & per-route policy reference.
Note: This repository is a reference implementation, not a production-hardened build. ACME (Let’s Encrypt) and bring-your-own TLS ARE supported and wired in the reference binary — see HTTPS / TLS certificates for the full walkthrough. What remains a prod-delta is automated signing-key rotation. Each recipe says so where it applies.
Pick your recipe:
- Deploy from the published image with pull-only Compose
- Move from the dev certificate to production TLS
- Cloak the origin so direct hits get a 421
- Test your integration behind Gate
- Stand up the admin listener with seeded role tokens
- Move a route from monitor to enforce
- Set rate limits and work the ban ladder
- Not yet in the reference: a good-bot allowlist
Recipe: deploy from the published image with pull-only Compose
Goal: run a production Gate by pulling the published container image instead of building from source.
Steps:
- Use the pull-only Compose file.
deployments/compose.release.yamlreferencesghcr.io/modootoday/humanymous-gate:${HMN_VERSION:-latest}(multi-archlinux/amd64+linux/arm64, cosign-signed), socompose upis a pull, not a source build. It runs Gate hardened — distroless, non-root, read-only root filesystem, dropped capabilities — with ACME TLS on:443, a sealed keystore, and a durable, replay-verified audit WAL, all on named volumes. -
Provide config. From
deployments/, copy the example env and route files and edit them:cd deployments cp .env.example .env # HMN_UPSTREAM, HMN_DOMAIN, HMN_UNSEAL, HMN_ADMIN_TOKENS cp routes.conf.example routes.conf.envsets your origin (HMN_UPSTREAM), the public domain for the Let’s Encrypt cert (HMN_DOMAIN, TLS-ALPN-01 needs:443reachable), the keystore unseal passphrase (HMN_UNSEAL), and real admin tokens (HMN_ADMIN_TOKENS— the binary refuses to boot on the shippede2e-*dev tokens).routes.confsets your per-route presets. Gate joins your existing origin network (HMN_ORIGIN_NET) and proxies to your app. -
Start it:
docker compose -f compose.release.yaml up -dThe default
HMN_VERSION=latesttracks the newest release.
Verify: the edge exposes HTTP probes — GET /__hmn/healthz (liveness) and /__hmn/readyz (readiness — 503s during drain). The image is distroless (no shell), so there is no Docker HEALTHCHECK; point your orchestrator’s httpGet probes at those paths. The admin console is served at https://<admin>/__hmn/admin/console on the loopback-mapped admin listener (127.0.0.1:8445).
Note:
-admin-addr :8445binds every container interface, so any container sharing the origin network can reachgate:8445— the host-loopback port mapping restricts host access, not intra-network access. Front the admin plane with mTLS/SSO or attach Gate to a dedicated internal admin network, and do not run untrusted workloads on the origin network. The standalone detection engine is published too, asghcr.io/modootoday/humanymous-core:latest, for self-testing and the demo; its own runbook (flags, ports, and the published-imagedocker runrecipes) lives in Run the detection engine. Building from source stays fully supported — see Install requirements; the recipes below apply whether you run the published image or your own build.
Recipe: move from the dev certificate to production TLS
Goal: understand what TLS the reference terminates, and what the path to production certificates is.
Steps:
- Run Gate and observe the certificate it presents. The reference generates a self-signed certificate in memory on boot: ECDSA P-256, CN
humanymous.local, with SANslocalhost,humanymous.local,127.0.0.1, and::1. The negotiated TLS floor is version 1.2. - Expect a browser certificate warning when you visit
https://localhost:8444in development. This is the self-signed dev cert, and the warning is normal for local use. - Configure production TLS with a CLI flag. Production terminates TLS with ACME-issued or bring-your-own certificates, and the reference binary supports both:
-tls-cert/-tls-keyfor a bring-your-own PEM certificate, or-acme-domain(with optional-acme-cache/-acme-email) for a Let’s Encrypt certificate via TLS-ALPN-01, which requires binding:443.
Verify: in development, inspect the presented certificate (for example with openssl s_client) and confirm the CN is humanymous.local and the SANs include localhost and 127.0.0.1.
openssl s_client -connect localhost:8444 -servername localhost </dev/null 2>/dev/null | openssl x509 -noout -subject -ext subjectAltName
Expected (dev): a subject=CN=humanymous.local line and a SAN list containing localhost, humanymous.local, 127.0.0.1, ::1.
Note: production TLS is supplied by CLI flag. For bring-your-own certificates, pass
-tls-cert <pem>and-tls-key <pem>(PEM file paths). For automatic certificates,-acme-domain <domain[,domain]>requests a Let’s Encrypt cert over TLS-ALPN-01 (which requires binding:443),-acme-cache <dir>sets the on-disk cache directory (defaultacme-cache— in a container this must be an absolute path on a persistent volume, e.g./acme-cache),-acme-email <addr>sets the optional ACME account contact, and-acme-directory <url>selects the ACME server (empty = production Let’s Encrypt; set the LE staging URL to dry-run issuance without burning rate limits). With none set, Gate falls back to the in-memory self-signed dev cert. The admin listener always stays self-signed. Full walkthrough: HTTPS / TLS certificates.
For the complete certificate walkthrough — Let’s Encrypt (with a persistent ACME cache volume, staging dry-run, automatic renewal, and rate-limit caution), bring-your-own PEMs, and running behind an existing TLS terminator/CDN — see HTTPS / TLS certificates. For the full TLS/ingress hardening table, see the CLI, config & per-route policy reference.
Recipe: cloak the origin so direct hits get a 421
Goal: make sure clients cannot skip Gate by talking to the origin directly. A direct-to-origin request should be refused with a 421 (hard rule HR-24), so all traffic is forced through the edge where it is scored and enforced.
Steps:
-
Choose a shared secret and pass it to Gate as a hex origin-cloaking HMAC key with
-origin-key. When Gate proxies a request to the origin, it signs it with this key in theX-Hmny-Origin-Authheader.bin/gate.exe -addr :8444 -upstream http://127.0.0.1:9000 -origin-key <hex-key> - Configure your origin app to require and validate
X-Hmny-Origin-Authagainst the same key on every inbound request, and to reject requests that lack a valid value with421 Misdirected Request. Gate emits the signed header; the origin is what enforces the check. - Restrict origin network reachability as well (firewall/private network) so the origin is only addressable from Gate. The header check is the application-layer backstop; network isolation is the first line.
Note: If you do not set
-origin-key, the key is a random ephemeral value per boot. Cloaking only works end-to-end when the origin validates the same key you gave Gate, so a fixed, shared key is required for this recipe.
Verify: send a request straight to the origin, bypassing Gate. It should be refused.
curl -i http://127.0.0.1:9000/
Expected: HTTP/1.1 421 Misdirected Request from the origin (no valid X-Hmny-Origin-Auth). The same path fetched through the edge (https://localhost:8444/) is served normally. In the Ledger, a direct-origin bypass attempt surfaces as HR-24. See the Hard rules, verdicts & signal-ID reference.
Note: validating
X-Hmny-Origin-Auth(and returning421when it is absent or wrong) is the operator’s responsibility, but you do not have to write the check from scratch. The reference ships the public, importable packagegithub.com/modootoday/humanymous/pkg/origincloak— a Go origin validates with one call,origincloak.Valid(key, r.Header.Get(origincloak.Header), time.Now())— plus a worked reference validator (validOriginAuth) intest/gate/e2e.mjsyou can port to njs/Lua. The full construction, the mandatory ±1-bucket epoch grace, and copy-paste Go and nginx/njs validators are in the Gate → origin request contract. The bundled demo origin (deployments/origin/default.conf, plain nginx) does not perform this check — it is there to be proxied, not to demonstrate cloaking enforcement.
Test your integration behind Gate
Goal: exercise your own app through Gate and confirm it behaves correctly on each verdict — an ALLOW is served, a DENY/CHALLENGE never reaches it, and a direct-to-origin hit is refused with 421.
Steps:
-
Run Gate in front of your app with a fixed origin key (so cloaking validates end to end) and your app validating
X-Hmny-Origin-Authper the Gate → origin request contract.bin/gate.exe -addr :8444 -admin-addr :8445 -upstream http://127.0.0.1:9000 -origin-key <hex-key> - Drive each verdict through the edge (
https://localhost:8444), not against your app directly:- ALLOW — a normal browser-shaped request is served your app’s response (
200, your body). With a valid verdict token on the fast path it stays200. - CHALLENGE — a request that scores as suspect on an enforcing route gets the PoW interstitial (or
401on a strict route with no beacon); your origin is not contacted. - DENY — a request that scores automated (e.g. a
webdriver/beacon signal) is blocked at the edge with403; your origin is not contacted. - 421 — a request sent straight to your app (bypassing Gate) lacks a valid
X-Hmny-Origin-Authand your validator returns421 Misdirected Request.
- ALLOW — a normal browser-shaped request is served your app’s response (
- Use the worked harness as your template.
test/gate/e2e.mjsstands up an upstream stub that enforces the cloaking check and asserts exactly these outcomes against a live Gate —human-passes(ALLOW200),bot-blocked-at-edge+origin-not-contacted(DENY403, zero upstream hits),strict-route-fail-closed(CHALLENGE), andorigin-direct-hit-blocked(421). It targets Gate’s edge on:8444and the admin listener on:8445. Copy its request shapes to drive your own app.
Important: this is the Gate edge harness. The red-team CLI (
cmd/redteam,bin/redteam.exe -attack … -host …) is a different target — its default127.0.0.1:8443is the standalone Core detection engine, not Gate. Do not point it at your Gate edge (:8444); to exercise your app behind Gate, use thetest/gate/e2e.mjsflow above. See Self-validation & red team.
Verify: the four outcomes above each hold: 200 served from your app on ALLOW, 403/challenge with your origin never hit on DENY/CHALLENGE, and 421 on a direct-to-origin hit. Watch the decisions land in the Overview view of the Ledger.
Recipe: stand up the admin listener with seeded role tokens
Goal: run the authenticated admin listener with the four RBAC roles, using deterministic tokens you control instead of random per-boot tokens.
Steps:
-
Choose one token string per role and set them in
HMN_ADMIN_TOKENS, in the formatauditor:<tok>,operator:<tok>,approver:<tok>,dpo:<tok>. Then start Gate; the admin listener defaults to127.0.0.1:8445(loopback) (a separate, cross-origin listener from the public edge).HMN_ADMIN_TOKENS="auditor:<tokA>,operator:<tokB>,approver:<tokC>,dpo:<tokD>" bin/gate.exe -addr :8444 -admin-addr :8445 -upstream http://127.0.0.1:9000 - If you leave
HMN_ADMIN_TOKENSunset, Gate mints random tokens per boot and prints them at startup. That is fine for a quick local look but changes on every restart; seed the variable when you want stable tokens. - Keep the roles separate. The four roles map to distinct capabilities: Auditor reads only; Operator reads and operates (request bans/erasure, lift bans, cancel erasure); Approver reads and approves (ban/kill-switch commits); DPO reads, operates, approves, and is the only role that can commit an erasure. Dual-control actions require the second action to come from a distinct role holder — so hand each token to a different person. See the RBAC & separation-of-duties reference.
Verify: call whoami with one of your tokens and confirm the server-derived identity. Actor identity comes from the token, not from any request body.
curl -sk -H "Authorization: Bearer <tokB>" https://localhost:8445/__hmn/admin/whoami
Expected: a response identifying the Operator role. A missing or invalid token returns 404 (deny-by-default), not 401 — the admin surface is non-discoverable. Note that /__hmn/admin/* is not served on the public edge (:8444 404s it); it lives only on the admin listener.
Then open the Ledger at https://localhost:8445/__hmn/admin/console and walk the views with the Ledger tour.
Recipe: move a route from monitor to enforce
Goal: take a route that is only scoring-and-logging and have it actually enforce verdicts (for example, promote a path from monitor to balanced, or to strict).
Important: Route presets are set at startup via the route table (
Config.Routesincmd/gate/main.go). There is no runtime per-route policy-write endpoint in the reference. Changing one route’s preset means editing the route table and restarting the process. The only runtime enforcement levers are the fleet-wide kill switch and the boot-global-monitorflag.
Steps:
-
Confirm the current per-route posture. Read it live from the Policy view in the Ledger, or via the admin API:
curl -sk -H "Authorization: Bearer <auditor-tok>" https://localhost:8445/__hmn/admin/policy - Edit the route table in
cmd/gate/main.go(Config.Routes). Set the target prefix to the preset you want —monitor(inject, score, log, no enforce),balanced(enforce, fail-open on an unknown safe GET/HEAD), orstrict(enforce, fail-closed on unknown, synchronous re-score before state-changing actions). Routes match by longest-prefix wins; unmatched paths usebalanced. Do not useloworapi— those names are reserved and fall back tobalanced. -
Rebuild and restart Gate so the new route table takes effect.
go build -o bin/gate.exe ./cmd/gate - Make sure you are not globally demoting enforcement. If Gate is running with
-monitor, or the kill switch is active, every route is downgraded to monitor regardless of its preset. Remove-monitorfor the route to enforce.
Verify: re-read /__hmn/admin/policy and confirm the route now shows the enforcing preset. Then send traffic that would score as automated and confirm the edge acts on it — a DENY blocks before the origin is contacted, a CHALLENGE serves the PoW interstitial. Watch the decision land in the Overview view of the Ledger; every decision is written to the audit log before it takes effect.
Tip: Roll out enforcement the low-risk way — start a route in
monitor, watch its would-be verdicts in Overview for a while, then promote it. See Will this break my app?.
Recipe: set rate limits and work the ban ladder
Goal: understand the control-plane flood limiter and the auto-escalating ban ladder, and apply or lift a ban from the admin API.
Steps:
- Know the flood limiter. A per-IP sliding-window limiter guards the control-plane endpoints
/__hmn/collectand/__hmn/session. Reference defaults: window 10s, soft threshold 60, hard threshold 120. A hard breach returns429before scoring. These defaults are set from config fields (Config.RateWindow/Config.RateSoft/Config.RateHard) at startup, the same way the route table is — changing them means editing the config and restarting, not a runtime call. -
Understand the auto-ban ladder. Repeated abuse escalates with strike decay: 1h → 6h → 24h → permanent. Auto bans are recorded with
Source=auto; bans you add by hand areSource=manual. Ban keys areip:<addr>orfp:<fingerprint>.stateDiagram-v2 [*] --> None None --> Ban1h: abuse (auto) or manual temp ban Ban1h --> Ban6h: re-offend Ban6h --> Ban24h: re-offend Ban24h --> Perm: re-offend Ban1h --> None: expiry / strike decay / lift Ban6h --> None: expiry / strike decay / lift Ban24h --> None: expiry / strike decay / lift Perm --> None: lift (single Operator) note right of Perm: manual permanent / CIDR ban = dual-control commit by a distinct Approver -
Apply a temporary ban by hand. A temporary ban is a single Operator action (no second approver). Post it to the admin listener with the Operator token:
curl -sk -X POST -H "Authorization: Bearer <operator-tok>" https://localhost:8445/__hmn/admin/bans -H "Content-Type: application/json" -d '{"Key":"fp:abc123","Reason":"scraper","DurationSec":3600}'A positive
DurationSecon anip:/fp:key applies immediately and returns{"ok":true,"permanent":false}.DurationSec:0(permanent) or acidr:key instead returns a pending dual-control action for a distinct Approver. -
Lift a ban. Unblocking is also a single Operator action, and it takes a
?key=query parameter, not a body:curl -sk -X POST -H "Authorization: Bearer <operator-tok>" "https://localhost:8445/__hmn/admin/bans/lift?key=fp:abc123"Full admin request/response shapes are in the CLI, config & policy reference.
Verify: list active bans and confirm your change:
curl -sk -H "Authorization: Bearer <auditor-tok>" https://localhost:8445/__hmn/admin/bans
Expected: the ban appears (or is gone, after a lift) with its key, source, and remaining duration; it is also visible in the Rate Limits & Bans view of the Ledger.
Warning: Permanent and CIDR bans require dual-control — the commit must come from a distinct Approver, not the Operator who requested it. Bulk ban (
/__hmn/admin/bans/bulk) is temporary-only; permanent and CIDR entries are rejected from bulk. For the full apply/approve/lift workflow, escalation, and the kill switch, see the Kill switch & bans runbook.
Not yet in the reference: a good-bot allowlist
A good-bot allowlist (letting a named, trusted automated client through without challenge) is a reserved concept in this project. There is no admin endpoint for an allowlist in the reference build, and no working recipe for one. Do not configure or promise an allowlist as a shipping feature.
If you need to let known-good automation through today, the honest levers are the route table (an off or monitor preset on a specific prefix, set at startup) and the operational controls above — with the understanding that a preset applies to all traffic on that route, not to a specific client identity.
TODO(verify): the intended production shape of a good-bot allowlist (identity model and admin surface), for readers planning ahead.
Related pages
- CLI, config & per-route policy reference — every flag, preset, threshold, and endpoint.
- Kill switch & bans runbook — the fleet-wide kill switch and the full ban workflow.
- Key management — the sealed keystore (
-keystore/HMN_UNSEAL), persistent identity, and why unsealed keys are ephemeral. - RBAC & separation-of-duties reference — roles, capabilities, and dual-control.
- How Gate sees a request — the L1–L7 pipeline and verdict flow behind these controls.