Skip to content

feat: vnc feature serving a contained GUI - #24

Open
sdirix wants to merge 1 commit into
mainfrom
feat/vnc-feature
Open

feat: vnc feature serving a contained GUI#24
sdirix wants to merge 1 commit into
mainfrom
feat/vnc-feature

Conversation

@sdirix

@sdirix sdirix commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

What it does

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.

How to test

  1. Build the local enclave
  2. Run enclave with the feature, e.g. ./bin/enclave --features +vnc --rebuild
  3. Check the port by executing ./bin/enclave ps --json
  4. Retrieve the vnc password: docker exec <container-id> cat /tmp/enclave-vnc/vnc-password
  5. Connect: xtigervncviewer 127.0.0.1:<port>
  6. Enter the password

You can ask the agent to open tabs or show other GUI tools in a way so that it's visible to you

image

If you want a more convenient way to test you can

  • check out my fork which includes a separate feature to transform vnc to a websocket and includes an application to discover and open the respective views within browser tabs, OR
  • use feat: offer 'enclave gui' #29

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

  • This PR introduces breaking changes and has been coordinated with maintainers.

Review checklist

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.
@sdirix sdirix mentioned this pull request Jul 29, 2026
2 tasks
@planger

planger commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

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. :-)

@sdirix

sdirix commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

I see, yes, "gui" could be misleading. What about "vnc-viewer"?

@planger

planger commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Yes, vnc-viewer sounds great to me. Clear and descriptive and no collision with any other UI component.

@EclipseSourceAI EclipseSourceAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 &

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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" "$@"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants