6.1 KiB
Neural Hunt security notes
Network split
:8080(HTTP_ADDR) is the public listener. It does not expose/adminor/api/admin/*.:8081(ADMIN_HTTP_ADDR) is the private control plane. Publish it only through a VPN/private reverse-proxy route.- The supplied Compose file binds host port 8081 to
127.0.0.1. A reverse proxy container on the same Docker network can route directly toapp:8081instead.
Secrets and sessions
OPENAI_API_KEYis read only by the backend and must stay in runtime environment/.env..envis ignored by Git and Docker build context and is deliberately not shipped in release ZIPs.- Normal startup rejects weak/default
JWT_SECRETandADMIN_PASSWORD.ALLOW_INSECURE_DEV_DEFAULTS=1is only for throwaway local development. - Admin authentication uses an HttpOnly, SameSite=Strict cookie plus a server-side session allowlist. Logout revokes the current session ID. Restarting the process also invalidates existing admin sessions.
Public abuse controls
- New identities can require a one-time SHA-256 proof-of-work and a warm-up interval before their first eligible guess. This raises the cost of creating many identities but is not proof of a real human and cannot make Sybil attacks impossible without an external identity/attestation system.
- One active presence per cryptographic client identity remains enforced. The guess lottery continues to allow at most one outstanding ticket per client/sequence.
- WebSocket browser Origins are same-origin checked (plus optional
WS_ALLOWED_ORIGINS), user bearer tokens are not placed in URLs by bundled clients, connection counts are globally capped, frames have read limits/deadlines, and persistently slow readers are disconnected. - IP/request-rate/concurrency controls are intentionally left to the reverse proxy, where they can use trusted client IP information correctly.
OpenAI circuit breaker
Before every OpenAI image request, the backend checks rolling 1-hour and 24-hour successful-call counts plus a rolling 24-hour locally estimated USD cost. A configurable safety reserve is added before the next call. If a limit is reached, automatic artifact work is returned to pending and retried later without making another OpenAI request. Manual anchor generation returns HTTP 429 while the breaker is active.
The USD figure is a local safety estimate based on provider-reported usage and pinned pricing data, not an OpenAI billing-system balance. Keep provider-side project budgets/limits enabled as an independent final backstop.
Browser headers
Responses include CSP with script-src 'self', frame-ancestors 'none', X-Frame-Options: DENY, HSTS, nosniff, no-referrer, restricted browser permissions, and no-store caching for auth/admin resources.
Hosted Customer Service trust boundaries (V4.1)
The optional Customer Service is a separate control plane. Treat its listeners as three different trust zones: 8090 public customer portal, 8091 private/VPN admin, and 8092 Docker-network-only worker registration/lease. The game private listener on 8081 is also required for delegation and must not be exposed to the public Internet.
CUSTOMER_SERVICE_SHARED_SECRET authenticates Customer Service to the game
private API. Example/default-looking values are rejected by the Customer Service
process. Customer reward ownership uses a one-shot 10-minute nhlink_* proof
issued only to an already authenticated game identity. The game stores only a
SHA-256 hash of that proof and consumes it atomically, so Customer Service never
needs the main P-256 private key.
V4.1 uses three separate runtime images. The public game image contains only the game binary, the Customer Service image contains only the commercial control plane, and managed workers contain only the CLI agent. This reduces accidental cross-role exposure compared with the former monolithic runtime image.
CS_WORKER_IMAGE is treated as an operator-controlled image reference. If
CS_WORKER_AUTO_PULL=true, Customer Service may ask Docker Engine to pull it.
For a private registry, use a registry-scoped read-only deploy/robot token in
CS_WORKER_REGISTRY_USERNAME/PASSWORD; those credentials are used only for the
Docker X-Registry-Auth pull request and are never injected into worker
containers. Pinning production workers to an immutable digest is recommended
when your registry/deployment workflow supports it.
Managed worker containers never receive the Docker socket. The Customer Service process itself does require Docker Engine control; a direct docker.sock mount is therefore a high-trust capability and should preferably be replaced with a narrowly permissioned socket proxy in production. Global and per-customer worker inventory/running limits are enforced even when reverse-proxy rate limits are configured separately.
PayPal live mode is deliberately gated, manual credit grants are disabled by default and available only from the private Customer Service admin listener, and all credit changes are written to an idempotent ledger. These controls are operational safeguards, not a legal classification of the commercial product.
Service Controller trust boundary (V4.3)
A Service Controller is privileged infrastructure because it controls a Docker Engine. Its control listener (SC_CONTROL_ADDR, default :8102) and emergency admin listener (SC_ADMIN_ADDR, default :8101) must remain on a private/VPN network. Do not route either through the public Customer Portal.
Master/controller traffic is authenticated with the separate SERVICE_CONTROLLER_SHARED_SECRET. Controller registration is not an unauthenticated service-discovery mechanism. Customer Service stores the authenticated controller's advertised private URL and routes only worker-runtime operations to it.
In CS_WORKER_ORCHESTRATION_MODE=controller, remove the Docker socket from Customer Service entirely. On worker hosts, a restricted Docker Socket Proxy is preferable to raw /var/run/docker.sock when available.
Worker identity volumes are host-sticky. Customer Service never silently reassigns a worker with an existing container/identity volume to another controller. This avoids replacing a private P-256 identity with a new empty volume during a host outage.