Security Policy¶
SQL2API executes SQL on behalf of whoever can reach it, so please treat it as a sensitive piece of infrastructure.
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.
Hardening checklist for deployments¶
- Set
SQL2API_API_KEY, and put the service behind TLS (a reverse proxy) - the key is sent in a header. It is a full-access admin key; give anyone or anything that only needs to run queries a scoped key instead (POST /api_keys), limited to specific connections and, separately, to read-only access. - Leave
SQL2API_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 credentials out of
db_connections.json: use"password": "${ENV_VAR}"references. - Do not enable
SQL2API_DEBUGon a reachable host. - Set
SQL2API_RATE_LIMITon anything reachable beyond a trusted network; it also throttles API key guessing. - Behind a reverse proxy, set
SQL2API_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
SQL2API_CORS_ORIGINSfor sites you control, and never*without an API key. - Keep the query time limit (
SQL2API_QUERY_TIMEOUT, 30 seconds by default) andSQL2API_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 /connectionsreveals hosts, ports, users and database names (passwords are masked).