spec: add SPEC-019 private Backstage session (GUI private desktop for interactive uninstall)

Extends SPEC-013 backstage from terminal-only to a private GUI desktop so a tech
can drive a stubborn uninstaller's UI invisibly to the logged-on user. Builds on
SPEC-018 broker; deep-linked from GuruRMM SPEC-030 'needs remote removal' flag.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-22 16:05:43 -07:00
parent ded99c5882
commit f8f384f8d8
2 changed files with 120 additions and 0 deletions

View File

@@ -0,0 +1,119 @@
# 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).