fix(browser)!: make the browser layer reachable on stock Ubuntu 26.04, and remove rtk (2.4.0) - #43
Merged
Merged
Conversation
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.
…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.
This was referenced Aug 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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-mcpwas 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
systemd restarted it seven times, each dying with
status=6/ABRT. 23.10+ restrict unprivileged user namespaces through AppArmor andkernel.apparmor_restrict_unprivileged_userns=1is 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-mcpandplaywright-cliwere 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_usernsstays 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 sourcesWith the service up, the apply failed at the integrity contract instead:
Git records only the executable bit, so a clone under
umask 002writes those eight files 664. The gate failed on a pristine tree whileumask 022hosts 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 cloneunderumask 002produced 252 group-writable paths, andnddev-codex-app'sinstall-buildercorrectly 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 pipefailan abort there stranded every layer behind it - which is how a desktop ended up missing 24 of the 46 commandsverify.shrequired. The step stays fatal; it is now fatal to itself rather than to the whole device.Removed: rtk (owner decision)
install_rtkand its 200 lines, the foursupply_chainpins, 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 hashedf160611f…against a contract pin offf8a1e77…. 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:
chrome-devtools-mcp --versionplaywright-cli --versioncloakbrowser-cdp-healthcloak_health: provendart mcp-server --versionpython3 -m pytestscripts/ci/validate.sh/lint.shci-validate-ok/scripts-lint-okVersion moves to 2.4.0: the contract loses declared pins and a published command.