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 sqlparse to parse and validate SQL queries before database execution.
  • Validation occurs in db_query/tools/sql_query.py and db_query_pre_auth/tools/sql_query.py at lines 40-45.
  • The plugin enforces single-statement execution by checking len(statements) == 1.
  • Only SELECT statements are permitted; statement.get_type() != 'SELECT' triggers a ValueError.
  • 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →