Security Policy
Reporting a vulnerability
Do not open a public GitHub issue for security vulnerabilities. Email: [email protected] Include:- A description of the vulnerability
- Steps to reproduce
- The version / commit SHA you tested against
- Your disclosure timeline preference
Scope
In scope:- The HQ UI (
apps/ui/) - The gateway image and its daemons (
gateway/) - The Supabase migration and its triggers/RPCs (
db/) - The installer (
installer/) - Published Docker images on GHCR
- Supabase itself (report to Supabase)
- OpenClaw (report to openclaw.ai)
- Third-party dependencies (report to the respective maintainers; if HQ is affected we’ll track + patch)
- Social engineering of users or maintainers
Trust model and known risks
HQ is self-hosted admin software with tenant-scoped row-level security. Self-hosted installs use a single default tenant, so all authenticated users share the workspace. If you’re planning to deploy it, these are the trust boundaries you should understand.Supabase is fully trusted
The gateway uses your Supabase service-role key to write directly to the database. Anyone with that key has full read/write access to every table. Keep it in.env on disk; never commit it; rotate it if leaked.
The Docker socket is mounted into the runner
Therunner service mounts /var/run/docker.sock so it can execute docker compose restart gateway, docker compose pull (update), and similar actions triggered from the UI. Any RCE in the runner is equivalent to root on the host. This is by design — the alternative would be shelling out to SSH, which has its own risks. If this is unacceptable for your threat model, run HQ in a dedicated VM or don’t expose the UI to untrusted users.
A tighter future alternative is docker-socket-proxy with a narrowed action allowlist. Contributions welcome.
RLS and tenant isolation
Every table uses tenant-scoped RLS policies — authenticated users can only access rows wheretenant_id matches the JWT’s app_metadata.tenant_id. Self-hosted installs use a single default tenant, so in practice all authenticated users see all data. Make sure Supabase email signup is disabled or invite-only, otherwise anyone who signs up for your Supabase project gets full HQ access within the default tenant. The multi-workspace registry adds database-level isolation on top; fine-grained per-role permissions are not implemented.
Default port bindings are loopback-only
docker compose up -d without the installer binds all HQ services (UI, noVNC, files-API) to 127.0.0.1. The installer’s “Tailscale” or “Public” mode explicitly flips them to 0.0.0.0. This is deliberate — if you docker compose up blindly on a cloud VM, nothing is publicly reachable.
If you override UI_HOST_PORT or FILES_API_HOST_PORT to 0.0.0.0:* manually, you’re exposing those services to every interface on the host. Make sure you have a firewall, reverse proxy, or Tailscale between them and the internet. noVNC is not host-exposed — the UI proxies it through /api/novnc over Docker’s internal network.
The installer runs curl | bash
The one-line installer (curl install.yourhq.ai | bash) pipes untrusted input through shell. This is a standard open-source install pattern, but not risk-free. For production installs:
- Download the script first and read it:
- Pin a specific release rather than
main: - Clone the repo and run
./installer/install.shdirectly.
curl | sh from get.docker.com and tailscale.com/install.sh — we’re not signing or pinning those scripts. They’re canonical upstream install scripts. If you don’t trust them, install Docker and Tailscale manually and skip the installer.
Secrets are encrypted at rest
All credentials stored in thesecrets table (channel tokens, OAuth tokens, user API keys) are encrypted with AES-256-GCM before writing to the database. The plaintext value is never stored in Supabase and never returned in API responses.
Key derivation (self-hosted): The encryption key is derived from your SUPABASE_SERVICE_ROLE_KEY via HKDF-SHA256 (salt: yourhq-secrets-v1, info: aes-256-gcm, 32 bytes). Anyone with your service role key can derive the encryption key and decrypt secrets — this is the same trust boundary as the service role key itself.
Key derivation (hosted): Uses a dedicated HOSTED_SECRETS_KEY held in the worker environment, separate from the service role key.
Gateway-side decryption: The secrets_sync daemon (running in the runner container) fetches encrypted values, decrypts them in memory, and writes plaintext to .env files on the gateway-state volume (chmod 0600). These files are the same trust boundary as the service role key and the Docker socket. Agent tools read secrets from the environment — the LLM never sees the values.
What this protects against:
- Database dumps or backups leaking plaintext credentials.
- SQL injection or RLS bypass exposing raw values.
- Supabase dashboard viewers accidentally seeing token values.
- Compromise of the gateway container (secrets are in plaintext
.envfiles on the volume). - Compromise of the service role key (derives the encryption key).
- An agent’s tools intentionally logging or exfiltrating secrets (treat agent tool code as trusted).
Agent capabilities
Each agent runs in the gateway container with a full Chrome browser, a messaging channel (Telegram, Discord, Slack, or none), and the ability to call openclaw plugins. Agents can:- Browse any URL
- Log into websites using saved cookies
- Read/write to their git branch in the gateway’s volume
- Read/write to your Supabase via the shared service-role credentials
- Call openclaw plugins (browser automation, voice, phone control, etc.)
- Escape the gateway container (unless they compromise the Docker socket via an RCE in the runner)
- Read arbitrary files on the host
- Install new plugins (openclaw manages the plugin list)
Dependencies and supply chain
HQ pins specific versions of:- Next.js (UI)
- OpenClaw (agent runtime)
- openclaw plugins (installed from the openclaw marketplace)
- Base Docker images (
ubuntu:24.04,node:24-slim)
npm audit in CI and track advisories. CVEs in the dependency tree are tracked as GitHub issues labeled security. We bump patches in main and expect users to pull updated images.
For production deployments, pin image tags:
Hardening recommendations
In rough order of importance:- Enable MFA on your Supabase account. Dashboard → Project Settings → Authentication → enable MFA for your admin user.
- Disable Supabase email signup. Authentication → Providers → Email → disable “Enable Email Signups” if you don’t need users to register.
- Use Tailscale instead of public HTTPS. The installer’s “Tailscale” mode keeps HQ unreachable from the public internet. Only use “Public HTTPS” when you actually need shared access.
- Pin image versions instead of
:latest. - Put HQ behind an auth gate. Cloudflare Access, Tailscale, or a reverse proxy with basic-auth — anything that requires authentication before a request hits HQ.
- Review the installer before running.
curl -o install.sh ...; less install.sh; bash install.sh. - Generate strong secrets.
openssl rand -hex 32forGATEWAY_AUTH_TOKEN. Use a password manager for your Supabase credentials. - Update regularly. Until gateway update actions are available in the UI, run
docker compose pull && docker compose up -dmanually.
Hosted trust model
If you use the hosted offering at app.yourhq.ai, the trust boundaries are different from self-hosted. Here’s what you should know.Your data lives in a dedicated database
Each hosted workspace gets its own Supabase project. Your data is not shared with other tenants — there is no multi-tenant database. Row-level security policies enforcetenant_id isolation within each project, but in practice each project has exactly one tenant.
Credentials are encrypted at rest
Your Supabase service-role key, database password, and VNC password are encrypted with ChaCha20-Poly1305 before being stored in the control-plane database. The encryption key (HOSTED_SECRETS_KEY) is held in the worker’s environment, not in the database.
User-managed secrets (API keys, channel tokens, OAuth tokens) in your workspace’s secrets table are separately encrypted with AES-256-GCM using the same HOSTED_SECRETS_KEY. This is a different encryption scheme from the control-plane credentials above, but uses the same key material.
What the control plane stores
The master database (hosted_workspaces) stores:
- Your email address and display name
- Stripe customer and subscription IDs
- Your workspace’s Supabase project reference, URL, and anon key (not secret — same data the browser uses)
- Encrypted service-role key and database password
- E2B sandbox ID, status, and access URLs
- Provisioning progress and setup metadata (workspace name, chosen template)
What we can access
- Supabase Management API: we use this to create, configure, and delete your Supabase project. We can read project status and API keys. We cannot read your data through the management API.
- Service-role key: decrypted at provisioning time and passed to your gateway sandbox. After provisioning, the worker only decrypts it when the UI requests your workspace config (to connect your browser to your Supabase). We do not read your Supabase data.
- E2B sandbox: we can pause, resume, and destroy your sandbox. We do not connect to it, run commands in it, or read files from it during normal operation.
- Worker internal token: authenticates requests between the UI and the worker service. It is not a user credential.
What we cannot access
- Your Supabase data: we don’t query your contacts, tasks, knowledge items, or agent data. The UI connects directly from your browser to your Supabase project.
- Your agent sessions: agents run inside the E2B sandbox. We don’t intercept, log, or monitor agent activity.
- Your VNC sessions: noVNC traffic flows directly between your browser and the sandbox. We don’t proxy or record it.
Sandbox isolation
Each workspace runs in a dedicated E2B sandbox — an isolated Linux VM with its own kernel, filesystem, and network namespace. Sandboxes cannot communicate with each other. The gateway, daemons, and agents all run inside this single sandbox.Logging policy
The worker logs provisioning stages, sandbox lifecycle events (created, paused, resumed, destroyed), Stripe webhook processing, and errors. Logs include workspace IDs and event types. They do not include your Supabase data, agent outputs, message content, or VNC session data.Cancellation and data deletion
When you cancel a workspace, the sandbox is paused immediately (no compute charges during the grace period). After the 30-day grace period:- The E2B sandbox is permanently destroyed
- The Supabase project is permanently deleted via the management API
- All encrypted credentials are cleared from the control-plane database
Secrets and data we collect
HQ collects no telemetry. The self-hosted stack never phones home. The only network calls HQ makes are:- To your Supabase project (required)
- To Tailscale’s control plane (if you enable Tailscale, managed by Tailscale not us)
- To openclaw for plugin updates and agent-model auth (when you use Codex OAuth, etc.)
- Installer:
get.docker.com,tailscale.com/install.sh, GHCR (for image pulls) - Dependency updates: npm, PyPI, apt

