Skip to content

fix(browser)!: make the browser layer reachable on stock Ubuntu 26.04, and remove rtk (2.4.0) - #43

Merged
rldyourmnd merged 4 commits into
mainfrom
fix/cloakbrowser-headless-no-sandbox
Aug 3, 2026
Merged

fix(browser)!: make the browser layer reachable on stock Ubuntu 26.04, and remove rtk (2.4.0)#43
rldyourmnd merged 4 commits into
mainfrom
fix/cloakbrowser-headless-no-sandbox

Conversation

@rldyourmnd

@rldyourmnd rldyourmnd commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Everything here was found by running the 2.3.0 apply on a real Ubuntu 26.04 desktop. Three independent defects each made the mandatory browser layer unreachable, so chrome-devtools-mcp was never published on a stock supported host. rtk is removed by owner decision.

1. The headless CDP service could not start on any Ubuntu from 23.10 onward

FATAL ... zygote_host_impl_linux.cc:128] No usable sandbox! If you are running on
Ubuntu 23.10+ ... that has disabled unprivileged user namespaces with AppArmor ...

systemd restarted it seven times, each dying with status=6/ABRT. 23.10+ restrict unprivileged user namespaces through AppArmor and kernel.apparmor_restrict_unprivileged_userns=1 is stock on 26.04, so the zygote has no sandbox to enter. The service passed no --no-sandbox, so the layer could not come up at all - and because the providers are published after the daemon step, chrome-devtools-mcp and playwright-cli were never installed.

The flag goes on the Linux service only, and both provenance validators expect it for fingerprint == "linux". macOS keeps its sandbox; the validators compare the argument tail exactly, so one shared list would necessarily break a platform. The stealth launcher already passed it - only the service was missing it.

No machine-wide change. kernel.apparmor_restrict_unprivileged_userns stays enabled: relaxing a kernel hardening for every process on the host to fix one headless browser is a far larger blast radius for the same outcome.

2. policy_hashes() enforced private mode on Git-tracked sources

With the service up, the apply failed at the integrity contract instead:

NOT_PROVEN: path is group/world-writable: scripts/browser_runtime_integrity.py

Git records only the executable bit, so a clone under umask 002 writes those eight files 664. The gate failed on a pristine tree while umask 022 hosts and CI stayed green. This is the call site the 2.2.0 fix missed. Installed runtime paths still fail closed, the checkout is untouched, and the eight files are still hashed - which is what actually pins them.

3. A materialized harness checkout inherited the caller's umask

git clone under umask 002 produced 252 group-writable paths, and nddev-codex-app's install-builder correctly refused the tree - after the checkout helper had reported success. Both the clone path and the fast path now normalize through the shared managed-tree helper. The fast path matters most: it is the only one a host with an already-pinned checkout ever takes again.

Changed: the harness layer runs last

It delegates to a module whose fail-closed guards depend on local state this repository does not own. Under set -euo pipefail an abort there stranded every layer behind it - which is how a desktop ended up missing 24 of the 46 commands verify.sh required. The step stays fatal; it is now fatal to itself rather than to the whole device.

Removed: rtk (owner decision)

install_rtk and its 200 lines, the four supply_chain pins, the exact-version gates in both verifiers, its managed-shell requirement, and its two tests.

The apply had been failing on it anyway with managed RTK destination or receipt is invalid, and the refusal was correct for a reason worth recording: the binary on the reporting host hashed f160611f… against a contract pin of ff8a1e77…. What was installed had never been the pinned artifact - most likely the Homebrew formula the runtime-versions file names as its source - and the receipt gate was the only thing that noticed.

Evidence

Live on the reporting host, after these changes:

Check Result
chrome-devtools-mcp --version 1.6.0
playwright-cli --version 0.1.17
cloakbrowser-cdp-health exit 0, cloak_health: proven
dart mcp-server --version 0.1.4
browser runtime integrity receipt published and proven
python3 -m pytest 95 passed
scripts/ci/validate.sh / lint.sh ci-validate-ok / scripts-lint-ok

Version moves to 2.4.0: the contract loses declared pins and a published command.

The mandatory CloakBrowser health gate failed on a real Ubuntu 26.04 desktop and
correctly aborted the apply. The gate was right; the service could not start:

  FATAL ... zygote_host_impl_linux.cc:128] No usable sandbox! If you are running
  on Ubuntu 23.10+ ... that has disabled unprivileged user namespaces with
  AppArmor ...

systemd restarted it seven times, each attempt dying with status=6/ABRT, before
giving up. Ubuntu 23.10 and later restrict unprivileged user namespaces through
AppArmor, and `kernel.apparmor_restrict_unprivileged_userns=1` is the stock
setting on 26.04, so the headless Chromium zygote has no sandbox to enter. The
managed service passed no --no-sandbox, so on every supported Ubuntu release from
23.10 onward the browser layer could not come up at all - which also meant
chrome-devtools-mcp and playwright-cli were never published, because the abort
happens before the provider bundle.

The flag is added to the Linux service only, at the end of the argument tail, and
both provenance validators now expect it there for `fingerprint == "linux"`.
macOS has no such restriction and keeps its sandbox: the validators compare the
tail exactly, so one shared list would necessarily break a platform. The stealth
launcher already passed the flag; only the service was missing it.

Verified on the reporting host: the same binary with the same arguments plus the
flag serves CDP (`Chrome/146.0.7680.177`, `Protocol-Version 1.3`) on the loopback
endpoint. No machine-wide change was needed - the AppArmor userns restriction
stays enabled, which is a far smaller blast radius than relaxing a kernel
hardening setting for every process on the host.

The legacy marker-less unit comparator is deliberately unchanged: it recognizes
the pre-marker format for a safe one-time migration, and teaching it the new flag
would stop it recognizing what it exists to recognize.
With the service able to start, the apply reached the integrity contract and
failed there instead:

  browser-runtime-integrity: NOT_PROVEN: path is group/world-writable:
  scripts/browser_runtime_integrity.py

policy_hashes() reads eight Git-tracked files - this script, common.sh, the
contract, and five templates - and called regular_owned() with private mode still
enforced. Git records only the executable bit, so a clone or submodule update
under `umask 002` writes them 664: the gate failed on a pristine tree while
`umask 022` hosts and CI stayed green.

This is the call site the 2.2.0 fix missed. That change taught content_id() and
cloak_runtime_identity() to pass repository_sources, and the note left behind said
that if this ever fails under one umask and passes under another, the flag has
been dropped at a call site - do not weaken the installed-path check and do not
chmod the checkout. Neither was done: installed runtime paths still fail closed,
the checkout is untouched, and these eight files are still hashed, which is what
actually pins them. A writability refusal is real tamper resistance for a file the
installer created and owns, and says nothing about a source anyone able to write
can simply edit.
BREAKING CHANGE: this adapter no longer installs rtk, publishes no `rtk`
launcher, and carries no rtk supply-chain pins. Neither verifier requires it.

Owner decision: rtk is not needed and must not be installed by bootstrap.

The apply had been failing on it anyway, with `managed RTK destination or receipt
is invalid`, and the refusal was correct for a reason worth recording: the binary
on the reporting host hashed f160611f… against a contract pin of ff8a1e77…, so
what was installed had never been the pinned artifact. Something else - most
likely the Homebrew formula the runtime-versions file names as its source - had
put a different 0.43.0 build there, and the receipt gate was the only thing that
noticed. Removing the feature removes that mismatch with it.

Gone: rldyour::install_rtk and its 200 lines, the four supply_chain rtk_* pins,
the exact-version gates in both verifiers, `rtk` from the managed-shell required
commands, and the two tests that covered the artifact install and its
transactional safety. The staging-pattern test keeps its node/uv/bun assertions.

Version moves to 2.4.0 because the contract loses declared pins and a published
command.
@rldyourmnd rldyourmnd changed the title fix(browser): let the headless CDP service start on Ubuntu 23.10+ fix(browser)!: make the browser layer reachable on stock Ubuntu 26.04, and remove rtk (2.4.0) Aug 3, 2026
…findings

Provenance for all 23 memories moves to the 2.4.0 work. The composed contract
version, the drift statement, and the published-tag relationship advance; the
"as of 2.3.0" facts stay as written, because ADR 0006, the zcode delegation, and
the Dart admission genuinely happened there.

Five durable facts are added, all of them things only a real apply revealed:

- rtk is out of scope, with the provenance mismatch that removing it also
  resolved (installed f160611f... against a pin of ff8a1e77...);
- the browser layer could not come up on any Ubuntu from 23.10 onward, and why
  the kernel userns restriction is deliberately left enabled;
- policy_hashes() was the call site the 2.2.0 umask fix missed, with the
  recognition rule for next time: look for a dropped repository_sources
  argument, do not weaken the installed-path check, do not chmod the checkout;
- materialized harness checkouts inherit the caller's umask, and why the fast
  path is the one that matters;
- the owner desktop's ~/.codex/nddev-builder.config.toml points at a patched
  module, so the harness layer fails at the end of an apply. Recorded as local
  state rather than a module defect, and no longer able to strand anything.
@rldyourmnd
rldyourmnd merged commit be0a3d5 into main Aug 3, 2026
29 checks passed
@rldyourmnd
rldyourmnd deleted the fix/cloakbrowser-headless-no-sandbox branch August 3, 2026 19:56
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.

1 participant