Repository navigation
fix(security): replace naive SQL validator with sqlglot AST check - #23
Merged
Merged
Conversation
The previous validator uppercased the whole query and blocked it if any of DELETE/DROP/INSERT/UPDATE/CREATE/ALTER appeared as a substring. That rejected legitimate queries (SELECT * FROM update_log, WHERE x = 'create') and could be bypassed with comments or unusual whitespace. This PR replaces it with a sqlglot-based AST validator that: - parses with the DuckDB dialect and rejects parse errors, empty input, and multi-statement inputs - accepts only SELECT / WITH / UNION / INTERSECT / EXCEPT / VALUES at the top level - walks the tree and rejects any Insert / Update / Delete / Create / Drop / Alter / TruncateTable / Commit / Rollback / Transaction / Use / Attach / Detach / Merge / Copy / Command nested anywhere - injects an outer LIMIT via AST manipulation if the user didn't supply one, replacing the old _limit_query string append Also cleans up the /api/query error flow so 400s raised by the validator are re-raised unchanged rather than re-wrapped inside a 'Query execution failed: 400: ...' string. Adds sqlglot==30.4.3 to backend/requirements.txt. Verified against the demo stack: - happy path: 37,537 rows - update_log / literal 'create' queries: accepted (false-positive fix) - DROP / INSERT / UPDATE / DELETE / CREATE / ALTER / ATTACH / COPY: 400 - SELECT 1; DROP TABLE x: 400 (multi-statement) - /*DROP*/ SELECT 1: accepted (comment stripped at parse) - SELECT FROM: 400 (parse error) - rowLimit honoured when no explicit LIMIT; user LIMIT preserved Closes #6
Closed
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.
Summary
The previous validator in
/api/queryuppercased the whole query and blocked it if any ofDELETE / DROP / INSERT / UPDATE / CREATE / ALTERappeared as a substring. That:SELECT * FROM update_log,WHERE status = 'create'This PR replaces it with a
sqlglot-based AST validator.How it works
sqlglot.parse(..., dialect="duckdb")and rejects parse errors, empty input, and multi-statement inputsSELECT / WITH / UNION / INTERSECT / EXCEPT / VALUESat the top levelInsert / Update / Delete / Create / Drop / Alter / TruncateTable / Commit / Rollback / Transaction / Use / Attach / Detach / Merge / Copy / Commandnode, even nested in subqueries or CTEsLIMITvia AST manipulation if the user didn't supply one — replaces the old_limit_querystring-appendAlso cleans up the
/api/queryerror flow so 400s raised by the validator are re-raised unchanged rather than re-wrapped as"Query execution failed: 400: ...".Adds
sqlglot==30.4.3tobackend/requirements.txt.Verification (done locally against the demo stack)
WITH update_log AS (...) SELECT * FROM update_logSELECT 1 WHERE 'create' = 'create'DROP TABLE xINSERT INTO x VALUES (1)UPDATE x SET a=1DELETE FROM xCREATE TABLE y AS SELECT 1ALTER TABLE x ...ATTACH 'evil.db'COPY x TO 's3://evil'SELECT 1; DROP TABLE x/*DROP*/ SELECT 1SELECT FROM(malformed)Query execution failed: 400: Only SELECT...Only SELECT queries are allowed (got DROP)rowLimit=3, no explicit LIMITLIMIT 2,rowLimit=10Test plan for reviewer
docker compose down -v && docker compose up --buildDROP TABLE xand run — expect400 Only SELECT queries are allowed (got DROP)SELECT * FROM update_log(with a real CTE) — expect successCloses #6