# SPEC-019: Private Backstage Session (interactive uninstall / hidden desktop) **Status:** Proposed **Priority:** P2 **Requested By:** Howard (2026-06-22) **Estimated Effort:** Large ## Overview A **private session** mode for GuruConnect: the tech gets an interactive remote view of a process's UI on the target machine that the **logged-on user does not see**. The motivating use case is GuruRMM's Tier-2 software removal — when a program has no silent uninstall (flagged `needs_remote` by the RMM removal engine), the tech opens a Backstage session, the stubborn uninstaller's GUI runs on a **private desktop**, and the tech clicks through it remotely while the end user's screen is undisturbed. This is GuruConnect's equivalent of ScreenConnect Backstage. **Success criteria:** - A tech can launch a program (e.g. an uninstaller) on a target and drive its UI remotely. - The logged-on user does not see the window or the interaction (private-desktop mode). - Works with no interactive user logged in (system-spawned private desktop). - Deep-linkable from the GuruRMM "Needs remote removal" flag. ## The Windows constraint (why this is non-trivial) A GUI process needs an interactive **window station + desktop**. The agent runs as SYSTEM in **Session 0**, which is isolated and cannot present UI. So GuruConnect must either (a) run the target process on the **active user's desktop** and mirror it (visible to the user), or (b) create a **separate private desktop** (`CreateDesktop`) and stream only that desktop to the tech (invisible to the user). (b) is the Backstage model. ## Scope ### Included in v1 - **Private-desktop capture/input:** capture a *specific* desktop's framebuffer and inject input into it (not just the active console desktop), via a dedicated window station/desktop the agent creates (`CreateWindowStation`/`CreateDesktop` + `SetThreadDesktop` on the capture/input threads). - **Launch-process-in-session:** start an arbitrary command (the uninstaller) bound to that private desktop, as SYSTEM or as a chosen user token. - **Backstage viewer mode** in the web/native viewer: a session flagged "private" with a clear "user cannot see this" indicator. - **GuruRMM deep link:** accept a launch target (program path/uninstall string) so the RMM `needs_remote` action opens straight into a Backstage session pointed at that program. ### Explicitly out of scope - Mirroring the *active user* desktop — that is ordinary remote control (already covered); this spec is specifically the **private/hidden** desktop. - Logging in a brand-new interactive user session (secondary-logon) — heavier and fragile; considered in Future Considerations only. - The RMM-side removal engine + tracking (lives in GuruRMM SPEC-030; this is the GC counterpart). ## Architecture - **agent (Windows):** new private-desktop subsystem — create a window station + desktop, run the capture loop with `SetThreadDesktop` bound to it (DXGI won't capture an off-screen desktop, so fall back to GDI `BitBlt`/`PrintWindow` for the private desktop), inject input with `SendInput`/`PostMessage` targeted at that desktop's windows. Process launch via `CreateProcessAsUser`/`CreateProcess` with `STARTUPINFO.lpDesktop` set to the private desktop. - **relay-server (Axum):** a new session kind `private`/`backstage`; same protobuf transport, flagged so the dashboard/viewer render the "hidden from user" state and audit it distinctly. - **viewer:** render the private-desktop stream; banner indicating the user can't see it. - **dashboard:** "Start Backstage session" entry; accept the deep-link launch target. - **proto (`proto/guruconnect.proto`):** add a session-mode enum (`NORMAL`/`PRIVATE`) and a `LaunchProcess { command, desktop: PRIVATE, run_as }` control message. ## Implementation details - Capture: DXGI Desktop Duplication is tied to the active output/desktop; a private off-screen desktop is **not** duplicatable that way — use GDI capture of the private desktop's windows (`PrintWindow` per top-level window or `BitBlt` of the desktop DC after `SetThreadDesktop`). Lower FPS is acceptable for clicking through an installer. - Input: `SendInput` operates on the calling thread's desktop — bind the input thread with `SetThreadDesktop(hPrivateDesktop)` before injecting; for stubborn controls use `PostMessage`/`SendMessage` to the target HWND. - Lifecycle: tear down the private desktop + window station when the session ends; kill orphaned child processes bound to it. ## Security considerations - A private/hidden session that the end user cannot see is **powerful and must be audited loudly** — record start/stop, operator, target, and the launched command. Consider an org/policy toggle to require consent or to disallow private mode per tenant. - Reuse GuruConnect's existing auth (JWT/support code/agent key). Launching arbitrary processes as SYSTEM on a private desktop is high privilege — gate behind tech-role auth. - Threat model: a hidden remote session is an attractive abuse target; treat parity with the existing remote-control trust boundary plus extra audit. ## Testing strategy - Unit: desktop/window-station create+destroy; thread-desktop binding. - Manual: launch Notepad on a private desktop, confirm the logged-on user does NOT see it and the tech can type into it; then a real GUI uninstaller (e.g. an NVIDIA/Office leftover) driven to completion; then with no user logged in. - Integration: RMM `needs_remote` deep-link opens a Backstage session at the right program. ## Relationship to existing specs (read first) - **SPEC-018 (managed-agent SYSTEM service host + session broker)** is the prerequisite — it provides the SYSTEM service + per-session worker spawning this builds on. - **SPEC-013 (session selection + backstage)** already defines backstage as a **terminal/command** interface (services management). SPEC-019 **extends** backstage from terminal-only to a **private GUI desktop** — the ability to see and click an arbitrary program's *window* (an uninstaller) that the logged-on user cannot see. Consider folding this into SPEC-013 as its "GUI backstage" phase if the team prefers one spec. ## Effort estimate & dependencies - **Large.** Depends on SPEC-018 (broker) and the existing capture/input pipeline; complements SPEC-013 (backstage). The private-desktop GDI capture + input-desktop binding is the hard, novel part. Unblocks GuruRMM Tier-2 interactive removal and any "do something the user shouldn't see" support workflow. ## Open questions - DXGI vs GDI for the private desktop — confirm DXGI truly can't target it; measure GDI FPS. - Run the uninstaller as SYSTEM or as the logged-on user's token on the private desktop? (Some per-user uninstallers need the user's profile/HKCU.) - Default posture: should private mode require explicit per-session consent or a tenant policy? ## References - GuruRMM SPEC-030 (remote software inventory + bulk uninstall) — the `needs_remote` flag + the removal knowledge base that feeds this. Tier-2 is the last resort; the RMM vendor table shrinks the set over time. - ScreenConnect Backstage (prior art for a hidden support session).