SQLGuard MCP
SQLGuard 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延迟 |
| 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作为根节点解析。第一条删除了表。只读强制的实现必须遍历整棵树,而不仅仅是检查其顶层节点。
强制执行的内容
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因为正在核对退款差异而扫描了它。只有后者是可审查的。
工具接口
五个工具,而非四十个。每个表暴露一个工具的服务器会降低工具选择效率,并在模型读取模式之前就消耗掉上下文窗口。
工具 | 用途 |
| 可读的表,然后是一个表的列。渐进式披露。 |
| 验证并定价不运行。免费。 |
| 完整流水线。唯一花钱的工具。 |
| 继续获取被截断的结果。 |
| 剩余预算,以便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。
This server cannot be installed
Maintenance
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
See, price, and control every tool call your AI agents make: policy checks, cost, and audit tools.
The WAF for agents. Pattern-based + heuristic firewall scans prompts, RAG documents, tool argume...
Query PostgreSQL databases in plain English — LLM-generated, safety-validated SQL.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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