Security
ZERG provides defense-in-depth security across authentication, authorization, input validation, and process isolation. This guide covers the security architecture and configuration.
Threat Model
Localhost-only (Default)
By default, Sol binds the HTTP API to 127.0.0.1:21434. Suitable for single-user development on a trusted machine.
- HTTP API:
127.0.0.1only - ZMQ gateway:
127.0.0.1only - No API key required
- No TLS
Network-exposed (Requires Hardening)
Exposing Sol to a network requires additional security measures:
- Change
http_ipto bind a specific interface (not0.0.0.0) - Use a reverse proxy (nginx, caddy) for TLS termination
- Restrict access with firewall rules
- Enable API key authentication
- Configure per-key rate limiting
Authentication
Bearer Token Authentication
Sol validates Bearer tokens via sol_auth gen_server and Cowboy middleware. The /health endpoint is exempt from authentication.
curl -H "Authorization: Bearer <token>" https://api.nonsense.ws/api/v1/statusMango Token Delegation
Sol delegates Bearer token validation to the Mango HTTP service. Mango returns the user's UUID and RBAC permissions. User identity is propagated through ZMQ to workers via user_uuid.
Auth Middleware
The sol_auth_middleware module enforces authentication on all requests except exempt paths:
- Exempt paths are cached in
persistent_termfor performance - Cache invalidation via
POST /api/v1/infra/cache-invalidate - Cowboy option is
middlewares(plural) -- singularmiddlewaresilently falls back to defaults
Authorization
RBAC (Role-Based Access Control)
Mango manages RBAC with a permission chain:
user -> user_teams -> team_roles -> roles.permissionsThe admin RBAC chain:
admin user -> system-admin team -> admin role -> permissions: ["*"]Admin Enforcement
Sol's is_admin() checks for <<"*">> in the permissions list. 24+ infrastructure handlers require admin access. Non-admin users receive 403 Forbidden. RBAC expanded to 56 endpoint-specific permissions covering full CRUD for all API resources.
Resource Ownership
Two-layer ownership enforcement:
- Middleware (auth-enabled requests) -- checks user owns the resource
- Handler defense-in-depth (always active) -- validates ownership in the handler itself
Centralized resource scopes in ?RESOURCE_SCOPES map {Method, PathPrefix} to {Scope, {Module, Function, BindingKey}}.
Input Validation
Shell Quoting
All Luna shell arguments use cel.shell_quote (single-quote wrapping) to prevent injection from LLM-generated parameters. This blocks $(), backticks, $VAR expansion, and other shell metacharacters.
Secret Redaction
Luna's redaction engine detects and masks 10 secret patterns before display:
- API keys and tokens
- Passwords in connection strings
- Private keys
- Bearer tokens
- AWS credentials
The has_secrets() function is used before logging or displaying tool output.
Path Traversal Protection
File paths in HTTP requests are normalized and checked for .. traversal sequences. The workspace file API (read/write/edit/search/list) enforces workspace boundary constraints:
/workspace/root/../../etc/passwd -> rejectedSSRF Prevention
Webhook URL validation blocks requests to private and local IP addresses:
| Range | Description |
|---|---|
127.0.0.0/8 | Loopback |
10.0.0.0/8 | Private class A |
172.16.0.0/12 | Private class B |
192.168.0.0/16 | Private class C |
169.254.0.0/16 | Link-local |
DNS resolution is performed before checking. Resolution failures reject the request (do not bypass).
Header Injection Prevention
HTTP header values are validated to reject CR/LF characters, preventing response splitting and header injection attacks.
Method Enforcement
Mutation endpoints require POST method. Other methods receive 405 Method Not Allowed:
/api/v1/start,/api/v1/stop,/api/v1/stop-all/api/v1/discover/api/v1/agents/spawn,/api/v1/agents/:id/kill/api/v1/cluster/join,/api/v1/cluster/leave/api/v1/lua/eval,/api/v1/lua/workers/spawn/api/v1/zmq/dispatch
Process Isolation
Worker Path Validation
The worker_path configuration is validated against a configurable prefix. This prevents arbitrary binary execution:
{worker_path, "/opt/sol/bin/luna"}Plugin Sandbox
Lua plugins run in a sandboxed Luerl VM with restricted standard libraries:
| Library | Status |
|---|---|
os | Removed |
io | Removed |
debug | Removed |
package | Removed |
string | Available |
table | Available |
math | Available |
Additional protections:
- Eval timeout: 15 seconds per lifecycle call
- Periodic GC between calls
- Network access only via
sol.httpbridge - No filesystem access
Root User Safety
When running as root, erlexec downgrades the effective user to nobody (configurable). Command arguments are passed as lists, not shell strings.
ZMQ State Caps
| Cap | Default | Description |
|---|---|---|
worker_cap | 256 | Max workers |
listener_cap | 1024 | Max listeners |
pid_cap | 1024 | Max tracked PIDs |
Error Handling
Error Sanitization
HTTP error responses never contain internal Erlang terms. All errors are mapped to generic strings:
{"error": "model not found"}Internal details (process IDs, supervisor references, stack traces) are logged server-side but not exposed to clients.
Bearer Timing-Safe Comparison
API key validation uses constant-time comparison to prevent timing side-channel attacks.
Prompt Injection Detection
Client-side detection applied to all user input before LLM processing:
- 19 patterns across 4 categories:
system_override-- attempts to override system promptrole_manipulation-- attempts to change the assistant's behaviorexfiltration-- attempts to extract system prompts or configtool_abuse+encoding_evasion-- attempts to misuse tools
| Severity | Behavior |
|---|---|
info | Logged but allowed |
warning | Logged and surfaced to user |
block | Request rejected before reaching LLM |
Resilience
ETS Heir Protection
Critical ETS tables survive owner process crashes via {heir, Pid}:
| Table | Data |
|---|---|
sol_models | Model registry |
sol_global_catalog | Cluster catalog |
sol_metrics | Metrics counters |
sol_task_results | Task results |
sol_scheduled_jobs | Scheduled jobs |
sol_provider_pool | Provider keys |
Graceful Shutdown
prep_stop drains in-flight requests before the VM shuts down. This prevents data loss during rolling upgrades.
Air-Gapped Deployment
Sol supports fully air-gapped deployment with no outbound network connections:
- Docker Compose profiles for optional services
- nginx TLS termination
- Export/import scripts for offline installation
- All inter-service communication stays within the Docker network
Rate Limiting
Per-key rate limiting via sol_rate_limiter:
- Default: 60 RPM per key
- Configurable burst
- Token bucket algorithm