Zero Trust Guard's security model rests on a few deliberate choices. Reviewed together
with docs/threat-model.md and the ADRs.
The gateway is the only way into a protected application. If a request does not
carry a token the gateway can positively verify, it is rejected before the upstream
server processes it. The upstream must also validate the x-ztg-* headers (see the demo
upstream), because defense-in-depth means the gateway is trusted but not implicitly
sufficient.
- Passwords are stored as scrypt hashes (
utils.ts), verified at boot againstconfigs/users.json; plaintext is never stored. - Login issues a short-lived access JWT (default 900s) and an opaque refresh token (default 7 days) with rotation — each refresh consumes the old refresh token.
- The access token is HS256-signed with
node:crypto(jwt.ts) usingZTG_JWT_SECRET.
- Every request is checked against the session and device server-side state, not just the token signature, so revocation is immediate.
- The
PolicyEnginedecides authentication/device-verification requirements, required roles, explicit deny rules, and per-route rate limits. - Admin control-plane endpoints (
/sessions /devices /routes /policies /metrics /audit/events) require theadminrole.
- The gateway sets
x-content-type-options: nosniff,x-frame-options: DENY, andreferrer-policy: no-referrer. - It injects
x-ztg-verified,x-ztg-user-id, andx-ztg-request-idonly after the gate passes. Clients cannot forge these without passing the gate.
- Fail-closed: an unavailable session store denies rather than accepts.
- Fail-open only where it cannot create risk: audit persistence (Postgres) degrades to an NDJSON file sink so audit writes never block the request path.
| Secret | Location | Notes |
|---|---|---|
ZTG_JWT_SECRET |
environment | Change from the dev default |
ZTG_ADMIN_TOKEN |
environment | For gateway-level admin bootstrap |
| Postgres/Redis creds | environment / compose | Not committed |
Never commit real secrets. Rotate ZTG_JWT_SECRET if compromised; all tokens become
invalid, forcing re-login.
- Per-IP login attempts: 5/min (fail-closed).
- Global per-IP request guard: 1000/min.
- Per user+route policy limit (e.g. payments: 20/min).
- Run in front of TLS (terminate at a reverse proxy or use an HTTPS listener).
- Keep
ZTG_JWT_SECRETlong, random, and rotated. - Use the in-memory KV fallback for development and tests only; production needs Valkey/Redis.
- Confirm the network only permits traffic to upstreams from the gateway.
- Monitor
/metricsand/audit/eventsfor anomalies.