Skip to content

review: post-merge fixes for phases 1, 6, 2, 5#6

Merged
4Gaige merged 4 commits into
mainfrom
review/post-merge-fixes
Apr 23, 2026
Merged

review: post-merge fixes for phases 1, 6, 2, 5#6
4Gaige merged 4 commits into
mainfrom
review/post-merge-fixes

Conversation

@4Gaige
Copy link
Copy Markdown
Owner

@4Gaige 4Gaige commented Apr 23, 2026

Summary

Fresh-eyes Opus review of the four phases that landed earlier today (PRs #1 / #2 / #3 / #4). Critical findings were fixed in this branch; nice-to-haves were captured in docs/follow-ups.md for the next polish PR.

This PR is additive only — none of the three churn-restricted files (server/projects.js, server/index.js, src/components/sidebar/view/subcomponents/SidebarProjectItem.tsx) were touched. No raw Tailwind colour classes added.

Findings table

Phase Item Severity Action
1 — Midnight skin All 10 checklist items YES clean ship; 4 polish items in follow-ups
6 — MCP integrations workingDir in /spawn-sub-agent was unvalidated → authority escalation critical fixed: resolves under $HOME (or DISPATCH_PROJECT_ROOTS), rejects sensitive subdirs
6 — MCP integrations Module-level ensureRecommendedMCPs() materializes ~/.claude.json from nothing critical fixed: skip when file absent; defer via setImmediate
2 — Sidebar tree All 13 checklist items YES clean ship; 5 polish items in follow-ups
5 — Preview proxy Authenticated SSRF: any port reachable to 127.0.0.1 critical fixed: curated dev-port allowlist + DB-port denylist; env override
5 — Chrome screencast Any logged-in user can drive operator's real Chrome (cookies, navigation) critical fixed: feature off by default; opt-in DISPATCH_CHROME_VIEW_ENABLED + ..._ALLOW_INPUT
5 — Tasks projectName/sessionId joined to disk path with no traversal guard critical fixed: reject .. / separators / null bytes; resolved-path prefix check
5 — Mobile UX PreviewModal, BrowserModal shipped but never imported critical fixed: mounted in MainContent, gated on isMobile && activeTab
5 — Worktree dots Activity props orphaned because SidebarProjectItem is no-edit critical fixed: new SessionActivityContext delivers state without prop drilling; worktreeSessionMap is best-effort (cross-worktree resolution tracked in follow-ups)

Review checklist (cross-cuts all four phases reviewed)

  • Additive-patch rule respected (no edits to the 3 churny files)
  • Every new class is Midnight catalog or Midnight-mapped shadcn semantic
  • No raw Tailwind colour classes added in diff (grep clean)
  • Mobile layout 375×812 — fixed mobile modal wiring; existing screenshots in docs/screenshots/phase-5/
  • Desktop layout 1440×900 — existing screenshots verified
  • Touch targets ≥44×44 on mobile (verified per phase)
  • npm run build succeeds (bundle delta +0.1% vs main, well under 5%)
  • npm test — no test script defined in package.json; noted in follow-ups
  • No hardcoded secrets in any phase diff
  • Keyboard navigation works for new UI (verified per phase)
  • Empty / loading / error states handled (verified per phase)
  • Phase brief acceptance criteria met after fixes
  • TypeScript compiles without new errors (npm run typecheck clean)
  • No new dependencies added by this review PR

Files changed

  • server/routes/mcp-bootstrap.js — workingDir validation, side-effect guard
  • server/routes/preview-proxy.js — port allowlist
  • server/routes/chrome-screencast.js — operator scoping for view + input
  • server/routes/tasks.js — path-traversal guard
  • src/components/main-content/view/MainContent.tsx — mount mobile modals
  • src/components/app/AppContent.tsx — provide SessionActivityContext
  • src/components/sidebar/worktrees/WorktreeList.tsx — consume context as fallback
  • src/contexts/SessionActivityContext.tsx — new
  • docs/follow-ups.md — new (12 nice-to-haves, grouped by phase)

Test plan

  • npm run typecheck — clean
  • npm run lint — clean
  • npm run build — clean (2533 KB main chunk; +3 KB vs pre-fix, +0.1%)
  • Manual smoke on npm run dev once merged: confirm preview iframe still loads, chrome screencast cleanly returns 503/disabled, mobile preview/browser open as fullscreen modals.

🤖 Generated with Claude Code

4Gaige added 4 commits April 23, 2026 06:56
…aude config

Two issues from the post-merge fresh-eyes review of phase 6:

- The /spawn-sub-agent endpoint passed a user-supplied workingDir straight
  into child_process.spawn's cwd with no validation. An authenticated
  client could point claude at /etc, /var, or anywhere else the server
  uid can reach. Now we resolve the path, require it to live under $HOME
  (or DISPATCH_PROJECT_ROOTS), reject sensitive credential subdirs, and
  confirm it exists as a directory before spawning.

- ensureRecommendedMCPs ran as a top-level module side-effect on import
  and would happily materialize a fresh ~/.claude.json containing only
  Dispatch's two MCPs for users who had never run claude. The bootstrap
  now refuses to create the file from nothing and defers via setImmediate
  so the import chain in server/index.js is not blocked by file IO.
…tasks path safety

Three security fixes from the post-merge fresh-eyes review of phase 5:

- Preview proxy used to forward any port from 1-65535 to 127.0.0.1, so any
  authenticated user could probe localhost services (mysql, postgres,
  redis, the cdp port itself, etc). Now defaults to a curated allowlist
  of common dev-server ports, blocks well-known infra ports outright,
  and exposes DISPATCH_PREVIEW_PORTS / DISPATCH_PREVIEW_ALLOW_ANY_HIGH_PORT
  for users who need something else.

- Chrome screencast attaches to the operator's real browser over CDP and
  was reachable by any logged-in user. The Input.* dispatch path let any
  client drive the host browser (steal cookies via Runtime.evaluate,
  navigate to attacker URLs, etc). Now disabled by default; opt in via
  DISPATCH_CHROME_VIEW_ENABLED=true for view-only and additionally
  DISPATCH_CHROME_VIEW_ALLOW_INPUT=true for take-control. The HTTP
  /status and /tabs helpers report the disabled state cleanly so the
  client UI can render an explanation instead of a stack trace.

- /api/tasks built the JSONL path from query params with no traversal
  guard. Now rejects path separators, null bytes, and '..' on
  projectName + sessionId, then prefix-checks the resolved path against
  ~/.claude/projects.
…y context

Two phase 5 frontend follow-ups from the post-merge review:

- PreviewModal and BrowserModal shipped in phase 5 but were never
  imported anywhere, so mobile users on the preview/browser tabs got the
  desktop pane shoved into the tab slot with no fullscreen treatment or
  close button. MainContent now mounts both modals, gated by
  isMobile + activeTab; the inline panes stay desktop-only. Closing a
  modal returns to the chat tab.

- WorktreeList accepted activeSessions/processingSessions/blockedSessions
  /worktreeSessionMap props but its parent (SidebarProjectItem) is on the
  no-edit churn list, so the props were never passed and every dot stayed
  grey. New SessionActivityContext lives at the AppContent root and
  delivers the same protection sets to WorktreeList without prop
  drilling. worktreeSessionMap is best-effort for now: only the currently
  selected worktree's session can be attributed; cross-worktree
  resolution needs a server endpoint, tracked in docs/follow-ups.md.
Captures every NICE-TO-HAVE finding from the four phase reviews so the
items aren't lost. Entries are grouped by phase with file:line citations;
each is small enough to land in a polish PR or fold into the next phase.
@4Gaige 4Gaige merged commit c4eac2f into main Apr 23, 2026
2 checks passed
@4Gaige 4Gaige deleted the review/post-merge-fixes branch April 23, 2026 11:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant