# Phase 2e 逐行走读 — `web/app/*.{html,js}`

**标准**: `docs/project/line-walk-audit-standard.md`  
**日期**: 2026-05-22  
**范围**: 16 个前端文件（14 HTML + `api.js` + `layout.js`）

---

## 走读签字

| 项 | 值 |
|----|-----|
| 计划文件数 | 16 |
| 完成文件数 | 16 |
| 新增 FINDING | 7 |
| 已修复参考 | `terminal.html` WebSSH 专用 token（P0 批次） |

---

## 文件清单

| 文件 | N(约) | esc() | 状态 |
|------|-------|-------|------|
| `api.js` | 190 | — | ✓ |
| `layout.js` | 189 | `_esc` | ✓ |
| `index.html` | 301 | `esc` | ✓ |
| `login.html` | 300 | 局部 | ✓ |
| `servers.html` | 362 | `esc` | ✓ |
| `settings.html` | 402 | `esc` | ✓ |
| `scripts.html` | 102 | `esc` | ✓ |
| `commands.html` | 168 | `esc` | ✓ |
| `terminal.html` | 194 | — | ✓ |
| `push.html` | 232 | `esc` | ✓ |
| `audit.html` | 82 | `esc` | ✓ |
| `schedules.html` | 62 | `esc` | ✓ |
| `retries.html` | 80 | `esc` | ✓ |
| `files.html` | 115 | `esc`+`escAttr` | ✓ |
| `credentials.html` | 165 | `esc` | ✓ |
| `assets.html` | 298 | `esc`+`escAttr` | ✓ |
| `install.html` | 642 | Alpine 绑定 | ✓ |

---

## Findings

| ID | 文件:行 | 规则 | 度 | 描述 | 修复建议 |
|----|---------|------|----|------|----------|
| F2e-01 | `servers.html:161` | FE-01 | P1 | `s.category` 直接拼进 innerHTML，未 `esc()` | `${esc(s.category\|\|'--')}` |
| F2e-02 | `settings.html:147` | FE-05 | P1 | `onclick="saveSetting('${key}')"`，settings key 含 `'` 可断属性/注入 | `escAttr(key)` 或 `data-key` + 监听器 |
| F2e-03 | `index.html:208` | FE-03 | P2 | 告警 WebSocket JWT 在 URL query | 换票/子协议（同后端 P2-6） |
| F2e-04 | `terminal.html:97` | FE-03 | P2 | WebSSH token 仍在 URL（已缩短寿命+绑定 server） | POST 换票 |
| F2e-05 | `api.js:8-33,125` | FE-02 | P2 | JWT 存 localStorage（XSS 可窃取） | CSP + HttpOnly 会话（大改） |
| F2e-06 | 全站 HTML head | FE-06 | P2 | jsDelivr 脚本/样式无 SRI | 自托管 + integrity |
| F2e-07 | `scripts.html:36` | FE-01 | P1 | `s.category` 未 `esc()` 拼入 innerHTML | `${esc(s.category\|\|'ops')}` |

---

## Closure 全表（按文件摘要）

### `api.js`（H=5）

| 行号 | 规则 | 判定 | 理由 |
|------|------|------|------|
| 8-33 | FE-02 | FINDING | P2 | F2e-05 |
| 49-80 | FE-04 | SAFE | — | 统一 `apiFetch` |
| 23,74 | FE-04 | SAFE | — | refresh 用裸 fetch（无 token 头，合理） |
| 163 | FE-01 | SAFE | — | toast 用 `_toastEsc` |
| 125-130 | FE-02 | RISK | P2 | 页面加载即需 token |

### `layout.js`（H=3）

| 行号 | 规则 | 判定 | 理由 |
|------|------|------|------|
| 41-68 | FE-01 | SAFE | — | 侧栏静态模板 |
| 160-172 | FE-01 | SAFE | — | 搜索结果 `_esc()` |
| 152 | FE-04 | SAFE | — | `encodeURIComponent(q)` |

### `index.html`（H=4）

| 行号 | 规则 | 判定 | 理由 |
|------|------|------|------|
| 159-189 | FE-01 | SAFE | — | 列表字段均 `esc()` |
| 208 | FE-03 | FINDING | P2 | F2e-03 |
| 248-249 | FE-01 | SAFE | — | 告警文案 `esc(a.text)` |
| 218-227 | FE-01 | SAFE | — | WS 消息拼文本前未直接 HTML 注入 |

### `login.html`（H=2）

| 行号 | 规则 | 判定 | 理由 |
|------|------|------|------|
| 151,210 | FE-04 | SAFE | — | 登录/刷新用 fetch（公开端点） |
| 263-266 | FE-02 | RISK | P2 | 登录成功写 localStorage |

### `servers.html`（H=2）

| 行号 | 规则 | 判定 | 理由 |
|------|------|------|------|
| 159-160 | FE-01 | SAFE | — | name/domain esc |
| 161 | FE-01 | FINDING | P1 | F2e-01 category 未 esc |
| 156-164 | FE-05 | SAFE | — | onclick 仅数字 id |

### `settings.html`（H=2）

| 行号 | 规则 | 判定 | 理由 |
|------|------|------|------|
| 134-148 | FE-01 | SAFE | — | value/label 多数 esc |
| 147 | FE-05 | FINDING | P1 | F2e-02 |

### `scripts.html` / `commands.html` / `push.html` / `audit.html` / `schedules.html` / `retries.html` / `credentials.html` / `assets.html`

| 规则 | 判定 | 理由 |
|------|------|------|
| FE-01 | SAFE | — | 动态 HTML 普遍 `esc()`；脚本内容在 `<pre>` 内 esc |
| FE-05 | SAFE | — | onclick 多为数字 id |

### `commands.html`（补充）

| 行号 | 规则 | 判定 | 理由 |
|------|------|------|------|
| 95 | FE-05 | RISK | P2 | session_id 在 onclick 引号内（UUID 低风险） |

### `files.html`（ exemplar）

| 行号 | 规则 | 判定 | 理由 |
|------|------|------|------|
| 64,73,104 | FE-05 | SAFE | — | `escAttr()` 用于路径 onclick |
| 54,75 | FE-01 | SAFE | — | 文件名 `esc(e.name)` |

### `terminal.html`（H=2）

| 行号 | 规则 | 判定 | 理由 |
|------|------|------|------|
| 79-97 | FE-03 | FINDING | P2 | F2e-04；已用 webssh-token |
| 79 | FE-02 | SAFE | — | 仅检查 access_token 存在 |

### `install.html`（H=1）

| 行号 | 规则 | 判定 | 理由 |
|------|------|------|------|
| 7-8 | FE-06 | FINDING | P2 | CDN 无 SRI |
| 全文 | FE-01 | SAFE | — | Alpine `x-text`/`x-model`，无危险 innerHTML 拼用户输入 |

---

## 八类核对

| 类 | 条数 |
|----|------|
| VULN/STYLE | 2（F2e-01/02 XSS） |
| RISK | 4（JWT 存储、WS URL、CDN） |
| SAFE 主导 | 其余页面 esc 体系一致 |

---

## Phase 1 复核

| 项 | 结论 |
|----|------|
| Phase 1 XSS innerHTML | **VERIFY** — `servers.html:161`、`settings.html:147` 仍缺 esc |
| WebSSH | **FIXED** — `terminal.html` 已改 webssh-token 流程 |

---

## 下一批

**Phase 2f**: `web/agent/` + `tests/` + `scripts/`（标准推荐批次）
