- Add MySQL sandbox note: three defense layers (semantic/connection/output), fail-closed validation, allowlist, parameterization, cancellation, redaction - Mark tool domain complete in roadmap; next: guard
8.3 KiB
Harness MySQL 沙箱学习笔记:从 SQL 校验到脱敏投影
更新日期:2026-08-04 主题:query_mysql 工具完整链路——三层防线(语义/连接/输出) 配套:tool 域代码学习笔记(注册/调用/执行全链路)
1. 定位:可查询、不可破坏、不可越界、不可拖库
query_mysql 让模型查询授权数据库,但封死三种攻击面:
破坏:写/删/改(非 SELECT)→ 语义层拒绝
越界:未授权表/列 → 白名单拒绝
拖库:全表通配(*)/无界读取 → 禁通配符 + 三重有界截断
为什么 MySQL 要安全层而 RAG 不要:Milvus 天然只读检索无破坏面;MySQL 直接连数据库,SELECT 之外全是风险面——安全设计随攻击面走。
2. 架构总览(三层防线 + 接线员)
flowchart LR
E["MysqlToolAdapter<br/>接线员"] -->|"parse 请求"| V["第 1 层 语义层<br/>MysqlSqlValidator<br/>AST fail-closed"]
V -->|"MysqlQueryPlan"| X["第 2 层 连接层<br/>JdbcMysqlReadOnlyExecutor<br/>JDBC 只读+超时+取消"]
X -->|"MysqlRawResult<br/>(Harness-only)"| P["第 3 层 输出层<br/>MysqlResultProjector<br/>脱敏+有界"]
P -->|"MysqlToolResult<br/>(冻结契约)"| B["ToolBoundary<br/>统一门禁"]
接线员:Adapter 把 validator/executor/projector 组装进 ToolBoundary;任何安全/参数异常统一映射 INVALID_REQUEST(不泄露内部细节)。
3. 定义层(6 个小文件)
| 类 | 作用 |
|---|---|
MysqlReadOnlyExecutor |
函数式接口(执行端口):plan + RunContext → raw 结果 |
MysqlQueryPlan |
执行计划:request + dataSource + normalizedSql + params(params 深拷贝) |
MysqlRawResult |
Harness-only raw 结果:columns + rows + truncated(不直接给 Agent) |
MysqlSecurityException |
安全异常:Adapter 映射稳定错误码 |
MysqlToolLimits |
限额:100 行 / 2000 字单元 / 64KB 总字节 / 5 秒超时 |
MysqlDataSourceDefinition |
逻辑数据源 + schema → table → 列三级白名单(访问边界) |
白名单深拷贝(TreeSet 保证确定性)+ defaultSchema 必须出现在白名单里——配置即边界。
4. 第 1 层:Validator——语义层(fail-closed)
4.1 禁止清单(JSqlParser 解析 AST,逐节点拒绝)
非 SELECT / 多条语句 / WITH / 子查询(SubSelect) / 通配符投影(* 和 t.*)
窗口函数(Analytic) / CASE / EXISTS / 分层查询(OracleHierarchical)
锁读(FOR UPDATE / SKIP LOCKED) / OFFSET/FETCH/TOP/SKIP/FIRST/OPTIMIZE FOR
字面量:StringValue/LongValue/DoubleValue/HexValue/DateValue/TimeValue/TimestampValue
—— 全部必须参数化(防注入的最强形态)
4.2 允许清单
白名单内表列的 INNER/LEFT JOIN(禁 CROSS/RIGHT/FULL/OUTER)
聚合函数:COUNT/SUM/AVG/MIN/MAX(COUNT(*) 只允许 COUNT)
占位符参数 ?(PreparedStatement 绑定)
4.3 附加校验
恰好一条语句 + 必须是 Select + 无 WITH + 普通 PlainSelect(禁集合操作/值语句)
FROM 必须是白名单内实体表(禁子查询来源);重复别名拒绝(不区分大小写)
列校验:限定表 → 查该表列白名单;未限定 → 已注册表恰好一个命中(防歧义/未授权)
占位符数量 == params 数量(防参数错位/少传)
4.4 fail-closed 原则
任何解析/校验异常 → 统一转 MysqlSecurityException(默认拒绝,不是默认放行)
业务规则违规原样穿出;解析/未知异常包装统一信息(不泄露内部细节)
→ 安全策略是「拒绝清单外的全允许」的反面:「允许清单外的全拒绝」
5. 第 2 层:Executor——连接层(双保险)
connection.setReadOnly(true) ← JDBC 连接层强制只读(语义层之外的物理防线)
PreparedStatement 参数化 ← 占位符绑定(防注入第二道)
setQueryTimeout(5s) ← 慢查询截断
setMaxRows(maxRows+1) ← 多取一行用于检测截断
context.cancellation().onCancel → statement.cancel() ← Run 取消联动
checkRun 每行检查 ← 取消/超时即刻中止(与 core 终态联动)
jsonSafe:byte[] → Base64;字符串截断 maxCellChars
estimatedBytes:字节预算超限移除末行并标记截断
关键:查询不是独立资源——受 Run 生命周期管(取消 → 立即 cancel 语句),与 RAG 检索同理(checkRun 与 Harness core 终态联动)。
6. 第 3 层:Projector——输出层
6.1 脱敏(输出时,不是查询时)
列名含 password/passwd/token/secret/api_key/apikey/credential → [REDACTED]
→ raw 保留真实值,只对 Agent 可见层脱敏(查询照常执行,输出才遮)
6.2 有界(三重截断 + 兜底)
行数(maxRows) + 单元格字符(maxCellChars) + 总字节(maxResultBytes)
列名必须非空且唯一(防歧义投影)
fitBudget 兜底:逐行裁掉尾部 → 裁空诚实降级 NO_EVIDENCE → 仍超限 fail closed
6.3 客观证据语义
rows 空 → NO_EVIDENCE;非空 → EVIDENCE_FOUND
→ 与 RAG 的 evidence_status 同一套契约(证据状态由「有没有内容」客观决定)
7. 与 RAG 对照(同类架构,不同复杂度)
| query_mysql | lookup_knowledge | |
|---|---|---|
| 安全层 | 有(Validator + 只读连接 + 脱敏) | 无(Milvus 天然只读检索) |
| 后端复杂度 | 简单(Validator→Executor→Projector) | 复杂(三段 + 降级 + 双 trace) |
| raw → 契约 | MysqlRawResult → MysqlToolResult | LookupResult → RagToolResult |
| 数量限制 | maxRows=100 / 64KB | returnN=5 / 8 条 / 16KB |
| 证据语义 | rows 空不空(NO_EVIDENCE/EVIDENCE_FOUND) | evidenceBlocks + relevance_level |
| 与 Harness 衔接 | 同为 Boundary/Projector/Adapter 模式 | 同为 Boundary/Projector/Adapter 模式 |
8. 易错点
| 易错 | 正确 |
|---|---|
| Validator 只查 SELECT | 还有白名单表列、禁字面量、禁通配符、占位符计数 |
| 语义层够了 | 连接层 setReadOnly + 参数化是物理防线(纵深防御) |
| 查询独立于 Run | 查询受 Run 取消/超时联动(onCancel → statement.cancel) |
| 脱敏在查询层 | 脱敏在投影层(raw 保留真实值,只对 Agent 脱敏) |
| 有界只限行数 | 行 + 单元格 + 字节三重截断 + fitBudget 兜底 |
| 安全异常抛原样 | 统一映射 INVALID_REQUEST(不泄露内部细节) |
| 字面量可以清洗放行 | 字面量全拒必须参数化(清洗是弱防线,参数化是强防线) |
9. 面试话术(30 秒)
"query_mysql 是三层防线的只读沙箱:语义层(JSqlParser 解析 AST,fail-closed 拒绝一切不安全形态——非 SELECT、多语句、子查询、通配符、字面量、未授权表列、锁读全部拒绝,只允许白名单表列的 INNER/LEFT JOIN 和聚合 + 参数化占位符,且占位符数量必须与 params 匹配);连接层(JDBC setReadOnly + PreparedStatement 参数化 + 超时 + maxRows + 取消联动——Run 取消立即 cancel 语句);输出层(敏感列脱敏 [REDACTED] + 行/单元格/字节三重截断 + rows 空不空定证据状态)。安全异常统一映射稳定错误码,不泄露内部细节。"
10. 代码位置索引
| 类 | 文件 |
|---|---|
MysqlToolAdapter |
src/main/java/com/superbiz/agent/harness/tool/adapter/MysqlToolAdapter.java |
MysqlSqlValidator |
src/main/java/com/superbiz/agent/harness/tool/mysql/MysqlSqlValidator.java |
JdbcMysqlReadOnlyExecutor |
src/main/java/com/superbiz/agent/harness/tool/mysql/JdbcMysqlReadOnlyExecutor.java |
MysqlResultProjector |
src/main/java/com/superbiz/agent/harness/tool/mysql/MysqlResultProjector.java |
MysqlDataSourceDefinition |
src/main/java/com/superbiz/agent/harness/tool/mysql/MysqlDataSourceDefinition.java |
MysqlReadOnlyExecutor |
src/main/java/com/superbiz/agent/harness/tool/mysql/MysqlReadOnlyExecutor.java |
MysqlQueryPlan / MysqlRawResult |
src/main/java/com/superbiz/agent/harness/tool/mysql/MysqlQueryPlan.java 等 |
| 契约(MysqlToolRequest/Result) | src/main/java/com/superbiz/agent/harness/tool/contract/MysqlTool*.java |