Desktop transport comparison: initial container experiment

Desktop transport comparison: initial container experiment

Date: 2026-09-13. Scope: #842 prerequisite for #856 and AIWG #2546/#2547. Status: partial; adapter selection remains open.

What was proved

A disposable XFCE container and private guacd worker exercised real browser display/input through both Guacamole/RDP and noVNC/TigerVNC. Each path typed into Mousepad, disconnected with the document unsaved, reconnected at a changed resolution, appended text, and saved the document through the GUI. Guest file readback matched both parts, and the corresponding display-server PID remained the same.

CheckRDP/GuacamoleTigerVNC/noVNC
Browser input produced the expected saved markerPassPass
Unsaved content survived disconnect/reconnectPassPass
Display-server PID retainedPass, Xorg `:10`Pass, Xtigervnc `:1`
Changed reconnect size1280×800 → 1024×7681280×800 → 1024×768
Initial display check, one sample2037 ms650 ms
Resized reconnect check, one sample2053 ms577 ms

Timing includes a fixed 500 ms settling pause and local automation overhead. These single samples are functional readiness observations, not input latency, percentile measurements, production performance, or a basis for selecting the adapter.

Correct RDP certificate pinning produced display updates. A wrong pin produced Guacamole error 519 within 454 ms and no display-size/image instructions. The pinned FreeRDP build expects `sha256:` followed by colon-separated bytes. Compact hex failed verification. Certificate checking stayed enabled.

The browser fixture's final configuration disables RDP clipboard copy/paste, audio output/input, drive forwarding and printing. This configuration alone does not qualify adversarial channel enforcement or production defaults.

Fixture versions

ComponentObserved version
XFCE4.18
xrdp0.9.21.1-1+deb12u3
xorgxrdp1:0.9.19-1
TigerVNC1.12.0+dfsg-8
noVNC1:1.3.0-1
Guacamole client/server1.6.0
Worker FreeRDP library2.11.7
Guest Chromium / sandbox helper152.0.7977.82-1~deb12u1
Automation Playwright1.63.0

Base Debian image: `sha256:88200866dfff7ea7f5cbcb6ec7c8a701889efe6fe859fe64d6990e4b07ea4171`. Observed desktop build: `sha256:a7b528be018fc615fa91f48b85e3079b515848e4da266ad9bdabe22ccd7beb07`. The sandbox helper was installed into the running fixture afterward; initial and final package inventories record that mutation. The Dockerfile now includes the helper. Apt package inventory is captured on every run; the pinned base alone does not make future apt repository contents immutable.

Guacd image: `sha256:8974eaa9ba32f713daf311e7cc8cd7e4cdfba1edea39eed75524e78ef4b08f4f`. Guacamole WAR SHA-256: `b41ceb1e2df010b54db563e0b00edb8d5fe9f073c6168462e4c978df0fc6e716`. The WAR checksum and detached signature were verified using the release key served by Apache. Third-party binaries are downloaded dependencies, not vendored source in this change. SBOM/license/advisory qualification remains required.

Failed attempts and unresolved checks

1. The guessed SHA-512 sidecar URL returned 404. The release page identifies SHA-256 and OpenPGP verification files; both were then verified. 2. Compact certificate fingerprints failed. Pinned FreeRDP source identified the colon-separated format; positive and wrong-pin tests followed. 3. An early wrong-pin attempt overlapped prior connection teardown and produced no display within five seconds but no explicit error. It was inconclusive. The fresh negative probe returned error 519; the inconclusive attempt was retained rather than counted as a pass. 4. The first editor save attempt left its dialog open. Restarting the editor then triggered session recovery, invalidating the next attempt. Fresh editor configuration and an explicit save action produced verified file readback. 5. Chromium initially lacked its sandbox helper. Installing the matching helper resolved that missing package, but Chromium still exited because a required namespace operation was denied by the container boundary. Sandboxing was not disabled. Shared browser-profile/login qualification requires a VM fixture.

The eight native-probe parser tests pass. They qualify that probe's bounded reader, not the production RFB broker or the native translators' complete malformed-input behavior. The remaining #842 clipboard/parallel-viewer corpus, WebRTC comparison, repeated workload measurements, coordinated control/isolation contracts, and the #843 implementation readiness review are still required.

No production gateway, organizational provider, enrolled VM incarnation, active revocation budget, or native-shell matrix was qualified in this experiment.

Cleanup and evidence

The fixture HTTP process exited. Both labelled containers were removed and the generated secret directory was deleted. Daemon logs, initial/final package inventories, screenshots, marker readbacks and cleanup receipts were retained under the local run directory. A digest manifest and selected non-secret results are in the evidence summary.

Reproduction commands and exact limits are in the fixture README.

VM follow-up: browser startup (2026-09-13)

A disposable local KVM guest now clears the container's Chromium startup blocker. The Debian 12 genericcloud base was checked against its published SHA-512 digest, and a fresh overlay received XFCE, Chromium and the matching sandbox helper through cloud-init. The guest uses two vCPUs and 4 GiB RAM. Pinned SSH access and completed provisioning were verified.

Chromium 152.0.7977.82 rendered a synthetic page in a fresh headless profile with exit code zero, without `--no-sandbox`. A separate live process inspection found three renderer processes with `Seccomp: 2`, `NoNewPrivs: 1`, and PID namespaces distinct from the browser process. An attempted `chrome://sandbox` DOM dump returned New Tab and was not accepted as evidence. These observations establish startup feasibility; graphical login/profile handoff remains open.

The initial CD-ROM seed was not detected by this cloud kernel. Switching the seed to virtio-blk enabled cloud-init. The new VM preparation helper includes that setting, rejects an incorrect image digest before creating output, and checks ownership before QMP shutdown. Its status command was tested against this guest, and its stop command against a separate owned, paused QEMU fixture. The comparison VM remains private and running for the next experiment; the earlier container cleanup receipt does not cover this VM.

The bounded result is recorded in VM browser startup evidence.

VM follow-up: noVNC browser-profile handoff (2026-09-13)

The graphical baseline now passes a synthetic login and shared active-profile handoff. Actual noVNC key events entered the test login and an unsaved textarea draft. After viewer disconnect, Playwright attached over CDP to the existing graphical Chromium process, verified the authenticated HTTP-only session and unchanged draft, and appended text. Reconnecting the viewer and typing again produced the exact expected combined draft in the same browser process.

This uses a synthetic guest-loopback site and generated fixture credentials; it does not exercise organizational identity. The noVNC password is available to the isolated test browser, as in the earlier baseline. RDP handoff, production credential custody and the wider qualification matrix remain open. The screenshots retain visible XFCE color-management and Chromium password-save prompts. No privilege was granted or real password supplied to those prompts, and this result does not establish desktop UX readiness.

The host-side viewer server and pinned SSH tunnel were stopped through checked process identities; all three host listening ports were verified closed. The private VM remains running for the RDP experiment. The setup code was extracted into separate scripts after the run; replaying the complete extracted setup in a fresh fixture is still outstanding. The probe and cleanup scripts were run against the live fixture. See the handoff evidence and source digests.

VM follow-up: RDP browser-profile handoff (2026-09-13)

RDP/Guacamole now passes the same synthetic active-profile invariant. The native positive probe established a pinned TLS desktop at 1280×800. A fresh graphical Chromium profile ran on the resulting xrdp display. Actual RDP input entered the synthetic login and unsaved draft; after detach, automation found the authenticated HTTP-only session and exact draft in that browser, appended text, and the returning RDP viewer appended more text. The final draft matched exactly and the browser PID was unchanged.

The wrong-pin probe returned certificate error 519 in 416 ms, without display instructions. Live graphical renderer inspection showed seccomp filtering, `NoNewPrivs` and distinct PID namespaces. xrdp, guacd and CDP listeners were verified at guest-loopback addresses. The pinned guacd container runs inside the disposable VM with guest host networking and a 512 MiB/2 CPU/128 PID limit; this is a comparison topology, not the proposed production gateway.

The first worker launch failed because the fixture supplied `-p` for the listening port. Daemon usage identifies `-p` as the PID file and `-l` as the listening port. After inspecting the exited worker, the owned failed container was replaced using `-l`; the setup script now also waits for its listener. The full setup still needs a fresh replay after that readiness check and the automatic guest Docker-install step were added. The native and browser probes and host-helper cleanup ran against the actual corrected worker.

The Chromium password-save prompt remains visible in the screenshots. No real account or production custody was tested. Host viewer/SSH helpers are stopped and their three ports were verified closed; the private VM and guest worker remain available for adversarial testing. See the RDP profile evidence and digests. Both transport candidates now satisfy the synthetic active-profile feasibility check. Parser/adversarial coverage, operational comparison, contract coordination and implementation readiness remain open before final adapter selection.

Final fixture stop

After recording both browser-profile handoffs, the owned QEMU VM was stopped through its checked QMP socket. The process was verified gone; all guest test services stopped with it. Its private overlay, seed keys and raw logs remain local and must not be published. The earlier statements that the VM was retained running describe intermediate checkpoints, superseded by this final stop receipt.