Conversation
…CTs (restricted mode) Functions outside the static ALLOWED_FUNCTIONS list are no longer hard-rejected: the validator collects them and allows execution only when pg_proc proves every overload of the name non-volatile (IMMUTABLE/STABLE cannot write or carry side effects). Volatile names (pg_sleep, pg_read_file, lo_import, ...) and names absent from pg_proc stay rejected — fail closed. EXPLAIN ANALYZE is now allowed when the explained statement is a SELECT; the recursive walk still validates the inner statement and execution stays inside the forced read-only transaction. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The restricted-mode SQL validator (
SafeSqlDriver) rejects more than the read-only boundary requires, in two ways:ALLOWED_FUNCTIONSlist is hard-rejected. Databases with custom read-only functions (IMMUTABLE/STABLE SQL or PL/pgSQL helpers) can't call them at all in restricted mode, even though such functions cannot write or carry side effects.EXPLAIN ANALYZEis rejected categorically, which makes theexplain_querytool's ownanalyze=trueparameter unusable in restricted mode — the tool offers a flag the validator can never accept.Change
pg_procprobe allows them only when every overload of the name is proven non-volatile (provolatile <> 'v'). Volatile names (pg_sleep,pg_read_file,lo_import, ...) and names absent frompg_procstay rejected — fail closed, including when the probe itself fails or returns nothing.EXPLAIN ANALYZEis allowed when the explained statement is a SELECT. The recursive walk still validates the inner statement (including the new function gate); non-SELECT inner statements are still rejected.Why this is safe
The volatility proof is enforced by Postgres itself, not by trust in the allowlist: a non-volatile function that attempts a write errors server-side, and
SafeSqlDrivercontinues to execute everything inside a forced read-only transaction (force_readonly=True). Mislabeled volatility on untrusted-language functions requires superuser to create in the first place. Operators and casts (which can invoke functions withoutFuncCallparse nodes) are outside this change's surface — behavior there is identical before and after.Testing
Each new branch has a driving test in
tests/unit/sql/test_safe_sql.py: proven-non-volatile allow, volatile-overload reject, absent-from-pg_proc reject (schema-qualified), probe-returns-None fail-closed, partial-proof rejects only the unproven names, EXPLAIN ANALYZE SELECT allow / non-SELECT reject, and probe contents (allowlisted names are never probed).uv run pytest tests/unit— 198 passed, 1 xfailed;ruff check/ruff format --checkclean (validated against this repo's lockfile).🤖 Generated with Claude Code