Follow-up from PR #158 second-round review (finding #5).
Current state (after #158): The ad-hoc /query endpoint denies the auth schema via a syntactic guard — check_denied_schemas walks every relation with sqlparser's visit_relations (FROM/JOIN/subquery/CTE/set-op/DML-target/EXPLAIN/DESCRIBE, quoted and multi-part names). Coverage tests pin the indirect-relation cases.
Residual gap: This is a name filter. It cannot see through an indirect handle that doesn't surface as an auth-qualified relation — e.g. an operator-defined view or a federated alias over auth.sessions. Ad-hoc SQL can't create such a handle (DDL is rejected by the allowlist), so the residual surface is operator config, which is trusted. But a truly structural fix would make auth tables unreachable from /query regardless of SQL text.
Why not done in #158: auth.users/auth.sessions are a real, documented feature — docs/auth/pipelines/active-users.yaml reads them, and pipelines execute raw SQL through the shared engine SessionContext. So auth tables must stay in the pipeline/jobs context. Structural isolation therefore requires /query to run against a separate SessionContext that registers data sources but not the auth schema. That means a second data-source registration pass at startup — which for DB-backed sources (postgres/mysql) risks opening duplicate connection pools. That cost/risk is disproportionate to a residual that only trusted operator config can reach, so it's split out here.
Proposed approach when picked up:
- Factor the SessionState/context builder in
setup_app_state into a helper.
- Build a
query_ctx that registers data sources + UDFs but skips register_auth_tables; give /query its own engine over it.
- Confirm data-source registration is lazy enough not to double connection pools (or make it so) before landing.
🤖 Generated with Claude Code
Follow-up from PR #158 second-round review (finding #5).
Current state (after #158): The ad-hoc
/queryendpoint denies theauthschema via a syntactic guard —check_denied_schemaswalks every relation with sqlparser'svisit_relations(FROM/JOIN/subquery/CTE/set-op/DML-target/EXPLAIN/DESCRIBE, quoted and multi-part names). Coverage tests pin the indirect-relation cases.Residual gap: This is a name filter. It cannot see through an indirect handle that doesn't surface as an
auth-qualified relation — e.g. an operator-defined view or a federated alias overauth.sessions. Ad-hoc SQL can't create such a handle (DDL is rejected by the allowlist), so the residual surface is operator config, which is trusted. But a truly structural fix would make auth tables unreachable from/queryregardless of SQL text.Why not done in #158:
auth.users/auth.sessionsare a real, documented feature —docs/auth/pipelines/active-users.yamlreads them, and pipelines execute raw SQL through the shared engineSessionContext. So auth tables must stay in the pipeline/jobs context. Structural isolation therefore requires/queryto run against a separate SessionContext that registers data sources but not the auth schema. That means a second data-source registration pass at startup — which for DB-backed sources (postgres/mysql) risks opening duplicate connection pools. That cost/risk is disproportionate to a residual that only trusted operator config can reach, so it's split out here.Proposed approach when picked up:
setup_app_stateinto a helper.query_ctxthat registers data sources + UDFs but skipsregister_auth_tables; give/queryits own engine over it.🤖 Generated with Claude Code