Skip to main content
Glama
Advaith789

SQLGuard MCP

by Advaith789

SQLGuard MCP

CI Python 3.10+ License: Apache 2.0 MCP

Un envoltorio de seguridad y gobernanza para el acceso de agentes a almacenes SQL.

Cada servidor MCP de almacén hoy en día toma una cadena SQL de un modelo y la ejecuta. La conexión nunca fue la parte difícil. La parte difícil es todo lo que la rodea: demostrar que la consulta solo lee, saber lo que costará antes de pagar por ello, vincularla a los permisos de quien la solicita en lugar de los de la cuenta de servicio, devolver un resultado sobre el que un agente pueda realmente razonar, y dejar un registro que un humano pueda revisar después.

SQLGuard se sitúa entre el modelo y el almacén y aplica las cinco.

  model ──▶ AST guard ──▶ policy ──▶ cost estimate ──▶ budget ──▶ warehouse
              │             │             │              │
              └── read-only └── identity  └── dry run    └── ceilings
                  proof         scoped        or bound       + session cap
                                                                  │
                                          governed results ◀──────┘
                                          (capped, summarized, cursored)
                                                    │
                                          append-only audit log
                                          (including refusals, with intent)

Una sesión gobernada: una consulta ordinaria, cuatro rechazos y el rastro de auditoría

Salida real de python scripts/demo.py — nada en esa transcripción está simulado.

El resultado que motivó el diseño

El corpus consta de 76 consultas etiquetadas: 46 ataques y 30 piezas de análisis legítimos. "Bypass" significa que se permitió un ataque. "Falsa alarma" significa que se bloqueó trabajo real.

Guardia

Elusiones

Falsas alarmas

F1

latencia p50

startswith("SELECT"/"WITH")

17/46 (37%)

0/30

0.773

<0.01 ms

lista negra de palabras clave (regex)

12/46 (26%)

5/30 (17%)

0.800

<0.01 ms

prefijo + rechazar punto y coma

13/46 (28%)

0/30

0.835

<0.01 ms

Análisis AST, solo nodo raíz

12/46 (26%)

0/30

0.850

0.04 ms

SQLGuard (raíz + recorrido completo)

0/46 (0%)

0/30 (0%)

1.000

0.11 ms

Reproducir con python evals/run_eval.py.

La cuarta fila es la interesante. Analizar el SQL correctamente y verificar el tipo de nodo de nivel superior — el enfoque de apariencia sofisticada — aún falla en una cuarta parte del corpus. Tres sentencias son la razón:

WITH d AS (DELETE FROM orders RETURNING *) SELECT * FROM d   -- Postgres
SELECT * INTO staging_copy FROM orders                       -- T-SQL / PG
SELECT * FROM orders FOR UPDATE                              -- row locks

Las tres se analizan con Select en la raíz. La primera elimina la tabla. La aplicación de solo lectura debe recorrer todo el árbol, no inspeccionar su parte superior.

Lo que aplica

1. Solo lectura, a nivel de AST. Dos capas independientes, y una sentencia debe sobrevivir a ambas: una lista blanca de nodos raíz, y un recorrido completo del árbol contra un conjunto denegado que cubre DML, DDL, mutación de sesión, control de transacciones, egreso de datos (COPY TO, EXPORT DATA), mutación de catálogo, SELECT ... INTO, cláusulas de bloqueo y funciones con efectos secundarios. Cualquier cosa que el analizador no pueda modelar cae en un nodo de comando genérico y se deniega por esa razón — desconocido significa denegado, que es lo que detiene EXECUTE IMMEDIATE, CALL y extensiones de proveedor.

2. Límites de costo, en tres ámbitos y dos dimensiones. En BigQuery, la estimación es una ejecución en seco real: bytes exactos, gratuita, antes de que se facture nada. Las consultas que superan el límite son rechazadas con una carga útil estructurada que nombra la estimación, el límite, el exceso y la corrección derivada del propio AST de la consulta. Un límite de sesión se acumula entre llamadas, porque el modo de fallo del agente es la repetición, no el tamaño.

La segunda dimensión es la cardinalidad de salida, y existe debido a una consulta que pasó todas las comprobaciones de bytes: un auto-join con ON 1=1 escanea 15 MB y emite 14.4 mil millones de filas. Los bytes escaneados limitan E/S, no trabajo.

3. Política con ámbito de identidad. Las tablas están en lista blanca, las columnas restringidas son rechazadas al ser referenciadas, y los filtros de fila se inyectan en el AST — cada tabla gobernada se reescribe como una subconsulta filtrada, por lo que el predicado sobrevive a joins, uniones y anidamientos. Concatenar una cláusula WHERE como cadena sería derrotada por el primer OR 1=1 que apareciera.

La identidad es configuración, nunca un parámetro de herramienta. Ninguna herramienta acepta un argumento principal, y hay una prueba que afirma que ninguna lo hará jamás. Un agente que puede nombrar su propio principal no tiene principal.

4. Gobernanza de resultados. Los resultados tienen un límite, la truncación se indica explícitamente en lugar de silenciosamente, y la continuación utiliza un manejador de cursor del lado del servidor. El cursor es un id opaco en un almacén al que el modelo no puede escribir — puede decir "más de eso", nunca influir en qué fue "eso". Cada página se reestima y se recarga, porque la paginación vuelve a escanear en la mayoría de los almacenes.

5. Rastro de auditoría, incluyendo rechazos. JSONL de solo añadido, fsynced por registro. Cada llamada lleva una cadena intent — la propia declaración del modelo de por qué ejecutó la consulta, requerida en el momento de la llamada. Un registro de almacén dice que una cuenta de servicio escaneó 4 TB de la tabla de pagos a las 03:14. Esto dice que un agente la escaneó porque estaba conciliando una discrepancia de reembolso. Solo uno es revisable.

La superficie de herramientas

Cinco herramientas, no cuarenta. Un servidor que expone una herramienta por tabla degrada la selección de herramientas y consume la ventana de contexto antes de que el modelo haya leído el esquema.

Herramienta

Propósito

describe_schema(table?)

Tablas legibles, luego las columnas de una tabla. Divulgación progresiva.

plan_query(sql)

Validar y cotizar sin ejecutar. Gratuito.

run_query(sql, intent, max_rows?)

El pipeline completo. La única herramienta que cuesta dinero.

fetch_page(cursor)

Continuar un resultado truncado.

session_status()

Presupuesto restante, para que el agente pueda dimensionar su trabajo.

plan_query es la herramienta que más cambia el comportamiento del agente. Dada una forma gratuita de preguntar "¿esto estaría permitido y cuánto costaría?", un modelo la usa — y sus costosos errores se convierten en rechazos baratos contra los que puede iterar. Sin ella, la única forma de descubrir que una consulta es demasiado cara es que se le facture.

Inicio rápido

pip install "sqlguard-mcp[duckdb] @ git+https://github.com/Advaith789/ast-level-sql-mcp"

O desde un clon, para ejecutar las pruebas y la evaluación:

python -m venv .venv && .venv/bin/pip install -e ".[dev]"
.venv/bin/python examples/seed_demo.py          # builds a 120k-row demo warehouse
.venv/bin/python -m pytest -q                   # 73 tests
.venv/bin/python evals/run_eval.py              # the table above
.venv/bin/python scripts/demo.py                # the walkthrough pictured above

Registrar con un cliente MCP:

{
  "mcpServers": {
    "sqlguard": {
      "command": "/path/to/.venv/bin/sqlguard-mcp",
      "args": ["--config", "/path/to/examples/policy.example.yaml"]
    }
  }
}

Política

dialect: bigquery
driver:
  name: bigquery
  project: my-project

principal: analyst@example.com     # never a tool parameter

roles:
  analyst:
    tables: ["analytics.*"]
    denied_columns:
      analytics.customers: [ssn, email]
    row_filters:
      analytics.orders: "region = 'US'"   # injected into the AST
    budget:
      per_query_bytes: 50GB          # one catastrophic scan
      per_session_bytes: 500GB       # one runaway conversation
      per_day_bytes: 2TB             # durable: survives restarts
      max_estimated_rows: 10000000   # output size, not just input
      max_rows: 200

Reglas de combinación cuando un principal tiene varios roles: unión de concesiones (tablas, visibilidad de filas, presupuestos), unión de denegaciones (una columna denegada por cualquier rol permanece denegada). Denegar gana. Los valores predeterminados de implementación solo llenan campos no establecidos — nunca amplían un límite que un conjunto de roles haya establecido, lo cual fue un error real encontrado en pruebas y ahora tiene una prueba de regresión.

Lo que esto no hace

Declarado claramente, porque los límites determinan dónde es seguro usarlo.

  • El corpus no es independiente. Yo escribí los ataques y la guardia. Demuestra la clase de bypass que derrota enfoques más simples; no es una afirmación de completitud contra un atacante adaptativo. Los casos de ataque contribuidos son la contribución más útil posible.

  • La seguridad de la guardia está limitada por el analizador de sqlglot. Una construcción de dialecto que sqlglot analice incorrectamente como un nodo benigno no sería detectada. Las construcciones que falla al analizar son denegadas, por lo que el modo de fallo está sesgado hacia el rechazo, pero "sesgado hacia seguro" no es "seguro".

  • El controlador de BigQuery está escrito contra la API documentada y no se ha ejecutado contra un proyecto real. La ruta de DuckDB está completamente ejercitada por las pruebas.

  • Las referencias a columnas no calificadas fallan de forma cerrada. Sin resolución de nombres consciente del esquema, un ssn desnudo en una consulta de múltiples tablas es rechazado si alguna tabla en el ámbito lo restringe. El exceso de rechazo es recuperable calificando la columna; la falta de rechazo filtraría.

  • Las estimaciones de costo de DuckDB son límites superiores, no ejecuciones en seco — escaneos completos de cada tabla referenciada, sin crédito por pushdown. Solo BigQuery proporciona números exactos previos a la ejecución.

  • La política a nivel de columna no enmascara, rechaza. Devolver silenciosamente columnas diferentes a las que el modelo solicitó produce análisis que son incorrectos de maneras que nadie puede ver.

Estructura

src/sqlguard/
  ast_guard.py    read-only enforcement (the core)
  policy.py       identity-scoped table/column/row policy
  cost.py         estimation, budgets, actionable refusals
  governance.py   result caps, summaries, cursors
  audit.py        append-only JSONL trail
  spend.py        durable per-principal spend ledger (SQLite)
  errors.py       structured refusals
  server.py       the five MCP tools
  drivers/        duckdb (offline) + bigquery (dry run)
evals/            labeled corpus, baselines, metrics runner
tests/            73 tests: adversarial corpus + end-to-end pipeline
scripts/          demo walkthrough + SVG renderer
.github/          CI: tests on 3.10-3.12, evaluation, package build

Las contribuciones son bienvenidas — consulte CONTRIBUTING.md. La más útil es un ataque que logre pasar.

Apache-2.0.

-
license - not tested
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Advaith789/ast-level-sql-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server