Security
9routerx holds provider credentials, client API keys, and the prompts that pass through it. This page covers every control that protects them.
Dashboard authentication
| Access | Requirement |
|---|---|
| Public | /health, dashboard HTML/assets, /login, OAuth callbacks |
Proxy routes (/v1/*, /api/chat) |
Active client API key (Authorization: Bearer / X-API-Key) |
Dashboard APIs (/api/*) |
Session cookie, CLI token, or API key |
| Destructive operations (shutdown, update, DB import, vault rotation) | Session or CLI token — never a client API key |
| Console log APIs | Full dashboard session or local CLI token; client API keys are rejected |
First login. On localhost the initial password is 123456; change it in
Settings → Profile. On a remote host the default password is blocked (CVE-2026-56679
— a fresh install with a known default can be taken over publicly), so set
INITIAL_PASSWORD or connect through an SSH port-forward first.
Client IP and rate-limit bucketing
A forwarded client-IP header is only read when the TCP peer is inside the trusted
set. Otherwise a client could rotate X-Forwarded-For and give every login
attempt its own rate-limit bucket.
| Variable | Effect |
|---|---|
TRUST_PROXY=true |
Trusts loopback and the RFC1918/link-local ranges |
TRUST_CLOUDFLARE=true |
Trusts Cloudflare's forwarded headers |
TRUSTED_PROXIES |
Comma-separated CIDRs or IPs that replace the defaults |
Behind an untrusted proxy, every request is bucketed by the proxy's own socket
address, so all its users share one login bucket: five failed attempts lock them
all out. TRUSTED_PROXIES separates them again.
Credential vault
Provider credentials are sealed with AES-256-GCM envelope encryption
(internal/vault/vault.go). The master key is the root of trust — losing it
loses every stored credential, so the dashboard exposes status and rotation
through /api/vault/status and /api/vault/rotate rather than leaving it to a
file edit.
Credentials are decrypted in memory only when a dispatch needs them; the database never holds a plaintext copy after migration.
API key hashing
Client API keys are stored as Argon2id verifiers with a SHA-256 lookup index
(internal/keikey/apikey.go). The plaintext key is shown once at creation.
An auth cache keeps the 64 MiB-per-verification cost off the request path, so a
busy gateway does not spend its CPU re-verifying the same key.
Guardrails
PII and prompt-injection detection runs before a request leaves and on the
response stream (internal/guardrails/). Layers resolve global → provider →
model → chain → API key, most-specific wins, and actions are
log_only / warn / mask / block. Policy CRUD and the audit log live behind
/api/guardrails/policies and /api/guardrails/logs.
Guardrails and Persona pull in opposite directions. Persona can instruct a model to ignore its own rules; guardrails inspect what leaves your gateway for exactly that. Turning guardrails off does not stop the provider from seeing the same request — it only stops you from seeing it first. If an account matters, read the warning on the Advanced Features page before using Persona to bypass a model's limits.
SSRF protection
Requests the gateway makes on your behalf — provider validation, proxy tests,
web fetch — are checked against SSRF rules before dispatch
(internal/handlerutil/ssrf.go), so a custom node cannot be pointed at a
link-local or metadata address and have the gateway fetch it.
Netguard and fetch gate
internal/netguard and internal/fetchgate gate outbound fetches, so a
provider URL or a model-catalog sync cannot reach an address it should not.
Security Log and Audit Log
Two sidebar pages record what happened, at two levels:
- Security Log (
/api/security-log,internal/securitylog/) — per-event security records: login successes and failures, default-password rotation attempts, lockouts, and classified anomalies. Each entry carries severity, actor, IP, method, path, and status, so an incident review starts here rather than in the raw request log. - Audit Log (
/api/audit,internal/db/auditlog.go) — the compliance trail of dashboard actions: key created or rotated, plan changed, update triggered, backup exported. Write-ahead entries record the intent even when the action itself fails.
TLS stealth sits under the same umbrella as an opt-in lane: with
ROUTERX_TLS_STEALTH=1, upstream connections present a browser TLS fingerprint
(JA3/JA4, internal/tlsstealth/) instead of the Go default handshake — a
compatibility layer for your own official accounts, not an evasion tool. It
ships off, and while it is on the gateway emits a daily compliance notice in the
notification feed and over webhooks.
Session and CLI tokens
Dashboard sessions live in internal/auth/session.go; the local CLI token that
lets 9routerx talk to its own daemon lives in internal/auth/clitoken.go.
The CLI token is what makes destructive operations work from a terminal without a
browser session.
Further reading
- Security panel UI:
web/src/components/security/SecurityPanel.svelte - Security log classification:
internal/securitylog/classify.go - Audit log:
internal/db/auditlog.go - Vault internals:
internal/vault/vault.go,internal/db/vault.go - Guardrails:
internal/guardrails/
