> ## Documentation Index
> Fetch the complete documentation index at: https://docs.yourhq.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Security policy

# Security Policy

## Reporting a vulnerability

**Do not open a public GitHub issue for security vulnerabilities.**

Email: **[security@yourhq.ai](mailto:security@yourhq.ai)**

Include:

* A description of the vulnerability
* Steps to reproduce
* The version / commit SHA you tested against
* Your disclosure timeline preference

We'll acknowledge within 72 hours and keep you updated on our fix. We'll credit you in the release notes unless you prefer to remain anonymous.

## 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

Out of scope:

* Supabase itself (report to [Supabase](https://supabase.com/.well-known/security.txt))
* OpenClaw (report to [openclaw.ai](https://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

The `runner` 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](https://github.com/Tecnativa/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 where `tenant_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:

1. Download the script first and read it:
   ```bash theme={null}
   curl -fsSL install.yourhq.ai -o install.sh
   less install.sh
   bash install.sh
   ```
2. Pin a specific release rather than `main`:
   ```bash theme={null}
   curl -fsSL https://raw.githubusercontent.com/yourhq/yourhq/v0.1.0/installer/install.sh | bash
   ```
3. Clone the repo and run `./installer/install.sh` directly.

The installer itself invokes `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 the `secrets` 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.

**What this does NOT protect against:**

* Compromise of the gateway container (secrets are in plaintext `.env` files 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.)

Agents cannot:

* 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)

Treat agent outputs like untrusted input. If you have an agent that writes files, commit messages, or database rows, sanitize before acting on them.

### 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`)

We run `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:

```yaml theme={null}
# docker-compose.yml
services:
  ui:
    image: ghcr.io/yourhq/yourhq-ui:v0.1.0  # not :latest
```

## Hardening recommendations

In rough order of importance:

1. **Enable MFA on your Supabase account.** Dashboard → Project Settings → Authentication → enable MFA for your admin user.
2. **Disable Supabase email signup.** Authentication → Providers → Email → disable "Enable Email Signups" if you don't need users to register.
3. **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.
4. **Pin image versions** instead of `:latest`.
5. **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.
6. **Review the installer before running.** `curl -o install.sh ...; less install.sh; bash install.sh`.
7. **Generate strong secrets.** `openssl rand -hex 32` for `GATEWAY_AUTH_TOKEN`. Use a password manager for your Supabase credentials.
8. **Update regularly.** Until gateway update actions are available in the UI, run `docker compose pull && docker compose up -d` manually.

## Hosted trust model

If you use the hosted offering at [app.yourhq.ai](https://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 enforce `tenant_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:

1. The E2B sandbox is permanently destroyed
2. The Supabase project is permanently deleted via the management API
3. All encrypted credentials are cleared from the control-plane database

Both deletions must succeed before the workspace is marked as fully canceled. If either fails, the cleanup loop retries every 6 hours until confirmed.

## 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

No user data, telemetry, or usage metrics flow to a YourHQ server. There isn't one.
