-
Notifications
You must be signed in to change notification settings - Fork 1
Security
This page documents the security architecture, controls, and hardening recommendations for White-Ops.
- JWT Authentication
- RBAC Roles and Permissions
- Rate Limiting
- Security Headers
- Code Sandbox Restrictions
- Audit Logging
- Secrets Management
- Production Hardening Checklist
White-Ops uses JSON Web Tokens (JWT) for stateless authentication.
Implementation: server/app/core/security.py
- User sends credentials to
POST /api/v1/auth/login - Server verifies password against bcrypt hash in PostgreSQL
- Server issues a signed JWT containing user ID, email, and role
- Client stores the token and includes it in all subsequent requests via the
Authorization: Bearer <token>header - Server middleware validates the token signature and expiration on every request
| Setting | Variable | Default |
|---|---|---|
| Secret Key | JWT_SECRET_KEY |
(must be set) |
| Algorithm | JWT_ALGORITHM |
HS256 |
| Expiration | JWT_ACCESS_TOKEN_EXPIRE_MINUTES |
1440 (24 hours) |
{
"sub": "user-uuid",
"email": "admin@whiteops.local",
"role": "admin",
"exp": 1704153600
}Passwords are hashed using bcrypt via the passlib library. Plaintext passwords are never stored or logged.
- Use a unique, random
JWT_SECRET_KEY(64+ characters) - Reduce
JWT_ACCESS_TOKEN_EXPIRE_MINUTESfor 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
Implementation: server/app/core/permissions.py
White-Ops uses role-based access control with three built-in 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. |
| 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 | -- | -- |
- Request arrives with JWT token
- Middleware extracts and validates the token
- User's role is read from the token payload
- Route handler checks
has_permission(role, required_permission) - If denied, a
403 Forbiddenresponse is returned
Implementation: server/app/core/middleware.py (RateLimitMiddleware)
| Setting | Default | Description |
|---|---|---|
| Requests per window | 120 | Maximum requests allowed |
| Window duration | 60 seconds | Sliding window |
| Key | Client IP | Rate limit key |
- Uses an in-memory sliding window per client IP
- Returns
429 Too Many RequestswithRetry-After: 60header when exceeded - Exempt paths:
/health,/docs,/openapi.json - Logged as a warning:
rate_limit_exceeded
- 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
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 |
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) |
Agents can execute code via the shell and code_exec tools. These operate within a restricted sandbox to prevent damage.
| 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) |
- 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
Implementation: server/app/services/audit.py, server/app/models/audit.py
Every significant action in the system is recorded in the audit log.
| 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 |
| 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 |
- Admin Panel: System > Audit Log (filterable, searchable)
-
API:
GET /api/v1/admin/audit(requiresaudit:readpermission) -
Export:
GET /api/v1/exports/audit?format=csv
Audit records are stored in PostgreSQL indefinitely. For compliance, configure a retention policy using database-level cleanup jobs or export and archive old records.
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 |
-
Never commit
.envto version control. It is in.gitignore. - Generate secrets using
openssl rand -hex 32or 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
- 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)
- All default passwords changed (
SECRET_KEY,JWT_SECRET_KEY,POSTGRES_PASSWORD,REDIS_PASSWORD,MINIO_ROOT_PASSWORD,ADMIN_PASSWORD) -
DEBUG=falseandAPP_ENV=production -
CORS_ORIGINSrestricted 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
- 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
- Docker images pinned to specific versions (not
latestin 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
- 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