Skip to content

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.1 only
  • ZMQ gateway: 127.0.0.1 only
  • No API key required
  • No TLS

Network-exposed (Requires Hardening)

Exposing Sol to a network requires additional security measures:

  • Change http_ip to bind a specific interface (not 0.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.

bash
curl -H "Authorization: Bearer <token>" https://api.nonsense.ws/api/v1/status

Mango 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_term for performance
  • Cache invalidation via POST /api/v1/infra/cache-invalidate
  • Cowboy option is middlewares (plural) -- singular middleware silently falls back to defaults

Authorization

RBAC (Role-Based Access Control)

Mango manages RBAC with a permission chain:

user -> user_teams -> team_roles -> roles.permissions

The 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:

  1. Middleware (auth-enabled requests) -- checks user owns the resource
  2. 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  ->  rejected

SSRF Prevention

Webhook URL validation blocks requests to private and local IP addresses:

RangeDescription
127.0.0.0/8Loopback
10.0.0.0/8Private class A
172.16.0.0/12Private class B
192.168.0.0/16Private class C
169.254.0.0/16Link-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:

erlang
{worker_path, "/opt/sol/bin/luna"}

Plugin Sandbox

Lua plugins run in a sandboxed Luerl VM with restricted standard libraries:

LibraryStatus
osRemoved
ioRemoved
debugRemoved
packageRemoved
stringAvailable
tableAvailable
mathAvailable

Additional protections:

  • Eval timeout: 15 seconds per lifecycle call
  • Periodic GC between calls
  • Network access only via sol.http bridge
  • 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

CapDefaultDescription
worker_cap256Max workers
listener_cap1024Max listeners
pid_cap1024Max tracked PIDs

Error Handling

Error Sanitization

HTTP error responses never contain internal Erlang terms. All errors are mapped to generic strings:

json
{"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 prompt
    • role_manipulation -- attempts to change the assistant's behavior
    • exfiltration -- attempts to extract system prompts or config
    • tool_abuse + encoding_evasion -- attempts to misuse tools
SeverityBehavior
infoLogged but allowed
warningLogged and surfaced to user
blockRequest rejected before reaching LLM

Resilience

ETS Heir Protection

Critical ETS tables survive owner process crashes via {heir, Pid}:

TableData
sol_modelsModel registry
sol_global_catalogCluster catalog
sol_metricsMetrics counters
sol_task_resultsTask results
sol_scheduled_jobsScheduled jobs
sol_provider_poolProvider 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

Released under the MIT License.