Files
SuperBizAgent-java/mvp/engineering/harness/Harness MySQL 沙箱学习笔记-从 SQL 校验到脱敏投影.md
zhuyongxin 074d1aa5a9 docs(harness): add MySQL sandbox learning note and mark tool domain complete
- 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
2026-08-06 17:46:53 +08:00

159 lines
8.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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<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,逐节点拒绝)
```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` |