Skip to content

feat: add the vnc feature with an enclave vnc-viewer host command - #5

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

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

Conversation

@sdirix

@sdirix sdirix commented Sep 18, 2026

Copy link
Copy Markdown

What it does

Opt-in mixin that gives a session a contained GUI: a virtual X display (Xvnc) running a fullscreened Chromium, served over VNC (RFB). A supervisor keeps Xvnc, matchbox-window-manager, and the browser alive with per-component restart loops. The RFB port is published on the host loopback so any VNC client can attach, and Xvnc enforces a per-session password (VncAuth) at the RFB layer.

Exporting DISPLAY and BROWSER=vnc-open routes "open in browser" flows onto the contained display, where browsing stays on the session's gateway-restricted network.

commands/host/vnc-viewer adds enclave vnc-viewer. It resolves the session's published port from enclave ps --json, reads the password through the container engine's non-TTY exec, and launches a host-installed viewer with the password in the environment rather than argv. It runs on the host, outside the sandbox, because driving a host GUI is what a session cannot do. It needs jq and takes its viewer command from the vnc_viewer key in enclave's config.json.

Derived from

How to test

enclave features add eclipse-enclave/enclave-extensions

To test the vnc-viewer command you need enclave with hostCommand support: eclipse-enclave/enclave#95

Opt-in mixin that gives a session a contained GUI: a virtual X display (Xvnc)
running a fullscreened Chromium, served over VNC (RFB). A supervisor keeps
Xvnc, matchbox-window-manager, and the browser alive with per-component restart
loops. The RFB port is published on the host loopback so any VNC client can
attach, and Xvnc enforces a per-session password (VncAuth) at the RFB layer.

Exporting DISPLAY and BROWSER=vnc-open routes "open in browser" flows onto the
contained display, where browsing stays on the session's gateway-restricted
network.

commands/host/vnc-viewer adds `enclave vnc-viewer`. It resolves the session's
published port from `enclave ps --json`, reads the password through the
container engine's non-TTY exec, and launches a host-installed viewer with the
password in the environment rather than argv. It runs on the host, outside the
sandbox, because driving a host GUI is what a session cannot do. It needs jq
and takes its viewer command from the vnc_viewer key in enclave's config.json.
@tortmayr
tortmayr self-requested a review September 21, 2026 09:09

@tortmayr tortmayr 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.

Amazing feature 👍🏼
Tested it with the corresponding enclave PR and it works almost as expected.

During testing I noticed that for me the $DISPLAY and $BROWSER env vars never reached the container.

As a consequence xdg-open inside the container did not work as descriped in the readme i.e. it did not use vnc-open.

I had to manually declare them in the spec.yaml

environment:
    variables:
      DISPLAY: ":99"
      BROWSER: /usr/local/bin/vnc-open

Needs to be double checked. If we really need the spec declaration we probably cannot use $VNC_DISPLAY and have to hardcode it to :99

--hide-crash-restore-bubble \
--password-store=basic \
--start-maximized \
--user-data-dir=/tmp/enclave-vnc/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.

The profile lives in /tmp, so cookies, logins, and open tabs are gone whenever the container is recreated. Persistence is the other topic the repo rules ask every extension README to cover (link) and the vnc README does not mention it.

# TigerVNC's, which takes the password from the environment.
if [ "${#viewer[@]}" -eq 0 ]; then
if [ "$(uname -s)" = "Darwin" ]; then
viewer=(open "vnc://:{password}@{host}:{port}")

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 macOS default puts the password in argv, which contradicts the access-control section of the README ("hands it to the client through the environment ... rather than argv", features/vnc/README.md L113-117).

Comment thread features/vnc/bin/vnc-open

# vnc-open <url> opens a URL in the session's contained (VNC) browser. It
# shares the supervisor's Chromium profile, so a running instance adopts the
# URL as a new (fullscreened) window instead of a second browser starting.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

URL is adopted as tab.It not a window. With the supervisor's Chromium running, vnc-open <url> prints Opening in existing browser session. and adds a tab to the existing window (verified in a live session). Same wording in features/vnc/README.md L81.

Comment thread features/vnc/install.sh
apt-get clean
rm -rf /var/lib/apt/lists/*

install -D -m 755 "$dir/bin/vnc-supervisor" /usr/local/bin/vnc-supervisor

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Two naming points. The scripts land as bare vnc-* on the image-wide PATH while both existing extensions prefix theirs (dsh, openclaw), and this PR's own desktop file is already enclave-vnc-open.desktop. Line 33 also plants the waiting page in /usr/local/share/enclave/, next to enclave's own runtime assets (kit-init.sh, net.sh, tmux-session.conf); that made sense when this was a built-in, less so for an installed extension.

shift
(
while :; do
"$@" >> "$LOG_DIR/$_name.log" 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.

Fixed 2s backoff with no cap, appending to /tmp every cycle. A component that can never start (Xvnc failing to bind 5900, say) grows its log for the life of the session.

set -u

exec chromium \
--no-sandbox \

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

--no-sandbox makes Chromium show the "You are using an unsupported command-line flag" infobar across the top of the waiting page, so it is the first thing anyone attaching a viewer sees.
Could be supressed with --test-type suppresses it.

Comment thread features/vnc/README.md

Holding that password is what grants control of the display, and nothing else
does. It is generated per session, so it reaches exactly one session's display
and no other — which is why the (untrusted) agent knowing it is harmless, and

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Em dash here and on line 110. Nothing else in the repo uses them, commas or a period would match.

| select((.protocol // "") as $proto | $proto == "" or $proto == "tcp")
| [$session.name, ($session.sessionName // ""), ($session.projectHash // ""), (.hostIP // ""), .hostPort]
| @tsv
' | sort)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

One row per binding, not per container. Docker gives a wildcard publish two bindings on an IPv6-enabled daemon (0.0.0.0 and [::]), which is exactly the case the host_ip switch below anticipates, and then both paths bail: auto-select hits "multiple running enclave containers with a VNC display" and passing the name hits "several containers match", both listing the same name twice with no way out. Dedupe by container name before counting.

Comment thread features/vnc/README.md
inside the container's network namespace. Under network isolation that
namespace belongs to the session's gateway container on a bridge shared with
other containers of the same engine, so those (including other sessions'
gateways) can reach the RFB port directly, with VncAuth as the only gate.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

VncAuth authenticates, it does not encrypt. Since this bullet already says other containers on the bridge can reach the RFB port, it should also say the framebuffer, keystrokes and clipboard travel in the clear.

@sdirix

sdirix commented Sep 21, 2026

Copy link
Copy Markdown
Author

Amazing feature 👍🏼 Tested it with the corresponding enclave PR and it works almost as expected.

During testing I noticed that for me the $DISPLAY and $BROWSER env vars never reached the container.

As a consequence xdg-open inside the container did not work as descriped in the readme i.e. it did not use vnc-open.

I had to manually declare them in the spec.yaml

environment:
    variables:
      DISPLAY: ":99"
      BROWSER: /usr/local/bin/vnc-open

Needs to be double checked. If we really need the spec declaration we probably cannot use $VNC_DISPLAY and have to hardcode it to :99

I wonder why this works for me out of the box. I have to check what's going on

@tortmayr

Copy link
Copy Markdown

I wonder why this works for me out of the box. I have to check what's going on

I now retested the extension in an isolated enclave environment and there the env vars are set in the container.
So I think there is nothing to do from your side for now.
I will try to find out why its not working for me when I use the extension with my real enclave config (might be a conflicting extension or something like that)

@tortmayr

Copy link
Copy Markdown

Also just a heads up for Ubunut/Gnome users:
Enabling Locate Pointer accessibility feature is enabled (Reveal mouse pointer by pressing left ctrl)
might break forwarding of keyboard events to the vnc viewer. For me keyboard shortcuts involving left ctrl only worked after disabling this setting.

@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. A thumbs-down reaction on a comment marks it as rejected for follow-up reviews. Noting why in a reply helps, since replies are read too.

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.

Running in Eclipse Enclave, submitted via review-guard-mcp

Adds the repo's first feature extension: an opt-in vnc mixin that gives a session a contained GUI (Xvnc, matchbox, fullscreened Chromium), published as raw RFB on the host loopback with a per-session VncAuth password, plus a commands/host/vnc-viewer host command that resolves the port from enclave ps --json, reads the password through docker/podman exec, and launches a host viewer.

The approach fits enclave's extension model well. spec.yaml uses documented mixin fields only (aptPackages, commands.startup with background: true, ports with hostAllocation: auto), feature-entrypoint.d/setup.sh matches the built-in features' style, and the commands/host/ layout matches what enclave#95 defines. I verified the CLI contracts the host command leans on against enclave main: ps --json emits containerPort/hostPort/hostIP/projectHash/sessionName as strings and lists running sessions only, config --json exposes paths.global/paths.project, unknown config keys like vnc_viewer are ignored rather than rejected, and enclave exec really does hardcode a TTY, so the direct engine exec is justified. The scripts are shellcheck-clean, the VncAuth fallback key and bit-reversal are correct, and the macOS path avoids the bash 3.2 empty-array trap.

Where attention is worth spending: the relationship to the still-open enclave core PRs #24/#29, which would make enclave vnc-viewer and the vnc_viewer config key built-ins and shadow this extension's verb; supervisor lifecycle (no trap, so children outlive the supervisor, which compounds the unbounded restart-log growth already raised); and the README, which still has no egress coverage for a feature whose whole point is a browser on the session's gateway-restricted network.

#
# SPDX-License-Identifier: MIT

# `enclave vnc-viewer [container-or-session-name]`

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

eclipse-enclave/enclave#29 is still open and adds enclave vnc-viewer plus the vnc_viewer config key as built-ins. Per #95's own rules a built-in verb wins and the extension's command is skipped with a warning, so a maintainer should decide which of the two ships before this merges.


log "stack started (display $DISPLAY_NUM, RFB port $RFB_PORT)"

wait

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No trap here, so killing the supervisor leaves Xvnc, matchbox and Chromium running orphaned, and a manual restart then spawns a second Xvnc that can never bind 5900. The openclaw session wrapper solves the same problem with a trap ... EXIT (link).

Comment thread features/vnc/README.md
`xvnc.log`, `wm.log`, `browser.log`, and `vnc-open.log` for the individual
components.

## Residual risks

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nothing in the README covers egress, which the repo rules ask every extension README for (link) and both existing extensions have a section for (dsh, openclaw). It matters for this feature: the contained Chromium browses through the session gateway, so any URL outside the allowlist just fails in the viewer with no hint that --allow-domain is the lever.

enclave_verb=$(basename "$enclave_bin")

die() {
printf 'vnc-viewer: %s\n' "$*" >&2

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Tab-indented, while every other script in the repo uses 4 spaces, including this PR's own bin/vnc-* and openclaw/install.sh.

Comment thread features/.gitkeep

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 PR fills features/ with features/vnc/ in the same commit, so the placeholder has no job. Drop it.

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