Security Hardening

Secure your Rackd installation

Security considerations and hardening guide for production Rackd deployments.

Authentication

Rackd requires authentication on all API endpoints. Three methods are supported:

  • Session cookies (Web UI login)
  • API keys (Bearer token, tied to a user account)
  • OAuth 2.1 with PKCE (MCP clients)

See Authentication for details on each method.

Secure Defaults

Rackd ships with secure defaults for production use:

SettingDefaultDescription
COOKIE_SECUREtrueSession cookies only sent over HTTPS
RATE_LIMIT_ENABLEDtrueAPI rate limiting active
RATE_LIMIT_REQUESTS100100 requests per minute per IP
LOGIN_RATE_LIMIT_REQUESTS55 login attempts per minute per IP

For local development without TLS, use --dev-mode which disables COOKIE_SECURE and RATE_LIMIT_ENABLED automatically.

CSRF Protection

State-changing requests (POST, PUT, DELETE, PATCH) from session-authenticated users must include the X-Requested-With: XMLHttpRequest header. Requests without it are rejected with 403. This prevents cross-origin form submissions from exploiting session cookies.

The built-in Web UI sends this header on all requests. External integrations using session auth must include it as well.

Password Security

  • Minimum length: 8 characters (enforced server-side)
  • Hashing: bcrypt with cost factor 14
  • Password changes invalidate all active sessions for the user
  • Login rate limiting prevents brute-force attacks

API Key Security

  • Keys are SHA-256 hashed before storage — plaintext is never persisted
  • Token comparison uses constant-time comparison to prevent timing attacks
  • Every key must be associated with a user account for RBAC enforcement
  • Legacy keys (no user association) are rejected at the auth boundary
  • Expired keys return a specific EXPIRED_KEY error code
  • Last-used timestamps are tracked for auditing

Session Security

  • Sessions use cryptographically random tokens
  • Cookies are HttpOnly, SameSite=Lax, and Secure (by default)
  • 24-hour TTL with sliding expiration
  • Sessions are invalidated on password change
  • Session store supports SQLite (default) or Valkey for distributed deployments

OAuth 2.1 Security

  • PKCE required (no plain code challenge); confidential clients must also present their client_secret at the token endpoint
  • Refresh token rotation with replay detection and expiry enforcement
  • Dynamic client registration requires https redirect URIs (loopback http exempt) and explicit catalog scopes — wildcards rejected
  • Issued scopes are clamped to the consenting user’s own permissions and re-clamped on refresh
  • Token revocation endpoint authenticates clients and only revokes their own tokens
  • OAuth client management (list/delete) is admin-only

Credential Encryption

Device credentials (SSH passwords, SNMP community strings), DNS provider tokens, and webhook signing secrets are encrypted at rest using AES-256-GCM with a per-encryption random nonce. The key is the ENCRYPTION_KEY environment variable (64 hex chars; generate with openssl rand -hex 32). Rotate it with rackd credentials rotate-key --new-key <key> — the command re-encrypts credentials, DNS tokens, and webhook secrets. Without the key (outside --dev-mode) the encrypted features are disabled rather than falling back to plaintext.

Encryption at Rest

  • Session tokens are stored hashed — a database leak cannot resurrect live sessions
  • API keys and OAuth tokens are stored as SHA-256 hashes
  • Webhook signing secrets are AES-256-GCM encrypted (legacy plaintext rows keep working and re-encrypt on next save)
  • Backups are written with 0600 permissions

Webhook Security

  • HMAC-SHA256 signatures on webhook payloads (when a secret is configured)
  • SSRF protection: loopback, unspecified, link-local (cloud metadata 169.254.0.0/16, IPv6 fe80::/10), multicast and — unless SSRF_ALLOW_PRIVATE_TARGETS=true — RFC1918/CGNAT/ULA targets are blocked, including hostnames that resolve into those ranges; redirect targets are re-validated on every hop
  • Secure HTTP client with SafeDialContext prevents DNS rebinding
  • URL length capped at 2048 characters
  • Only HTTP and HTTPS schemes allowed

Content Security Policy

The Web UI sets a strict CSP header:

  • No unsafe-eval or unsafe-inline for scripts
  • Alpine.js CSP-compatible build
  • default-src 'self' baseline

Security Headers

Rackd sets the following headers on all responses:

  • X-Content-Type-Options: nosniff
  • X-Frame-Options: DENY
  • Referrer-Policy: strict-origin-when-cross-origin
  • Content-Security-Policy (see above)

Audit Logging

When AUDIT_ENABLED=true, Rackd logs all API operations including:

  • Timestamp, action, resource type, resource ID
  • User ID and username (from API key or session)
  • Client IP address
  • Success/failure status
  • Change details (before/after for updates)

Audit logs are retained for 90 days by default (AUDIT_RETENTION_DAYS).

Query audit logs:

  • GET /api/audit — list with filters (action, resource, user, date range)
  • GET /api/audit/export — export as CSV
  • GET /api/audit/{id} — get single entry

Rate Limiting

Two rate limiters protect against abuse:

  1. General API: 100 requests/minute per IP (configurable)
  2. Login/sensitive endpoints: 5 requests/minute per IP (configurable)

Rate limit headers are included in responses:

  • X-RateLimit-Limit
  • X-RateLimit-Remaining
  • X-RateLimit-Reset

RBAC

All API operations are authorized through role-based access control at the service layer. Both REST API and MCP requests go through the same RBAC checks.

Built-in system roles:

  • admin — all permissions
  • operator — read/write on infrastructure resources
  • viewer — read-only access

Custom roles can be created and assigned arbitrary permission sets.

Network Security Recommendations

Reverse Proxy

Run Rackd behind a reverse proxy for TLS termination:

server {
    listen 443 ssl http2;
    server_name rackd.example.com;

    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;
    ssl_protocols TLSv1.2 TLSv1.3;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Set TRUST_PROXY=true together with TRUSTED_PROXIES (comma-separated IPs/CIDRs, e.g. 127.0.0.1,10.0.0.0/8) when behind a reverse proxy: forwarded headers are honored only from those peers, otherwise ignored (fail-closed) so clients cannot spoof their IP.

File Permissions

chmod 600 /var/lib/rackd/rackd.db
chmod 600 /etc/rackd/.env
chown rackd:rackd /var/lib/rackd/

Run as Non-Root

useradd -r -s /bin/false rackd
sudo -u rackd ./rackd server

Firewall

Only expose the HTTP port. Bind to localhost if using a reverse proxy:

LISTEN_ADDR=127.0.0.1:8080

Discovery Security

Network discovery scans probe hosts via ARP, TCP, and optionally SNMP. Considerations:

  • Scans are limited to the configured network’s subnet
  • Maximum subnet size is /16 (65,534 hosts) — larger subnets may cause resource exhaustion
  • SNMPv2c is disabled by default (DISCOVERY_SNMPV2C_ENABLED=false)
  • Discovery credentials are encrypted at rest

See Also