82 lines
6.1 KiB
Markdown
82 lines
6.1 KiB
Markdown
# Neural Hunt security notes
|
|
|
|
## Network split
|
|
|
|
- `:8080` (`HTTP_ADDR`) is the public listener. It does not expose `/admin` or `/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 to `app:8081` instead.
|
|
|
|
## Secrets and sessions
|
|
|
|
- `OPENAI_API_KEY` is read only by the backend and must stay in runtime environment/.env. `.env` is ignored by Git and Docker build context and is deliberately not shipped in release ZIPs.
|
|
- Normal startup rejects weak/default `JWT_SECRET` and `ADMIN_PASSWORD`. `ALLOW_INSECURE_DEV_DEFAULTS=1` is 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.
|