# How the Dify DB-Query Plugin Enforces SELECT-Only Queries for Security

> Learn how the Dify DB-Query plugin secures your database by enforcing SELECT-only queries, preventing unauthorized data modifications and ensuring read-only access.

- Repository: [Junjie.M/dify-plugin-tools-dbquery](https://github.com/junjiem/dify-plugin-tools-dbquery)
- Tags: security
- Published: 2026-03-05

---

**The Dify db-query plugin guarantees read-only database access by parsing incoming SQL with sqlparse and rejecting any statement that is not a single SELECT query before establishing a database connection.**

The junjiem/dify-plugin-tools-dbquery repository provides LLM tools for querying databases within the Dify ecosystem. To prevent destructive SQL injection attacks such as table drops or data exfiltration, the plugin implements a strict **SELECT-only** security gate that analyzes every query at the application layer.

## The Three-Step Security Validation in `_invoke`

The enforcement logic resides in the `_invoke` method of both plugin variants. When a user submits a query, the plugin executes three validation checks using the **sqlparse** library.

First, the plugin parses the raw SQL into an abstract syntax tree:

```python
statements = sqlparse.parse(query_sql)

```

Second, it verifies that exactly one statement exists. If `len(statements) != 1`, the plugin raises a `ValueError` with the message **"Only a single query SQL can be filled"**.

Third, the plugin inspects the statement type via `statement.get_type()`. If the result is anything other than `'SELECT'`, execution halts with the error **"Query SQL can only be a single SELECT statement"**.

```python
statements = sqlparse.parse(query_sql)
if len(statements) != 1:
    raise ValueError("Only a single query SQL can be filled")
statement = statements[0]
if statement.get_type() != 'SELECT':
    raise ValueError("Query SQL can only be a single SELECT statement")

```

This logic appears at lines 44-45 in [`db_query/tools/sql_query.py`](https://github.com/junjiem/dify-plugin-tools-dbquery/blob/main/db_query/tools/sql_query.py) for the standard plugin and at identical lines in [`db_query_pre_auth/tools/sql_query.py`](https://github.com/junjiem/dify-plugin-tools-dbquery/blob/main/db_query_pre_auth/tools/sql_query.py) for the pre-authenticated variant.

## Pre-Connection Enforcement Architecture

The validation occurs **before** the plugin opens any database connection. This design ensures that malicious payloads—such as `DROP TABLE`, `INSERT`, or multi-statement attacks—never reach the database engine.

By blocking non-SELECT queries at the application layer, the plugin eliminates the risk of accidental or intentional data modification, even if connection credentials are compromised. The credential validation happens separately, but the **SELECT-only gate** serves as the primary security boundary.

## Practical Examples: Allowed vs. Blocked Queries

### ✅ Valid Single SELECT Statement

When submitting a pure read query, the plugin connects and returns results:

```json
{
  "tool_name": "sql_query",
  "tool_parameters": {
    "db_type": "mysql",
    "db_host": "127.0.0.1",
    "db_port": "3306",
    "db_username": "user",
    "db_password": "pwd",
    "db_name": "mydb",
    "query_sql": "SELECT id, name FROM users LIMIT 10",
    "output_format": "markdown"
  }
}

```

### ❌ Blocked Non-SELECT Statements

Attempting data modification triggers immediate rejection:

```json
{
  "tool_name": "sql_query",
  "tool_parameters": {
    "db_type": "mysql",
    "db_host": "127.0.0.1",
    "db_username": "user",
    "db_password": "pwd",
    "db_name": "mydb",
    "query_sql": "DELETE FROM users WHERE id = 1"
  }
}

```

**Result:**

```

ValueError: Query SQL can only be a single SELECT statement

```

### ❌ Blocked Multiple Statements

The parser also rejects queries containing semicolon-separated statements:

```json
{
  "tool_name": "sql_query",
  "tool_parameters": {
    "query_sql": "SELECT * FROM users; SELECT * FROM orders"
  }
}

```

**Result:**

```

ValueError: Only a single query SQL can be filled

```

## Key Source Files and Implementation Locations

The SELECT-only enforcement is implemented consistently across both plugin variants:

- **[`db_query/tools/sql_query.py`](https://github.com/junjiem/dify-plugin-tools-dbquery/blob/main/db_query/tools/sql_query.py)** (lines 44-45): Contains the `_invoke` method with validation logic for the standard plugin.
- **[`db_query_pre_auth/tools/sql_query.py`](https://github.com/junjiem/dify-plugin-tools-dbquery/blob/main/db_query_pre_auth/tools/sql_query.py)** (lines 44-45): Identical validation for the pre-auth variant, which sources credentials from runtime configuration rather than user input.
- **[`tools/db_util.py`](https://github.com/junjiem/dify-plugin-tools-dbquery/blob/main/tools/db_util.py)**: Utility module that handles the actual database execution only after validation passes.

## Summary

- The plugin uses **sqlparse** to build an AST from user-supplied SQL.
- It requires exactly **one statement** per invocation, rejecting multiple statements with a `ValueError`.
- It verifies `statement.get_type() == 'SELECT'` and raises `ValueError` for any other command type.
- Validation occurs **before** database connection, ensuring destructive commands never execute.
- Both the standard and pre-auth variants implement identical security logic at lines 44-45 of their respective [`sql_query.py`](https://github.com/junjiem/dify-plugin-tools-dbquery/blob/main/sql_query.py) files.

## Frequently Asked Questions

### What happens if I try to run an INSERT or UPDATE statement?

The plugin raises a `ValueError` with the message "Query SQL can only be a single SELECT statement" and terminates execution before connecting to the database. This prevents any data modification regardless of user permissions or database privileges.

### Does the pre-auth version have the same security checks?

Yes. The pre-auth variant in [`db_query_pre_auth/tools/sql_query.py`](https://github.com/junjiem/dify-plugin-tools-dbquery/blob/main/db_query_pre_auth/tools/sql_query.py) contains identical validation logic at lines 44-45, ensuring consistent SELECT-only enforcement across both plugin modes.

### Can I bypass the SELECT-only restriction using SQL comments or subqueries?

No. The `sqlparse` library analyzes the actual statement type, not just the raw text. While subqueries within a SELECT are permitted, the outermost statement must still be a SELECT. Comments do not alter the parsed statement type returned by `get_type()`.

### Which parsing library does the plugin use to analyze SQL statements?

The plugin uses **sqlparse**, a Python library for parsing and splitting SQL statements. It calls `sqlparse.parse()` to generate an AST and then inspects the statement type to enforce the security policy.