# 【安全测试】问题处理 - 安全测试执行报错 'str' object has no attribute 'get'

> **文档类型**: 问题处理文档（安全测试模块）
> **创建日期**: 2026-07-21
> **最后更新**: 2026-07-21（修正根因）
> **问题等级**: P0（阻塞，安全测试全部执行失败）
> **关联模块**: 安全测试（security）
> **状态**: ✅ 已修复并验证

---

## 一、问题现象

在执行中心执行安全测试用例时，**全部用例执行失败**，前端报错：

```
执行异常: 'str' object has no attribute 'get'
```

后端日志关键行：
```
app.executors.playwright_executor - ERROR - 用例执行异常: 水平越权访问其他用户会议详情, 'str' object has no attribute 'get'
```

同时观察到执行记录的**时间显示为 UTC 时间**（如本地 17 点执行，显示为 9 点）。

---

## 二、根因分析（已修正）

### 2.1 ⚠️ 真正根因：安全测试用例被误交给 PlaywrightExecutor 执行

**最初误判**：以为是 `run_in_executor` 线程池导致 ORM 对象脱钩、JSON 字段退化为字符串。

**实际真相**（通过后端日志确认）：

错误来源是 **`playwright_executor`**，而不是我新写的 `security_executor`。这说明安全测试用例**根本没有走安全测试执行器**，而是走了 UI 用例的通用执行链路。

**执行链路**：

```
前端 Cases.vue 点"执行选中"
  └─ executionApi.create()           # 通用执行接口
  └─ executionApi.run()              # 通用执行触发
  └─ ExecutionService.run_execution()# 后端统一入口
       └─ PlaywrightExecutor         # ⚠️ 无脑用 UI 执行器
            └─ 解析 steps dict        # 安全用例 steps 是 dict，不是步骤列表
            └─ dict.get('action')     # ❌ str 没 .get() → 报错
```

**根因**：

1. 前端从「用例管理页 Cases.vue」点"执行选中"，调用的是**通用执行 API**（`/api/executions`），不是安全测试专用 API（`/api/security/executions`）
2. 后端 `ExecutionService.run_execution()` **没有按 case_type 分流**，所有用例都交给 `PlaywrightExecutor`
3. 安全测试用例的 `steps` 是 dict（`{"test_type":"api_security", ...}`），而 `PlaywrightExecutor` 期望的是步骤列表 `[{order, action, ...}]`
4. Playwright 解析 dict 时 `dict[xxx].get(...)` 或 `str.get(...)` 触发 `'str' object has no attribute 'get'`

**验证**：

```bash
# 端到端测试脚本 test_security_e2e.py
# 修复前：日志显示 playwright_executor 报错
# 修复后：日志显示 security_executor 执行，3/3 用例正常完成
```

### 2.2 次要问题：模型字段名 `metadata` 与 SQLAlchemy 保留字冲突

用户/linter 已将 `VulnerabilityResult.metadata` 改名为 `extra_data`（`metadata` 是 SQLAlchemy 保留字段名），但以下代码未同步：

- `security_service.py` 的 `_save_vulnerability_result()` 仍用 `metadata=result.metadata`
- `schemas/security.py` 的 `VulnerabilityResultResponse` 仍用 `metadata`

这会导致**漏洞结果保存失败**和**接口返回报错**。

### 2.3 防御性增强：ORM 对象在 run_in_executor 中脱钩

虽然真正根因不是脱钩，但作为**防御性增强**，在丢入线程池前把 ORM 对象转纯字典仍然是好实践，避免后续 session 行为变化引入隐患。

### 2.4 时间显示问题（已有行为，非安全测试独有）

全项目统一使用 `datetime.utcnow()` 存储 UTC 时间，前端直接显示未做 +8 时区转换。
- 数据库存 UTC 时间（如 09:00 UTC = 17:00 本地）
- 前端按字符串直接显示，显示为 09:00

这是**全项目既有行为**（会议管理、数据分析等所有模块都一样），不属于本次安全测试引入的问题，记录在案但**不在本次修复范围**（修复需统一改全项目时间显示逻辑，影响面大）。

---

## 三、修复方案

### 3.1 修复点 1（核心）：execution_service 按 case_type 分流（P0）

**文件**: `backend/app/services/execution_service.py` 的 `run_execution()`

**改动**：在 `run_execution()` 入口处判断 `case_type`，安全测试分流到专用方法：

```python
async def run_execution(self, execution_id, config=None):
    execution = await self.get_execution(execution_id)
    if not execution:
        raise ValueError(f"执行记录不存在: {execution_id}")

    # ===== 按用例类型分流：安全测试走专用执行器 =====
    if execution.case_type == "security":
        return await self._run_security_execution(execution_id, config)

    # 以下为原有 UI 用例执行逻辑（PlaywrightExecutor）...
```

**新增方法** `_run_security_execution()`：

1. 从执行记录取关联用例（按模块分组排序）
2. 加载默认安全测试配置（`SecurityConfig`）
3. 在 async 上下文内预先把 ORM 对象转纯字典（防御脱钩）
4. `run_in_executor` 调用 `SecurityExecutor` 执行
5. 结果写回 `CaseResult`（供执行中心展示）+ `VulnerabilityResult`（供报告中心）
6. 广播 WebSocket 执行完成

### 3.2 修复点 2：模型字段名同步 `metadata` → `extra_data`（P0）

| 文件 | 改动 |
|------|------|
| `services/security_service.py` | `_save_vulnerability_result()` 中 `metadata=result.metadata` → `extra_data=result.metadata` |
| `services/execution_service.py` | `_run_security_execution()` 中 `metadata=r.metadata` → `extra_data=r.metadata` |
| `schemas/security.py` | `VulnerabilityResultResponse` 中 `metadata` → `extra_data` |

### 3.3 修复点 3：前端 `metadata` 引用同步（P0）

| 文件 | 改动 |
|------|------|
| `frontend/src/types/security.ts` | `VulnerabilityResult.metadata` → `extraData` |

### 3.4 时间显示问题（不在本次修复范围，记录备案）

全项目使用 UTC 时间存储，若需修复需统一在前端 `formatTime` 工具函数中做时区转换，影响所有页面，单独评估。

---

## 四、验证结果（已自测通过）

端到端测试脚本：`backend/scripts/test_security_e2e.py`

```
=== 1. 创建执行 ===
创建状态码: 201
执行ID: exec_xxx
case_type: security

=== 2. 触发执行 ===
触发状态码: 200
{"message":"执行完成","status":"completed"}

=== 3. 等待执行完成 ===
  轮询 1: 3/3 完成

=== 4. 执行结果 ===
  Nacos未授权访问: failed | Nacos控制台可匿名访问
  Swagger文档暴露: passed |
  SQL注入-会议查询接口: passed |
```

**验证结论**：
- ✅ 安全用例正确走 SecurityExecutor（不再走 PlaywrightExecutor）
- ✅ 3/3 用例完成，无 `'str' object has no attribute 'get'` 错误
- ✅ 漏洞判定正确（Nacos 未授权=漏洞，Swagger/SQL注入=已防护）
- ✅ 结果同时写入 CaseResult 和 VulnerabilityResult

---

## 五、踩坑记录（修正后）

| # | 现象 | 根因 | 正确做法 |
|---|------|------|---------|
| S1 | 安全测试执行报 `'str' object has no attribute 'get'` | `execution_service` 未按 case_type 分流，安全用例被交给 PlaywrightExecutor 解析 dict steps | **在 `run_execution` 入口按 `case_type` 分流**，security 走专用 `_run_security_execution` |
| S2 | `metadata` 字段保存/查询报错 | `metadata` 是 SQLAlchemy 保留字段名 | 模型用 `extra_data`，service/schema 同步 |
| S3 | 执行时间显示比实际少 8 小时 | 全项目用 `utcnow()` 存 UTC 时间，前端未转时区 | 全项目既有行为，需统一在前端做时区转换（单独评估） |
| S4 | 误判为"ORM 脱钩" | 调试时用同步 engine 复现不出，未直接看生产日志 | **排查异常时先看后端日志的错误来源模块**，不要只看错误消息臆测 |

---

## 六、教训

1. **排查异常先看日志来源模块**：日志明确写了 `app.executors.playwright_executor`，我一开始却以为是 `security_executor` 的脱钩问题，浪费了时间。**应该先 grep 错误消息确认是哪个模块报的**。
2. **多入口要统一分流**：安全测试有两个执行入口（Cases.vue 的通用执行 + Execution.vue 的专用执行），后端 `run_execution` 必须在入口统一分流，不能假设前端会调用专用 API。
3. **防御性编程**：即便不是真正根因，在 `run_in_executor` 前把 ORM 转纯字典也是好习惯，避免 session 行为变化引入隐患。

---

*本文档为安全测试模块问题处理记录，供后续维护参考。*
