Security
Prometheal is designed to give AI agents maximum capability while preventing data exfiltration and credential exposure. This document covers every security mechanism.
Sandbox isolation
Network isolation (Docker provider)
Every sandbox container has iptables rules that:
- ALLOW traffic to the Prometheal server IP (for LLM proxy and tool calls)
- ALLOW traffic to admin-configured allowed domains (Settings > Allowed Outbound Domains)
- DENY all other outbound traffic
This means agents cannot:
- Access the internet directly
- Reach other containers or hosts on the network
- Exfiltrate data to external servers
Kernel isolation (gVisor)
When gVisor (runsc) is installed, sandbox containers run with an additional kernel-level sandbox. gVisor intercepts system calls and runs them in a user-space kernel, preventing container escape exploits.
Prometheal auto-detects gVisor and uses it when available. Without gVisor, containers use the default runc runtime (less isolated but functional).
E2B isolation
E2B sandboxes run as Firecracker microVMs — each agent gets its own lightweight virtual machine with hardware-level isolation.
Browser isolation
The sandbox includes Google Chrome for CDP-based browser automation. The browser runs inside the same network-isolated container, so it can only reach:
- The Prometheal server (for LLM proxy calls)
- Admin-configured allowed domains (Settings > Allowed Outbound Domains)
Chrome is launched with --ignore-certificate-errors since the sandbox environment may not have a complete certificate authority store. This is safe because the browser runs inside the isolated sandbox — not on the host or with access to sensitive networks.
Workspace persistence and quotas
Agent workspaces are persisted via Docker named volumes (prometheal-workspace-{agentId}). Files created by agents survive sandbox container restarts. This means agents can build up project files, configurations, and state over time. Volumes are scoped per-agent — no cross-agent filesystem access.
Each agent has a configurable workspace quota (workspaceMaxSizeMB, default 3 GB). The runtime checks disk usage before file writes and blocks them when over quota. This prevents agents from filling up the host disk. Admins can monitor usage and reset workspaces from the agent settings page.
Unprivileged execution
The sandbox image runs processes as an unprivileged user account, not root. This limits the blast radius of any exploit within the container.
Credential protection
Credentials never enter sandboxes
MCP integrations (GitHub tokens, Slack tokens, database passwords, etc.) execute server-side via the MCP client manager. The sandbox only sees tool names and results — never the underlying credentials.
OAuth token management
For integrations that require OAuth (like Google Workspace), the entire flow runs through Prometheal's web UI — no CLI or manual file placement needed:
- Admin enters the client ID and client secret in the integration form
- The Authorize button redirects to the provider's consent screen
- After authorization, tokens are stored encrypted in the database alongside the client credentials
- A CSRF state parameter (random 32 bytes, stored in KV with 10-minute TTL) prevents authorization code injection attacks
- Before spawning the MCP server, Prometheal writes credential files to a managed directory (
.mcp-oauth/) with0600permissions — the files are never committed to git - Tokens auto-refresh via the MCP server's built-in refresh logic
Encryption at rest
All sensitive data is encrypted with AES-256-GCM before storage:
- LLM API keys (OpenRouter, Anthropic, OpenAI)
- MCP integration credentials (tokens, passwords, connection strings)
- OAuth access and refresh tokens
Encryption uses the ENCRYPTION_KEY environment variable (32 bytes / 64 hex chars). The ciphertext format is {iv}:{ciphertext}:{authTag} (all hex-encoded).
Protected environment variables
When MCP credentials are injected as environment variables into stdio subprocesses, the following system variables are protected and can never be overwritten:
PATH, HOME, USER, SHELL, NODE_ENV, NODE_OPTIONS, DATABASE_URL, JWT_SECRET, ENCRYPTION_KEY, E2B_API_KEY
Authentication
Password hashing
Passwords are hashed with bcryptjs using 12 salt rounds. Plain-text passwords are never stored or logged.
OIDC / SSO
Prometheal supports single sign-on via OpenID Connect. When configured:
- The login page shows a "Log in with SSO" button alongside the standard email/password form
- OIDC discovery (
.well-known/openid-configuration) automatically fetches provider endpoints and JWKS - ID tokens are verified cryptographically using the provider's JWKS (JSON Web Key Set) — not by trusting the access token
- CSRF protection: A random state parameter (32 bytes, 10-minute TTL in KV) prevents authorization code injection
- Client secrets are encrypted at rest with AES-256-GCM (same as other credentials)
- User matching: first by OIDC subject (
subclaim), then by email. Existing accounts are automatically linked on first SSO login. - Auto-provisioning (optional): new users are created with USER role and empty password hash (SSO-only login)
- The
returnToparameter is validated to only accept relative paths, preventing open redirect attacks
Multi-factor authentication (TOTP)
Prometheal supports TOTP-based two-factor authentication using any authenticator app (Google Authenticator, Authy, 1Password, etc.).
- Setup: Users navigate to Security in the sidebar, click "Enable MFA", scan a QR code, and verify with a 6-digit code
- Login flow: After entering email/password, users with MFA enabled are prompted for a TOTP code before the session is created
- TOTP secrets are encrypted at rest with AES-256-GCM (same key as other credentials)
- Time window: Codes are valid for ±1 period (30 seconds each), allowing for minor clock drift
- Disable: Requires password confirmation to prevent unauthorized disabling
- Admin controls: Admins can reset MFA for any user from the Users management page
Account lockout
Per-account lockout prevents brute-force attacks that rotate source IPs to bypass IP-based rate limiting:
- After N consecutive failed login attempts, the account is locked for M minutes (configurable in Settings > Authentication > Account Security)
- Defaults: 5 attempts, 15-minute lockout
- The lockout counter resets on successful login
- Locked accounts receive a
423 Lockedresponse with the remaining lockout duration - Admins can unlock accounts from the Users management page
- All failed login attempts and lockouts are logged to the audit trail
Failed login logging
Every failed login attempt is logged as an audit event (user.login.failed) with:
- Email address (whether or not the account exists)
- Source IP address
- Failure reason:
user_not_found,invalid_password, orinvalid_totp - User ID (when the account exists)
Lockout events (user.login.lockout) are also logged. These events are forwarded to SIEM if configured.
Session management
- Sessions use JWT tokens (HS256 algorithm, 7-day expiry)
- Tokens are stored in httpOnly cookies (not accessible to JavaScript)
- Secure flag is set in production (HTTPS only)
- SameSite=lax prevents CSRF via cross-origin form submissions
- Sessions are cached in KV with a 60-second TTL (reduces database lookups)
- Tokens auto-refresh when past 50% of their lifetime (sliding window)
Session invalidation
When an admin changes a user's role, the session cache for that user is invalidated immediately. The user's next request will re-validate against the database.
CSRF protection
The middleware validates the Origin header on all state-changing requests (POST, PUT, PATCH, DELETE) to /api/*. Requests from a different origin than NEXT_PUBLIC_APP_URL are rejected with a 403.
Rate limiting
Redis-backed (with in-memory fallback) fixed-window rate limits:
| Endpoint | Limit | Window | Key |
|---|---|---|---|
POST /api/auth/login | 10 requests | 15 minutes | IP address |
POST /api/auth/signup | 5 requests | 1 hour | IP address |
POST /api/chat/.../messages | 30 requests | 60 seconds | User ID |
POST /api/llm-proxy/.../completions | 60 requests | 60 seconds | Agent ID |
When a limit is exceeded, the API returns 429 Too Many Requests.
In addition to IP-based rate limiting, per-account lockout blocks login after 5 consecutive failures for 15 minutes (returns 423 Locked). This prevents brute-force attacks that rotate source IPs.
Access control
Role-based access
| Capability | Admin | Manager | User |
|---|---|---|---|
| Chat with agents | Yes | Yes | Assigned only |
| Create/edit agents | Yes | Yes | No |
| Manage integrations | Yes | Yes | No |
| Manage library | Yes | Yes | No |
| View audit logs | Yes | No | No |
| View data flow | Yes | No | No |
| Manage users | Yes | No | No |
| Change settings | Yes | No | No |
| Create invites | Yes | No | No |
Agent access control
- If an agent has no
AgentAccessrows, it's accessible to all authenticated users - If an agent has any
AgentAccessrows, only listed users (plus admins and managers) can access it - Access is enforced on chat, agent details, and sandbox endpoints
Agent-to-agent communication
Agent communication is a potential privilege escalation vector — a low-permission agent could call a high-permission agent to access tools it shouldn't have. This is controlled by:
- Explicit directional allowlist:
Agent.allowedCallTargetsis an array of agent IDs. Empty by default. Admin must explicitly authorize each call path. A→B does not imply B→A. - Fresh context only: The target agent receives only its system prompt + the single message. It cannot see the caller's conversations, history, or identity.
- Cycle detection: A call chain array tracks visited agents. If the target is already in the chain, the call is rejected immediately.
- Depth limit: Maximum 3 levels of nesting. Beyond this, agent tools are not offered to the LLM.
- Re-verification at execution: The allowlist is checked again when the tool executes, not just when tools are loaded. This prevents TOCTOU issues if an admin revokes access mid-conversation.
- Spending attribution: Costs are charged to the calling agent's budget, preventing one agent from draining another's spending limit.
- Full audit trail: Every cross-agent call creates
DataFlowLogentries with direction, message content, response, and cost metadata.
Middleware route protection
/admin/*routes are blocked for non-admin users- All dashboard routes require authentication
- Public routes (
/login,/signup,/setup) redirect authenticated users to the dashboard
Security headers
The middleware sets these headers on all responses:
| Header | Value | Purpose |
|---|---|---|
Content-Security-Policy | See below | Prevent XSS and injection attacks |
X-Frame-Options | SAMEORIGIN | Prevent clickjacking |
X-Content-Type-Options | nosniff | Prevent MIME sniffing |
Strict-Transport-Security | max-age=31536000; includeSubDomains | Force HTTPS (production only) |
Referrer-Policy | strict-origin-when-cross-origin | Limit referrer leakage |
Permissions-Policy | camera=(), microphone=(), geolocation=() | Disable sensitive browser APIs |
Content Security Policy
The CSP header restricts resource loading to prevent XSS attacks:
default-src 'self'— only load resources from the same originscript-src 'self' 'unsafe-inline' 'unsafe-eval'— scripts from same origin (Next.js requires inline/eval)style-src 'self' 'unsafe-inline'— styles from same origin (Tailwind requires inline)img-src 'self' data: blob:— images from same origin, data URIs (QR codes), and blobs (screenshots)connect-src 'self' ws: wss:— API calls and WebSocket connections (noVNC desktop streaming)object-src 'none'— block plugins (Flash, Java)base-uri 'self'— prevent base tag hijackingform-action 'self'— forms can only submit to same originframe-ancestors 'self'— prevent embedding in iframes (supplements X-Frame-Options)
File upload validation
File uploads (agent documents, library documents) are validated with:
- Size limit — 50 MB maximum per file
- MIME type whitelist — Only allowed types: PDF, text, CSV, Markdown, HTML, JSON, XML, Office formats (Word, Excel, PowerPoint), and images
- Magic bytes validation — The first bytes of the file are checked against known file signatures to prevent MIME type spoofing (e.g., renaming an executable to
.pdf) - Filename sanitization — Non-alphanumeric characters (except
.,-,_) are replaced with_to prevent path traversal
Spending limits
Per-agent monthly spending limits use pessimistic cost reservation to prevent concurrent bypass:
- Before each agent turn, the estimated cost is atomically reserved
- If
current_spend + reservation >= limit, the turn is rejected - After the turn completes, the reservation is replaced with the actual cost
- A per-agent mutex serializes concurrent requests to prevent race conditions
This prevents scenarios where multiple simultaneous requests could each pass the limit check before any cost is recorded.
Tool confirmation
Admins can configure tools that require explicit user approval before execution. This is useful for destructive operations (deleting files, sending messages, etc.).
- Configured per agent-integration binding via
confirmToolsarray - 5-minute timeout — if the user doesn't respond, the tool is automatically denied
- The agent sees a denial message and can adjust its approach
SIEM / Log export
Prometheal can forward every audit event to an external SIEM or log collector in real time. Configuration lives in Settings > SIEM / Log Export (admin only).
Transports
| Transport | Protocol | Endpoint format | Auth |
|---|---|---|---|
| Webhook | HTTP POST | https://siem.example.com/api/events | Optional Authorization header (encrypted at rest) |
| Syslog | UDP | siem.example.com:514 | N/A |
Output formats
JSON (default) — one JSON object per event:
{
"eventType": "user.login",
"eventData": { "email": "admin@example.com", "ip": "10.0.0.1" },
"userId": "clxyz123",
"timestamp": "2026-03-06T12:00:00.000Z",
"source": "prometheal"
}CEF (Common Event Format) — compatible with Splunk, ArcSight, QRadar, and other SIEM platforms:
CEF:0|Prometheal|Prometheal|1.0|user.login|user.login|3|rt=1741262400000 cs2=clxyz123 cs2Label=userId msg={"email":"admin@example.com"}CEF severity levels are mapped automatically:
| Event pattern | CEF severity |
|---|---|
*.error, *.expired | 7 (High) |
settings.* | 6 (Medium) |
*.deleted | 5 (Medium) |
| Everything else | 3 (Low) |
Syslog framing
When using the syslog transport, messages follow RFC 5424 format:
<107>1 2026-03-06T12:00:00.000Z prometheal prometheal - user.login - {JSON or CEF payload}- Facility: 13 (log audit)
- Severity: mapped from event type (same table as CEF)
Bulk export
For historical log ingestion, two bulk export endpoints are available (admin only):
| Endpoint | Format | Content-Type | Default limit |
|---|---|---|---|
GET /api/audit/export | CSV | text/csv | 5,000 rows |
GET /api/audit/export-jsonl | JSONL or CEF | application/x-ndjson or text/plain | 10,000 rows (max 50,000) |
Both support the same filters: agentId, eventType, from, to.
The JSONL endpoint also accepts format=cef and limit query parameters.
Configuration
- Navigate to Settings > SIEM / Log Export
- Choose a transport (Webhook or Syslog)
- Choose a format (JSON or CEF)
- Enter the endpoint URL or host:port
- (Webhook only) Enter an Authorization header value — it is encrypted at rest
- Click Save SIEM Settings
- Click Test Connection to send a
siem.testevent and verify connectivity - Toggle Enable SIEM Export to start forwarding
Events are forwarded asynchronously (fire-and-forget) — a slow or failing SIEM endpoint will never block audit logging or user-facing operations. Failures are logged server-side at the error level.
The SIEM configuration is cached for 30 seconds to avoid a database lookup on every audit event. Changes take effect within 30 seconds, or immediately after clicking Save.
Recommendations for production
- Use gVisor — Install
runscfor kernel-level sandbox isolation - Use HTTPS — Set
NEXT_PUBLIC_APP_URLto anhttps://URL and terminate TLS at your reverse proxy - Use Redis — Set
REDIS_URLfor distributed rate limiting and session invalidation - Restrict outbound domains — Only allow domains agents genuinely need
- Use invite-only registration — Keep
registrationEnabledoff and use invite links - Set spending limits — Prevent runaway costs with per-agent monthly limits
- Review audit logs — Regularly check Settings > Audit for unexpected activity
- Enable SIEM export — Forward audit events to your SIEM for centralized monitoring, alerting, and compliance
- Rotate encryption key — If compromised, generate a new
ENCRYPTION_KEYand re-encrypt credentials. Note: existing encrypted values will needsafeDecrypt()fallback handling. - Read-only database users — For PostgreSQL integrations, create a read-only database user
- Minimal MCP scopes — Use allowlists and scope rules to limit what tools agents can access