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:
@@ -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` |
|
||||
Reference in New Issue
Block a user