4.6 KiB
Browser Jail FreeBSD Viability
Date: 11.maj.2026 Status: PASS — headless Chromium + CDP works in a Bastille VNET jail
Summary
Phase 0.5 passed on the FreeBSD host. A fresh throwaway Bastille VNET jail
was able to install system-pkg Chromium, launch it in headless mode with a
remote debugging port, and accept a puppeteer-core CDP connection from Node
inside the jail.
The committed vision-validation renderer (scripts/browser-jail-validation/render.mjs)
was also copied into the jail and successfully re-rendered all deterministic
fixtures through the same CDP path.
Environment
- Host: FreeBSD 15.0-RELEASE-p8 amd64
- Bastille release used for jail creation:
15.0-RELEASE - Jail reported release after creation:
15.0-RELEASE-p4 - Bridge:
warden0 - Gateway:
192.168.72.1 - Validation jail requested name:
browser-validation - Actual validation jail name:
browservalidation - Validation jail IP:
192.168.72.150/24
browser-validation could not be used as a VNET jail name because Bastille rejected
hyphens for VNET jail names:
[ERROR]: VNET jail names may not contain (-|_) characters.
The production jail name browser is unaffected by this limitation. Future
validation docs should use browservalidation if VNET parity is desired.
Package versions
Installed in the throwaway jail:
chromium-147.0.7727.101
node22-22.22.2
npm-node22-11.11.0
puppeteer-core@24.43.0
Commands that worked
Create the throwaway VNET jail:
sudo bastille create -B -g 192.168.72.1 browservalidation 15.0-RELEASE 192.168.72.150/24 warden0
Install packages:
sudo bastille pkg browservalidation install -y chromium node22 npm-node22
Create the Chromium profile directory:
sudo bastille cmd browservalidation mkdir -p /var/tmp/validation-profile
Start headless Chromium under daemon:
sudo bastille cmd browservalidation daemon -f -p /var/run/browser-validation-chromium.pid \
/usr/local/bin/chrome \
--headless=new \
--no-sandbox \
--disable-gpu \
--remote-debugging-address=127.0.0.1 \
--remote-debugging-port=9222 \
--user-data-dir=/var/tmp/validation-profile \
about:blank
Verify CDP is reachable from inside the jail:
sudo bastille cmd browservalidation fetch -qo - http://127.0.0.1:9222/json/version
Observed response:
{
"Browser": "Chrome/147.0.7727.101",
"Protocol-Version": "1.3",
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/147.0.0.0 Safari/537.36",
"V8-Version": "14.7.173.19",
"WebKit-Version": "537.36 (@56536d2a8034c51b0e68e1a0483ab9f1a0165ae3)",
"webSocketDebuggerUrl": "ws://127.0.0.1:9222/devtools/browser/..."
}
Install the CDP client in the jail workspace:
sudo bastille cmd browservalidation sh -c 'cd /var/tmp/browser-jail-validation && npm install'
Run the smoke script:
sudo bastille cmd browservalidation sh -c 'cd /var/tmp/browser-jail-validation && node freebsd-cdp-smoke.mjs'
Observed output:
OK text=hello screenshot=/var/tmp/browser-jail-validation/hello.png
Run the deterministic renderer through the jail's Chromium:
sudo bastille cmd browservalidation sh -c 'cd /var/tmp/browser-jail-validation && node render.mjs'
Observed output:
[01-login] /var/tmp/browser-jail-validation/screenshots/01-login.png (5 targets)
[02-dashboard] /var/tmp/browser-jail-validation/screenshots/02-dashboard.png (7 targets)
[03-modal] /var/tmp/browser-jail-validation/screenshots/03-modal.png (5 targets)
done
Working smoke script
The script is committed at:
scripts/browser-jail-validation/freebsd-cdp-smoke.mjs
It connects to http://127.0.0.1:9222, opens a page, navigates to a data URL,
reads <h1> text through page.evaluate, writes a PNG screenshot, prints OK,
and disconnects.
FreeBSD-specific notes
- The Chromium executable from the FreeBSD package is
/usr/local/bin/chrome, notchromium. --no-sandboxwas required for this validation launch shape. This matches the handoff's validation-only allowance. Production should re-test sandboxed launch during Phase 1 jail-service hardening.--disable-gpuwas used to keep headless launch minimal in the jail.puppeteer-coreworks against system Chromium; no bundled browser download was involved.- npm emitted
Unknown global config "python"; it did not block install or execution.
Gate decision
Phase 0.5 is green. Phase 1 can proceed using:
- system-pkg Chromium from FreeBSD ports,
- Node 22,
puppeteer-coreover CDP,- a jail-side HTTP service that owns the Chromium process lifecycle.
chrome-remote-interface fallback was not needed.