本文件说明 mcp-dm8-server 的 SQL 访问控制模型、已知局限,以及部署侧必须落实的纵深防御。
MCP server 把数据库暴露给 LLM,LLM 可能生成任意 SQL。核心目标:execute_query 默认只读;写 / DDL / 会话级破坏只有在管理员显式配置 sqlAccess 级别后才被放行,且无法通过拼接等方式绕过级别限制。
自 3.0.0 起,execute_query 的语句权限由所选连接的 sqlAccess 级别决定(readonly/dml/ddl/full,默认 readonly)。级别通过配置显式开启——不配置任何 sqlAccess 时行为与历史版本完全一致(仅 SELECT/SHOW/DESCRIBE/EXPLAIN)。详见 README「SQL 访问级别」一节。
所有级别共同的硬规则:
- 分号多语句拦截(
/;\s*\S/)——即使full也不允许一次执行多条语句; - Schema 白名单(
validateSchemaAccess)对写操作同样生效; WITH(CTE)前缀在 readonly/dml/ddl 拦截(见「固有局限」)。
位置:src/utils/validation.ts
- 级别化前缀白名单:trim + toUpperCase 后,语句必须以当前级别允许的前缀开头(readonly:
SELECT/SHOW/DESCRIBE/EXPLAIN;dml 追加INSERT/UPDATE/DELETE/MERGE;ddl 追加CREATE/ALTER/DROP/TRUNCATE;full 不做前缀检查)。 - 分号拦截:
/;\s*\S/禁止分号后跟非空白字符,堵多语句注入。
2025 年 Datadog 安全实验室披露 Anthropic 官方 server-postgres 的只读绕过漏洞(分析原文)。两类攻击向量与本项目的对照:
官方当初用 BEGIN READ ONLY 事务隔离,被 COMMIT; <写操作> 提前结束事务后注入。
本项目不依赖事务隔离,在字符串层拦截:
| Payload | 结论 |
|---|---|
SELECT 1; DROP TABLE x |
分号后有 D,拦截 ✅(所有级别) |
COMMIT; ... |
COMMIT 不在白名单前缀,拦截 ✅(full 级别无前缀限制,但分号规则仍拦截多语句形态) |
/* */SELECT 1 |
trim 后首字符 / 不匹配前缀,拦截 ✅ |
SELECT 1; -- c\nDROP TABLE x |
分号后有 -,拦截 ✅ |
官方当初 SET 会话变量后连接归还池,污染下一个用户。
本项目:SET 不在 readonly/dml/ddl 白名单前缀,应用层拦截 ✅。但 full 级别会放行 SET——连接池 testOnBorrow 仅做 SELECT 1 探活,不重置会话状态,SET 污染的会话会被下一个借用者继承。这是 full 级别必须仅在受控环境使用的核心原因之一。
assertSqlAllowed 是纯字符串前缀检查,无法防御"以允许前缀开头但语义有副作用"的语句。若 DM8 未来出现 SELECT 开头却可建表/写数据的方言,字符串检查会放行。同理,WITH(CTE)前缀语句无法静态定位主语句动词,在 readonly/dml/ddl 一律拒绝(宁可误拒不可漏放),full 下因无前缀限制而放开。
曾尝试用 connection.getStatementInfo() 做权威只读判定,但 dmdb 1.0.52452 存在 statement handle 泄漏(累积触发 [160]),已回退到字符串检查,详见 validation.ts 注释与 commit f89b22c。
结论:字符串检查是第一道闸,不能是唯一一道。
- DB 账号权限仍应最小化:开启
dml的连接,其数据库账号只授予必要表的 DML 权限;开启ddl的连接不应使用SYSDBA。full级别等于把语句类型控制完全交给 DB 层——务必配合只读/最小权限账号,或仅在一次性/沙箱环境使用。 - autocommit 语义:dmdb 驱动默认 autocommit,写语句执行即提交、无法回滚。误操作恢复依赖备份,请为可写的连接环境配置好备份策略。
- 审计:所有写操作通过 MCP logging 以
warning级别上报(含语句、动词、影响行数);启动时 stderr 会列出开启写权限的连接及级别。
应用层防护之外,必须给 MCP 使用的数据库账号设置只读权限:
- 创建专用账号,仅
GRANT SELECT需要查询的表 / 视图; - 禁止
INSERT/UPDATE/DELETE及任何 DDL(CREATE/ALTER/DROP/TRUNCATE); - 禁止授予
DBA/RESOURCE等含写权限的角色; - 生产库优先使用只读备库或 DM8 的只读用户。
即使应用层字符串检查被绕过,DB 层权限也会兜底拒绝写操作。这是 Datadog 强调的核心结论:"经典的数据库权限控制对 MCP 同样适用,且是最后一道闸。"
| 特性 | 实现 |
|---|---|
| 标识符校验 | normalizeIdentifier:/^[A-Za-z_][A-Za-z0-9_]*$/,防 schema/table 参数注入 |
| 参数化查询 | 所有用户输入走绑定参数(:owner、:table),不拼 SQL |
| Schema 白名单 | validateSchemaAccess 限制可访问 schema 范围 |
| 凭据 URL 编码 | 连接串中密码 encodeURIComponent |
| 配置文件忽略 | .claude/dm8-mcp.json 已加入 .gitignore |
| 查询超时 | 见 P1b:连接级 socketTimeout,防止恶意慢查询 |
请开 GitHub Issue 或直接提 PR。