Hide pgsqlite internal objects from client sqlite_master queries (result filtering) - #83
Open
robertjbass wants to merge 1 commit into
Open
Hide pgsqlite internal objects from client sqlite_master queries (result filtering)#83robertjbass wants to merge 1 commit into
robertjbass wants to merge 1 commit into
Conversation
…ltering A client-issued sqlite_master / sqlite_schema SELECT should not expose pgsqlite's internal objects: the __pgsqlite_* bookkeeping tables, the pg_catalog tables/views pgsqlite materializes as real SQLite objects, or the indexes/triggers owned by them. Rather than rewriting the query text (which can corrupt complex predicates such as a large NOT IN list or an ESCAPE clause), the catalog interceptor now runs the client's ORIGINAL, UNMODIFIED query and filters pgsqlite-internal rows out of the result. Because the query text is never changed, no client query can be corrupted by this path. A small probe of sqlite_master builds the authoritative set of internal object names (including internal-owned indexes/triggers whose own names are not reserved) so they are hidden even when the client projects only the name column. The reserved pg_catalog sets are exact-name (never a pg_% wildcard) so a user-owned object like pg_indexes or pg_my_report is preserved. A projection with no name/tbl_name column (e.g. SELECT sql FROM sqlite_master) is returned unfiltered, and any unrecognized query shape fails open and runs unmodified. Covered by unit tests for detection/filtering and wire-level integration tests, including a large non-matching NOT IN list that proves the client's query is not corrupted.
This was referenced Jul 11, 2026
erans
added a commit
that referenced
this pull request
Aug 5, 2026
…nt sqlite_master queries (#80) Adds an opt-in, default-off --hide-internal-tables flag (env PGSQLITE_HIDE_INTERNAL_TABLES). When on, SqliteMasterFilter replaces each client sqlite_master / sqlite_schema relation with a filtered derived table, so pgsqlite's __pgsqlite_* bookkeeping tables and the index rows they own are not listed. The client's projections, predicates, and joins are never modified, so count(*), joins, subqueries, CTEs, EXISTS, and SELECT sql are all correct without extra handling. Hooked in at the two wire-protocol entry points only, never at DbHandler::process_query, so pgsqlite's own sqlite_master probes still see the real catalog and migrations do not re-run. Also exempts pgsqlite's own generated wrapper from the SQL-injection detector's nesting-depth rule, which the added derived table would otherwise trip on benign listings. The exemption does not reset depth, so client-written nesting is still detected. Closes #80. Supersedes #81, #82, #83. Co-Authored-By: Claude Opus 5 (1M context) <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
Client
sqlite_master/sqlite_schemaqueries return pgsqlite's own internal objects alongside the user's tables: the__pgsqlite_*bookkeeping tables, the materialized pg_catalog tables (pg_attrdef,pg_constraint,pg_depend,pg_index), the materialized pg_catalog / information_schema views, and internal indexes on them. These leak into schema browsers, ORMs, and\dt-style tooling connected over the wire.Approach: filter results, do not rewrite the query
This hides those internal objects by filtering the result rows, never by modifying the client's SQL. When
intercept_querysees a single plain SELECT whose FROM referencessqlite_master/sqlite_schema, it:SELECT name, tbl_name FROM sqlite_masterprobe (so internal-owned indexes/triggers are caught bytbl_nameeven when the client only projectsname),name(or projectedtbl_name) is an internal object:__pgsqlite_%prefix, the reserved pg_catalog table/view names (exact-name set, never apg_%wildcard, so a user's ownpg_myreportsurvives), or in the probe-derived denylist.Because the query text is never altered, this cannot corrupt a query (an earlier query-rewriting attempt broke complex predicates - large
NOT INlists,ESCAPEclauses). Fail-open: any unrecognized shape (subquery-wrapped FROM, set ops, noname/tbl_nameprojection such asSELECT sqlorSELECT count(*), or a parse failure) returns the original result unchanged.Boundary
A query whose text itself trips an earlier catalog-interceptor branch (e.g. one embedding many
pg_*names) is handled by that branch before this one and is out of scope here. SimpleSELECT * FROM sqlite_masterfrom external tools is handled cleanly.Tests
Wire-level integration tests plus unit tests: internal tables/views/indexes hidden from
SELECT *,type='table',type='index',type='view'scans; user tables/views/indexes returned; a userpg_myreportreturned; a large non-pgNOT IN+ESCAPEreturns correct rows (no corruption);SELECT sql FROM sqlite_masterreturned unfiltered. Verified against real psql.