Skip to main content
Glama
Advaith789

SQLGuard MCP

by Advaith789

SQLGuard MCP

CI Python 3.10+ License: Apache 2.0 MCP

面向Agent访问SQL仓库的安全与治理包裹层。

目前所有仓库MCP服务器都是从模型接收SQL字符串并直接执行。连接从来不是难点。难点在于围绕它的所有事情:证明查询只读、在付费前预先知道成本、将查询绑定到调用者权限而非服务账户权限、返回Agent能够理性处理的结果、以及留下可供人类事后审查的记录。

SQLGuard位于模型与仓库之间,强制执行以上全部五个要点。

  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)

受治理的会话:一条普通查询、四次拒绝以及审计追踪

来自python scripts/demo.py的真实输出——该记录中没有任何内容是伪造的。

促成该设计的结果

语料库包含76条已标记的查询:46条攻击和30条合法的分析查询。"绕过"意味着攻击被允许。"误报"意味着合法操作被阻止。

防护措施

绕过次数

误报次数

F1

p50延迟

startswith("SELECT"/"WITH")

17/46 (37%)

0/30

0.773

<0.01 ms

关键字黑名单(正则)

12/46 (26%)

5/30 (17%)

0.800

<0.01 ms

前缀 + 拒绝分号

13/46 (28%)

0/30

0.835

<0.01 ms

AST解析,仅检查根节点

12/46 (26%)

0/30

0.850

0.04 ms

SQLGuard(根节点+全树遍历)

0/46 (0%)

0/30 (0%)

1.000

0.11 ms

使用python evals/run_eval.py可重现。

第四行很有趣。正确解析SQL并检查顶层节点类型——看似复杂的方法——仍然漏掉了语料库的四分之一。以下是三条导致此问题的语句:

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

这三条语句都以Select作为根节点解析。第一条删除了表。只读强制的实现必须遍历整棵树,而不仅仅是检查其顶层节点。

Related MCP server: MCPBridge

强制执行的内容

1. 只读,在AST层面。 两个独立的层,一条语句必须通过两者:根节点白名单,以及针对被禁止集合的全树遍历,该集合覆盖DML、DDL、会话修改、事务控制、数据外泄(COPY TO、EXPORT DATA)、目录修改、SELECT ... INTO、锁定子句以及副作用函数。解析器无法建模的任何内容都会落入泛型命令节点,并因此被拒绝——未知即拒绝,这正是阻止EXECUTE IMMEDIATE、CALL以及供应商扩展的方式。

2. 成本上限,涵盖三个范围和两个维度。 在BigQuery上,预估是真实的预运行:精确的字节数,免费,且在产生任何费用之前进行。超过上限的查询会被拒绝,并携带一个结构化载荷,其中包含预估成本、上限、超出量以及从查询自身AST衍生的补救措施。会话上限跨调用累积,因为Agent的失败模式是重复,而非单次大小。

第二个维度是输出基数,其存在是因为有一条查询通过了所有字节检查:一个自连接ON 1=1扫描了15 MB,却产生了144亿行。扫描的字节数约束的是I/O,而非工作量。

3. 身份作用域策略。 表被白名单化,受限列在被引用时会遭拒绝,而行过滤器会被注入到AST中——每个受治理的表都被重写为带过滤的子查询,因此谓词能在连接、并集和嵌套中存活。字符串拼接WHERE子句会被第一个出现的OR 1=1击败。

身份是配置,绝非工具参数。没有工具接受principal参数,并且有一个测试断言永远不会有这样的工具。一个能指定自己主体的Agent就没有主体可言。

4. 结果治理。 结果有上限,截断会明确声明而非静默处理,并且继续获取使用服务端游标句柄。游标是通往模型无法写入的存储的不透明ID——它只能说"继续那个",而无法影响"那个"是什么。每一页都会被重新估算并重新计费,因为分页在大多数仓库上会重新扫描。

5. 审计追踪,包括拒绝记录。 仅追加的JSONL格式,每条记录都执行fsync。每次调用都带有一个intent字符串——模型自己说明执行查询的原因,必须在调用时提供。仓库日志显示一个服务账户在03:14扫描了支付表的4 TB数据。而这里显示的是Agent因为正在核对退款差异而扫描了它。只有后者是可审查的。

工具接口

五个工具,而非四十个。每个表暴露一个工具的服务器会降低工具选择效率,并在模型读取模式之前就消耗掉上下文窗口。

工具

用途

describe_schema(table?)

可读的表,然后是一个表的列。渐进式披露。

plan_query(sql)

验证并定价不运行。免费。

run_query(sql, intent, max_rows?)

完整流水线。唯一花钱的工具。

fetch_page(cursor)

继续获取被截断的结果。

session_status()

剩余预算,以便Agent衡量其工作规模。

plan_query是改变Agent行为最多的工具。当有一个免费途径来询问"这个查询会被允许吗,以及它会花费多少"时,模型会使用它——于是昂贵的错误变成了廉价的拒绝,模型可以针对这些拒绝进行迭代。没有它,发现查询过于昂贵的唯一方式就是被收费。

快速开始

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

或者从克隆版本开始,运行测试和评估:

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

使用MCP客户端注册:

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

策略

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

当一个主体拥有多个角色时的组合规则:授权联合(表、行可见性、预算),拒绝联合(被任何角色拒绝的列保持拒绝)。拒绝优先。部署默认值仅填充未设置的字段——它们永远不会加宽角色集合设定的上限,这在测试中是一个真实发现的bug,现在已有回归测试。

这不做什么

明确说明,因为这些限制决定了可以在哪些地方安全使用。

  • 语料库并非独立。 我编写了攻击和防护。它展示了击败更简单方法的绕过类别;但并非声称在自适应攻击者面前具有完备性。贡献的攻击案例是最可能有用的贡献。

  • 防护的安全性受限于sqlglot的解析器。 如果某个方言结构被sqlglot错误地解析为良性的节点,就不会被捕获。解析器无法解析的结构会被拒绝,因此失败模式偏向于拒绝,但"偏向安全"并非"安全"。

  • BigQuery驱动程序是针对文档化API编写的,尚未在真实项目上运行。 DuckDB路径已通过测试全面验证。

  • 未限定的列引用会以拒绝方式失败。 在没有模式感知的名称解析的情况下,如果作用域内任何表对该列进行了限制,则多表查询中裸写的ssn会被拒绝。过度拒绝可以通过限定列名来恢复;而拒绝不足则会导致泄露。

  • DuckDB成本估算是上限值,而非预运行——每次都是对所有被引用表的全扫描,不计算下推优化。只有BigQuery能给出确切的执行前数字。

  • 列级策略不会掩码,而是拒绝。 悄悄返回与模型请求不同的列,会产生无人能发现的错误分析结果。

布局

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

欢迎贡献——请参阅CONTRIBUTING.md。最有用的贡献是能够通过防护的攻击。

Apache-2.0。

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    A
    maintenance
    Secure SQL proxy for AI agents. Translates natural language to safe SQL via Claude, validates at the AST level (SELECT-only, no DDL/DML), enforces per-agent row-level security, and audit-logs every query.
    1
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    Connects AI assistants to PostgreSQL databases with production-grade safety features including query validation, guarded writes, rate limiting, and audit logging.
    3
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    SQL guardrails for AI agents, sitting between the agent and Postgres to enforce policies, block destructive queries, and audit all access.
    5
    MIT
  • A
    license
    A
    quality
    A
    maintenance
    Enables AI agents to query Trino and Apache Pinot lakehouses under enforced governance, where every SQL statement is AST-validated, table-allowlisted, priced from the engine's own plan before it runs, and blocked or admitted against scan-byte and intermediate-row budgets. It also grounds agents with schema discovery tools, returns verified results with warnings instead of misleading answers, and records every tool call on an audit trail.
    3
    1
    Apache 2.0