Skip to content

Security

Ömer Tarık Yılmaz edited this page Apr 5, 2026 · 1 revision

Security

This page documents the security architecture, controls, and hardening recommendations for White-Ops.

Table of Contents


JWT Authentication

White-Ops uses JSON Web Tokens (JWT) for stateless authentication.

Implementation: server/app/core/security.py

How It Works

  1. User sends credentials to POST /api/v1/auth/login
  2. Server verifies password against bcrypt hash in PostgreSQL
  3. Server issues a signed JWT containing user ID, email, and role
  4. Client stores the token and includes it in all subsequent requests via the Authorization: Bearer <token> header
  5. Server middleware validates the token signature and expiration on every request

Token Configuration

Setting Variable Default
Secret Key JWT_SECRET_KEY (must be set)
Algorithm JWT_ALGORITHM HS256
Expiration JWT_ACCESS_TOKEN_EXPIRE_MINUTES 1440 (24 hours)

Token Payload

{
  "sub": "user-uuid",
  "email": "admin@whiteops.local",
  "role": "admin",
  "exp": 1704153600
}

Password Hashing

Passwords are hashed using bcrypt via the passlib library. Plaintext passwords are never stored or logged.

Best Practices

  • Use a unique, random JWT_SECRET_KEY (64+ characters)
  • Reduce JWT_ACCESS_TOKEN_EXPIRE_MINUTES for higher security (e.g., 60 minutes)
  • Rotate the JWT secret periodically (this invalidates all existing tokens)
  • Never expose the JWT secret in client code or logs

RBAC Roles and Permissions

Implementation: server/app/core/permissions.py

White-Ops uses role-based access control with three built-in roles.

Roles

Role Description
admin Full system access. Can manage users, workers, settings, and audit logs.
operator Can manage agents, tasks, and workflows. Cannot manage users or system settings.
viewer Read-only access to dashboards, agents, tasks, workflows, files, and messages.

Permissions Matrix

Permission Admin Operator Viewer
agents:create Yes Yes --
agents:read Yes Yes Yes
agents:update Yes Yes --
agents:delete Yes -- --
tasks:create Yes Yes --
tasks:read Yes Yes Yes
tasks:update Yes Yes --
tasks:delete Yes -- --
workflows:create Yes Yes --
workflows:read Yes Yes Yes
workflows:update Yes Yes --
workflows:delete Yes -- --
users:create Yes -- --
users:read Yes -- --
users:update Yes -- --
users:delete Yes -- --
workers:read Yes Yes Yes
workers:approve Yes -- --
workers:remove Yes -- --
files:read Yes Yes Yes
files:upload Yes Yes --
files:delete Yes -- --
messages:read Yes Yes Yes
settings:read Yes -- --
settings:update Yes -- --
audit:read Yes -- --

Authorization Flow

  1. Request arrives with JWT token
  2. Middleware extracts and validates the token
  3. User's role is read from the token payload
  4. Route handler checks has_permission(role, required_permission)
  5. If denied, a 403 Forbidden response is returned

Rate Limiting

Implementation: server/app/core/middleware.py (RateLimitMiddleware)

Configuration

Setting Default Description
Requests per window 120 Maximum requests allowed
Window duration 60 seconds Sliding window
Key Client IP Rate limit key

Behavior

  • Uses an in-memory sliding window per client IP
  • Returns 429 Too Many Requests with Retry-After: 60 header when exceeded
  • Exempt paths: /health, /docs, /openapi.json
  • Logged as a warning: rate_limit_exceeded

Recommendations for Production

  • Deploy behind a reverse proxy (Nginx, Cloudflare) with its own rate limiting
  • Consider per-user rate limits (by JWT subject) in addition to per-IP
  • Adjust limits based on expected traffic patterns
  • Monitor rate limit events in the audit log

Security Headers

Implementation: server/app/core/middleware.py (SecurityHeadersMiddleware)

All API responses include the following security headers:

Header Value Purpose
X-Content-Type-Options nosniff Prevents MIME type sniffing
X-Frame-Options DENY Prevents clickjacking via iframes
X-XSS-Protection 1; mode=block Enables browser XSS filter
Referrer-Policy strict-origin-when-cross-origin Controls referrer information
Permissions-Policy geolocation=(), microphone=(), camera=() Disables unnecessary browser APIs
Cache-Control no-store Prevents caching of API responses
Pragma no-cache HTTP/1.0 cache prevention
X-Request-ID <unique-id> Request tracing identifier
X-Response-Time <ms>ms Response timing for debugging

Additional Recommendations

When deploying behind a reverse proxy, add:

Header Value
Strict-Transport-Security max-age=31536000; includeSubDomains
Content-Security-Policy default-src 'self' (adjust for your frontend)

Code Sandbox Restrictions

Agents can execute code via the shell and code_exec tools. These operate within a restricted sandbox to prevent damage.

Restrictions

Category Restriction
Network No outbound network access from code execution (tool-level network access is managed separately)
Filesystem Write access limited to a per-task temporary workspace directory
System Calls Blocked: fork, exec of system binaries, mount, chroot, privilege escalation
Resource Limits CPU time limit per execution, memory cap, output size limit
Packages Python packages restricted to an allowlist (pandas, numpy, requests, etc.)
Commands Shell commands blocklisted: rm -rf /, dd, mkfs, shutdown, reboot, etc.
Timeout Maximum execution time enforced (configurable, default: 60 seconds)

Architecture

  • Code runs in an isolated Docker container or process namespace
  • Workspace is created per task and cleaned up after completion
  • No persistent state between code executions
  • Output is captured and returned to the agent loop

Audit Logging

Implementation: server/app/services/audit.py, server/app/models/audit.py

Every significant action in the system is recorded in the audit log.

What Is Logged

Category Events
Authentication Login success, login failure, password change
Agents Create, update, delete, start, stop
Tasks Create, update, cancel, delete
Workflows Create, update, delete, execute
Workers Register, approve, remove, offline
Users Create, update, delete, role change
Settings Any configuration change
Files Upload, delete

Audit Record Structure

Field Description
id Unique record ID
timestamp When the action occurred (UTC)
user_id Who performed the action
user_email User's email address
action Action type (e.g., agent.create, task.delete)
resource_type Affected resource type
resource_id Affected resource ID
details JSON object with action-specific data
ip_address Client IP address

Accessing the Audit Log

  • Admin Panel: System > Audit Log (filterable, searchable)
  • API: GET /api/v1/admin/audit (requires audit:read permission)
  • Export: GET /api/v1/exports/audit?format=csv

Retention

Audit records are stored in PostgreSQL indefinitely. For compliance, configure a retention policy using database-level cleanup jobs or export and archive old records.


Secrets Management

Secrets in the Environment

All sensitive configuration is provided via environment variables (.env file or Docker secrets).

Secret Variable Notes
Application secret SECRET_KEY Used for general encryption
JWT signing key JWT_SECRET_KEY Used to sign authentication tokens
Database password POSTGRES_PASSWORD PostgreSQL credentials
Redis password REDIS_PASSWORD Redis authentication
MinIO password MINIO_ROOT_PASSWORD File storage credentials
Admin password ADMIN_PASSWORD Initial admin account
LLM API keys ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_API_KEY Third-party API credentials
SMTP credentials SMTP_USER, SMTP_PASSWORD External email
IMAP credentials IMAP_USER, IMAP_PASSWORD External email

Best Practices

  • Never commit .env to version control. It is in .gitignore.
  • Generate secrets using openssl rand -hex 32 or equivalent
  • Use different secrets for each environment (dev, staging, production)
  • In Docker Swarm or Kubernetes, use native secret management instead of .env
  • Rotate secrets periodically, especially after personnel changes
  • Use a secrets manager (HashiCorp Vault, AWS Secrets Manager) in enterprise deployments
  • Restrict file permissions on .env: chmod 600 .env

LLM API Key Handling

  • API keys are stored in worker environment variables, not in the database
  • Keys are passed to the LiteLLM library at runtime
  • Keys are never logged (structlog is configured to exclude sensitive fields)
  • Keys are never returned by the Settings API (masked in responses)

Production Hardening Checklist

Application Level

  • All default passwords changed (SECRET_KEY, JWT_SECRET_KEY, POSTGRES_PASSWORD, REDIS_PASSWORD, MINIO_ROOT_PASSWORD, ADMIN_PASSWORD)
  • DEBUG=false and APP_ENV=production
  • CORS_ORIGINS restricted to the actual frontend domain
  • JWT expiration reduced from 24 hours to an appropriate value
  • Rate limiting tuned for expected traffic
  • Unused LLM provider keys removed from environment

Network Level

  • HTTPS enabled via reverse proxy with TLS 1.2+ only
  • HSTS header enabled
  • Internal services (PostgreSQL, Redis, MinIO) not exposed to public internet
  • Firewall rules restricting access to management ports
  • Workers communicate with master over a VPN or private network
  • SSH access secured with key-based auth, no root login

Infrastructure Level

  • Docker images pinned to specific versions (not latest in production)
  • Docker resource limits configured for all services
  • Docker log rotation enabled
  • Automatic OS security updates enabled
  • Non-root users used inside containers where possible
  • Docker socket not exposed to containers

Monitoring and Response

  • Prometheus and Grafana deployed and configured
  • Alert rules configured for critical events
  • Audit log exported and archived regularly
  • Incident response plan documented
  • Backup and restore procedures tested
  • Rate limit exceeded alerts configured

Clone this wiki locally