# Harness MySQL 沙箱学习笔记:从 SQL 校验到脱敏投影 **更新日期**:2026-08-04 **主题**:query_mysql 工具完整链路——三层防线(语义/连接/输出) **配套**:[tool 域代码学习笔记](Harness%20tool%20域代码学习笔记-工具的注册调用与执行链路.md)(注册/调用/执行全链路) ## 1. 定位:可查询、不可破坏、不可越界、不可拖库 query_mysql 让模型查询授权数据库,但封死三种攻击面: ```text 破坏:写/删/改(非 SELECT)→ 语义层拒绝 越界:未授权表/列 → 白名单拒绝 拖库:全表通配(*)/无界读取 → 禁通配符 + 三重有界截断 ``` **为什么 MySQL 要安全层而 RAG 不要**:Milvus 天然只读检索无破坏面;MySQL 直接连数据库,SELECT 之外全是风险面——**安全设计随攻击面走**。 ## 2. 架构总览(三层防线 + 接线员) ```mermaid flowchart LR E["MysqlToolAdapter
接线员"] -->|"parse 请求"| V["第 1 层 语义层
MysqlSqlValidator
AST fail-closed"] V -->|"MysqlQueryPlan"| X["第 2 层 连接层
JdbcMysqlReadOnlyExecutor
JDBC 只读+超时+取消"] X -->|"MysqlRawResult
(Harness-only)"| P["第 3 层 输出层
MysqlResultProjector
脱敏+有界"] P -->|"MysqlToolResult
(冻结契约)"| B["ToolBoundary
统一门禁"] ``` **接线员**: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,逐节点拒绝) ```text 非 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 允许清单 ```text 白名单内表列的 INNER/LEFT JOIN(禁 CROSS/RIGHT/FULL/OUTER) 聚合函数:COUNT/SUM/AVG/MIN/MAX(COUNT(*) 只允许 COUNT) 占位符参数 ?(PreparedStatement 绑定) ``` ### 4.3 附加校验 ```text 恰好一条语句 + 必须是 Select + 无 WITH + 普通 PlainSelect(禁集合操作/值语句) FROM 必须是白名单内实体表(禁子查询来源);重复别名拒绝(不区分大小写) 列校验:限定表 → 查该表列白名单;未限定 → 已注册表恰好一个命中(防歧义/未授权) 占位符数量 == params 数量(防参数错位/少传) ``` ### 4.4 fail-closed 原则 ```text 任何解析/校验异常 → 统一转 MysqlSecurityException(默认拒绝,不是默认放行) 业务规则违规原样穿出;解析/未知异常包装统一信息(不泄露内部细节) → 安全策略是「拒绝清单外的全允许」的反面:「允许清单外的全拒绝」 ``` ## 5. 第 2 层:Executor——连接层(双保险) ```text 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 脱敏(输出时,不是查询时) ```text 列名含 password/passwd/token/secret/api_key/apikey/credential → [REDACTED] → raw 保留真实值,只对 Agent 可见层脱敏(查询照常执行,输出才遮) ``` ### 6.2 有界(三重截断 + 兜底) ```text 行数(maxRows) + 单元格字符(maxCellChars) + 总字节(maxResultBytes) 列名必须非空且唯一(防歧义投影) fitBudget 兜底:逐行裁掉尾部 → 裁空诚实降级 NO_EVIDENCE → 仍超限 fail closed ``` ### 6.3 客观证据语义 ```text 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` |