Skip to content

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:

  1. ALLOW traffic to the Prometheal server IP (for LLM proxy and tool calls)
  2. ALLOW traffic to admin-configured allowed domains (Settings > Allowed Outbound Domains)
  3. 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/) with 0600 permissions — 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 (sub claim), 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 returnTo parameter 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 Locked response 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, or invalid_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:

EndpointLimitWindowKey
POST /api/auth/login10 requests15 minutesIP address
POST /api/auth/signup5 requests1 hourIP address
POST /api/chat/.../messages30 requests60 secondsUser ID
POST /api/llm-proxy/.../completions60 requests60 secondsAgent 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

CapabilityAdminManagerUser
Chat with agentsYesYesAssigned only
Create/edit agentsYesYesNo
Manage integrationsYesYesNo
Manage libraryYesYesNo
View audit logsYesNoNo
View data flowYesNoNo
Manage usersYesNoNo
Change settingsYesNoNo
Create invitesYesNoNo

Agent access control

  • If an agent has no AgentAccess rows, it's accessible to all authenticated users
  • If an agent has any AgentAccess rows, 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.allowedCallTargets is 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 DataFlowLog entries 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:

HeaderValuePurpose
Content-Security-PolicySee belowPrevent XSS and injection attacks
X-Frame-OptionsSAMEORIGINPrevent clickjacking
X-Content-Type-OptionsnosniffPrevent MIME sniffing
Strict-Transport-Securitymax-age=31536000; includeSubDomainsForce HTTPS (production only)
Referrer-Policystrict-origin-when-cross-originLimit referrer leakage
Permissions-Policycamera=(), 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 origin
  • script-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 hijacking
  • form-action 'self' — forms can only submit to same origin
  • frame-ancestors 'self' — prevent embedding in iframes (supplements X-Frame-Options)

File upload validation

File uploads (agent documents, library documents) are validated with:

  1. Size limit — 50 MB maximum per file
  2. MIME type whitelist — Only allowed types: PDF, text, CSV, Markdown, HTML, JSON, XML, Office formats (Word, Excel, PowerPoint), and images
  3. 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)
  4. 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:

  1. Before each agent turn, the estimated cost is atomically reserved
  2. If current_spend + reservation >= limit, the turn is rejected
  3. After the turn completes, the reservation is replaced with the actual cost
  4. 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 confirmTools array
  • 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

TransportProtocolEndpoint formatAuth
WebhookHTTP POSThttps://siem.example.com/api/eventsOptional Authorization header (encrypted at rest)
SyslogUDPsiem.example.com:514N/A

Output formats

JSON (default) — one JSON object per event:

json
{
  "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 patternCEF severity
*.error, *.expired7 (High)
settings.*6 (Medium)
*.deleted5 (Medium)
Everything else3 (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):

EndpointFormatContent-TypeDefault limit
GET /api/audit/exportCSVtext/csv5,000 rows
GET /api/audit/export-jsonlJSONL or CEFapplication/x-ndjson or text/plain10,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

  1. Navigate to Settings > SIEM / Log Export
  2. Choose a transport (Webhook or Syslog)
  3. Choose a format (JSON or CEF)
  4. Enter the endpoint URL or host:port
  5. (Webhook only) Enter an Authorization header value — it is encrypted at rest
  6. Click Save SIEM Settings
  7. Click Test Connection to send a siem.test event and verify connectivity
  8. 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

  1. Use gVisor — Install runsc for kernel-level sandbox isolation
  2. Use HTTPS — Set NEXT_PUBLIC_APP_URL to an https:// URL and terminate TLS at your reverse proxy
  3. Use Redis — Set REDIS_URL for distributed rate limiting and session invalidation
  4. Restrict outbound domains — Only allow domains agents genuinely need
  5. Use invite-only registration — Keep registrationEnabled off and use invite links
  6. Set spending limits — Prevent runaway costs with per-agent monthly limits
  7. Review audit logs — Regularly check Settings > Audit for unexpected activity
  8. Enable SIEM export — Forward audit events to your SIEM for centralized monitoring, alerting, and compliance
  9. Rotate encryption key — If compromised, generate a new ENCRYPTION_KEY and re-encrypt credentials. Note: existing encrypted values will need safeDecrypt() fallback handling.
  10. Read-only database users — For PostgreSQL integrations, create a read-only database user
  11. Minimal MCP scopes — Use allowlists and scope rules to limit what tools agents can access

Released under the MIT License.