feat: vnc feature serving a contained GUI - #24
Conversation
Opt-in `vnc` mixin: a virtual X display (Xvnc) running a fullscreened Chromium, served over VNC (RFB). The raw RFB port is published on the host loopback so any VNC client can attach directly. Find the port with `enclave ps --json` and read the per-session password from /tmp/enclave-vnc/vnc-password. A supervisor keeps Xvnc, matchbox-window-manager, and a headful Chromium at $VNC_URL alive with restart loops. Xvnc listens on all container interfaces so the published port reaches it, and enforces a random password at the RFB layer (VncAuth). That password is what shapes the boundary: holding it is what grants control of the display, and because it is generated per session it reaches exactly one session's display, which is why the agent knowing it is harmless. Exporting DISPLAY and BROWSER=vnc-open routes "open in browser" flows onto the contained display, where all real browsing stays on the session's gateway-restricted network.
|
Love it! Didn't get to try it out yet. Just one thought about the naming... I tend to prefer something like "enclave-vnc" instead of GUI to avoid any collisions with the upcoming UI of enclave (homeshell). Edit: I'm mostly talking about #29 but already wanted to mention it here as this is going to be discussed first and may be impacted by any naming discussions. :-) |
|
I see, yes, "gui" could be misleading. What about "vnc-viewer"? |
|
Yes, vnc-viewer sounds great to me. Clear and descriptive and no collision with any other UI component. |
EclipseSourceAI
left a comment
There was a problem hiding this comment.
Note
Autonomous AI review.
This review was done by an AI agent and therefore may contain mistakes. Feel free to ignore any comment you disagree with. Noting why helps, since replies are read and taken into account in follow-up reviews.
Resolving all AI comments does not lead to an automatic approval. A maintainer still needs to review and sign off on the overall architecture and design.
To get an updated review after pushing changes, a maintainer may re-request a review from this account.
Submitted via review-guard-mcp
Opt-in vnc mixin: Xvnc + matchbox + fullscreened Chromium kept alive by an in-container supervisor, RFB published on an auto-assigned host port and gated by a per-session VncAuth password, plus DISPLAY/BROWSER and an image-wide http(s) scheme handler so "open in browser" lands on the contained display.
No Go changes, and it is the first extension to use both feature-level commands.startup and feature-level ports, so it exercises existing machinery rather than adding any. Docs and the golden surface snapshot are updated in the right places; go test ./..., check-license-headers, validate-extensions and shellcheck are all clean, and I verified the hand-rolled VncAuth derivation is byte-identical to a reference vncpasswd implementation.
Worth a human look at three things: the Chromium restart loop in vnc-supervisor, which can spin every 2s (and flood the display with waiting-page windows) whenever another process owns the profile singleton; the chromium apt package against the documented ubuntu:24.04 base, where one unavailable package fails the shared feature apt step for every feature; and the reachability of the RFB port, which is broader than "host loopback" because Xvnc binds all interfaces in a netns that is shared with the gateway on a Docker bridge. The rest are smaller notes (unusable VNC_RFB_PORT knob, mimeapps.list clobber, shellcheck coverage gap, repeated rationale comments).
| # The container is the sandbox. Chromium's own sandbox needs unprivileged | ||
| # user namespaces, which the default Docker seccomp profile blocks, so it is | ||
| # disabled here. Everything Chromium loads egresses via the session gateway. | ||
| run_forever browser chromium \ |
There was a problem hiding this comment.
This loop restarts Chromium unconditionally, but a chromium that finds a live profile singleton just forwards its URL and exits 0. So if anything else owns the singleton (which is exactly what vnc-open's fallback launch does once its 30s wait runs out), this respawns every 2 seconds forever, each time popping a new fullscreened waiting page and appending to the log. Worth checking for a live singleton before restarting instead of relying on the bounded wait in vnc-open.
| sleep 0.3 | ||
| done | ||
| exec chromium --no-sandbox --user-data-dir="$PROFILE" "$@" | ||
| ) >/dev/null 2>&1 & |
There was a problem hiding this comment.
Discarding stdout/stderr makes a failed open undiagnosable, and since install.sh registers this as the image-wide http/https handler, every xdg-open/$BROWSER caller in the session silently loses its URL (after a 30s stall) whenever the stack is down. The supervisor already logs per component to $STATE_DIR/log/; sending this there too would cost nothing.
| priority: 75 | ||
| aptPackages: | ||
| - tigervnc-standalone-server | ||
| - chromium |
There was a problem hiding this comment.
chromium is not an installable apt package on the Ubuntu base that docs/tools.md documents (link), and all selected features' packages go through a single apt-get install (link), so one unavailable package fails the whole feature stage, not just vnc. Did you try --features +vnc against --base-image ubuntu:24.04?
|
|
||
| Opt-in feature that gives a session a **contained GUI**: a virtual X display | ||
| (TigerVNC's `Xvnc`) running a fullscreened Chromium, served over **VNC (RFB)**. | ||
| The raw RFB port is published on the host loopback, so you can attach any VNC |
There was a problem hiding this comment.
The host binding is loopback, but Xvnc listens on all interfaces inside the netns, and under network isolation that netns belongs to the gateway container on a shared Docker bridge. Other containers (including other sessions' gateways) can therefore hit 5900 directly with VncAuth as the only gate. That belongs in Residual risks. Longer term the gateway already has a loopback-forwarding path for exactly this shape of problem (link), though today it is only driven by tool Go handlers and would not cover unrestricted mode.
|
|
||
| DISPLAY_NUM="${VNC_DISPLAY:-:99}" | ||
| GEOMETRY="${VNC_GEOMETRY:-1600x1000}" | ||
| RFB_PORT="${VNC_RFB_PORT:-5900}" |
There was a problem hiding this comment.
This knob cannot actually be used: the published container port is hardcoded to 5900 in spec.yaml, so a consuming feature that sets VNC_RFB_PORT only makes the display unreachable. Either drop it or have the supervisor refuse to start when it does not match 5900.
| '[Default Applications]' \ | ||
| 'x-scheme-handler/http=enclave-vnc-open.desktop' \ | ||
| 'x-scheme-handler/https=enclave-vnc-open.desktop' \ | ||
| > /etc/xdg/mimeapps.list |
There was a problem hiding this comment.
This truncates an image-wide shared file rather than merging into it, so any other extension that registers a scheme handler gets silently dropped depending on install priority. Appending the two lines when the file already exists would be safer.
| @@ -0,0 +1,186 @@ | |||
| #!/bin/bash | |||
There was a problem hiding this comment.
vnc-supervisor and vnc-open have no .sh extension, so make lint's shellcheck pass skips them (link). They are clean today (checked at all severities), so adding a path pattern for them, like the one that exists for build-scripts/bin/*, keeps them that way.
| # into the VNC feature's Chromium instead of the apt-installed | ||
| # chromium.desktop, whose plain `chromium %U` crashes without --no-sandbox | ||
| # and silently drops the URL. Wired up via /etc/xdg/mimeapps.list in | ||
| # install.sh. |
There was a problem hiding this comment.
The xdg-mime-resolves-before-$BROWSER rationale is now spelled out four times (here, install.sh, the vnc-open header, and the README). Keep it in one place and point at it.
| [ -e "$PROFILE/SingletonSocket" ] && break | ||
| sleep 0.3 | ||
| done | ||
| exec chromium --no-sandbox --user-data-dir="$PROFILE" "$@" |
There was a problem hiding this comment.
When this wins the singleton it starts Chromium without the supervisor's flags (--no-first-run, --hide-crash-restore-bubble, --password-store=basic, --disable-gpu, --disable-dev-shm-usage), so the browser behaves differently depending on which path started it. Sharing one flag list between the two would avoid the drift.
| --disable-dev-shm-usage \ | ||
| --no-first-run \ | ||
| --no-default-browser-check \ | ||
| --hide-crash-restore-bubble \ |
There was a problem hiding this comment.
Chromium's component updater, safe browsing, and optimization-guide fetches will keep hitting gateway-denied domains and filling the network log. --disable-background-networking --disable-component-update --disable-sync would keep the contained browser quiet.
What it does
Opt-in
vncmixin: a virtual X display (Xvnc) running a fullscreened Chromium, served over VNC (RFB). The raw RFB port is published on the host loopback so any VNC client can attach directly. Find the port withenclave ps --jsonand read the per-session password from /tmp/enclave-vnc/vnc-password.A supervisor keeps Xvnc, matchbox-window-manager, and a headful Chromium at $VNC_URL alive with restart loops. Xvnc listens on all container interfaces so the published port reaches it, and enforces a random password at the RFB layer (VncAuth). That password is what shapes the boundary: holding it is what grants control of the display, and because it is generated per session it reaches exactly one session's display, which is why the agent knowing it is harmless.
Exporting DISPLAY and BROWSER=vnc-open routes "open in browser" flows onto the contained display, where all real browsing stays on the session's gateway-restricted network.
How to test
./bin/enclave --features +vnc --rebuild./bin/enclave ps --jsondocker exec <container-id> cat /tmp/enclave-vnc/vnc-passwordxtigervncviewer 127.0.0.1:<port>You can ask the agent to open tabs or show other GUI tools in a way so that it's visible to you
If you want a more convenient way to test you can
I created the latter after the first one and it's conceptually the better approach for Enclave itself I think. The HomeShell could then later have a built-in feature similar to the first web-based approach.
Follow-ups
Breaking changes
Review checklist