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
This commit is contained in:
zhuyongxin
2026-08-06 17:46:53 +08:00
parent f26d395650
commit 074d1aa5a9
2 changed files with 169 additions and 8 deletions
@@ -0,0 +1,158 @@
# 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` |
@@ -30,7 +30,7 @@
### 0.4 当前会话的起始上下文(供追溯)
本次学习从 Harness 入口文档开始,已完整走过:入口导读 → 面试速查 → RunContext → 执行控制(budget/cancel/lifecycle/checkActive)→ 取消广播与打断机制 → RunBudget 深挖 → retry(设计+实现+超时+幂等性)→ progress(设计+代码双视角,含 ToolBoundary/canonical/Release 衔接)。当前停在「progress ✅ 已完成,下一步 tool」的位置。
本次学习从 Harness 入口文档开始,已完整走过:入口导读 → 面试速查 → RunContext → 执行控制(budget/cancel/lifecycle/checkActive)→ RunBudget 深挖 → retry → progress(设计+代码双视角)→ tool 域(49 文件全注释 + 注册调用执行链路 + Tool 调用链旅程)→ RAG 检索体系(lookup_knowledge 后端:L0/多路召回+RRF/qualityScore/降级/契约/审计/离线评测,已闭环)。当前停在「tool 域只差 MySQL 沙箱线,下一步 tool 收尾」的位置。
### 0.5 面试准备策略(学习目标)
@@ -88,7 +88,7 @@
| `application` | **Run 应用所有者**:创建 Run / 路由意图 / 执行分支 / 持久化 / SSE 输出 | ⬜ 部分 | ChatApplicationUseCase 入口(cancel 链路) | — |
| `guard` | **验证分离**:EvidenceGuard 机械验引用真实性 + SemanticGuard 隔离判结论支持度 | ⬜ 部分 | GuardModelCall(预算/超时/取消订阅) | — |
| `release` | **唯一发布点**:SUCCESS / FALLBACK 裁决,EvidenceRepair 只修引用,SafeFallback 确定性构造 | ⬜ 部分 | DiagnosisReleaseResult(结果类型) | — |
| `tool` | **证据边界**:ToolBoundary 统一执行规则、canonical 保存真相、projector 有界投影、MySQL 只读沙箱 | ⬜ 空白 | JdbcMysqlReadOnlyExecutor(取消订阅) | — |
| `tool` | **证据边界**:ToolBoundary 统一执行规则、canonical 保存真相、projector 有界投影、MySQL 只读沙箱 | ✅ 深入 | 49 文件全注释、Boundary/Contract/Projection/Store/Adapter/注册链、RAG 后端(L0/RRF/qualityScore/降级)、MySQL 沙箱(Validator/Executor/Projector 三层防线) | [tool 域注册调用执行链路](Harness%20tool%20域代码学习笔记-工具的注册调用与执行链路.md)、[Tool 调用链旅程](Harness%20Tool%20调用链-一次工具调用的完整旅程.md)、[RAG 检索体系](Harness%20RAG%20检索体系学习笔记-从%20query%20到可验证证据.md)、[MySQL 沙箱](Harness%20MySQL%20沙箱学习笔记-从%20SQL%20校验到脱敏投影.md) |
| `progress` | **收敛控制**:信息增益(GAINED/NO_GAIN)、重复检测、饱和停止(预算之外的第二套停止机制) | ✅ 深入 | 设计动机、Tracker 双计数/pending/软硬停止、拦截器五道门、canonical 生命周期、Projector 投影、Release 消费 | [代码学习笔记](Harness progress 代码学习笔记-从拦截器五道门到唯一发布点.md)(与[设计视角](Harness信息增益停止-让无证据诊断正常收敛.md)配套) |
图例:✅ 深入 = 已完整学透,能面试讲 2 分钟;⬜ 部分 = 接触过但没系统学;⬜ 空白 = 未开始
@@ -102,6 +102,10 @@
| [Retry 重试机制-显式可计量的 attempt 循环](Retry重试机制-显式可计量的attempt循环.md) | Retry 设计动机 / 分类裁决 / 剩余超时 / 幂等性 | ✅ 已沉淀 |
| [Harness 信息增益停止-让无证据诊断正常收敛](Harness信息增益停止-让无证据诊断正常收敛.md) | progress 设计视角:双停止机制 / 三方判断权 / 状态机 / 协议 / 真实问题 | ✅ 已沉淀(设计视角) |
| [Harness progress 代码学习笔记-从拦截器五道门到唯一发布点](Harness%20progress%20代码学习笔记-从拦截器五道门到唯一发布点.md) | progress 代码视角:类地图 / 五道门 / Tracker 状态机 / canonical 生命周期 / 门禁 / 投影发布 / 易错点 / 面试话术 | ✅ 已沉淀(代码视角) |
| [Harness tool 域代码学习笔记-工具的注册调用与执行链路](Harness%20tool%20域代码学习笔记-工具的注册调用与执行链路.md) | tool 域:装配/注册(bridge/callbacks)/调用/执行(ToolBoundary)/返回全链路 + 面试话术 | ✅ 已沉淀 |
| [Harness Tool 调用链-一次工具调用的完整旅程](Harness%20Tool%20调用链-一次工具调用的完整旅程.md) | 动态时序:模型决定 → 拦截器 → invoke → Adapter → ToolBoundary → 返回 → 模型观察 | ✅ 已沉淀 |
| [Harness RAG 检索体系学习笔记-从 query 到可验证证据](Harness%20RAG%20检索体系学习笔记-从%20query%20到可验证证据.md) | RAG 后端:L0/多路召回+RRF/qualityScore/去重判级/降级/契约/审计/离线评测/讨论沉淀 | ✅ 已沉淀 |
| [Harness MySQL 沙箱学习笔记-从 SQL 校验到脱敏投影](Harness%20MySQL%20沙箱学习笔记-从%20SQL%20校验到脱敏投影.md) | MySQL 工具:三层防线(语义/连接/输出)/白名单/参数化强制/取消联动/脱敏/与 RAG 对照 | ✅ 已沉淀 |
## 3. 一次请求的完整学习主线
@@ -109,7 +113,7 @@
flowchart LR
A["core<br/>执行控制 ✅"] --> B["retry<br/>重试 ✅"]
B --> C["progress<br/>信息增益 ✅"]
C --> D["tool<br/>事实边界 ⬜"]
C --> D["tool<br/>事实边界 ✅"]
D --> E["guard<br/>验证 ⬜"]
E --> F["release<br/>发布 ⬜"]
F --> G["application + audit<br/>收尾 ⬜"]
@@ -119,14 +123,13 @@ flowchart LR
## 4. 下一步规划
```text
progress ✅ 已完成(设计+代码双视角笔记沉淀,代码核心类全部加注释)
tool 域 ✅ 完成(49 文件全注释 + 5 篇笔记:注册执行链路 / Tool 调用链 / RAG 检索体系 / MySQL 沙箱)
下一个:tool(49 个文件,按四层理解:Boundary → Canonical → Projector → Adapter)
—— 已在 progress 学习中顺带摸过 ToolBoundary / CanonicalInvocationStore / Adapter / Projector
—— 正式系统学时按四层主线走,把投影器、JPA store、MySQL 沙箱补齐
下一个:guard(15 个文件:EvidenceGuard + SemanticGuard)
—— 证据安全链核心:机械验引用真实性 + 语义判结论支持度
—— 面试高频:如何防止 Agent 编造证据
之后顺序:
guard(15 个文件:EvidenceGuard + SemanticGuard)
release(6 个文件,小而关键:唯一发布点——已接触 DiagnosisReleaseUseCase)
补 application(路由/执行器/SSE 收尾)和 audit(Trace 回放)
最后状态流(RunState ↔ ReleaseOutcome ↔ SseOutcome 正交全景)