Skip to content

MySQL & PostgreSQL — SQL Safety Model

Unlike the Redis adapter, the MySQL and PostgreSQL adapters do not maintain an explicit SQL-statement denylist. Instead, they use method-level gating combined with per-query timeouts to achieve the same safety goals.


Method-level gating

Both mysql.ts and postgres.ts expose exactly three methods:

MethodPurpose
pingHealth check — runs SELECT 1 (PG) or conn.ping() (MySQL)
queryExecute a single SQL statement with optional parameters
transactionExecute an array of SQL statements in a transaction (BEGIN/COMMIT/ROLLBACK)

Any other method name is rejected with a ZEDGI_VALIDATION_ERROR. This means there is no call-style escape hatch — callers cannot invoke arbitrary database functions or admin commands by name. They can only send SQL text.

Why there is no SQL-statement denylist

A denylist of dangerous SQL statements (e.g., DROP TABLE, ALTER SYSTEM, CREATE USER) would require the proxy to parse and understand every SQL dialect, version, and extension. This is a losing battle:

  • Dialect fragmentation. MySQL, MariaDB, PostgreSQL, CockroachDB, and Aurora each have their own extensions, system functions, and administrative commands. A denylist that covers all of them is unbounded maintenance work.
  • Parser complexity. SQL is not a regular language. A regex-based denylist would have false positives (legitimate queries containing blocked keywords) and false negatives (obfuscated malicious statements). A full SQL parser in the proxy is out of scope.
  • The user owns the database. The proxy connects to the user's own database with the user's own credentials. If the user wants to run DROP TABLE their_own_table, that's their data and their risk. The proxy's job is to prevent abuse of the proxy itself (connection exhaustion, runaway queries), not to police what users do with their own schema.

The safety boundary is drawn differently for SQL vs Redis:

ConcernRedisMySQL / PostgreSQL
Admin commandsBlocked via CALL_DENYLISTNot exposed — no call method exists
Data operationsAllowed via ALLOWED_METHODSAllowed via query / transaction
Connection abuseioredis built-in pool5s per-query timeout
Schema changesN/A (no schema in Redis)Allowed — user's own database

What IS protected

1. Connection exhaustion (timeout)

Every query call gets a 5-second server-side timeout. See proxy-adapters-query-timeout.md for the full rationale and safety analysis.

2. Connection pool limits

The proxy's connection pool has a finite size configured by the operator. pool.connect() / pool.getConnection() will queue or reject when the pool is exhausted, preventing a single caller from consuming all database connections.

3. No admin command surface

There is no call or exec method for SQL adapters. The only way to interact with the database is through query (single statement) and transaction (multi-statement with rollback). Admin commands like ALTER SYSTEM, CREATE USER, GRANT, REVOKE, SHUTDOWN are only reachable if the user writes them as SQL text — and they would execute with the user's own database credentials, which already have whatever permissions the user granted them.

4. Parameterized queries

Both adapters support parameterized queries (payload.params). The proxy does not interpolate user input into SQL strings — parameters are passed separately to the underlying driver, which handles escaping. This prevents SQL injection through the proxy layer itself (though the caller is still responsible for not interpolating untrusted input into their own SQL text before sending it).

What is NOT protected (by design)

  • Destructive DDL. DROP TABLE, TRUNCATE, ALTER TABLE ... DROP COLUMN are allowed. The proxy connects to the user's database with the user's credentials; if the user has permission to drop their own tables, the proxy will forward that query.
  • Long-running transactions. handleTransaction does not apply a timeout. A transaction that holds locks for minutes is allowed. Operators should configure idle_in_transaction_session_timeout (PG) or wait_timeout/interactive_timeout (MySQL) on the database server itself.
  • Resource-intensive queries that finish under 5s. A query that scans 10M rows in 4.9 seconds succeeds. The timeout is a ceiling, not a cost governor.

Recommendations for operators

  1. Use a dedicated database user for the proxy with the minimum required permissions (e.g., no CREATE USER, no ALTER SYSTEM, no SUPERUSER). The database's own ACL is the strongest line of defense.

  2. Set server-side connection timeouts:

    • PostgreSQL: idle_in_transaction_session_timeout, statement_timeout (as a server default, not just per-query)
    • MySQL: wait_timeout, interactive_timeout, max_execution_time (as a server default)
  3. Monitor slow queries on the database server itself. The proxy's 5s timeout is a safety net, not an observability tool.