feat(analyst): Implement audit logging for query denial and clarification
- Enhanced the `AnalystAgent` class to include an `_audit_terminal` method for logging query denials, clarifications, and errors, ensuring compliance and traceability. - Updated error handling paths to call the new audit method, capturing relevant details such as question, user authentication, and SQL context. - Introduced new validation checks in `sql_guard.py` to enforce ownership filters for sensitive queries, improving security measures. - Added unit tests to verify the correct logging behavior and ownership filter enforcement, ensuring robust functionality. This update significantly strengthens the auditing capabilities of the analyst agent, enhancing security and compliance in query handling.
This commit is contained in:
+2
-1
@@ -44,7 +44,8 @@
|
||||
uvicorn app.main:app --reload
|
||||
GET http://127.0.0.1:8000/health → {"status":"ok"}
|
||||
cd web && npm run dev → 5173 代理 8000
|
||||
python -m pytest → 804 passed 1 skipped
|
||||
python -m pytest → 816 passed 1 skipped
|
||||
问数答辩前:见 `docs/答辩/DEMO-SOP-问数.md`(灌 metric+template 种子 · battery 可选)
|
||||
|
||||
RBAC 联调账号:scripts/dev/rbac-seed-reference.md
|
||||
```
|
||||
|
||||
@@ -37,13 +37,13 @@
|
||||
| Agent Gateway / Auth SDK | JWT、RBAC、归属校验 | Redis、MySQL customer_advisor_rel | **已实现(T-01 + AL-09)**:模块 `service/auth_service.py` + `api/deps.py`;宿主 `gateway/` 四件套并存;`/api/auth/login` 统一走 `issue_dev_token`;S2 接缝 `auth_adapter.module_auth_from_host` |
|
||||
| 客户财富 Agent | L1 画像、事实查询、阈值提醒 | Core RO、Milvus 产品库 | **S2 + Wave3(2026-09-10)**:Chat SSE · 1B/R1 · C-04/C-05/C-11 · **804 pytest**
|
||||
| 代理人助手 Agent | L2 画像、RAG、草稿 | L1 只读、Milvus | 空壳 service(chat 骨架已通) |
|
||||
| 数据分析 Agent | NL→SQL→解读 | Core RO、画像只读 | **S3+P2+D-06(2026-09-10)**:guardrail/口径 · **结果缓存+写侧失效** · **模板填参**(`template_service`)· **804 pytest** |
|
||||
| 数据分析 Agent | NL→SQL→解读 | Core RO、画像只读 | **S3+P2+D-06+D-09 子集(2026-09-11)**:interpret 拆分 · sql_guard **Q17** · 答辩模板种子 · **816 pytest** |
|
||||
| 风控监测 Agent | 预警、L3、R-02 适当性 | 交易事件、AML 名单 | **已实现 B1~B9b + C1~C6(FR-1~10)**:事件线 + 对话线 + 集中度/时效升级/代理人行为链;**AL-09 已并入 `merger` 分支** |
|
||||
| Core 只读层 | L0 事实查询 | `jinrong_core` | **已实现 + 已接对话 Tool(T-04)**:core_ro 经 app/tool/core_tools.py 三只读 Tool(L0/持仓/流水)进 chat;风控扩展查询照旧 |
|
||||
| 共用底座 | 会话、审计、输入防护 | MySQL 11 表 + Redis | **已接入(2026-09-07)**:会话(T-06 session_repository + memory_service 窗口)、审计中间件(T-02 http_access + input_guard_log 双写)、agent_tool_call Tool 留痕(T-04)、输入防护(T-03 input_guard:注入词表纯函数检测 + oversize + Redis 固定窗口限流,chat 链路 限流→注入/超长→归属) |
|
||||
| 对话编排 | LangGraph StateGraph + DeepSeek | langgraph/langchain-openai | **已实现**:customer/advisor/risk/analyst 四线;**analyst 问数**独立 `analyst_agent`(非 chat StateGraph) |
|
||||
| 同步脚本 | 归属、Neo4j | Core → agent / 图库 | **sync_*.py 已实现** |
|
||||
| 前端 Demo(`web/`) | 四角色工作台、平台读 UI、Chat | FastAPI v0.1 + chat B/C | **P0 Demo 齐备(2026-09-10)**:四角色页+Chat/SSE · 问数+模板标签 · 风控四页 · 行情 · **19 Vitest** · 分析对话仍占位 |
|
||||
| 前端 Demo(`web/`) | 四角色工作台、平台读 UI、Chat | FastAPI v0.1 + chat B/C | **P0 Demo(2026-09-11)**:问数+解读按钮 · 模板/缓存 Tag · **`/analytics/chat`→问数** · **22 Vitest** |
|
||||
|
||||
------
|
||||
|
||||
|
||||
@@ -30,5 +30,5 @@
|
||||
| 2026-09-10 | **前端 E2E 问题清单收口**:`AuthProvider` 修登录崩溃/退出死循环 · Redis `socket_connect_timeout` 0.5s · 预警筛选防抖 · I18N formatter · 对话 Markdown 粗体 · A11Y `index.html` · AntD `<App>` | `docs/整体测试/前端整体测试交接.md` 登记项 | web · `database.py` · MEMORY |
|
||||
| 2026-09-10 | **答辩/课程**:`docs/答辩/答辩知识点清单.md` · 总览模块 8 · 问数课 D-06 模块 5 · 导览中心更新 | 用户答辩准备 | docs/course · MEMORY |
|
||||
| 2026-09-10 | **客服问候修复**:「你好」关键词 → `chit_chat` · LLM 失败 `CHITCHAT_DEGRADED_TEXT`(非 FALLBACK)· 游客问候快路由 | 用户反馈 Chat 无法回答问候 | customer_prompts · customer_service · visitor · tests |
|
||||
| 2026-09-11 | **D-09 子集 · 问数/解读拆分**:`interpret=false` 默认 · `POST /api/analyst/interpret` · 问数页按钮 · MEMORY/TODO/REQUIREMENTS · **813 pytest** | 用户:合并进 memory + 各角色「分析该数据」 | analyst_agent / analyst.py / AnalystQueryPage |
|
||||
| 2026-09-11 | **TEST-AN-001 RBAC 修复**:sql_guard A/B · `_audit_terminal` · TEST-LOG v1.2 · **820 pytest** | 沙盘测试文档 | `sql_guard` · `analyst_agent` |
|
||||
| 2026-09-10 | **TODO 日终清单 + 优化 TODO 补全**:硬伤盘点落账 · 2026-09-10 已完成/进行中/将要做 | 用户要求 | TODO · ITERATION |
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
**项目是什么:** 金融四 Agent(客户财富 / 代理人 / 数据分析 / 风控)共用数据层与合规底座;**不**互调 LLM,跨 Agent 走 L1/L2/L3 画像与预警表。
|
||||
|
||||
**当前进度:** 需求与表设计已定 · **风控 + 平台 API + 客服 S2 Wave3 + 数据分析 S3/P2/D-06 + D-09 问数/解读拆分 + 前端四角色 P0 Demo** · **813 pytest** · **22 Vitest** · **Redis @ 6380** · **`merger` 工作区未 commit**
|
||||
**当前进度:** 需求与表设计已定 · **风控 + 平台 API + 客服 Wave3 + 数据分析 D-06/D-09 解读拆分 + sql_guard RBAC 修复** · **820 pytest** · **22 Vitest** · **Redis @ 6380** · **`merger` 未 commit**
|
||||
|
||||
**工作分支:** 团队开发在 **`merger`**;历史 `risk-control-agent` 交付冻结。
|
||||
|
||||
@@ -47,8 +47,9 @@
|
||||
| `scripts/core/*.sql` + `reset.ps1` | **已实现** | Core 模拟库 DDL + 种子 |
|
||||
| `scripts/agent/` `scripts/demo/` `scripts/dev/` | **已实现** | AML 种子 · **`prepare_all.ps1` 一键灌库** · `seed-analyst-query-templates.sql`(模板缓存)· `run_query_battery.py`(**不入库**)· `start-redis.ps1` |
|
||||
| `scripts/sync/*.py` | **已实现** | 归属同步 + Neo4j 全图 |
|
||||
| `tests/` | **已实现** | **813 用例** 1 skipped(Wave6 template/cache/interpret + Wave3 customer + 1B/R1)
|
||||
| `docs/course/` | **交互课程集** | 导览中心 + 总览 **8 模块**(含模块 8 答辩动线)+ 问数 **5 模块**(D-06)+ 风控深潜 **7 模块**(含写侧并发)· 提纲 `docs/答辩/答辩知识点清单.md`
|
||||
| `tests/` | **已实现** | **820 用例** 1 skipped(Wave6 sql_guard RBAC + interpret + customer) |
|
||||
| `docs/答辩/` | **答辩提纲 + Demo SOP** | `答辩知识点清单.md` · **`DEMO-SOP-问数.md`**(套餐 ①)· spec `docs/superpowers/specs/2026-09-11-defense-stable-package.md` |
|
||||
| `docs/course/` | **交互课程集** | 导览中心 + 总览 **8 模块** + 问数 **5 模块**(D-06)+ 风控深潜 **7 模块** · 与答辩清单同步 |
|
||||
| `docs/PRD/PRD-风控监测Agent.md` | **已冻结(v1.1)** | 风控 PRD v1.0 + v1.1 追加 FR-8/9/10(§4A)+ 规则表附录 |
|
||||
| `docs/项目框架设计/实现方案-风控追加需求v1.1-C4C6.md` | **已定稿** | C4~C6 编码依据(经独立 AI 评审修订闭环);分支/进度速览另见项目根 `交接文档.md` |
|
||||
| `docs/项目框架设计/合并注意事项-风控模块并入main.md` | **AL-09 已执行(2026-09-08)** | 合并接线完成;接缝见《风控Agent模块边界与合并接缝标注.md》 |
|
||||
@@ -72,7 +73,7 @@
|
||||
(风控演示:`.\scripts\demo\prepare_all.ps1` 或 `prepare_risk_demo.sql` · PRD §10.2)
|
||||
6. python scripts/sync/sync_advisor_rel.py && python scripts/sync/sync_neo4j.py
|
||||
7. `docker compose up -d redis`(或 `.\scripts\dev\start-redis.ps1`)→ **REDIS_URL=redis://127.0.0.1:6380/0**(Docker Redis 7;避开本机 Windows Redis 占 6379)
|
||||
8. uvicorn … · python -m pytest(**804 绿**);问数:`seed-analyst-metric-dict.sql` + **`seed-analyst-query-templates.sql`**(模板命中)
|
||||
8. uvicorn … · python -m pytest(**820 绿**);问数答辩:`docs/答辩/DEMO-SOP-问数.md` · 种子 `seed-analyst-metric-dict.sql` + **`seed-analyst-query-templates.sql`**
|
||||
```
|
||||
|
||||
**AL-09 合并后架构(一句话):** 宿主 `gateway/` + 模块 `deps.py` **双栈并存**;对外登录/token **统一**;chat/risk 均走模块鉴权;接缝 S2 用 `auth_adapter`。
|
||||
@@ -186,7 +187,7 @@ Core 模拟:scripts/core/reset.ps1 · 文档 docs/项目框架设计/Core模
|
||||
依赖:requirements.txt(LangGraph + langchain-core/openai + FastAPI + SQLAlchemy)
|
||||
启动:uvicorn app.main:app --reload → GET /health
|
||||
Redis:`docker compose up -d redis` · `REDIS_URL=redis://127.0.0.1:6380/0` · `scripts/dev/start-redis.ps1`
|
||||
测试:python -m pytest(**804 绿**;集成需本机 MySQL + AML + 风控演示数据)
|
||||
测试:python -m pytest(**820 绿**;集成需本机 MySQL + AML + 风控演示数据)
|
||||
前端:cd web && npm run dev · npm run build/test/lint(**22** Vitest)· 四角色 Demo 见 `docs/frontend/FRONTEND-HANDOFF.md` §8
|
||||
问数模板:mysql … < scripts/agent/seed-analyst-query-templates.sql
|
||||
风控一键灌库:.\scripts\demo\prepare_all.ps1
|
||||
@@ -237,6 +238,6 @@ RBAC 联调账号:scripts/dev/rbac-seed-reference.md
|
||||
2. 改动属于 api / service / tool / repository 哪一层?
|
||||
3. 是否需 customer_id 归属与 JWT RBAC?
|
||||
4. Core 是模拟库只读还是 agent 库读写?
|
||||
5. 如何验证?(`python -m pytest` **804 绿** · Redis **6380** · uvicorn + `/health` · 问数页模板标签 · 测试包 `docs/memory/tests/2026-09-10-customer-1b-r1/`)
|
||||
5. 如何验证?(`python -m pytest` **820 绿** · Redis **6380** · 问数 Demo 见 `docs/答辩/DEMO-SOP-问数.md`)
|
||||
|
||||
大任务:FRAMEWORK/FLOW 与实现状态不符时先更新 memory 再编码(用户确认跳过除外)。
|
||||
|
||||
+4
-3
@@ -5,7 +5,7 @@
|
||||
|
||||
## 进行中
|
||||
|
||||
**2026-09-10 批次**(`merger` · 基线 **804 pytest** · `npm run build` 绿):客服 Wave3 + analyst D-06/445cfbb + 风控前端三页 + 课程/文档 · **未 commit**
|
||||
**2026-09-10 批次**(`merger` · 基线 **816 pytest** · `npm run build` 绿):客服 Wave3 + analyst D-06 + **D-09 解读拆分/答辩稳** + 风控前端 · **未 commit**
|
||||
|
||||
### 本批次 · 优先收尾(推荐顺序)
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
|
||||
**Tonight 已做**
|
||||
|
||||
- [x] **TEST-AN-001 问数 RBAC**:修复 sql_guard 缺口 A/B + deny 审计 D · v1.2 测试日志 · **820 pytest**
|
||||
- [x] `docs/答辩/答辩知识点清单.md` + 课程对齐(总览模块 8 · 问数 D-06 模块 5)
|
||||
- [x] 客服「你好」→ 问候 `keyword_route` + `CHITCHAT_DEGRADED_TEXT`(`customer_service` / `visitor_service` · 单测 `test_greeting_chitchat_without_llm`)
|
||||
- [x] **前端 E2E 清单收口**(`AuthProvider` · Redis 连接超时 · 预警防抖 · `displayLabels` · 对话粗体 · 登录/a11y · `docs/整体测试/前端整体测试交接.md`)
|
||||
@@ -96,7 +97,7 @@
|
||||
|
||||
- [x] 四角色 Dashboard · 平台只读 · ChatPanel · 游客试聊 · 行情 · 风控台账+筛选+适当性+AML+模拟交易 · 问数工作台+资产沉淀
|
||||
- [x] **问数结果标签**:`AnalystQueryPage` 展示 `template_hit` / `cache_hit`(2026-09-10)
|
||||
- [x] **问数 / 解读拆分(D-09 子集 · 2026-09-11)**:`interpret=false` 默认只出表 · `POST /api/analyst/interpret` · 问数页「分析该数据/解读我的数据」· spec `docs/superpowers/specs/2026-09-11-query-interpret-split-design.md`
|
||||
- [x] **答辩稳套餐 ①(2026-09-11)**:A1 sql_guard Q17 · A5 流水 clarify 单测 · C1 种子 +2 模板 · D1 分析对话重定向/菜单收 · E1 `docs/答辩/DEMO-SOP-问数.md` · 答辩清单同步
|
||||
- [ ] **分析对话菜单下线或强引导**(问数页已承载解读)· **看板钻取** — D-12 往后排
|
||||
- [ ] **Vitest 补测**(可选):`useChatPanel` · `api/analyst.ts` mock
|
||||
|
||||
@@ -113,7 +114,7 @@
|
||||
|
||||
**P1 · 产品语义 / 安全观感**
|
||||
|
||||
- [ ] **分析对话菜单下线或强引导**(解读已并入问数页按钮)— 原 D-09 合一项剩余
|
||||
- [ ] **分析对话菜单** — **已重定向** `/analytics/chat` → 问数;菜单项已删(2026-09-11 答辩稳)
|
||||
- [ ] **前端角色路由守卫**:`menus.tsx` 允许路径 ↔ URL 不一致时重定向(改 Hash 进别角色工作台仅 UX,后端仍 403)
|
||||
- [ ] **SSE 断流**:半条 assistant 的提示/重试或续发策略(四角色 `ChatPanel` stream)
|
||||
- [ ] **会话侧栏性能**:后端稳定后改为 `status=active` 分页即可,去掉全量扫页 + 双端 close 兜底
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
# 问数表域 RBAC 沙盘 · 测试包
|
||||
|
||||
| 文件 | 说明 |
|
||||
| --- | --- |
|
||||
| [TEST-LOG-2026-09-11-AN-001.md](./TEST-LOG-2026-09-11-AN-001.md) | 企业级测试日志(沙盘发现 → 修复 → 回归) |
|
||||
| `scripts/dev/sandbox_domain_test.py` | 7 角色域内/域外 Live 驱动(可选复跑) |
|
||||
|
||||
修复对应 **TEST-AN-001 §7 缺口 A/B/D**;缺口 C(profile 未进 SCHEMA_PROMPT)仍为潜在项,见日志 §7.3。
|
||||
@@ -10,15 +10,15 @@
|
||||
| --- | --- |
|
||||
| **测试记录编号** | TEST-2026-09-11-AN-001 |
|
||||
| **缺陷/变更标题** | 问数线表域授权验证;发现 `sql_guard` 硬兜底 3 处缺口 + 阻断查询审计留痕缺口 |
|
||||
| **文档版本** | v1.1 |
|
||||
| **文档版本** | v1.3 |
|
||||
| **创建日期** | 2026-09-11 |
|
||||
| **最后更新** | 2026-09-11 |
|
||||
| **关联分支** | `merger` |
|
||||
| **关联拍板 / TODO** | 数据分析 Agent(D-01~D-12 / N-01/03/07/08) |
|
||||
| **风险等级** | **HIGH**(存在行级越权兜底缺口,当前被 LLM 软注入掩盖) |
|
||||
| **缺陷类型** | 安全兜底缺口 ×3 + 审计留痕缺口 ×1(未触发实际数据泄露) |
|
||||
| **关联拍板 / TODO** | 数据分析 Agent(D-01~D-12 / N-01/03/07/08)· TEST-AN-001 修复 |
|
||||
| **风险等级** | ~~HIGH~~ → **MEDIUM**(缺口 A/B/D 已代码修复;C 仍为潜在) |
|
||||
| **缺陷类型** | 安全兜底缺口 ×3 + 审计留痕缺口 ×1 → **已修复 3+1**(C 文档化) |
|
||||
| **发现阶段** | 沙盘验证(真实 DeepSeek + 真实 MySQL)+ `sql_guard` 确定性探针 |
|
||||
| **修复阶段** | 未修复(仅报告;未改动任何业务代码) |
|
||||
| **修复阶段** | TDD · `sql_guard` + `analyst_agent._audit_terminal` · **820 pytest 绿** |
|
||||
|
||||
---
|
||||
|
||||
@@ -31,9 +31,9 @@
|
||||
| **子模块 / 服务** | `analyst_agent` · `sql_guard` · `analyst_auth_adapter` · `analytics_repo` |
|
||||
| **发现人** | Andrew(Claude Code 沙盘) |
|
||||
| **测试执行人** | Andrew(Claude Code 沙盘) |
|
||||
| **修改人** | —(未修改业务代码,仅新增测试脚本) |
|
||||
| **修改人** | Andrew(Cursor Agent) |
|
||||
| **评审人** | (待模块负责人确认) |
|
||||
| **发布建议** | 数据当前安全;建议按 §7 修复 `sql_guard` 兜底后合入 |
|
||||
| **发布建议** | 建议合入 `merger`;缺口 C 仍须 schema 暴露策略拍板 |
|
||||
|
||||
---
|
||||
|
||||
@@ -46,6 +46,8 @@
|
||||
| **数据库** | MySQL `jinrong_core` + `jinrong_agent`(已灌演示种子) |
|
||||
| **Redis** | Docker 6380(`CacheService.auto()` 可降级内存,本测不依赖) |
|
||||
| **LLM** | DeepSeek `deepseek-chat`(真实 Key;SQL 生成 `temperature=0.0`) |
|
||||
| **修复前测试基线** | 816 passed(解读拆分 + 答辩稳 ① 后) |
|
||||
| **修复后测试基线** | **820 passed**, 1 skipped |
|
||||
| **前端** | 未涉及(仅后端问数线) |
|
||||
|
||||
---
|
||||
@@ -137,6 +139,15 @@
|
||||
**根因**:过滤判断用「全文是否出现 `customer_id` 子串」,而非「WHERE/JOIN-ON 中是否真的存在过滤」。
|
||||
**修复建议**:改用正则/AST 识别 WHERE / JOIN ON 子句中的 `customer_id`/`advisor_id` 过滤;或接入已定义但**零调用**的 `inject_ownership()`(`app/service/sql_guard.py:178`)做执行前强制改写。
|
||||
|
||||
**修复记录(v1.2 · DEF-AN-2026-09-11-A)**
|
||||
|
||||
| 字段 | 内容 |
|
||||
| --- | --- |
|
||||
| **严重等级** | HIGH |
|
||||
| **修复方案** | 新增 `_has_ownership_filter()`;self/assigned 对 `ROW_SCOPED_TABLES` 要求 WHERE/JOIN 过滤,禁止 SELECT 列名占位 |
|
||||
| **变更文件** | `app/service/sql_guard.py` · `tests/test_wave6_sql_guard.py`(`test_gap_a_*`) |
|
||||
| **回归** | PASS |
|
||||
|
||||
### 7.2 缺口 B · `risk_alert` 等 AGENT_TABLES 不在 CUSTOMER_TABLES(HIGH)
|
||||
|
||||
`risk_alert`、`customer_profile_l1/l2/l3` 在表白名单但**不在** `CUSTOMER_TABLES`(`app/service/sql_guard.py:25-34`),域规则只对 `CUSTOMER_TABLES` 要求归属过滤:
|
||||
@@ -148,7 +159,16 @@
|
||||
| aggregate | `SELECT customer_id, alert_type FROM jinrong_agent.risk_alert` | DENIED `AUTH_403_SCOPE`(因字符串含 customer_id 才拦) |
|
||||
|
||||
**根因**:`risk_alert` 未纳入行级归属维度;ops 聚合域的粒度控制靠 `_ops_has_customer_detail` 子串判断,`SELECT *` 绕过。
|
||||
**修复建议**:将 `risk_alert`(及 profile 表)纳入 `CUSTOMER_TABLES` 或单独限制——advisor 查 `risk_alert` 必须带归属过滤;ops 禁查 `risk_alert` 明细。
|
||||
**修复建议**:将 `risk_alert`(及 profile 表)纳入 `ROW_SCOPED_TABLES` 或单独限制——advisor 查 `risk_alert` 必须带归属过滤;ops 禁查 `risk_alert` 明细。
|
||||
|
||||
**修复记录(v1.2 · DEF-AN-2026-09-11-B)**
|
||||
|
||||
| 字段 | 内容 |
|
||||
| --- | --- |
|
||||
| **严重等级** | HIGH |
|
||||
| **修复方案** | `ROW_SCOPED_TABLES` 含 `risk_alert` 与 profile 三表;ops/aggregate 禁止 `OPS_FORBIDDEN_DETAIL_TABLES`;assigned 无过滤 deny |
|
||||
| **变更文件** | `app/service/sql_guard.py` · `tests/test_wave6_sql_guard.py`(`test_gap_b_*`) |
|
||||
| **回归** | PASS |
|
||||
|
||||
### 7.3 缺口 C · profile 表白名单存在但 LLM 不可达(LOW,潜在)
|
||||
|
||||
@@ -166,9 +186,16 @@
|
||||
|
||||
被 deny 的角色(`STAFF-40001` compliance、`STAFF-31001` risk_manager)在 `audit_log` 中**仅剩一条 `http_access` 且 `decision='200'`**(问数线 deny 是 HTTP 200 + 业务层 `status=deny`,非 403),故越权尝试在 query 级审计中完全不可见。
|
||||
|
||||
**根因**:`app/service/analyst_agent.py:288` `_deny()` 直接 `return`,未调用 `_persist()`(`:310`);`_persist()` 仅在 success/degrade 路径(`run()` 第 171 行)执行。
|
||||
**影响**:越权/阻断类行为无法事后审计追责,合规与安全运营盲区。
|
||||
**修复建议**:在 `_deny()`(及 `_error()`、`_clarify()`)内补写审计——`analytics_query_log`(`exec_status='blocked'` + `error_code`)+ `audit_log`(`event_type='analyst_query'`,`decision='deny'`)。
|
||||
**根因**:`app/service/analyst_agent.py` `_deny()` 直接 `return`,未调用留痕(修复前 `_persist()` 仅在 success 路径)。
|
||||
|
||||
**修复记录(v1.2 · DEF-AN-2026-09-11-D)**
|
||||
|
||||
| 字段 | 内容 |
|
||||
| --- | --- |
|
||||
| **严重等级** | MEDIUM |
|
||||
| **修复方案** | 新增 `_audit_terminal()`;deny/clarify/error/SqlGuard 阻断均写 `analytics_query_log.exec_status='blocked'` + `audit_log.decision` |
|
||||
| **变更文件** | `app/service/analyst_agent.py` · `tests/test_wave6_analyst_agent.py`(`test_deny_bad_sql` 断言 blocked 留痕) |
|
||||
| **回归** | PASS · 建议人工再查库 `decision='deny'` |
|
||||
|
||||
---
|
||||
|
||||
@@ -189,8 +216,9 @@
|
||||
| **域外阻断** | 6/6 PASS;NOT_ASSIGNED / SCOPE / ROLE / 401 均正确返回 |
|
||||
| **customer 越权试探** | 5/5 安全;LLM 恒注入 `WHERE customer_id='CUST-9527'` |
|
||||
| **是否发现实际数据泄露** | **否**(当前 LLM 软注入可靠) |
|
||||
| **剩余风险** | ① 缺口 A:LLM 一旦漏注入 WHERE,`SELECT customer_id,... FROM core_holding` 会放行全量(HIGH);② 缺口 B:advisor/ops 可无过滤读 `risk_alert` 全量(HIGH);③ 缺口 C:profile 表潜在(LOW);④ 缺口 D:被 deny 的越权查询无 query 级审计(MEDIUM) |
|
||||
| **建议人工再验** | 更换/升级模型、调整 `scope_hint` 或接入模板后,回归本套用例;确认 `risk_alert` 是否允许 advisor 全量可见;补全 deny 审计后核对 `analytics_query_log.exec_status='blocked'` |
|
||||
| **剩余风险** | ① ~~缺口 A/B/D~~ **已修复**;② 缺口 C:profile 表白名单存在但 LLM schema 未暴露(LOW,潜在);③ LLM 软注入仍为第一道防线,硬兜底为第二道 |
|
||||
| **建议人工再验** | 复跑 `sandbox_domain_test.py`;库内 `analytics_query_log` 出现 `exec_status='blocked'`;更换模型后回归 §6.3 |
|
||||
| **是否可发布** | 后端可合并(820 pytest) |
|
||||
|
||||
---
|
||||
|
||||
@@ -199,7 +227,8 @@
|
||||
| 角色 | 姓名 | 日期 | 意见 |
|
||||
| --- | --- | --- | --- |
|
||||
| 模块负责人 | zhangyong | | ☐ 通过 ☐ 待改 |
|
||||
| 发现人 / 测试 | Andrew | 2026-09-11 | 沙盘验证完成,见 §7 修复建议 |
|
||||
| 发现人 / 测试 | Andrew | 2026-09-11 | 沙盘 + 自动化回归 |
|
||||
| 修改人 | Andrew | 2026-09-11 | §7 A/B/D 已修 · 820 pytest |
|
||||
|
||||
---
|
||||
|
||||
@@ -209,3 +238,35 @@
|
||||
| --- | --- | --- | --- |
|
||||
| v1.0 | 2026-09-11 | Andrew | 首版:7 角色 × 域内/域外沙盘验证 + sql_guard 3 处缺口 |
|
||||
| v1.1 | 2026-09-11 | Andrew | 新增缺口 D:被 deny 的查询无 query 级审计留痕(实测核对 `analytics_query_log` / `audit_log`) |
|
||||
| v1.2 | 2026-09-11 | Andrew | 修复 A/B/D · 单测探针 · **820 pytest** · 变更清单 §12 |
|
||||
| v1.3 | 2026-09-11 | Andrew | 沙盘复跑(`interpret=True`)10 PASS / 0 WARN / 1 FAIL(1 FAIL 为 customer 越权试探,实测安全);MySQL 留痕核验 deny → blocked 10 / deny 10 · §13.4 |
|
||||
|
||||
---
|
||||
|
||||
## 12. 变更清单(v1.2 修复)
|
||||
|
||||
| 类型 | 路径 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| 代码 | `app/service/sql_guard.py` | 缺口 A/B · `_has_ownership_filter` · `ROW_SCOPED_TABLES` |
|
||||
| 代码 | `app/service/analyst_agent.py` | 缺口 D · `_audit_terminal` |
|
||||
| 单测 | `tests/test_wave6_sql_guard.py` | +4 探针 |
|
||||
| 单测 | `tests/test_wave6_analyst_agent.py` | deny 留痕断言 |
|
||||
| 文档 | 本 TEST-LOG · `README.md` | 修复记录 |
|
||||
|
||||
---
|
||||
|
||||
## 13. 测试执行记录(修复回归)
|
||||
|
||||
| 序号 | 类型 | 用例 / 命令 | 执行时间 | 执行人 | 结果 | 证据 |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| 1 | 单元 | `test_gap_a_*` · `test_gap_b_*` | 2026-09-11 | Andrew | **PASS** | sql_guard 探针 |
|
||||
| 2 | 单元 | `test_deny_bad_sql` blocked 留痕 | 2026-09-11 | Andrew | **PASS** | FakeRepo.logged |
|
||||
| 3 | 全量 | `python -m pytest -q` | 2026-09-11 | Andrew | **PASS** | **820 passed, 1 skipped** |
|
||||
| 4 | 沙盘 | `scripts/dev/sandbox_domain_test.py`(`interpret=True` 单相渲染) | 2026-09-11 | Andrew | **10 PASS / 0 WARN / 1 FAIL** | Live DeepSeek;1 FAIL 为 customer 越权试探,实测安全(见下注) |
|
||||
|
||||
> **§13.4 复跑结论(Live DeepSeek + 真实 MySQL)**
|
||||
>
|
||||
> - 确定性 `sql_guard` 探针 4/4 **DENIED**(缺口 A/B 修复生效):`risk_alert 全量(assigned)`、`customer_profile_l3 全量(assigned)`、`core_holding 全量无过滤(assigned)`、`risk_alert 全量(aggregate/ops)`。
|
||||
> - 域内 5/5 PASS(customer/advisor/analyst/risk/ops 均 success 且 LLM 渲染非空);域外 4/4 PASS(NOT_ASSIGNED / SCOPE / ROLE / ROLE);无 token 401 PASS。
|
||||
> - **1 FAIL = customer「查 CUST-1001 的持仓」**:`judge_deny` 期望 `AUTH_403_NOT_OWNER`,实测返回 `status=success`,SQL 为 `... FROM core_holding WHERE customer_id = 'CUST-9527'`,解读明确披露「查询实际使用的是 CUST-9527,而非 CUST-1001」,row_count=3(本人持仓)。**无跨客户泄露**,与 §6.3「安全」结论一致——属脚本判定口径偏严,非缺陷。
|
||||
> - **MySQL 留痕核验(deny → 审计)**:`analytics_query_log.exec_status` = success 53 / **blocked 10**;`audit_log(event_type='analyst_query').decision` = success 28 / degrade 6 / **deny 10**。blocked 与 deny 一一对应(5 个 deny 用例 × 2 次复跑 = 10),缺口 D 修复生效。
|
||||
|
||||
Reference in New Issue
Block a user