Quickstart: Put Gate in front of an app in monitor mode (first 30 minutes)

Tutorial (learning-oriented) · Audience: integrator or evaluator, first run.

Welcome. In the next 30 minutes you will stand up humanymous Gate — the reverse-proxy enforcement layer — in front of a throwaway app on your own machine, running in global monitor mode. Monitor mode is where every deployment should start: Gate scores and logs every request across layers L1–L7, but it enforces nothing. No visitor is challenged, no visitor is blocked, and your origin app behaves exactly as it did before. You get to watch the detection work with zero risk to real traffic.

That monitor-first shape is deliberate. It is how the product is meant to be adopted: observe first, build confidence from the audit trail, and only then decide where to turn enforcement on. This guide follows a single path that is designed to succeed on the first try.

Note: This repository is a reference implementation, not a production-hardened build. The dev TLS certificate is self-signed and generated in memory, and the admin tokens shown here are development tokens. Everything below is for a local evaluation on 127.0.0.1.

The whole path at a glance — four steps, ending with verdicts you watch but never enforce:

flowchart LR
  A["Step 1: throwaway origin on 127.0.0.1:9000"] --> B["Step 2: start Gate with -monitor (Docker image or build from source)"]
  B --> C["Step 3: load https://localhost:8444 — origin intact, bundle injected"]
  C --> D["Step 4: Ledger on :8445 — verdicts scored and logged, nothing enforced"]

Before you start

You will need:

  • Either Docker (for Option A — run the published image, no build) or a Go toolchain (for Option B — build the binary from ./cmd/gate). You only need one.
  • Python 3 (used only for the one-line throwaway origin; any tiny static HTTP server works).
  • A web browser.
  • A shell open in the repository root.

You will use three ports on localhost:

  • 9000 — your throwaway origin app (what Gate fronts).
  • 8444 — Gate’s public edge (HTTPS).
  • 8445 — Gate’s separate admin listener (the Ledger).

Step 1 — Start a throwaway origin on 127.0.0.1:9000

Gate needs an origin to sit in front of. Any app that returns HTML will do. For this tutorial, serve a tiny static page.

First, create a page to serve:

mkdir -p origin-demo && printf '<!doctype html><title>My demo app</title><h1>Hello from my origin</h1>' > origin-demo/index.html

Now start a static server bound to loopback, on port 9000:

python -m http.server 9000 --bind 127.0.0.1 --directory origin-demo

You should see:

Serving HTTP on 127.0.0.1 port 9000 (http://127.0.0.1:9000/) ...

Leave this running and open a new shell for the next steps.

Tip: This is a plain, unprotected origin — exactly the app you are about to place Gate in front of. In a real deployment, Gate terminates TLS at the edge and forwards to your origin over the private network; the origin itself is not exposed to the internet.

Step 2 — Start Gate in global monitor mode

There are two ways to run Gate — pick one. Option A pulls the published container image (no build, no Go toolchain). Option B builds the binary from the source tree. Either way the -monitor flag is the important one: it puts the whole fleet in global monitor mode, which downgrades every route to monitor, so Gate scores and logs but enforces nothing, everywhere. Once Gate is up, the rest of the tutorial (Steps 3–4) is identical.

Option A — Docker (published image, no build)

The fastest path: pull the published, multi-arch image (linux/amd64 + linux/arm64, cosign-signed) and run it in front of the origin from Step 1. No Go toolchain, no source tree.

docker run -d -p 8444:8444 -p 127.0.0.1:8445:8445 -e HMN_ALLOW_DEV_TOKENS=1 \
  ghcr.io/modootoday/humanymous-gate:latest \
  -addr :8444 -admin-addr :8445 -upstream http://host.docker.internal:9000 -monitor
  • host.docker.internal:9000 is how the container reaches the origin you started on the host in Step 1. On Docker Desktop (macOS/Windows) this resolves automatically; on Linux, add --add-host=host.docker.internal:host-gateway to the docker run line.
  • HMN_ALLOW_DEV_TOKENS=1 is the local-demo switch: it makes Gate print the admin role tokens and auto-inject the operator token into the console so you can open it without pasting anything. Never set it outside a local demo.
  • :latest tracks the newest release.
  • The admin listener is mapped to host loopback only (127.0.0.1:8445) — keep it that way.

Because the container runs detached (-d), read the startup lines — including the dev tokens — from its logs:

docker logs <container-id>

You should see log lines like the ones shown under Option B below (monitor=true, the admin console URL, and the four dev tokens). Then skip to “Whichever option you chose”.

Option B — Build from source

If you have a Go toolchain and want to run from the tree, build the binary and start it. From the repository root:

go build -o bin/gate.exe ./cmd/gate

When it finishes, the command returns with no output and bin/gate.exe exists. That is success — go build is quiet when it works. Now start Gate:

HMN_ALLOW_DEV_TOKENS=1 bin/gate.exe -addr :8444 -upstream http://127.0.0.1:9000 -monitor

HMN_ALLOW_DEV_TOKENS=1 is the local-demo switch: it makes Gate print the admin role tokens and auto-inject the operator token into the console so you can open it without pasting anything. Never set it outside a local demo. (PowerShell: run $env:HMN_ALLOW_DEV_TOKENS=1 first, then the bin/gate.exe … line.)

You should see startup log lines like these:

humanymous Gate dev on https://localhost:8444 -> http://127.0.0.1:9000 (monitor=true, tls=self-signed)
humanymous Gate admin console on https://localhost:8445/__hmn/admin/console
  dev tokens (demo mode) — auditor:<hex> operator:<hex> approver:<hex> dpo:<hex>

Whichever option you chose

A few things to notice in those startup lines:

  • (monitor=true) confirms enforcement is off across the board. Every verdict is still computed and written to the audit log — it just never changes what the visitor receives.
  • The admin console is on a separate listener (:8445), cross-origin to the public edge. This is by design: the admin plane is not reachable from the public edge.
  • The dev tokens line prints four development admin tokens, one per role (Auditor, Operator, Approver, DPO). These are generated fresh each boot unless you set HMN_ADMIN_TOKENS. Keep this output visible — you may want the operator token shortly.

Leave Gate running.

Step 3 — Load your app through Gate

Open your browser to:

https://localhost:8444

Expect a certificate warning. The dev certificate is self-signed in memory (CN humanymous.local, valid for localhost, humanymous.local, 127.0.0.1, ::1). This is expected in the reference build — production deployments use ACME or a bring-your-own certificate. Accept the warning to proceed (in most browsers: Advancedproceed to localhost).

You should now see your origin’s page — the “Hello from my origin” heading you created in Step 1. Gate fronted the request, terminated TLS, forwarded to 127.0.0.1:9000, and streamed the response back.

Here is the interesting part: the HTML that came back is not byte-for-byte your origin’s HTML. Gate injected the detection bundle into it. To confirm this, view the page source (or fetch it directly) and look for a reference to the control path /__hmn/ — the injected bundle loads via the control plane, for example /__hmn/loader.js.

You can verify from the shell too (the -k flag accepts the self-signed dev cert):

curl -k https://localhost:8444/

Look in the returned HTML for your original <h1>Hello from my origin</h1> and the injected reference to /__hmn/. Seeing both is the proof: your app’s content is intact, and Gate’s detection bundle is now riding along with it.

The exact markup Gate inserts once at the first <head> boundary is:

<!--hmn-injected--><script src="/__hmn/loader.js" defer></script>

The <!--hmn-injected--> comment is an idempotency marker — if it is already present, Gate passes the page through untouched, so injection never happens twice.

Step 4 — Open the Ledger and watch a verdict appear

Open the Ledger:

https://localhost:8445/__hmn/admin/console

You will get the same self-signed certificate warning — accept it. Because you started Gate with HMN_ALLOW_DEV_TOKENS=1, the console is injected with the operator token, so it can read and request without you pasting anything.

Go to the Overview view (“live edge decisions”). As you reload https://localhost:8444 in your other browser tab, watch a verdict flow in for each request. Every request Gate handled — including your own page loads — was scored across layers L1–L7 and written to the tamper-evident audit log.

Because you are in monitor mode, note what did not happen:

  • Nothing was challenged.
  • Nothing was blocked.
  • Your origin was contacted normally and returned its real content every time.

The verdict on the stream is a decision that was recorded, not enforced. That is exactly the point of monitor mode: you get full visibility into what Gate would decide, with no effect on live traffic.

You did it

In about half an hour you have:

  • Stood up an origin, started Gate (from the published image or your own build), and placed it at the edge in front of your app.
  • Run the whole fleet in global monitor mode with enforcement off.
  • Confirmed your app’s HTML came back intact, with the detection bundle injected.
  • Watched real verdicts appear on the Overview view — scored and logged, enforced nowhere.

That is the recommended starting posture for any deployment: observe first, trust the audit trail, then decide. Nicely done.

What next

  • Will this break my app? — how enforcement, fail-open behavior, and staged rollout work before you turn anything on.
  • CLI, Config & Per-Route Policy Reference — every flag, environment variable, policy preset, and the per-route policy table, for when you move past monitor mode.
  • Configure attested routes — the attested preset: put an attestation floor on high-value mutating routes so a scoring-ALLOW there is priced to CHALLENGE → Pass, once you are ready to enforce.