Skip to content

v2 hardening + v3 web agent: client alignment, fixes, and a secured browser UI#7

Open
m4cd4r4 wants to merge 5 commits into
masterfrom
claude/amazing-davinci-JSUmc
Open

v2 hardening + v3 web agent: client alignment, fixes, and a secured browser UI#7
m4cd4r4 wants to merge 5 commits into
masterfrom
claude/amazing-davinci-JSUmc

Conversation

@m4cd4r4
Copy link
Copy Markdown
Owner

@m4cd4r4 m4cd4r4 commented Jun 1, 2026

Overview

Addresses the v2.0 drift between the three clients (desktop app, MCP server, VS Code extension), fixes a set of real bugs, and scaffolds a v3 web interface: the same PortPilot UI in a browser, served by a hardened loopback agent.

⚠️ Testing note: all backend/security/transport behaviour was verified here with curl + Node, and every file syntax-checked / type-checked. The Electron UI and browser-rendered delegation could not be run in the headless build env — please click-test those locally (npm start, npm run agent, and Settings → Web Access).


🔧 v2 fixes & alignment (1ed725c, 10bbe65)

  • Config-path unification — MCP server + VS Code extension used %APPDATA%/PortPilot (capital) while Electron uses lowercase portpilot; on Linux/macOS they read a different file and never saw the desktop app's apps. All three now share one path.
  • Config data-loss fixsaveApp rebuilt a fixed-shape object that dropped description/startupDelay; starring/editing an MCP-added app destroyed its description. Now merges onto the existing record (runtime-verified).
  • Live auto-scan — the autoScan/scanInterval settings were inert; wired a real interval (pauses when hidden).
  • VS Code perf — scanning was synchronous + one wmic per port on the UI thread every 10s; now async with a single batched tasklist.
  • Dropped wmic (deprecated/removed on Windows 11) → PowerShell/CIM with a wmic fallback; non-English killPort fixed in MCP + extension (no more findstr LISTENING).
  • MCP — honest start_app (polls the port; reports real failure instead of false success); two-phase running detection (CWD/keyword, not just exact port); concurrent bulk_start.
  • Desktop — app log viewer (streams captured stdout/stderr), opt-in window auto-resize, app-card density + keyboard a11y, dead toast IPC channel fixed.
  • Docs/tests — corrected the false "36/36" badge, the theme count (6, not 7 — "Brutalist Light" doesn't exist), and broken v2.0.0 Linux download links; rewrote the E2E smoke suite for the v2 single-pane UI.

🌐 v3 web agent (f77e745, acabc3c, 95f70bf)

Run PortPilot in a browser without Electron: npm run agenthttp://127.0.0.1:7317/, or toggle it on from Settings → Web Access in the desktop app (runs in-process, sharing the same config + process table).

Architecture (no logic duplication):

  • src/core/configPath.js — shared cross-platform path resolver.
  • src/core/dispatch.js — transport-agnostic action dispatcher reusing the existing backend modules.
  • src/agent/server.js — embeddable hardened HTTP server (createAgent + standalone CLI).
  • src/agent/public/portpilot-web.jswindow.portpilot fetch shim, so the renderer runs in a browser unchanged.

Security model (see SECURITY.md) — layered, verified with curl:

  • binds 127.0.0.1 only; per-session 256-bit token (constant-time compare)
  • Host-header allowlist (defeats DNS rebinding); Origin allowlist + locked CORS
  • custom header forces CORS preflight (blocks simple-request CSRF)
  • CSP with no inline scripts — all ~48 inline on* handlers converted to delegated data-act/data-change listeners, so script-src 'self' (no 'unsafe-inline')
  • 1 MB body cap, path-traversal guard, agent.json chmod 600
  • SSE /events for live updates (external MCP edits push to the browser)

Probed results: bad Host → 403, no token → 401, foreign Origin → 403, valid → 200; SSE delivers config-changed; traversal blocked; token file 600.


Commits

  • 1ed725c align clients, stop config data loss, live auto-scan, faster VS Code, drop wmic
  • 10bbe65 honest MCP start/detection, app log viewer, opt-in resize, card a11y
  • f77e745 v3 preview — hardened loopback web agent + web UI shim
  • acabc3c SSE live updates, auto-launch, in-app opt-in toggle (Option C)
  • 95f70bf remove script-src 'unsafe-inline' via event delegation

Suggested local test checklist

  • npm start — desktop app loads; cards, drag-reorder, checkboxes, keyboard expand work (delegation)
  • Settings → Web Access toggle → Open in Browser
  • npm run agent — browser UI loads and is functional
  • Windows: process names/memory populate (PowerShell path); killPort works
  • MCP server + desktop app see the same apps (config path)

https://claude.ai/code/session_01JB8Xqf9D5iD2uEUhBjFC8c


Generated by Claude Code

claude added 5 commits June 1, 2026 09:41
…Code, drop wmic

Cross-cutting fixes addressing v2.0 drift between the desktop app, MCP server,
and VS Code extension.

Config path unification
- MCP server and VS Code extension used %APPDATA%/PortPilot (capital), while the
  Electron app uses app.getPath('userData') -> lowercase "portpilot". On
  case-sensitive Linux/macOS the two clients read a DIFFERENT file and never saw
  the desktop app's apps. All three now use the lowercase "portpilot" path.

Config data loss
- ConfigStore.saveApp rebuilt a fixed-shape object that dropped fields such as
  description and startupDelay, so starring or editing an MCP-added app destroyed
  its description. saveApp now merges onto the existing record, preserving fields
  the caller didn't supply while still applying real edits.

Live auto-scan (desktop)
- The autoScan/scanInterval settings were inert; the app only scanned once at
  startup. Wired a real interval that silently refreshes ports + running status,
  skipping work while the window is hidden or a startup countdown is active.

VS Code extension performance
- Port scanning was synchronous and spawned one wmic process per listening port
  on the extension-host thread every 10s, causing the slow/janky load. Now async
  with a single batched tasklist lookup for all PIDs.

Drop wmic / locale fixes
- wmic is deprecated/removed on recent Windows 11. Desktop scanner now resolves
  process name + command line via PowerShell/CIM (wmic fallback for old systems);
  process details (memory/uptime) likewise. MCP scan uses a single tasklist call.
- killPort in the MCP server and VS Code extension matched "LISTENING" from
  findstr, which fails on non-English Windows (the bug fixed in the app in v1.7);
  both now resolve the PID via the locale-independent scan.

Misc correctness + docs
- Whitelisted the 'toast' IPC channel so the tray "Stop All Apps" confirmation
  reaches the renderer, and refresh the app list when it fires.
- Rewrote the E2E smoke suite for the v2.0 single-pane UI (the old suite drove
  the removed tab layout); repointed test scripts off the stale legacy specs.
- README: corrected the test badge/claims, theme count (6, not 7 - "Brutalist
  Light" doesn't exist), and the broken v2.0.0 Linux download links.

https://claude.ai/code/session_01JB8Xqf9D5iD2uEUhBjFC8c
… a11y

MCP server
- start_app now reports an HONEST result: when the app has a preferredPort it
  polls for the port to come up (so a command that fails immediately reports
  failure instead of a misleading "success"); without a port it says the result
  is unverified. bulk_start runs concurrently and reports succeeded/attempted.
- Running detection rewritten to mirror the desktop app's two-phase matching
  (command-line CWD match + folder/name-keyword evidence, then preferredPort).
  Apps without a preferredPort, or on a dynamic port, are now detected instead
  of always reading "stopped". get_status uses the same logic; list output now
  includes detectedPort.

Desktop app
- App log viewer: PortPilot-managed running apps get a "logs" button that opens
  a modal streaming captured stdout/stderr (2s refresh, auto-scroll, copy). The
  plumbing (process.logs IPC) already existed but was never surfaced.
- Window auto-resize is now opt-in via a new "Auto-resize window to app count"
  setting (default off) instead of always fighting manual resizing.
- App-card density + accessibility: drag handle and checkbox are revealed on
  hover/focus instead of always shown; cards are keyboard-operable (tabindex,
  role, aria-expanded, Enter/Space toggles details) with a focus ring; the
  selection checkbox has an aria-label.

https://claude.ai/code/session_01JB8Xqf9D5iD2uEUhBjFC8c
Adds an opt-in way to run PortPilot as a local web app (same UI, in the
browser) without Electron, via `npm run agent`. The desktop app and
ipcHandlers are untouched.

Architecture
- src/core/configPath.js: shared cross-platform config-path resolver so the
  desktop app, MCP server, and agent all read the SAME file (defers to
  Electron's userData when present, computes the lowercase "portpilot" dir
  otherwise). ConfigStore now uses it.
- src/core/dispatch.js: transport-agnostic action dispatcher mapping the
  preload's IPC action names to the port/process/config/discovery/docker
  operations. Reuses the existing low-level modules and exports
  matchPortsToApps/getProcessDetails from ipcHandlers (no logic duplication).
- src/agent/server.js: hardened HTTP server. Serves the existing renderer
  unchanged (injecting a token <meta> + the web shim), routes POST /api to the
  dispatcher.
- src/agent/public/portpilot-web.js: window.portpilot shim over fetch, same
  surface as the Electron preload, so the renderer runs in a browser as-is.

Security (see SECURITY.md) - layered, verified with curl:
- binds 127.0.0.1 only; per-session 256-bit token (constant-time check);
- Host-header allowlist (defeats DNS rebinding); Origin allowlist + locked CORS;
- custom header forces CORS preflight (blocks simple-request CSRF);
- 1MB body cap; path-traversal guard; agent.json written chmod 600;
- bad Host -> 403, no token -> 401, foreign Origin -> 403, valid -> 200.

Known preview limits documented in SECURITY.md (token grants full control, no
TLS on loopback, no live push events yet, one backend at a time).

https://claude.ai/code/session_01JB8Xqf9D5iD2uEUhBjFC8c
…(Option C)

- Embeddable agent: server.js now exports createAgent({configStore}) with
  start/stop/getInfo, plus the standalone CLI. The Electron main process runs it
  IN-PROCESS sharing the desktop configStore + process table, so apps started
  from the browser and the desktop are the same.
- Option C: opt-in "Web Access" toggle in Settings (desktop only). agent:start/
  stop/status/open IPC handlers, preload `agent` API, renderer wiring; the
  section auto-hides in the web build. Agent stops on app quit.
- SSE live updates: agent serves /events (Host+Origin+token checked; token via
  query since EventSource can't set headers). ConfigStore now fires an optional
  onConfigChange callback, which the agent broadcasts - so external MCP edits
  push to the browser live. Web shim's `on()` subscribes via EventSource.
- Auto-launch: standalone `npm run agent` opens the default browser
  (suppressed by --no-open or PORTPILOT_NO_OPEN).

Verified on Linux: SSE delivers config-changed on external edits; /events
returns 401 without token; auto-open suppressed by env flag.

https://claude.ai/code/session_01JB8Xqf9D5iD2uEUhBjFC8c
Converts every inline on* handler (≈48 across renderer.js + index.html) to
delegated listeners using data-act / data-change attributes routed through a
single click/change/keydown/drag delegation layer. Drag handlers rewritten to
resolve their card via closest() instead of currentTarget, and delegated on the
apps-list container.

This lets the Content-Security-Policy drop 'unsafe-inline' from script-src:
  default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self';
  connect-src 'self'; img-src 'self' data:
removing the main XSS -> code-execution vector. (style-src still allows inline
styles for things like group colour dots, which can't execute code.)

Bonus: data attributes replace the fragile inline-string escaping
(.replace(/\\/g,'\\\\').replace(/'/g,...)) for command lines and paths.

Verified: agent serves the hardened CSP; no inline on* handlers remain in the
served HTML; renderer emits data-act markup. SECURITY.md updated.

https://claude.ai/code/session_01JB8Xqf9D5iD2uEUhBjFC8c
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.

2 participants