How the Dify DB-Query Plugin Parses and Validates SQL Queries Using sqlparse
The Dify DB-Query plugin leverages the sqlparse library to tokenize SQL strings, enforce single-statement execution, and restrict operations to SELECT queries before any database connection is opened.
The junjiem/dify-plugin-tools-dbquery repository provides a secure database query tool for the Dify AI platform. To prevent injection attacks and unauthorized data modifications, the plugin implements a strict validation pipeline that parses and validates SQL queries using sqlparse. This validation occurs in both the standard and pre-authentication variants of the tool.
SQL Validation Pipeline in SqlQueryTool
The validation logic resides in db_query/tools/sql_query.py (standard mode) and db_query_pre_auth/tools/sql_query.py (pre-authentication mode). Both implementations follow an identical three-step validation process using the sqlparse library.
Importing and Parsing SQL Statements
At the top of the module, the plugin imports sqlparse to handle tokenization:
import sqlparse
Source: line 4
When a user submits a query, the _invoke method parses the raw string into statement objects:
statements = sqlparse.parse(query_sql)
The sqlparse.parse function returns a tuple of Statement objects, enabling structured analysis of the SQL syntax without relying on fragile regular expressions. Source: line 40
Enforcing Single-Statement Execution
To prevent batch injection attacks, the plugin strictly enforces single-statement execution. After parsing, it validates the statement count:
if len(statements) != 1:
raise ValueError("Only a single query SQL can be filled")
If the input contains multiple statements separated by semicolons, the tool raises a ValueError immediately, preventing the execution of chained commands. Source: line 42
Restricting Operations to SELECT Statements
The plugin restricts all queries to read-only SELECT operations. It inspects the statement type using sqlparse's built-in classification:
statement = statements[0]
if statement.get_type() != 'SELECT':
raise ValueError("Query SQL can only be a single SELECT statement")
This check occurs at lines 44-45, ensuring that INSERT, UPDATE, DELETE, or DDL commands are blocked before the plugin establishes a database connection.
Implementation Across Standard and Pre-Auth Variants
The validation logic is duplicated across both tool variants to maintain consistent security guarantees. The standard variant in db_query/tools/sql_query.py receives credentials via tool parameters, while the pre-auth variant in db_query_pre_auth/tools/sql_query.py reads credentials from self.runtime.credentials. Despite this difference in credential sourcing, both use identical sqlparse validation chains at lines 40-45.
Practical Code Examples
Standard Mode with Inline Credentials
from db_query.tools.sql_query import SqlQueryTool
tool = SqlQueryTool()
params = {
"db_type": "postgresql",
"db_host": "localhost",
"db_port": 5432,
"db_username": "admin",
"db_password": "secret",
"db_name": "sales",
"query_sql": "SELECT id, amount FROM orders WHERE status = 'paid'",
"output_format": "markdown",
}
for msg in tool._invoke(params):
print(msg.text)
Pre-Auth Mode with Runtime Credentials
from db_query_pre_auth.tools.sql_query import SqlQueryTool
runtime = type("Runtime", (), {"credentials": {
"db_type": "mysql",
"db_host": "db.example.com",
"db_port": "3306",
"db_username": "user",
"db_password": "pwd",
"db_name": "inventory",
"db_properties": "",
}})()
tool = SqlQueryTool()
tool.runtime = runtime
params = {"query_sql": "SELECT * FROM products", "output_format": "json"}
for msg in tool._invoke(params):
print(msg.json)
Both examples trigger the same validation pipeline. Attempting to execute INSERT INTO products VALUES (...) or SELECT * FROM users; DROP TABLE users; raises a ValueError before any database connection opens.
Why sqlparse for SQL Validation
Using sqlparse instead of regex-based validation provides three critical advantages for the Dify plugin:
- Robust Tokenisation: Handles quoted identifiers, SQL comments, and complex whitespace without false positives that often plague pattern-matching approaches.
- Accurate Statement Classification: The
Statement.get_type()method reliably distinguishes between SELECT, INSERT, UPDATE, and other operation types based on the parsed token stream. - Extensibility: The parsed Statement object enables future enhancements such as table name whitelisting or column-level permission checks without rewriting the validation engine.
Summary
- The Dify DB-Query plugin uses
sqlparseto parse and validate SQL queries before database execution. - Validation occurs in
db_query/tools/sql_query.pyanddb_query_pre_auth/tools/sql_query.pyat lines 40-45. - The plugin enforces single-statement execution by checking
len(statements) == 1. - Only SELECT statements are permitted;
statement.get_type() != 'SELECT'triggers aValueError. - Both standard and pre-auth variants share identical validation logic, differing only in credential sourcing from tool parameters versus runtime credentials.
Frequently Asked Questions
What happens if I submit multiple SQL statements separated by semicolons?
The plugin raises a ValueError with the message "Only a single query SQL can be filled" before establishing any database connection. This prevents batch injection attacks where malicious users attempt to append DROP or DELETE commands after a legitimate SELECT statement.
Can I use CTEs (WITH clauses) or subqueries with this plugin?
Yes. As long as the top-level statement type evaluates to 'SELECT' through sqlparse.parse(), Common Table Expressions and subqueries are fully supported. The validation checks the outer statement classification, not the internal complexity of the query structure.
Where does the actual database execution happen after validation?
After validation in sql_query.py, the plugin calls helper functions in db_query/tools/db_util.py (or db_query_pre_auth/tools/db_util.py for pre-auth mode) to create the SQLAlchemy engine and execute the sanitized query against the target database.
Is sqlparse sufficient to prevent all SQL injection attacks?
While sqlparse provides robust parsing and statement classification for the Dify plugin, it primarily serves as a structural validator ensuring only single SELECT statements pass. The plugin relies on parameterized queries and proper database user permissions to provide defense in depth against injection attacks.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →