Skip to content

Add virtual-browser templates - #464

Merged
june-hua merged 18 commits into
masterfrom
june/rbi-chromium-policy
Aug 27, 2026
Merged

Add virtual-browser templates#464
june-hua merged 18 commits into
masterfrom
june/rbi-chromium-policy

Conversation

@june-hua

@june-hua june-hua commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Based off changes in #463. Adds two app templates that serve a backend app (JupyterLab / RStudio)
through a server-side Chromium session rendered by Selkies and
streamed to the client as pixels, sharing one hardened browser front end.

Templates

  • virtual-browser-jupyter — JupyterLab through the streamed browser.
  • virtual-browser-rstudio — RStudio Server through the streamed browser.

Each runs two containers on an internal app-network: application-server (the Chromium/Selkies
front end, the only published container, port 3000) and the backend (jupyterlab:8888 /
rstudio:8787, internal only). Chromium opens the backend in --kiosk.

Shared browser front end (src/browser-common/)

Both templates share one image. Compose include: can't override an imported service, so each
template defines its own app service pointing build.context at ../browser-common and passing an
APP_ORIGIN build arg.

  • browser.Dockerfile — pinned Chromium base + Selkies/hardening env; generates the managed
    policy from the template at build.
  • managed-policy.json.tmpl — managed Chromium enterprise policy; __APP_ORIGIN__ is
    substituted with the template's origin.

Managed Chromium policy

Baked into /etc/chromium/policies/managed/workbench-rbi.json (immutable; baked, not mounted, since
the workspace mount is user-writable). URLAllowlist = the single app origin, URLBlocklist =
["*"] (fails closed). Also disables downloads/export, file dialogs/file-system access,
clipboard-out, printing, devtools + remote debugging, extensions, new tabs / pop-ups / off-app
navigation, incognito/extra profiles, and password manager / autofill / translation / notifications.

Selkies hardening (image env)

Sidebar hidden; sharing/collab disabled; clipboard-out and command disabled and |locked; file
transfers upload-only; audio/mic off; framerate/encoding tuned.

Base image pinning

lscr.io/linuxserver/chromium@sha256:4c7b9086… with a feature-versions/state.json entry so
update.sh tracks it (dedicated src/.*/browser\.Dockerfile filter, since the existing
Dockerfile filters don't match).

Tests & CI

Shared tests/common/virtual-browser.bats (parameterized by BACKEND_CONTAINER / APP_ORIGIN),
per-template runners, and both templates registered in test-pr.yaml.

Embedding requirement (important)

The streamed client only starts its video pipeline on load if the host page embeds it with an
autoplay-capable permissions policy:

<iframe  allow="autoplay; fullscreen; clipboard-read; clipboard-write; screen-wake-lock"></iframe>

Without this (cross-origin iframe), the app shows a black screen until the user switches tabs and
back. This is a host-page requirement, not a container setting — no Selkies/pixelflux env changes
are needed
(earlier H264_STREAMING_MODE / PIXELFLUX_WAYLAND experiments were removed as
unnecessary once the iframe permissions were fixed). The host-side fix lives in verily1 (adds these
to the app iframe's allow). See the templates' README "Embedding" sections.

@june-hua
june-hua force-pushed the june/rbi-chromium-policy branch 2 times, most recently from 9272bdf to fd4eca9 Compare August 24, 2026 17:58
@june-hua june-hua changed the title feat(virtual-browser-jupyter): add managed Chromium policy and pin base image feat: add virtual-browser-jupyter template (streamed Chromium + managed policy) Aug 24, 2026
@june-hua
june-hua changed the base branch from virtual-browser to master August 24, 2026 18:08
@june-hua
june-hua force-pushed the june/rbi-chromium-policy branch 4 times, most recently from a10bd24 to e5c5f58 Compare August 25, 2026 03:24
Comment thread src/virtual-browser-jupyter/docker-compose.yaml Outdated
@june-hua june-hua changed the title feat: add virtual-browser-jupyter template (streamed Chromium + managed policy) feat: add virtual-browser-jupyter and virtual-browser-rstudio templates (streamed Chromium + managed policy) Aug 25, 2026
@june-hua june-hua self-assigned this Aug 25, 2026
@june-hua june-hua changed the title feat: add virtual-browser-jupyter and virtual-browser-rstudio templates (streamed Chromium + managed policy) Add virtual-browser templates Aug 25, 2026

@yonghaoy yonghaoy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

can't wait to see the demo!

Comment thread src/virtual-browser-jupyter/.devcontainer.json
Comment thread src/virtual-browser-jupyter/docker-compose.yaml Outdated
Comment thread src/virtual-browser-jupyter/docker-compose.yaml Outdated
Comment thread src/virtual-browser-jupyter/docker-compose.yaml Outdated
Comment thread src/browser-common/Dockerfile Outdated
Comment thread src/browser-common/Dockerfile
Comment thread src/virtual-browser-jupyter/docker-compose.yaml Outdated
Comment thread src/browser-common/Dockerfile
Comment thread src/virtual-browser-rstudio/.devcontainer.json Outdated
@june-hua
june-hua force-pushed the june/rbi-chromium-policy branch from bdf30f7 to b95c809 Compare August 25, 2026 19:45
Comment thread tests/common/virtual-browser.bats
Comment thread src/browser-common/Dockerfile
@june-hua
june-hua marked this pull request as ready for review August 25, 2026 20:40
@june-hua
june-hua requested review from a team as code owners August 25, 2026 20:40
@june-hua
june-hua requested a review from pantherman594 August 25, 2026 20:40
Runs JupyterLab behind remote browser isolation: a server-side Chromium
(Selkies,
Wayland) streamed to the client as pixels, so app data never reaches the
client
browser.

- browser-common/: shared browser image. Pinned Chromium base; managed
enterprise
policy generated from the APP_ORIGIN build arg (URLAllowlist = the
app,
URLBlocklist = *,
downloads/devtools/remote-debugging/file-dialogs/clipboard/
extensions off). Exfil-relevant Selkies settings locked with
"<value>|locked" so
a client on the data websocket cannot re-enable them: clipboard-out,
command
(remote shell), sharing/collab/shared. File transfers upload-only;
sidebar hidden;
20fps/CRF28; audio/mic off (left unlocked for a future accessibility
path).
- virtual-browser-jupyter/: the template. app (browser, --kiosk) +
jupyterlab
(internal only, no published ports). shm_size 2gb so Chromium's
renderer doesn't
crash. CHROME_CLI uses --disable-features=WaylandFractionalScaleV1 to
fix the
Chromium/Wayland fractional-scaling click-offset (client-agnostic;
keeps Wayland
  auto-fit).
- Base image pinned in feature-versions/state.json.
- Smoke test asserts the managed policy loaded and the exfil settings
are locked;
  registered in test-pr.yaml.
RStudio Server behind the same remote-browser isolation as
virtual-browser-jupyter,
reusing the shared browser-common image (managed policy, Selkies exfil
locks,
--kiosk + WaylandFractionalScaleV1 offset fix + post-load resize).

- Backend is the prebuilt rocker/tidyverse image on 8787 with
DISABLE_AUTH; internal
only (no published ports), reached by the browser over app-network.
APP_ORIGIN and
  CHROME_CLI target http://rstudio:8787.
- Parameterized the shared smoke test (BACKEND_CONTAINER / APP_ORIGIN
via env) so it
covers both templates; added tests/virtual-browser-rstudio.sh and
registered the
  template in test-pr.yaml.
The synthetic resize event didn't fix the offset: the Selkies client re-negotiates
only on a real dimension change, so a dispatched resize with unchanged dimensions is
a no-op. Instead post the client's own "resetResolutionToWindow" message (it has a
handler that recomputes resolution from the container's current size) on staggered
timers after load, which is what actually re-syncs the click mapping.
The injected timers that posted resetResolutionToWindow (and the earlier
synthetic resize variant) re-negotiated the stream and turned it black a
few seconds after load, and did not correct the initial click mapping.
Drop the injection; the image keeps only the managed policy and Selkies env.
Removes the --disable-features=WaylandFractionalScaleV1 flag from both
templates' CHROME_CLI and the related comments, README troubleshooting
note, and Dockerfile header note. Pairs with the earlier removal of the
injected client resolution script, returning the image to policy + Selkies
env only.
- APP_ORIGIN is scheme-less so the policy URLAllowlist also covers ws://
  (kernel/RStudio websockets), not just http://.
- chmod 444 the baked managed policy so it can't be modified
in-container.
- Drop --proxy-server/--proxy-bypass-list from CHROME_CLI; the policy
  URLBlocklist already blocks Chromium from reaching any non-app origin.
@june-hua
june-hua force-pushed the june/rbi-chromium-policy branch from dcd0061 to 2dc8cc1 Compare August 25, 2026 20:41
It's the only Dockerfile in browser-common, so use the default name (and drop
the now-redundant compose `dockerfile:` keys). Update the state.json filter.
Trim redundant comments across the templates.
Comment thread tests/common/virtual-browser.bats Outdated
Set jdkDistro="tem" so the Java feature installs a prebuilt Temurin JDK
instead of building via SDKMAN, which was failing during the devcontainer
build. Matches the approach used for the other apps.
do not allow file browsing, this exposes the entire Chromium container's
filesystem (e.g. /config/ssl/cert.key) for browsing and upload.

@pantherman594 pantherman594 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks great, thanks!

minimize app in app mode causes app to permanently disappear
@june-hua
june-hua merged commit 82d2a40 into master Aug 27, 2026
31 of 38 checks passed
@june-hua
june-hua deleted the june/rbi-chromium-policy branch August 27, 2026 23:15
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