Security Policy¶
QueryAPIGate executes SQL on behalf of whoever can reach it, so please treat it as a sensitive piece of infrastructure. This page is an operational checklist; for the reasoning behind each control - what's actually being defended against, how it works, and where it stops - see documentation/THREAT_MODEL.md.
Reporting a vulnerability¶
Please do not open a public issue. Report it privately through GitHub's "Report a vulnerability" form on this repository, or email arcswdev@gmail.com. You can expect an acknowledgement within a few days.
Supported versions¶
Security fixes are made against the latest released version.
Verify a release¶
From 0.31, every image (ghcr.io/anantharajuc/queryapigate, and its -h2 variant) and the Helm chart
(oci://ghcr.io/anantharajuc/charts/queryapigate) is signed keyless with cosign, by
digest, by this repository's release workflow - and carries SLSA provenance, an SBOM and a GitHub attestation
(ADR 0018). A signature you can trust names exactly this identity:
cosign verify ghcr.io/anantharajuc/queryapigate:0.31.0 \
--certificate-identity https://github.com/AnanthaRajuC/QueryAPIGate/.github/workflows/docker.yml@refs/tags/v0.31.0 \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
gh attestation verify oci://ghcr.io/anantharajuc/queryapigate:0.31.0 --owner AnanthaRajuC
The same for the chart, ghcr.io/anantharajuc/charts/queryapigate:0.31.0. Anything else - another repository,
another workflow, another tag, or no signature - isn't a release of this project. To have a cluster refuse it, and
to read the SBOM: guide 59.
Releases before 0.31 aren't signed.
SLSA level¶
Releases meet SLSA Build Level 2: each artifact is built on a GitHub-hosted runner by a workflow in this repository, and comes with provenance - which workflow, at which commit, from which tag - that the build platform signs (GitHub artifact attestations, through Sigstore), so it can't be forged without that workflow. That holds for:
- the images and the Helm chart, from 0.31 (
docker.yml); - the sdist and wheel on PyPI, from 0.35.1 (
release.yml). Earlier ones carry only PyPI's own attestations (PEP 740), which say who uploaded them, not how they were built. To check one:
pip download queryapigate==0.35.1 --no-deps -d .
gh attestation verify queryapigate-0.35.1-py3-none-any.whl --owner AnanthaRajuC
Not Level 3. Level 3 asks that the steps doing the build can't influence the provenance. Here the attestation is made in the same job as the build, so a compromised build step - a poisoned dependency of the build, say - could in principle shape what gets attested. Building and attesting in a reusable workflow, isolated from the caller, is the documented way to Level 3; it isn't done yet.
Hardening checklist for deployments¶
- Set
QUERYAPIGATE_API_KEY, and put the service behind TLS (a reverse proxy) - the key is sent in a header. It is the shared, full-access admin key; give anyone or anything that only needs to run queries a scoped key instead (POST /api/v1/api-keys), limited to specific connections and, separately, to read-only access. - Give the people (and pipelines) who manage the server their own admin tokens, with the narrowest role that fits -
queryapigate admins create NAME --role ownerfor the first - and then keep the shared key sealed as a break-glass key, or remove it (guide 43). - Leave
QUERYAPIGATE_ALLOW_WRITESunset unless you really need writes, and connect with a database account that only has the privileges the API should have. The read-only guard - including the single-statement check it relies on - is defence in depth, not a substitute for grants; it is dialect-aware (MySQL/ClickHouse honour backslash escapes in string literals by default, PostgreSQL/SQLite/H2 do not) and is exercised by a fuzz test suite (tests/test_sql_guard_fuzz.py), but a regex-based guard can never be a full SQL parser for every server mode. - Keep database passwords out of the store: use
"password": "${ENV_VAR}"references, or setQUERYAPIGATE_SECRET_KEYto encrypt them at rest (guide 22). - Set
QUERYAPIGATE_SECRET_ENV_VARSto the variables your connections reference, andQUERYAPIGATE_METRICS_TOKENwherever/metricscan be reached from outside (guide 58). - Run only signed releases (above), by digest.
- Do not enable
QUERYAPIGATE_DEBUGon a reachable host. - Set
QUERYAPIGATE_RATE_LIMITon anything reachable beyond a trusted network; it also throttles API key guessing. - Behind a reverse proxy, set
QUERYAPIGATE_TRUST_PROXYto the number of proxies so limits apply per real client - and leave it at0when clients connect directly, otherwise they can forgeX-Forwarded-Forto evade the limit. - Only enable
QUERYAPIGATE_CORS_ORIGINSfor sites you control, and never*without an API key. - Keep the query time limit (
QUERYAPIGATE_QUERY_TIMEOUT, 30 seconds by default) andQUERYAPIGATE_MAX_PAGE_SIZEso one expensive request cannot monopolise the service; setting the timeout to0removes that protection. - Prefer bound
:nameparameters over{name}text placeholders. - Note that
GET /api/v1/connectionsreveals hosts, ports, users and database names (passwords are masked). - Plan for backing up and restoring the store -
queryapigate.dbinQUERYAPIGATE_HOME, or the PostgreSQL databaseQUERYAPIGATE_DATABASE_URLnames (backups and restores).