gateii exposes an HTTP proxy and an admin API. This page summarises the security posture and the controls in place. Full admin-API reference: admin-api.md.
| Topic | Status |
|---|---|
| SSL verification to upstreams | Enabled — ca-certificates installed at container startup |
| Auth cache TTL | Revoked keys work for up to 5 min — reduce in auth.lua if needed |
| Request size limit | 10 MB max body (supports vision payloads) — set in nginx.conf |
| Admin API auth | Internal-only: IP allow-list + ADMIN_TOKEN (cookie or header), constant-time compare |
| Admin login brute-force | limit_req_zone adminauth 5r/m with burst 3 on /internal/admin/login (429 on exhaustion) |
| Admin fail-closed | apikey mode without ADMIN_TOKEN returns 503 on /internal/admin/*. passthrough without ADMIN_TOKEN: GET (read-only console) stays open, every mutating method is refused with 403 |
| Admin session | HttpOnly, SameSite=Strict cookie (Secure added over HTTPS); crypto-random id; 1 h TTL; per-IP login lockout |
| Control plane (compose-ctl) | INTERNAL_TOKEN required — fails closed (health-only, 503) when unset; no host port, gateii-network-only |
| Console CSP | script-src 'self' 'nonce-<N>' — no inline scripts without per-request nonce |
| Bootstrap handshake | HMAC-SHA256, constant-time proof compare, one-time code, auto-revoke on failed install |
| Port bindings | PROXY_BIND, GRAFANA_BIND, PROMETHEUS_BIND default to 127.0.0.1 — set to 0.0.0.0 only for explicit LAN exposure |
The admin surface at /internal/admin/* accepts two mechanisms:
- Session cookie —
POST /internal/admin/loginwith{token}setsadmin_session=<hex>; HttpOnly; SameSite=Strict(1 h TTL;Secureadded when the request is over HTTPS). Used by the/consoleweb UI. - Header —
X-Admin-Token: <ADMIN_TOKEN>. Used byscripts/admin.shand curl.
Set ADMIN_TOKEN (≥ 32 random hex bytes) in .env for production.
PROXY_MODE |
ADMIN_TOKEN set? |
/internal/admin/* behaviour |
|---|---|---|
apikey |
yes | Normal — cookie or header required |
apikey |
no | 503 on every request (fail-closed — there are server-side keys to protect) |
passthrough |
yes | Normal |
passthrough |
no | Read-only: GET allowed behind the IP allow-list (console works zero-config), every mutating method (POST/PUT/DELETE) refused with 403 |
Token compares are constant-time. /internal/admin/login is rate-limited
at 5 req/min per IP.
Default: everything binds to 127.0.0.1. To expose beyond loopback:
# .env
PROXY_BIND=0.0.0.0Only do this if:
- You're running
PROXY_MODE=apikeywith real keys indata/keys.json(so unauthenticated requests get 401), AND - You've set a strong
ADMIN_TOKEN, AND - You understand the LAN / internet boundary of your deployment.
Or put gateii behind a reverse proxy with its own auth and TLS (Caddy, nginx, Cloudflare Tunnel, …).
.envis.gitignored. Nevergit add .env. Use.env.examplefor defaults.- Proxy keys (
sk-proxy-...) are stored indata/keys.json— also gitignored. Share via the bootstrap handshake, not over chat: bootstrap.md. - Admin token rotation:
ADMIN_TOKENhas no session revocation mechanism currently. To rotate: change in.env,gateii reload, issue new sessions from/consoleor CLI. - Log output: nginx access logs contain the proxy key in headers only if you explicitly log those headers. Default config does not. Still, scan any logs before sharing.
Copy-pasting sk-proxy-... keys over SSH / Slack / email is a common
leak vector. The bootstrap handshake replaces it with a one-time HMAC-SHA256
flow:
- Admin issues a one-time code + HMAC secret.
- Client (
scripts/gateii-connect.sh) performs challenge → exchange → confirm. - If any step fails or the client never confirms, the key auto-revokes.
Full protocol: bootstrap.md.
gateii's OpenResty image installs ca-certificates at build time and
nginx's proxy_ssl_verify is on. Don't flip to off — it disables
certificate validation for every upstream request. If you need to reach
a private API with a custom CA, add the CA to the image and rebuild.
The admin console (/console) serves HTML with
Content-Security-Policy: script-src 'self' 'nonce-<N>'. Every
<script> tag needs a per-request nonce. No unsafe-inline, no
unsafe-eval. This mitigates XSS even if a future bug reflects user
input.
- Counter exposure:
/metricsis unauthenticated and exposes user names as labels. If you LAN-expose the proxy, also bind-restrict the metrics endpoint or put it behind auth. - Admin token in URL: some misuse of the admin API with
?token=<val>query parameters is possible. Prefer headers; never log request URIs with tokens. - Shared-dict TTL drift: blocked users auto-unblock at midnight UTC — not the user's local midnight. Document this when setting team policy.