v2 hardening + v3 web agent: client alignment, fixes, and a secured browser UI#7
Open
m4cd4r4 wants to merge 5 commits into
Open
v2 hardening + v3 web agent: client alignment, fixes, and a secured browser UI#7m4cd4r4 wants to merge 5 commits into
m4cd4r4 wants to merge 5 commits into
Conversation
…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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.
🔧 v2 fixes & alignment (
1ed725c,10bbe65)%APPDATA%/PortPilot(capital) while Electron uses lowercaseportpilot; on Linux/macOS they read a different file and never saw the desktop app's apps. All three now share one path.saveApprebuilt a fixed-shape object that droppeddescription/startupDelay; starring/editing an MCP-added app destroyed its description. Now merges onto the existing record (runtime-verified).autoScan/scanIntervalsettings were inert; wired a real interval (pauses when hidden).wmicper port on the UI thread every 10s; now async with a single batchedtasklist.wmic(deprecated/removed on Windows 11) → PowerShell/CIM with a wmic fallback; non-EnglishkillPortfixed in MCP + extension (no morefindstr LISTENING).start_app(polls the port; reports real failure instead of false success); two-phase running detection (CWD/keyword, not just exact port); concurrentbulk_start.toastIPC channel fixed.🌐 v3 web agent (
f77e745,acabc3c,95f70bf)Run PortPilot in a browser without Electron:
npm run agent→http://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.js—window.portpilotfetch shim, so the renderer runs in a browser unchanged.Security model (see
SECURITY.md) — layered, verified withcurl:127.0.0.1only; per-session 256-bit token (constant-time compare)on*handlers converted to delegateddata-act/data-changelisteners, soscript-src 'self'(no'unsafe-inline')agent.jsonchmod 600/eventsfor 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 file600.Commits
1ed725calign clients, stop config data loss, live auto-scan, faster VS Code, drop wmic10bbe65honest MCP start/detection, app log viewer, opt-in resize, card a11yf77e745v3 preview — hardened loopback web agent + web UI shimacabc3cSSE live updates, auto-launch, in-app opt-in toggle (Option C)95f70bfremovescript-src 'unsafe-inline'via event delegationSuggested local test checklist
npm start— desktop app loads; cards, drag-reorder, checkboxes, keyboard expand work (delegation)npm run agent— browser UI loads and is functionalkillPortworkshttps://claude.ai/code/session_01JB8Xqf9D5iD2uEUhBjFC8c
Generated by Claude Code