Security

It executes SQL on behalf of whoever can reach it. It is built accordingly.

Read-only by default, scoped by default, recorded always. The controls below are defence in depth around database grants, never a substitute for them. The reasoning behind each one is in the threat model.

Read the threat modelReport a vulnerabilityTrust center
Defence in depth

What each layer defends against

Nine layers, each stopping a different mistake or attack. The panels replay what each one refuses, and what it lets through.

Identity01

The wrong caller

Scoped API keys, limited to connections and read-only access. Named administrators with the narrowest role that fits: owner, admin, developer, auditor. App users by JWT with JWKS. Console SSO over OIDC + PKCE and MCP OAuth 2.1, both stable since 0.30.

Injection02

Hostile parameters

Parameters are typed and bound, never spliced. A single-statement SQL guard blocks writes and multi-statement injection; it is dialect-aware and exercised by a Hypothesis fuzz suite on every commit. Still: connect with a database account that only has the privileges the API should have.

Policy03

Reaching too far

Per-query and collection grants, table allow-lists per key, write operations and a write ceiling, per-user parameters bound from token claims, rate limits that also throttle key guessing, IP allow-lists and key expiry.

Masking04

Seeing too much

Column masking traced to source columns with sqlglot, so an alias or a join does not leak what the column name hides. Applied before formatting, for every output format and every front door.

Agents05

An agent that acts

MCP write tools do not run. They queue for a person to approve in the Console, with per-agent usage recorded. An agent's call is governed exactly like an app's: same grants, same limits, same log.

Record06

Not knowing afterwards

An audit log of who changed what, run history for every query, request IDs in structured logs, Prometheus metrics and alerts. Passwords encrypted at rest with a Fernet key, or kept out of the store as references; the Fernet key and API key secrets are never in a backup.

Change control07

A change made outside review

Configuration as code with QUERYAPIGATE_CONFIG_LOCKED: what the file manages changes only through the file.

Access08

Access that grows unnoticed

Who can reach what, from the grants as they add up; every change to a key or a role is in the audit log.

Erasure09

A request to erase a person

history forget: runs, events and usage counts, erased, with the erasure audited by hash.

Before go-live

Hardening checklist

The operational list from SECURITY.md; security fixes are made against the latest released version. Tick items off as you go; this browser remembers.

{{ hcDone }} of 8 done
  • 01
    Then give people and pipelines their own admin tokens and keep the shared key sealed as break-glass, or remove it. Guide 23: a reverse proxy →
  • 02
    And connect with a database account that has only the privileges the API should have. Guide 10: when writes are needed →
  • 03
    "${ENV_VAR}" references, or QUERYAPIGATE_SECRET_KEY to encrypt them at rest. Guide 22: passwords at rest →
  • 04
    Behind a proxy, QUERYAPIGATE_TRUST_PROXY = number of proxies; leave it 0 when clients connect directly. Guide 17: rate limits →
  • 05
    Guide 38: a browser frontend →
  • 06
    30 seconds by default, so one expensive request cannot monopolise the service. Guide 37: limits and the pool →
  • 07
    Guide 6: bound parameters →
  • 08
    Passwords are masked. Guide 11: the schema browser →

Tested against real systems

CI runs against real PostgreSQL, MySQL, ClickHouse, S3 and Keycloak, upgrades from the previous release, and tests the oldest supported dependencies too.

Proof you can check

The signed audit log, FIPS-approved cryptography, signed releases and how to report a vulnerability are on the Enterprise page.