Run the standalone detection engine (dev)

Diátaxis quadrant: How-to. Audience: defensive-test and detection developers who want to build and run the standalone engine (bin/server.exe on 127.0.0.1:8443) directly.

Note: Defensive, local-only, self-target-only, educational. This page builds and runs your own detector on your own loopback so you can read its scoring output. It teaches nothing about evading third-party systems. Every number you observe is reference-measured on your machine, not a guarantee.

This page gets the detection engine compiled and serving on 127.0.0.1:8443, shows you how to read a verdict with and without the Detection Observatory, and marks the boundary where the engine stops and the Gate proxy begins.

When to use this instead of the Gate Quickstart

There are two binaries in this repo, and they do different jobs. The orientation map covers the full split — read which piece am I using? if you are unsure which one you want.

Use the standalone detection engine (this page) when you want the engine’s per-request scoring across seven detection stages without:

  • edge enforcement (the engine scores; it does not block a request at an edge),
  • TLS termination or streaming bundle injection,
  • the admin plane (no Ledger, no bans, no dual-control),
  • a tamper-evident audit log.

Use the Gate reverse proxy instead — via Quickstart: monitor mode — when you want those production behaviors wrapped around the same engine. Both binaries embed the same internal/scoring engine, so the verdicts are consistent; the difference is everything around the score.

Build

The engine needs two artifacts: the browser detection bundle (web/detector.wasm) and the server (bin/server.exe).

Build the WASM bundle first:

GOOS=js GOARCH=wasm go build -o web/detector.wasm ./cmd/wasm/

Important: web/detector.wasm is the browser-side detection bundle — the code that runs in the visitor’s page to collect the browser-side collection stages client signals. The server serves it from web/, so if you skip this step the page loads without a detector and the client signals never arrive. Build it before (or alongside) the server.

Then build the server:

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

Run

Start the engine on loopback, pointing -web at the directory that holds detector.wasm:

bin/server.exe -addr 127.0.0.1:8443 -web web

Core flags (full set)

The three flags above cover most local runs; the remaining flags configure TLS, logging, and the operator surface.

Flag Default Purpose
-addr :8443 (from source) Listen address. The published image overrides this to :443 (see below).
-web web Static web root — the directory holding detector.wasm, index.html, demo.html.
-rit true Enable request-integrity token anti-tamper (request-integrity token) verification.
-acme-domain "" Comma-separated domain(s) for a Let’s Encrypt cert via TLS-ALPN-01 (needs binding :443 directly). Empty = self-signed.
-acme-cache acme-cache Directory to cache issued ACME certificates + the account key (persist this across restarts).
-acme-email "" Optional contact email for the ACME account.
-log-level "" (off) Structured log level: off\|error\|warn\|info\|debug. Also settable via HMN_LOG_LEVEL.
-log-console-format plain Console format: off\|plain\|jsonl. Also settable via HMN_LOG_CONSOLE_FORMAT.
-log-console-stream stderr Console stream: stderr\|stdout. Also settable via HMN_LOG_CONSOLE_STREAM.
-log-plain-file "" Optional append-only formatted plain-text file. Also settable via HMN_LOG_PLAIN_FILE.
-log-jsonl-file "" Optional append-only JSON Lines file. Also settable via HMN_LOG_JSONL_FILE.
-ops-token "" (disabled) Operator bearer token that enables /api/explain + /api/counters + /api/mlcorrect. Also settable via HMN_OPS_TOKEN. Empty = those endpoints are not registered.
-redis "" (per-node) Redis host:port for fleet-wide cross-session correlation velocity (l5.correlation.ip_velocity counted across nodes behind a load balancer). Also HMN_REDIS. Verdict-neutral (the signal is weight-0) and fail-open (any Redis error → per-node counting). Set HMN_REDIS_KEY (≥16 bytes) so the coordinator key is HMAC-bound, and HMN_REDIS_PASSWORD/HMN_REDIS_USER for AUTH — the Core logs a warning if either is missing. This is a Core capability and a different flag from the Gate’s -redis (which shares ban/verdict state). See the data-processing inventory — enabling it adds a coordinator-side store to your RoPA. A worked two-node example lives in deployments/compose/core-fleet.yaml (docker compose -f deployments/compose.yaml -f deployments/compose/core-fleet.yaml -f deployments/compose/assertions.yaml run --rm assert-core-fleet), which demonstrates the cross-node count crossing the velocity floor that neither node reaches alone.

Plain and JSONL files may be enabled together. They are bounded, best-effort diagnostics, not the tamper-evident decision audit. Put them on a separately rotated logging volume and never on an audit, key, or settings volume.

Warning: The engine serves over TLS with a self-signed development certificate. Your browser and curl will flag it as untrusted — that is expected for a local dev cert. Trust it locally or pass your client’s insecure/skip-verify flag for loopback testing only; never carry that habit to a real endpoint.

There is no admin listener here — the engine exposes 127.0.0.1:8443 only. The :8445 admin plane and the :8444 edge belong to the Gate proxy, not to this binary.

Behavioral model flags (optional)

The Core can load an optional behavioral model that emits one weight-0 / audit-only signal (l4.ml.behavioral) — loading it changes no verdicts. Full task guide: Operate the behavioral model. These flags exist on the Core binary only; they are not Gate flags.

Flag Default Purpose
-ml-bundle "" (disabled) Path to the signed behavioral-model artifact. Also HMN_ML_BUNDLE. Empty = no model; the signal never fires. A load/verify failure is non-fatal → heuristics.
-ml-pubkey "" PKIX Ed25519 public-key PEM. When set, the artifact’s signature is required on the served model.
-ml-bundle-sig "" Detached Ed25519 signature file over the artifact bytes (needed when -ml-pubkey is set).
-ml-bundle-digest "" Optional pinned sha256 (hex) of the artifact; admission is refused on mismatch.
-ml-fp-budget 0.005 Realized human false-positive rate the model’s (weight-0) fire threshold self-maintains.
-ml-shadow-bundle "" A candidate scored in parallel and never served; divergence at /api/mlcorrect. Requires -ml-bundle.
-ml-canary false Arm autonomous auto-rollback: disable the model (→ heuristics) on a human-FP / drift / poisoning breach. Requires -ml-bundle.
-ml-canary-max-fp 0.02 Human-FP ceiling during canary probation before auto-rollback.
-ml-canary-probation 200 Confirmed humans to observe breach-free before the model graduates.
-ml-trace-dir "" (disabled) Lab-only. Append oracle-confirmed human traces (JSONL) for offline retraining. Writes behavioral aggregates to disk — read the data-processing inventory first.

When a model is admitted you will see a startup line like ml-bundle: admitted <version> (digest …, signed=true); -ml-canary adds ml-canary: probation armed …, and an auto-rollback logs ml-canary: AUTO-ROLLBACK ….

Core ops surface

The Core has a small, deliberately minimal operations surface:

  • GET /healthz — always on, unauthenticated liveness at the root path (not under /__hmn/). Returns status, version, and uptimeSec.
  • GET /api/explain/{id}, GET /api/counters, and GET /api/mlcorrect — mounted only when -ops-token / HMN_OPS_TOKEN is set, and require that bearer token (a missing or wrong token gets 404, so the surface is non-discoverable). /api/explain/{id} returns the ScoreTrace for a stored session (re-scored from a copy); /api/counters returns runtime counters (uptime, goroutines, heap, GC); /api/mlcorrect returns the behavioral model’s read-only status (active artifact version/digest, calibration, drift, shadow, canary — no personal data), or {"enabled":false} when no model is loaded.

Unlike the Gate, the Core has no /readyz readiness probe and no Prometheus /metrics endpoint — health is the single /healthz liveness check.

Run the published Core image

The Core is published as ghcr.io/modootoday/humanymous-core:latest (built from build/core.Dockerfile). Its ENTRYPOINT is /app/server -addr :443 -web /app/web -acme-cache /acme-cache, so the image binds :443 (whereas the from-source default is :8443) and terminates its own TLS — never front it with a TLS-terminating proxy or load balancer, since it needs the raw TLS/HTTP-2 handshake to read JA3/JA4.

Real TLS (public domain). Supply a domain and mount a persistent volume at /acme-cache so the issued cert + ACME account key survive restarts:

docker run -p 443:443 -v hmn-acme:/acme-cache \
  ghcr.io/modootoday/humanymous-core:latest -acme-domain your.domain

TLS-ALPN-01 needs :443 reachable from the internet. Without -acme-domain, the image falls back to a self-signed cert on :443.

Local demo (self-signed, non-privileged port). Override -addr to avoid :443 and skip ACME:

docker run -p 8443:8443 ghcr.io/modootoday/humanymous-core:latest -addr :8443

Note: HMN_PLAYGROUND is intentionally unset in the published image, so the Detection Observatory and the local Red launcher are never registered there — they are loopback-only dev features you get only from a source build (see below).

The HMN_PLAYGROUND gate

The Detection Observatory is a dev-gated page that runs on this engine. Whether it exists is controlled by one environment variable.

  • HMN_PLAYGROUND unset — plain engine. The Observatory routes are not registered, the hub is nil, and there is zero added surface and zero request-path cost. This is the default and what you want for straight scoring.
  • HMN_PLAYGROUND=1 — the Observatory routes appear (/playground, /playground/events, /playground/explain/{id}, and the rest), and the loopback fail-closed rule applies: the server refuses to start on a non-loopback -addr, and every request’s Host is re-checked for loopback.

To understand what those routes do and why they are safe to expose only locally, see how the Detection Observatory is built; to actually drive it, see the Detection Observatory how-to.

See a verdict without the Observatory

You do not need the Observatory to read a score — it is a viewer, not the scorer. A page load (or a posted client report) becomes a verdict through the collect path:

sequenceDiagram
  participant B as "Browser (detector.wasm)"
  participant S as "server.exe · /api/collect"
  participant E as "internal/scoring"
  B->>B: collect browser-side evidence
  B->>S: POST /api/collect (SessionReport)
  S->>S: merge server-side network evidence and consistency checks
  S->>E: score all signals
  E-->>S: risk 0–100 + verdict + hardRuleFired
  S-->>B: ScoreResult JSON
Load a page in a browser. Point a browser at https://127.0.0.1:8443/ (accepting the dev cert). The detector.wasm bundle collects the client signals and POSTs a session report to /api/collect; the response is the scored verdict as JSON, containing verdict (ALLOW CHALLENGE DENY), riskScore (0–100), hardRuleFired, and topContributors.

Or run a Red profile against it. The Red runner drives one profile and prints the verdict:

node test/redteam/run-one.mjs human https://127.0.0.1:8443

run-one.mjs prints a minimal JSON line — {label, verdict, riskScore, hardRuleFired} — and intentionally drops topContributors. For the full per-stage trace, use the Observatory’s /playground/explain/{id} or the e2e results.json; see the self-validation how-to.

Post a minimal client report directly and read the verdict (the -k accepts the self-signed dev cert):

curl -sk -X POST https://127.0.0.1:8443/api/collect \
  -H "Content-Type: application/json" \
  -d '{"userAgent":"Mozilla/5.0 Chrome/126","signals":[{"id":"l1.navigator.webdriver","layer":"L1","verdict":"BOT","weight":40,"score":40,"confidence":0.95,"collected":"js"}]}'

The response is the ScoreResult: {"sessionId":…,"riskScore":…,"verdict":…,"hardRuleFired":…,"topContributors":[…],"policyVersion":…}. The signals array is the client-collected browser-side collection stages portion; the server merges its own network and consistency-check network signals before scoring.

The score comes from internal/scoring: each signal contributes weight × severity × confidence, the combiner deduplicates correlated evidence, caps each stage, and combines the stages into a 0–100 risk score. The built-in policy challenges at 30 and denies at 70; Runtime Settings can change those thresholds. Core’s ordered enforcement rules can raise the score-based result. See how humanymous evaluates a request and detection engine internals.

The boundary: what this engine does not do

This binary scores. It does not enforce at an edge, and it does not write the tamper-evident audit log. Those are the Gate reverse proxy’s job — the proxy wraps this same engine and adds TLS termination, edge enforcement, the audit log, the Ledger, bans, and dual-control.

If you need any of that, do not try to bolt it onto the standalone engine — run the proxy instead, starting from Quickstart: monitor mode. The Observatory is a Core feature, not a Gate one, and the published core image never exposes it (HMN_PLAYGROUND is unset there); the Observatory (:8443) is never the Ledger (:8445).