Files

108 lines
4.5 KiB
Markdown
Raw Permalink Normal View History

# 审计记录:文件管理页全面巡检(面包屑 + 路径 + 浏览契约)
## 审计信息
- **日期**: 2026-06-01
- **审计人**: Claude (AI)
- **触发原因**: 用户反馈 `#/files` 面包屑不可用,要求全面巡检
## 审计范围(实际改动文件清单)
| 文件 | 行数 | 状态 |
|------|------|------|
| `frontend/src/pages/FilesPage.vue` | 615 | ☑ 已审(全文) |
| `frontend/src/utils/remotePath.ts` | 72 | ☑ 已审(全文) |
| `frontend/src/utils/fileBrowse.ts` | 51 | ☑ 已审(全文) |
| `frontend/src/types/api.ts` | BrowseResponse 段 | ☑ 已审 |
| `server/api/sync_v2.py` | browse_directory L142-216 | ☑ 已审 |
## 巡检结论摘要
| 问题 | 根因 | 处置 |
|------|------|------|
| 面包屑点击无效 | Vuetify 4 不消费 `props.onClick` | `#item` 槽 + `onBreadcrumbClick(index)` |
| 路径栏与列表路径不一致 | 未规范化 `//`、无尾斜杠 | `normalizeRemotePath` 统一 |
| 空目录不显示 | `items?.length` 误判 | `Array.isArray(body.items)` |
| 无法返回上级 | 列表过滤 `..` | 工具栏「上级」+ 面包屑 |
| 批量删除失败无反馈 | 空 `catch` | 统计成功/失败条数 |
## 审计8步结果
### Step 1: 登记 ✅
变更主题:文件管理 SPA 路径导航与 browse API 契约修复。
### Step 2: 全文 Read ✅
- `FilesPage.vue`:模板 + script 全文 Read
- `remotePath.ts``fileBrowse.ts`:全文 Read
- `sync_v2.py``browse_directory` 及相邻 `file-ops` 对照 Read
### Step 3: 规则扫描 H
| H# | 规则 | 文件:行号 | 说明 |
|----|------|-----------|------|
| PY-03 | SSH 命令拼接 | `sync_v2.py:166` | `ls -la` + `shlex.quote(path)` → 扫描 |
| PY-05 | JWT | `sync_v2.py:146` | `Depends(get_current_admin)` → 扫描 |
| PY-09 | 静默吞错 | `FilesPage.vue:427-435`(改前) | 批量删除空 catch → 已改为计数 |
| FE-01 | XSS | `FilesPage.vue:83` | `{{ item.name }}` 文本插值 → 扫描 |
| FE-04 | 裸 fetch | `FilesPage.vue` | 均经 `http` 封装 → 无命中 |
### Step 4: Closure 表
| 文件 | 行号 | 规则 | 判定 | 严重度 | 理由 |
|------|------|------|------|--------|------|
| sync_v2.py | 166 | PY-03 | SAFE | — | `path` 经 Pydantic + `shlex.quote`,不经 shell 拼接用户片段 |
| sync_v2.py | 146 | PY-05 | SAFE | — | JWT 管理员鉴权 + 审计 `browse_directory` |
| FilesPage.vue | 83 | FE-01 | SAFE | — | Vue 默认转义;文件名来自 SSH ls 输出 |
| FilesPage.vue | 427-438 | PY-09 | SAFE | — | 批量删除逐条 try/catch 并汇总 fail 计数 |
| FilesPage.vue | 293-301(旧) | UX/BUG | FINDING→已修复 | P2 | `props.onClick` 在 Vuetify 4 无效;已用 `#item` 槽 |
### Step 5: 入口表
| 方法 | 路径 | 鉴权 | 说明 |
|------|------|------|------|
| POST | `/api/sync/browse` | JWT `get_current_admin` | 远程目录列表(本次契约 `items` |
| POST | `/api/sync/file-ops` | JWT | 删/改名/建目录 |
| POST | `/api/sync/read-file` | JWT | 读文件(预览/编辑) |
| POST | `/api/sync/upload` | JWT | 上传 |
| POST | `/api/sync/download` | JWT | 下载 |
| POST | `/api/sync/chmod` | JWT | 改权限 |
### Step 6: 输入→Sink
| 输入 | Sink | 防护 |
|------|------|------|
| `currentPath`(路径栏/面包屑) | `POST /sync/browse` body.path | 前端 `normalizeRemotePath`;后端 `shlex.quote` |
| `item.name`(行点击) | `joinRemotePath` → browse | 单段拼接,不含 `/` |
| `mkdirName` | `file-ops` mkdir | `joinRemotePath` + 后端 `shlex.quote` |
| 上传 `FormData` | `/sync/upload` | JWT + 既有上传校验(未改) |
### Step 7: 归类
```
frontend/src/pages/FilesPage.vue 615行 5H扫描 1FINDING(已修复)
frontend/src/utils/remotePath.ts 72行 2H扫描 0FINDING
frontend/src/utils/fileBrowse.ts 51行 1H扫描 0FINDING
server/api/sync_v2.py (browse段) 75行 2H扫描 0FINDING
────────────────────────────────────────────────────────
总计 0 未关闭 FINDING
```
### Step 8: DoD ✅
- [x] 面包屑可点击跳转且当前层级 `disabled`
- [x] 路径栏 Enter/箭头与列表目录进入使用同一 `normalizeRemotePath`
- [x] 空目录 `items: []` 正常展示
- [x] `ruff check server/api/sync_v2.py` 通过
- [x] `npm run type-check` 通过
- [x] changelog 已更新
## FINDING 列表
无未关闭 FINDING(面包屑 P2 已在本次提交中修复)。
## 结论
☑ 审计通过,0 未关闭 FINDING,可部署前端构建产物至 `web/app/`