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:
| Method | Purpose |
|---|---|
ping | Health check — runs SELECT 1 (PG) or conn.ping() (MySQL) |
query | Execute a single SQL statement with optional parameters |
transaction | Execute 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:
| Concern | Redis | MySQL / PostgreSQL |
|---|---|---|
| Admin commands | Blocked via CALL_DENYLIST | Not exposed — no call method exists |
| Data operations | Allowed via ALLOWED_METHODS | Allowed via query / transaction |
| Connection abuse | ioredis built-in pool | 5s per-query timeout |
| Schema changes | N/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 COLUMNare 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.
handleTransactiondoes not apply a timeout. A transaction that holds locks for minutes is allowed. Operators should configureidle_in_transaction_session_timeout(PG) orwait_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
Use a dedicated database user for the proxy with the minimum required permissions (e.g., no
CREATE USER, noALTER SYSTEM, noSUPERUSER). The database's own ACL is the strongest line of defense.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)
- PostgreSQL:
Monitor slow queries on the database server itself. The proxy's 5s timeout is a safety net, not an observability tool.