Files
insar-management-system-v2/docs/archive/SECURITY_FIX_PLAN.md
T

479 lines
13 KiB
Markdown
Raw 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.
# 安全问题分阶段修复计划
## 修复原则
1. **安全第一**:每个阶段修复后必须充分测试,确保不影响现有功能
2. **最小改动**:优先选择改动最小、风险最低的方案
3. **可回滚**:每个阶段独立提交,出问题可以快速回滚
4. **渐进式**:从简单到复杂,从低风险到高风险
---
## 第一阶段:明确 Bug 修复(低风险)
**目标**:修复明确的代码错误,不涉及架构变更
### 1.1 ENVI 主流程返回值缺失 ✅ 已完成
**文件**`backend/app/services/envi_service.py:1630`
**修改**
```python
# 修改前
if error:
raise RuntimeError(error)
# 修改后
if error:
raise RuntimeError(error)
return record # 添加此行
```
**风险评估**:🟢 极低
- 只添加一行返回语句
- 不改变任何逻辑
- 修复了明确的 bug(成功时应该返回 record)
**测试计划**
- [ ] 运行一个完整的 D-InSAR 工作流(dinsar_custom
- [ ] 检查任务状态是否正确标记为 SUCCESS
- [ ] 检查日志是否包含 `workflow=dinsar_custom duration=XXXs`
- [ ] 验证前端是否正确显示任务完成
---
### 1.2 失败计数重复累加
**文件**`backend/app/services/envi_service.py:1717, 1727`
**修改**
```python
# 修改前(1717 行)
except OSError as e:
task_failed += 1
failed += 1 # 删除此行
task_status = f"error: {e}"
# 修改后
except OSError as e:
task_failed += 1
task_status = f"error: {e}"
# 修改前(1727 行)
except OSError as e:
task_failed += 1
failed += 1 # 删除此行
task_status = f"error: {e}"
# 修改后
except OSError as e:
task_failed += 1
task_status = f"error: {e}"
# 保留 1730 行的汇总
failed += task_failed # 只在这里累加一次
```
**风险评估**:🟢 极低
- 只删除重复的累加语句
- 不改变业务逻辑
- 修复统计数据不准确的问题
**测试计划**
- [ ] 运行 `extract_disp_results` 功能
- [ ] 故意触发一些失败(如权限问题)
- [ ] 验证返回的 `failed` 计数是否准确(不翻倍)
---
### 1.3 PowerShell 编码问题
**文件**`scripts/start_app.ps1:388`
**修改**
```powershell
# 修改前
$NewConfContent | Set-Content -LiteralPath "$NginxConfPath" -NoNewline
# 修改后
$NewConfContent | Set-Content -LiteralPath "$NginxConfPath" -NoNewline -Encoding UTF8
```
**风险评估**:🟢 极低
- 只添加编码参数
- 确保 nginx.conf 使用 UTF-8 编码
- 避免 PowerShell 5 默认 UTF-16 导致的问题
**测试计划**
- [ ] 在 PowerShell 5 环境运行启动脚本
- [ ] 检查生成的 nginx.conf 文件编码(使用 `file` 命令或文本编辑器)
- [ ] 验证 Nginx 是否能正常启动和解析配置
---
**第一阶段提交**
```
fix: 修复 ENVI 工作流返回值缺失和统计计数错误
- 修复 run_workflow 成功时不返回 record 的问题
- 修复 extract_disp_results 失败计数重复累加
- 修复 PowerShell 脚本 nginx.conf 编码问题
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
```
---
## 第二阶段:防御性增强(中风险)
**目标**:添加保护措施,不改变现有业务逻辑
### 2.1 AOI token 容量上限
**文件**`backend/app/routers/dependencies.py`
**修改策略**
1. 添加环境变量配置 `AOI_TOKEN_MAX_STORE_SIZE`(默认 1000
2.`_store_aoi_token` 中添加容量检查
3. 达到上限时删除最旧的 100 个 token
**实现**
```python
# 在 dependencies.py 顶部添加
AOI_TOKEN_MAX_STORE_SIZE = read_int_env(
"AOI_TOKEN_MAX_STORE_SIZE",
1000,
minimum=100,
maximum=10000,
)
# 修改 _store_aoi_token 函数
async def _store_aoi_token(aoi_wkt: str, feature_collection: Dict[str, Any]) -> str:
token = uuid.uuid4().hex
now = time.monotonic()
async with _AOI_TOKEN_LOCK:
_cleanup_expired_aoi_tokens(now)
# 检查容量上限
if len(_AOI_TOKEN_STORE) >= AOI_TOKEN_MAX_STORE_SIZE:
# 删除最旧的 token(按 expires_at 排序)
oldest_tokens = sorted(
_AOI_TOKEN_STORE.items(),
key=lambda x: x[1]["expires_at"]
)[:100]
for old_token, _ in oldest_tokens:
_AOI_TOKEN_STORE.pop(old_token, None)
# 记录日志
print(f"[INFO] AOI token store reached limit, cleaned {len(oldest_tokens)} oldest tokens")
_AOI_TOKEN_STORE[token] = {
"aoi_wkt": aoi_wkt,
"aoi_geojson": feature_collection,
"expires_at": now + AOI_TOKEN_TTL_SECONDS,
}
return token
```
**风险评估**:🟡 中等
- 添加了新的容量限制逻辑
- 可能影响高并发场景下的 AOI 查询
- 但不改变现有 API 接口
**测试计划**
- [ ] 创建 1000+ 个不同的 AOI token
- [ ] 验证内存占用是否稳定(不超过预期)
- [ ] 检查日志是否有 token 清理记录
- [ ] 验证被清理的 token 是否无法再使用
- [ ] 验证新创建的 token 仍然可用
---
### 2.2 AOI 解析异常处理
**文件**`backend/app/routers/dependencies.py`
**修改策略**
在所有 AOI 解析函数中添加统一的异常处理
**实现位置**
- `_parse_geojson_to_wkt` 调用处
- `_parse_shapefile_to_wkt` 调用处
- 文件上传端点
**实现**
```python
# 在相关端点中添加 try-except
try:
aoi_wkt = _parse_geojson_to_wkt(geojson_data)
except (ValueError, KeyError, TypeError) as e:
raise HTTPException(
status_code=400,
detail=f"GeoJSON 格式错误: {str(e)}"
)
except Exception as e:
logger.error(f"AOI 解析失败: {e}", exc_info=True)
raise HTTPException(
status_code=500,
detail="AOI 解析失败,请联系管理员"
)
```
**风险评估**:🟡 中等
- 改变了错误响应码(500 → 400)
- 可能影响前端错误处理逻辑
- 但提升了 API 语义正确性
**测试计划**
- [ ] 上传无效的 GeoJSON 文件
- [ ] 验证是否返回 400 而非 500
- [ ] 验证错误消息是否清晰
- [ ] 验证前端是否能正确处理 400 错误
---
### 2.3 路径归属判断优化
**文件**`backend/app/services/data_service.py:530`
**修改策略**
添加辅助函数 `is_subpath`,使用 `os.path.commonpath` 进行准确判断
**实现**
```python
# 在 data_service.py 顶部添加辅助函数
def is_subpath(child: str, parent: str) -> bool:
"""检查 child 是否是 parent 的子路径"""
try:
child_abs = os.path.abspath(child)
parent_abs = os.path.abspath(parent)
# 确保父路径以分隔符结尾,避免前缀误匹配
if not parent_abs.endswith(os.sep):
parent_abs += os.sep
if not child_abs.endswith(os.sep) and os.path.isdir(child_abs):
child_abs += os.sep
# 使用 commonpath 判断
common = os.path.commonpath([child_abs, parent_abs])
return common == parent_abs.rstrip(os.sep)
except (ValueError, TypeError):
return False
# 修改 530 行
# 修改前
if not any(file_path_abs.startswith(root) for root in radar_roots):
continue
# 修改后
if not any(is_subpath(file_path_abs, root) for root in radar_roots):
continue
```
**风险评估**:🟡 中等
- 改变了路径判断逻辑
- 可能影响文件扫描结果
- 但修复了潜在的安全问题
**测试计划**
- [ ]`MONITOR_RADAR_DIRS` 旁边创建 `MONITOR_RADAR_DIRS_backup` 目录
- [ ] 放入测试文件
- [ ] 运行数据扫描
- [ ] 验证 backup 目录的文件不会被误判为合法
---
**第二阶段提交**
```
feat: 添加 AOI token 容量限制和异常处理增强
- 添加 AOI_TOKEN_MAX_STORE_SIZE 配置(默认 1000
- 达到上限时自动清理最旧的 token
- 统一 AOI 解析异常处理(400 vs 500
- 优化路径归属判断,避免前缀误匹配
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
```
---
## 第三阶段:安全加固(高风险)
**目标**:解决权限和安全问题,可能涉及架构调整
### 3.1 解包安全校验增强
**文件**`scripts/unpack_archives.py`
**修改策略**
1. 修改 `_is_safe_tar_member` 接收 member 对象而非字符串
2. 添加符号链接和硬链接检查
3. 使用逐个提取替代 `extractall`
**实现**
```python
def _is_safe_tar_member(member):
"""检查 tar 成员是否安全(包括符号链接检查)"""
# 检查路径
norm_name = os.path.normpath(member.name)
if os.path.isabs(norm_name):
return False
if norm_name.startswith("..") or norm_name.startswith("../") or norm_name.startswith("..\\"):
return False
# 检查符号链接和硬链接
if member.issym() or member.islnk():
link_target = member.linkname
if os.path.isabs(link_target) or link_target.startswith(".."):
return False
return True
def _validate_tar_members(tar_obj, archive_path):
for member in tar_obj.getmembers():
if not _is_safe_tar_member(member):
raise IOError(f"unsafe tar entry detected: {member.name} in {archive_path}")
def _safe_extract_tar(tar_obj, output_dir):
"""安全地提取 tar 文件"""
for member in tar_obj.getmembers():
if not _is_safe_tar_member(member):
raise IOError(f"unsafe tar entry: {member.name}")
# 逐个提取
tar_obj.extract(member, path=output_dir)
# 在 181 行替换
with tarfile.open(archive_path, "r:*") as tar:
_validate_tar_members(tar, archive_path)
_safe_extract_tar(tar, output_dir) # 替换 tar.extractall
```
**风险评估**:🟡 中等
- 改变了解包逻辑
- 可能影响解包性能(逐个提取 vs 批量提取)
- 但提升了安全性
**测试计划**
- [ ] 创建包含符号链接的 tar 文件
- [ ] 验证是否被拒绝解包
- [ ] 创建正常的 tar 文件
- [ ] 验证是否能正常解包
- [ ] 对比解包性能(前后差异)
---
### 3.2 只读用户权限控制(暂缓)
**状态**:⏸️ 暂缓到第四阶段
**原因**
- 涉及权限系统重构
- 需要修改多个端点
- 可能影响前端调用逻辑
- 需要更充分的测试
**备选方案**
- 方案 A:添加权限检查依赖
- 方案 B:配置控制(允许/禁止只读用户触发构建)
- 方案 C:改为 POST 端点(需要前端配合)
**决策点**
- 是否有只读用户在使用系统?
- 只读用户是否需要查看预览图?
- 是否可以接受只读用户触发构建?
---
**第三阶段提交**
```
security: 增强 tar 解包安全校验
- 添加符号链接和硬链接检查
- 使用逐个提取替代 extractall
- 防止目录逃逸和文件覆盖攻击
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
```
---
## 第四阶段:权限重构(待定)
**目标**:解决只读用户权限问题
**前置条件**
- 第一、二、三阶段全部完成并稳定运行
- 明确只读用户的使用场景和需求
- 前端团队配合(如果需要修改 API 调用)
**待讨论问题**
1. 系统中是否有只读用户?
2. 只读用户的典型使用场景是什么?
3. 是否可以接受只读用户无法查看预览图?
4. 是否需要前端配合修改?
---
## 修复时间表
| 阶段 | 预计时间 | 风险等级 | 依赖 |
|------|---------|---------|------|
| 第一阶段 | 1 天 | 🟢 低 | 无 |
| 第二阶段 | 2-3 天 | 🟡 中 | 第一阶段完成 |
| 第三阶段 | 2-3 天 | 🟡 中 | 第二阶段完成 |
| 第四阶段 | 待定 | 🔴 高 | 需求确认 |
---
## 回滚计划
每个阶段独立提交,出现问题时可以:
1. **快速回滚**
```bash
git revert <commit-hash>
```
2. **部分回滚**
如果只有某个修复有问题,可以单独回滚该文件:
```bash
git checkout <previous-commit> -- <file-path>
git commit -m "revert: 回滚 <file-path> 的修改"
```
3. **紧急回滚**
如果影响生产环境,立即回滚到上一个稳定版本:
```bash
git reset --hard <last-stable-commit>
```
---
## 监控指标
修复后需要监控的关键指标:
### 第一阶段
- ENVI 工作流成功率(应该提升)
- 任务状态准确性
- 统计数据准确性
### 第二阶段
- AOI token 内存占用(应该稳定)
- 400 vs 500 错误比例(400 应该增加)
- 路径扫描结果准确性
### 第三阶段
- 解包成功率(应该不变)
- 解包性能(可能略微下降)
- 安全事件(应该为 0
---
## 总结
这个分阶段修复计划:
- ✅ 从低风险到高风险渐进式修复
- ✅ 每个阶段独立可测试、可回滚
- ✅ 优先修复明确的 bug,暂缓架构性改动
- ✅ 保留了灵活性,可以根据实际情况调整
**建议**:先完成第一阶段,充分测试后再决定是否继续第二阶段。