# How DBX Handles SQL Safety Checks in MCP Sessions: A Two-Layer Defense

> DBX secures MCP sessions with a two-layer defense combining Node-side evaluation and Rust-side risk classification for safe, read-only SQL execution by default.

- Repository: [skyler/dbx](https://github.com/t8y2/dbx)
- Tags: how-to-guide
- Published: 2026-07-10

---

**DBX protects Model-Context-Protocol (MCP) sessions through a two-layer safety system that combines Node-side statement evaluation with Rust-side risk classification, ensuring read-only execution by default and blocking dangerous operations unless explicitly enabled via environment variables.**

The open-source `t8y2/dbx` repository implements a defense-in-depth strategy for SQL safety checks in MCP sessions. Every query passes through both a fast TypeScript validation layer and a comprehensive Rust AST analyzer before reaching your database.

## The Two-Layer Safety Architecture

DBX enforces **SQL safety checks in MCP sessions** through complementary validation layers:

- **Node-side safety** ([`packages/node-core/src/sql-safety.ts`](https://github.com/t8y2/dbx/blob/main/packages/node-core/src/sql-safety.ts)): Performs lightweight, comment-aware parsing to block dangerous keywords and enforce `WHERE` clauses before forwarding queries to backends.
- **Rust-side risk classification** ([`crates/dbx-core/src/sql_risk.rs`](https://github.com/t8y2/dbx/blob/main/crates/dbx-core/src/sql_risk.rs)): Uses the `sqlparser` crate to classify statements into risk tiers (ReadOnly, Write, DDL, Transaction) for internal agent tools.

Both layers consult environment variables set for the current MCP session to determine permissible operations.

## Node-Side Safety: Pre-Execution Validation

The TypeScript implementation in [`packages/node-core/src/sql-safety.ts`](https://github.com/t8y2/dbx/blob/main/packages/node-core/src/sql-safety.ts) serves as the primary gatekeeper for MCP tool calls. The `evaluateSqlSafety()` function coordinates the validation flow.

### How evaluateSqlSafety Works

The validation process follows four sequential steps:

1. **Fetch session options**: `sqlSafetyFromEnv()` (lines 75-80) reads environment variables and returns a `SqlSafetyOptions` object.
2. **Split statements**: `splitSqlStatements()` tokenizes input while respecting quotes and comments (lines 83-94).
3. **Per-statement evaluation**: `evaluateSingleSqlStatementSafety()` performs:
   - Comment/string stripping via `stripSqlCommentsAndStrings`
   - Dangerous keyword detection against the `DANGEROUS_KEYWORDS` set (lines 50-53)
   - Write operation blocking when `allowWrites` is false (lines 55-59)
   - Mandatory `WHERE` clause enforcement for `UPDATE`/`DELETE` unless `allowDangerous` is true (lines 63-68)
4. **Aggregate decision**: If any statement fails, the entire query is rejected with `{ allowed: false, reason: ... }`; otherwise `{ allowed: true }` is returned (lines 23-42).

### Environment Variable Controls

The Node layer respects three session-specific environment variables:

| Variable | Default | Effect |
|----------|---------|--------|
| `DBX_MCP_ALLOW_WRITES` | `true` | When set to `0` or `false`, blocks `INSERT`, `UPDATE`, `DELETE`, and other write operations. |
| `DBX_MCP_ALLOW_DANGEROUS_SQL` | `false` | When `1` or `true`, permits DDL statements like `DROP`, `ALTER`, and `TRUNCATE`. |
| `DBX_MCP_ALLOW_MULTIPLE_STATEMENTS` | `false` | Controlled via the `allowMultipleStatements` parameter in `evaluateSqlSafety()` options. |

### Integration in MCP Server Tools

The MCP server invokes safety checks before executing any query. In [`packages/mcp-server/src/index.ts`](https://github.com/t8y2/dbx/blob/main/packages/mcp-server/src/index.ts), the `dbx_execute_query` tool calls:

```typescript
const safety = evaluateSqlSafety(sql, { 
  ...sqlSafetyFromEnv(), 
  allowMultipleStatements: true 
});
if (!safety.allowed) return toolError("SQL_BLOCKED", safety.reason);

```

This pattern appears at lines 46-48 for `dbx_execute_query` and lines 77-79 for `dbx_execute_and_show`. When blocked, the server returns a standardized `SQL_BLOCKED` error that client tools can surface to users.

## Rust-Side Safety: AST-Based Risk Classification

Internal DBX agents use the Rust module [`crates/dbx-core/src/sql_risk.rs`](https://github.com/t8y2/dbx/blob/main/crates/dbx-core/src/sql_risk.rs) for fine-grained permission control.

### classify_sql_risk Implementation

The `classify_sql_risk(sql, dialect)` function (lines 59-112) parses SQL using the `sqlparser` crate and walks the AST to assign a `SqlRisk` tier:

- **ReadOnly**: `SELECT`, `SHOW`, `EXPLAIN`
- **Write**: `INSERT`, `UPDATE`, `DELETE`
- **Ddl**: `CREATE`, `DROP`, `ALTER`, `TRUNCATE`
- **Transaction**: `BEGIN`, `COMMIT`, `ROLLBACK`

If parsing fails (e.g., for non-standard dialects), the system falls back to `query_execution_sql::is_write_sql` (lines 36-44), a simple keyword-based detector.

### AgentSqlPermissions and Enforcement

The `AgentSqlPermissions` struct (defined in [`crates/dbx-core/src/agent_tools.rs`](https://github.com/t8y2/dbx/blob/main/crates/dbx-core/src/agent_tools.rs)) defines which risk levels an agent may execute. By default, writes and dangerous statements are blocked.

Agent code verifies permissions by calling `sql_risk_allowed(risk, permissions)` (lines 35-38) before exposing any tool that could modify data:

```rust
use dbx_core::sql_risk::{classify_sql_risk, sql_risk_allowed};
use dbx_core::agent_tools::AgentSqlPermissions;

let sql = "UPDATE customers SET status = 'inactive'";
let risk = classify_sql_risk(sql, "postgres").unwrap(); 
let perms = AgentSqlPermissions::default(); // writes disallowed

assert!(!sql_risk_allowed(risk, perms)); // returns false, blocking execution

```

## Configuration Examples

Here is how to configure **SQL safety checks in MCP sessions** programmatically:

```typescript
// Read-only mode (safest default)
process.env.DBX_MCP_ALLOW_WRITES = "0";
const decision = evaluateSqlSafety(
  "SELECT * FROM users", 
  sqlSafetyFromEnv()
);
// → { allowed: true }

```

```typescript
// Attempting dangerous DDL without permission
process.env.DBX_MCP_ALLOW_WRITES = "1";
process.env.DBX_MCP_ALLOW_DANGEROUS_SQL = "0";
const decision = evaluateSqlSafety(
  "DROP TABLE users", 
  sqlSafetyFromEnv()
);
// → { allowed: false, reason: 'Dangerous SQL keyword "DROP" is blocked.' }

```

```typescript
// Enabling full write access
process.env.DBX_MCP_ALLOW_WRITES = "1";
process.env.DBX_MCP_ALLOW_DANGEROUS_SQL = "1";
const decision = evaluateSqlSafety(
  "DROP TABLE users", 
  sqlSafetyFromEnv()
);
// → { allowed: true }

```

## Summary

- **Node-side validation** ([`packages/node-core/src/sql-safety.ts`](https://github.com/t8y2/dbx/blob/main/packages/node-core/src/sql-safety.ts)) provides fast, environment-driven filtering of SQL statements before they reach the database, blocking dangerous keywords and enforcing `WHERE` clauses.
- **Rust-side classification** ([`crates/dbx-core/src/sql_risk.rs`](https://github.com/t8y2/dbx/blob/main/crates/dbx-core/src/sql_risk.rs)) offers precise AST-based risk tiering for internal agents, with fallback to keyword detection for parse failures.
- **Environment variables** (`DBX_MCP_ALLOW_WRITES`, `DBX_MCP_ALLOW_DANGEROUS_SQL`) control session permissions, defaulting to read-only safety.
- **Standardized errors** ensure that blocked queries return clear `SQL_BLOCKED` messages to client tools.

## Frequently Asked Questions

### What happens if a query fails the SQL safety check?

The MCP server returns a standardized error object with the code `SQL_BLOCKED` and a descriptive reason string. For example, attempting to run `DROP TABLE` without `DBX_MCP_ALLOW_DANGEROUS_SQL` set to `true` yields: `{ allowed: false, reason: 'Dangerous SQL keyword "DROP" is blocked.' }`. Client tools receive this error and can display it to the user without executing the query.

### How does DBX distinguish between read-only and write operations?

The system maintains keyword lists and AST analysis. In the TypeScript layer, `evaluateSingleSqlStatementSafety` checks the first token against allowed read-only keywords (`SELECT`, `SHOW`, etc.). In the Rust layer, `classify_sql_risk` uses the `sqlparser` crate to identify `Statement` variants that modify data (insertions, updates, deletions) versus those that only read.

### Can I allow multiple statements in a single MCP session?

Yes, by setting `allowMultipleStatements: true` in the options passed to `evaluateSqlSafety()`. However, this is typically controlled by the MCP server implementation rather than end users. When enabled, the safety check evaluates each statement individually and rejects the entire batch if any single statement violates the current permission settings.

### What is the difference between the Node-side and Rust-side checks?

The **Node-side** check ([`sql-safety.ts`](https://github.com/t8y2/dbx/blob/main/sql-safety.ts)) is a lightweight, regex-based filter designed for speed and MCP tool gatekeeping—it runs before network calls to the database. The **Rust-side** check ([`sql_risk.rs`](https://github.com/t8y2/dbx/blob/main/sql_risk.rs)) is a comprehensive AST parser used by internal DBX agents to classify query risk levels precisely. The Node layer prevents accidental execution, while the Rust layer enables fine-grained permission models for AI agents operating inside the DBX ecosystem.