Security
This page covers the security practices and requirements for Proxima Console.
Secrets Management
Environment Variables Only
All secrets (database credentials, API keys, tokens) are stored in environment variables. Secrets must never appear in source code, configuration files, or commit history.
.env Files
.envis listed in.gitignoreand must never be committed..env.exampleis committed to the repository with empty placeholder values so developers know which variables are required.
# .env.example
PROXIMA_DB_URL=
PROXIMA_NATS_URL=
API Authentication
Multi-Mode Authentication
The backend supports two authentication modes on all /api/v1/* routes:
- JWT Bearer tokens — User sessions with per-client RBAC permissions
- X-API-Key header — Service accounts with scoped permissions
See the Authentication page for full details.
JWT Security
- Access tokens: HS256 signed, 15-minute TTL
- Refresh tokens: 32 cryptographically random bytes, SHA-256 hashed for storage, 7-day TTL
- Token rotation on refresh: old refresh token invalidated, new one issued
Password Policy (NIST 800-63B)
- Minimum 12 characters, no complexity rules
- 10,000 common password blocklist via
go:embed - 5-password reuse prevention (bcrypt comparison against history table)
MFA (TOTP)
- TOTP secrets encrypted at rest with AES-256-GCM (32-byte key via
PROXIMA_MFA_ENCRYPTION_KEY) - Recovery codes: 8 per user, bcrypt-hashed, single-use
- MFA challenge tokens: single-use, 5-minute TTL, stored in Valkey
Progressive Lockout
Failed login attempts trigger escalating lockout durations (1min at 5 failures, up to 1h at 20+). State stored in Valkey with in-memory fallback.
Rate Limiting
API endpoints are rate-limited per IP address:
- General API (
/api/v1/*): 1000 requests/min (configurable viaPROXIMA_API_RATE_LIMIT) - Public auth (login, refresh, forgot-password): 5 requests/min (configurable via
PROXIMA_AUTH_RATE_LIMIT) - Protected auth routes (sessions, me, mfa) use the general API limit only
Email Enumeration Prevention
- Login returns the same 401 error for wrong email and wrong password
- Forgot password always returns 200 regardless of email existence
Google OIDC SSO
When configured (PROXIMA_GOOGLE_CLIENT_ID), users can authenticate with Google Workspace accounts:
- State parameter: 32 random bytes, SHA-256 hashed, stored in Valkey with single-use GETDEL (5-minute TTL) to prevent CSRF
- Domain restriction:
hdclaim enforced server-side in ID token validation (the URLhdhint alone is bypassable) - Email verification: only
email_verified: trueGoogle accounts are accepted - One-time auth code pattern: JWT tokens never appear in redirect URLs, preventing exposure in browser history and referrer headers
See the Authentication — Google OIDC SSO page for the full flow.
Service Account Security
Service accounts use API keys for programmatic access:
- Keys are generated as 32 cryptographically random bytes, base64url-encoded, prefixed with
prxm_ - Only the SHA-256 hash is stored in the database (and a 12-character prefix for identification)
- The plaintext key is returned once at creation and cannot be retrieved again
- Deactivated service accounts are filtered from API key lookups (
is_active = TRUEin query) - Each service account has a mandatory
expires_atdate (max 1 year)
Agent Authentication
Each agent receives a unique NATS JWT with permissions scoped to its own subjects. Agents enroll over HTTPS using an install token (single-use by default, with a configurable max_uses for reusable fleet enrollment). The agent's private nkey never leaves the host. Credential renewal proves nkey possession over a single-use, server-issued challenge nonce (the challenge/nonce protocol applies to renewal, not the initial enroll). Compromised hosts can be individually revoked without affecting other agents. See NATS Security for the full architecture.
NATS Transport Security
All NATS connections require mutual TLS 1.2+ with Vault-issued certificates:
- Server certificates: Issued by Vault PKI (EC P-256) with 30-day max TTL, renewed via
scripts/nats-cert-renew.sh - CA verification: Agents receive the CA certificate during enrollment and verify the NATS server's identity on every connection
- Cipher suites: Restricted to ECDHE + AES-GCM (256 and 128-bit) — no CBC, no RSA key exchange
- Client authentication: NATS verifies the agent's JWT signature against the account's signing key
Audit Logging
All security-relevant actions are recorded in the audit_log table:
- Authentication events (login success/failure, MFA verification, token refresh)
- User lifecycle (creation, invitation, activation, password change, MFA enable/disable)
- Admin operations (role changes, team membership, client assignments)
- Session management (creation, revocation)
Each audit entry includes: actor ID, action, resource type/ID, details (JSON), IP address, and timestamp. Query via GET /api/v1/audit-log with audit:read permission.
Rules:
- Never log secrets, tokens, or passwords in audit details
- Always include the actor's IP address (from
X-Forwarded-FororX-Real-IPfor proxied requests) - Audit log entries are append-only (no updates or deletes)
SQL Injection Prevention
All database queries use parameterized queries via sqlx. String concatenation is never used to build SQL queries.
// Correct -- parameterized query
err := db.GetContext(ctx, &host, "SELECT * FROM hosts WHERE id = $1", hostID)
// Wrong -- string concatenation (SQL injection risk)
query := fmt.Sprintf("SELECT * FROM hosts WHERE id = '%s'", hostID)
This rule applies to all query types: SELECT, INSERT, UPDATE, DELETE.
CORS
Cross-Origin Resource Sharing is configured via the PROXIMA_CORS_ORIGINS environment variable. Only explicitly listed origins are allowed to make requests to the backend API.
# Allow specific origins
PROXIMA_CORS_ORIGINS=http://localhost:5173,https://console.proxima.example.com
File Permissions
Agent Identity Files
The agent creates identity files (agent.id, host_id) on the local filesystem. These files are created with 0600 permissions (owner read/write only) to prevent other users on the system from reading agent identifiers.
os.WriteFile(filepath, data, 0600)
Container Scanning
Trivy in CI
The CI pipeline runs Trivy to scan container images for known vulnerabilities. The scan is configured to block the pipeline on HIGH and CRITICAL severity findings.
# CI stage
container_scan:
script:
- trivy image --severity HIGH,CRITICAL --exit-code 1 $IMAGE
All container images must pass the Trivy scan before they can be deployed.
Systemd Security Hardening
The agent runs as a systemd service with security hardening options enabled to limit its access to the host system:
NoNewPrivileges=yes-- Prevents the agent from gaining additional privileges.ProtectSystem=strict-- Mounts the filesystem as read-only except for explicitly allowed paths.ProtectHome=yes-- Prevents access to user home directories.PrivateTmp=yes-- Gives the agent its own private/tmpdirectory.
These settings limit the blast radius if the agent process is compromised.