fix: address review report across security, architecture, quality, tests

Implements the fixes from docs/REVIEW_REPORT.md (4-agent parallel review).
typecheck clean; 341 tests pass (16 files, +113); build:web ok; coverage
thresholds (80%) enforced in vitest.config.ts.

Critical:
- multi-device approval race: release held approval only when the last
  client detaches (closing one mirror no longer cancels another's prompt)
- unbounded session creation (DoS): Config.maxSessions cap (env MAX_SESSIONS),
  enforced in manager via the existing M4 exit(-1) path
- signal-handler leak: named SIGINT/SIGTERM/uncaughtException refs removed in close()
- terminal-session initialInput timer tracked + cleared on dispose
- tabs.addEntry null-as-cast type hole removed (build session before entry)

Should-fix:
- security-headers middleware + Origin/CSRF guard on DELETE /live-sessions[/:id]
- history.ts converted to fs/promises (async /sessions handler)
- removed dead clientDims map + blur protocol message end-to-end
- per-connection WS message rate limit (Config.maxMsgsPerSec)
- /sessions behavior kept; documented as accepted LAN risk (TECH_DOC §7)

Tests:
- new tmux / preview-grid / terminal-session (jsdom) / tabs (jsdom) suites
- extended history/config/manager/integration coverage incl. regressions

Hygiene:
- parsePositiveInt -> parseNonNegativeInt; ALLOWED_ORIGINS scheme validation
- log-injection sanitize; isLoopback handles 127.0.0.0/8 + IPv4-mapped
- operational constants moved into Config
- extracted public/preview-grid.ts (DRY launcher/manage)
- doc sweeps: ARCHITECTURE §8 runtime-handle exception, stale comments
This commit is contained in:
Yaojia Wang
2026-06-20 18:27:45 +02:00
parent 97d57326fd
commit d22dcd24f7
30 changed files with 2900 additions and 400 deletions

View File

@@ -421,11 +421,11 @@ client → {resize, cols, rows} // 独立消息,不混进 input
1. 服务端不解析 ANSI/终端语义,PTY 字节是不透明 blob(但 RingBuffer 淘汰须按 chunk/码点边界,不可任意字节切割,M2)。
2. WS 断开 **永不** kill PTY —— 只 detach;`ws.send` 失败也只 detach 该 ws,不动 PTY(M5)。
3. `parseClientMessage` 永不抛异常,非法输入走 `ParseResult.ok=false`
4. 会话 meta 不可变;运行时句柄(ws/detachedAt/lastOutputAt/exitedAt)单独可变
5. 一个会话同一时刻最多一个附着 ws;转发前先重置 `attachedWs` 指针再 close 旧 ws
4. 会话 meta(`SessionMeta`:id/createdAt/shellPath)全 `readonly` 不可变;**运行时句柄字段是明确的例外,允许就地变更**——`clients`(Set)、`detachedAt``lastOutputAt``exitedAt`/`exitCode``claudeStatus``cwd` 是每会话的可变运行时状态(H6)。这是对全局"绝不 mutate"风格的**有意例外**:不可变快照与可变句柄被刻意分离持有,在每次按键时复制一个 Set 是纯粹的浪费。新增运行时状态时,把它放在 `Session`(可变区)而非 `SessionMeta`
5. (v0.4 放宽)一个会话可有**多个**并发附着 ws(多设备镜像共享):新 attach **JOIN(不踢)**;output/exit/status 广播给所有 client;任意 client 可输入(共享控制);PTY 尺寸 **latest-writer-wins**(最近 fit/focus 的设备驱动)。原"同一时刻最多一个 ws"已不再成立
6. 任何 `ws.send` 前必须 `readyState === OPEN`(M5)。
7. Origin 校验在 upgrade 阶段完成(`noServer` + `handleUpgrade`),失败 401,不进入 WS 逻辑;路径限 `/term`(L3)。
8. 无硬编码:所有可变参数走 `config.ts`(端口、shell、TTL、scrollback、maxPayload、WS 路径、allowedOrigins)。
7. Origin 校验在 upgrade 阶段完成(`noServer` + `handleUpgrade`),失败 401,不进入 WS 逻辑;路径限 `/term`(L3)。**状态变更的 HTTP 路由(`DELETE /live-sessions[/:id]`)也复用同一 Origin 白名单做 CSRF 守卫(异源/缺失 → 403)。**
8. 无硬编码:所有可变参数走 `config.ts`(端口、shell、TTL、scrollback、maxPayload、WS 路径、allowedOrigins、maxSessions、maxMsgsPerSec、permTimeoutMs、reapIntervalMs、previewBytes)。
9. `input.data` 原样透传,不做内容过滤。
10. PTY 退出后 `writeInput`/`resize` 静默忽略;exitedAt 会话保留至被 attach 回放或 reapIdle 回收(L1/L4)。
11. 服务端不假设前端已防抖:`resize` 在服务端做幂等(值未变则跳过);WS 帧受 `maxPayload` 上限约束(L5)。