QueryProxy
Documentation menu

Production hardening

A checklist for running QueryProxy on a reachable network, from the application key and HTTPS to read-only connections and what the defaults already cover.

Updated:

QueryProxy stores the credentials to every database it fronts, so treat the instance itself as sensitive. Work through this list before exposing it to a network.

  • For teams or users who only read, register a separate connection with a database user that has SELECT privileges only. Keep write access on a different connection, since approved writes run through the connection they were submitted on. On SQL Server, which has no read-only session, give the connection a separate read username and password with SELECT only.
  • Point that read-only connection at a replica where you can.
  • Restrict the network path between QueryProxy and the target database.
  • Require TLS on every network connection and verify the server certificate (verify-ca or verify-full on PostgreSQL, “Verify the server certificate” on MySQL and MariaDB, TrustServerCertificate off on SQL Server). TLS is not enforced on connections created before v0.3.0 until you edit them; queryproxy:doctor lists them.

The SQL guard is not a last line of defence. Reads run in a read-only session on the database side, but the privileges of the database user remain the last line, so build the defence with them as well.

Checklist

  • Set APP_KEY explicitly and keep it stable. It encrypts connection credentials and chat secrets; changing it makes every stored credential unreadable. Generate one with docker run --rm queryproxy/queryproxy php artisan key:generate --show and pass it as -e APP_KEY=... (every container of the same instance must share it). When rotating, move the old key into APP_PREVIOUS_KEYS so existing ciphertext still decrypts. Without an explicit APP_KEY, the container generates one on first boot and persists it to the data volume.
  • Serve over HTTPS behind a TLS-terminating reverse proxy (the container serves plain HTTP on :7432). Then set SESSION_SECURE_COOKIE=true.
  • Set TRUSTED_PROXIES to your proxy’s IP or CIDR whenever a proxy sits in front of QueryProxy. No proxy is trusted by default, so until you set it the app reads the connecting peer as the client: X-Forwarded-Proto is ignored (HTTPS is not detected, so no HSTS header) and audit entries record the proxy’s address. Never set it to *: that lets any client forge X-Forwarded-For, which both fakes the audit trail’s client IP and hands out unlimited login and 2FA attempts, since those throttles are keyed per IP. See Configuration.
  • Supply secrets as files where your platform provides them: set APP_KEY_FILE, DB_PASSWORD_FILE and the other *_FILE variables instead of putting values in the environment. See Configuration.
  • Keep demo seeding off (QUERYPROXY_SEED_DEMO=false, the default) on any reachable instance.
  • Require 2FA for privileged roles with QUERYPROXY_REQUIRE_2FA=admins (or dba / all). Users are funnelled to enrolment on next login.
  • If you use single sign-on, enforce MFA at the identity provider. Users who sign in through SSO are outside QUERYPROXY_REQUIRE_2FA; QueryProxy cannot see which second factor the provider used. Connect only a provider that verifies e-mail addresses (QueryProxy links and creates accounts only when the provider asserts a verified address: email_verified, or xms_edov for Entra ID), and consider QUERYPROXY_LOCAL_LOGIN=admins_only so that a password stays available to system administrators only, for break-glass access. Signing out of QueryProxy does not sign the user out of the provider. See Single sign-on.
  • Treat API tokens as credentials. Give each token only the scopes it needs, and keep approvals:write for the few tokens that must decide requests: a leaked reviewer token can approve other users’ requests. Lower QUERYPROXY_API_TOKEN_MAX_DAYS (default 90) to the longest lifetime you accept, and revoke tokens of people who change roles. A token cannot approve its owner’s own request or take an override. Token creation and revocation are audited. See REST API and tokens.
  • On Kubernetes, turn the NetworkPolicy on (networkPolicy.enabled) and list your ingress controller in networkPolicy.ingressFrom, so only it reaches port 7432 (your network plugin must enforce NetworkPolicy). List the egress you need with networkPolicy.egress.restrict=true. Use your own Secret (secrets.existingSecret) rather than the chart-managed one. See Kubernetes, Helm and high availability.
  • Prefer time-bound grants for access that is not permanent: a DBA can grant a connection for a period and at the read level. See Approval workflow.
  • Keep a copy of the audit log outside the database. Turn on a SIEM sink (QUERYPROXY_SIEM_SINKS) so that someone with database access cannot rewrite history unseen. See Audit chain and SIEM export.
  • Protect /metrics with a token or an address allowlist, or leave it off (the default).
  • Run the doctor after every deploy: php artisan queryproxy:doctor --strict. See Troubleshooting.
  • Back up the data and the APP_KEY separately. See Backup and restore.
  • Restrict connection targets with QUERYPROXY_CONNECTION_HOST_DENYLIST / QUERYPROXY_SQLITE_ALLOWED_DIR if DBAs should not reach arbitrary hosts.

What is on by default

No configuration is needed for these:

  • Security response headers, including a Content-Security-Policy, and a rate limit on the Livewire endpoint.
  • Encrypted connection credentials at rest and TOTP two-factor authentication (optional until you require it).
  • Comment-normalized SQL guards with an executor-enforced row ceiling.
  • Replay-protected, identity-bound chat callbacks, and self-approval prevention.
  • A tamper-evident audit log (hash chain and database triggers), with the SQL shown redacted to Auditors and in chat, and login/webhook rate limiting.
  • SSRF-guarded outbound webhooks.
  • API tokens that always expire, are stored as a hash and stop working when their owner is suspended or deleted.
  • Masked-at-write, fail-closed result storage.

Next steps

Arrow keys to move, Enter to open.