qaitai docs

Security overview

Tenant isolation, egress control, secret handling and how runners are trusted.

This is a summary for evaluating qaitai. Report vulnerabilities to security@tryqaitai.com.

Tenant isolation

Every table with a workspace id has forced row-level security. Queries run inside a transaction that sets the workspace; outside it, tenant tables return nothing and reject writes. Cross-workspace lookups that have to start without a workspace (GitHub webhooks, cron) use a read-only transaction with a SELECT-only policy. The app connects as a role with no superuser, no BYPASSRLS and no table ownership; migrations run separately as the owner. CI checks that every tenant table is covered.

Secrets never enter the model's context

Test credentials are referenced by name (@secrets/login). The runner decrypts only the secrets the prompt references; fill_secret types the value into the page without returning it. Values are redacted from recorded thoughts, accessibility snapshots and page URLs. The system prompt treats all page content as untrusted (a prompt-injection defense).

Encryption

BYOK keys, test secrets and stored GitHub App credentials use envelope encryption: AES-256-GCM with a fresh data key per value, wrapped by a master key (QAITAI_MASTER_KEY when self-hosted, AWS KMS on qaitai Cloud). The workspace, table and name are bound as authenticated data, so a ciphertext copied into another workspace fails to decrypt.

What the browser can reach

Cloud runs may only target hosts the workspace has verified (DNS TXT record, well-known file, or a preview deployment from a connected repo); verification is re-checked daily. In the browser, an egress guard blocks top-level navigation off verified hosts, every request to private ranges (RFC 1918, loopback, link-local including cloud metadata, CGNAT, IPv6 ULA and link-local) and non-http(s) schemes. Self-hosted runs, on your own runner, may reach private networks.

Runners

  • Remote runners (yours) never listen on a port. Their only credential is a runner token (shown once, stored as a SHA-256 hash, compared in constant time). They have no database access, no master key and no queue token, and only see queued jobs of their own workspace. Each claimed job gets a short-lived token bound to that job, runner and workspace.
  • The managed model key never leaves the control plane: runs on your runner use your key.
  • Request bodies are size-capped and validated; usage minutes come from the control plane's own clock.
  • qaitai Cloud workers run each job on its own short-lived machine, destroyed on exit, with no inbound connections and only a per-job token.

GitHub App

Read-only access to contents, pull requests, deployments, statuses and metadata; write access to checks only. Signing in grants no code access. Webhooks are verified with the App's webhook secret in constant time.

Accounts and audit

Security-relevant actions (runs created, runner tokens issued, domain verification changes, GitHub App setup) are written to an audit log. Self-hosted sign-up is invite-only by default, for every sign-in method.

Email + password accounts: passwords of 10–128 characters, hashed with scrypt; on qaitai Cloud (optional when self-hosting) passwords found in Have I Been Pwned are rejected, sending only a 5-character hash prefix. An email must be confirmed before a password sign-in whenever the instance can send mail (always on qaitai Cloud). Sign-in, sign-up and reset answer the same whether or not an account exists, and are rate-limited per client IP across all web instances. Accounts link to GitHub or Google only when both sides have a verified email.

Known limits

We keep a threat model with open findings and fix them in the open. Current known limits include per-process rate limits (use a shared limiter at your edge for strict global limits) and no network-level egress allowlist on qaitai Cloud worker machines (enforcement is the in-browser guard plus Chromium's sandbox).

On this page