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

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): 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): 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 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, the dbx_execute_query tool calls:

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 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) 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:

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:

// Read-only mode (safest default)
process.env.DBX_MCP_ALLOW_WRITES = "0";
const decision = evaluateSqlSafety(
  "SELECT * FROM users", 
  sqlSafetyFromEnv()
);
// → { allowed: true }
// 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.' }
// 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) 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) 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) 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) 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.

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 →