-
Notifications
You must be signed in to change notification settings - Fork 2
plat 283
| Coordination | Value |
|---|---|
| Assigned agent | Claude Sonnet 5 |
| Ticket state |
fixed, live-verified on the Dominion deployment |
| Last synchronized | 2026-09-05 |
-
Priority: P1 — a hard capability wall, not a bug in one workflow: any
execute_shell_commandstep on any Linux deployment that needed a package not already in the run image had no way to get it, full stop. -
Owner:
workspace/security/environment.go(buildNativeEnvironment),workspace/security/isolator_linux.go(landlockCommand,packageManagerCacheEnv,writableCacheBase). -
Related: PLAT-281 (the same shape of bug — a real capability gap that
looked like a fundamental sandbox limitation but was actually one missing
grant — for
exec, not filesystem cache writes) and PLAT-118 (Landlock fail-closed design generally).
The Dominion trading workflow's agent reported, and had "tested and
documented" back on 2026-08-31, that installing a Python package inside its
sandboxed shell was not viable at all: pip3 install yfinance was refused
because Python is externally-managed (PEP 668); forcing it past that with
--break-system-packages then hit a raw permission error; and even
python3 -m venv failed the same way. The conclusion on file was that only
what ships in the run image (standard library + a few extras) is usable —
a permanent architectural limit.
That conclusion was wrong, and provably so: Landlock as configured in this
codebase (landlock_runner_linux.go) restricts filesystem access only —
there is no LANDLOCK_ACCESS_NET_* rule anywhere, and Dominion (like every
other Linux deployment here) has no egress firewall either
(deploy/dedicated-vm/dominion-hetzner.md confirms no firewall rules are
part of the deploy). PyPI/npm are reachable. The actual failure was two
separate, both-fixable gaps stacked on top of each other:
- Native-mode shells (
NATIVE_WORKSPACE=true, the standard mode per this session's STT-engine and Landlock work) never setPIP_BREAK_SYSTEM_PACKAGES=1— only the Docker environment builder did. That's exactly the "externally-managed-environment" refusal. - pip, npm, and
venvall default to caching under$HOME(~/.cache/pip,~/.local, a$TMPDIR-based build scratch directory). A step's Landlock write grant is scoped to its own workspace folder, not the real$HOME— on Dominion,HOME=/srv/dominion/home, entirely outside every step's grant.--break-system-packagesgenuinely bypasses the PEP 668 check and then hits Landlock's real, correct denial the first time pip (or venv) tries to write its cache. That denial looked identical to "installs don't work here" from the agent's vantage point, but it's a routing gap, not a wall. - Found only by running a real
pip installagainst the shipped launcher after gaps 1–2 were fixed (the synthetic cache-write probe passed while the real thing still failed): the Landlock read baseline granted an enumerated dozen/etcentries (ssl, resolv.conf, passwd, ld.so.*, fonts, ...), and pip reads two more —/etc/debian_version(its vendoreddistromodule, building the HTTP User-Agent before any network call; it catchesFileNotFoundErrorbut Landlock denies withEACCES, so thePermissionErroris fatal) and then/etc/mime.types. Granting just the distro files moved the failure tomime.types; granting all of/etcread-only made the full install succeed end-to-end. Enumerating files is whack-a-mole; every future tool that reads one more config file would be its own ticket. One subtlety, also found live: the existing explicit entries must stay alongside the/etcgrant, not be replaced by it — a Landlock rule on/etccovers what lives under it, not what a symlink there points at. On systemd-resolved hosts like Dominion,/etc/resolv.conf→/run/systemd/resolve/stub-resolv.conf, and a build that granted/etcalone failed every DNS lookup in the sandbox ("Temporary failure in name resolution");existingCanonicalPathsresolving each explicit entry individually is what grants the target.
-
buildNativeEnvironment(environment.go) now also setsPIP_BREAK_SYSTEM_PACKAGES=1, matching whatbuildDockerEnvironmentalready did — closing gap 1 for every native-mode deployment, not just Dominion. -
landlockCommand(isolator_linux.go) now appendsPIP_CACHE_DIR,XDG_CACHE_HOME,PYTHONUSERBASE,npm_config_cache, andTMPDIR, all redirected into a.cache/.local/.tmpsubtree of a directory the step can actually write to — closing gap 2. The base directory is chosen defensively: the step's ownWorkDirwhen it falls inside a granted write path (the normal case), otherwise the first granted write path, otherwise no redirection at all (so a step with no write grant fails exactly as it did before, rather than pointing pip at somewhere Landlock will also deny). This applies to every Linux deployment using this Landlock launcher, not a Dominion-specific env-var carve-out — the fix the user explicitly asked to have "generic for linux" rather than a one-off patch. -
landlockSystemReadPaths(landlock_runner_linux.go, i.e. thevideo-studio-landlock-runnerbinary itself) now grants all of/etcread-only in addition to the enumerated entries (which must stay — see theresolv.confsymlink note above; a regression test pins the resolved target) — closing gap 3. Why this is safe, and the one rule it imposes: it is read-only (the write baseline is untouched); DAC still applies, so it grants nothing the service user cannot already read outside the sandbox — verified on Dominion withfind /etc -type f -readable ! -perm -o=rreturning nothing, i.e. no/etcfile is readable by the service user without already being world-readable; and the baseline's "keep these narrow" rationale is about/proc(other processes' environments and credentials), which stays narrow. Because Landlock rules are additive, a deployment cannot carve a secret back out of this grant, so service-readable secrets must stay out of/etc— Dominion keeps them in/srv/dominion/.env, which is where they belong anyway.
- 5 new tests:
TestNativeEnvironmentAllowsPipInstallOnExternallyManagedPython(environment_test.go);TestPackageManagerCacheEnvPointsIntoTheGrantedWritePath,TestPackageManagerCacheEnvFallsBackWhenWorkDirIsNotWritable,TestPackageManagerCacheEnvNoOpsWithoutAnyWritePath, andTestLandlockCommandCanInstallIntoItsOwnCache(isolator_linux_test.go). - Cross-compiled for Linux and run live on the Dominion box against the
real, deployed
video-studio-landlock-runner— all 5 new tests pass, andTestLandlockCommandCanInstallIntoItsOwnCachereproduces the exact incident shape (write$PIP_CACHE_DIR/probe.whlinside a Landlock sandbox with only the step's own workspace folder granted) and confirms the write now lands inside the granted path. - Full
workspace/securitysuite run live on Dominion, both before and after this change: one pre-existing failure (TestLandlockEnforcesExternalFolderAccess) reproduces identically against a clean pre-change binary built from the sameorigin/maintip, confirming it predates this work and is not a regression from it. Everything else passes unchanged. - Real installs run live inside the shipped
video-studio-landlock-runneron Dominion, withHOMEleft at the unwritable/srv/dominion/homeand only a scratch directory granted for writes (the exact shape of a workflow step): with all of/etcreadable,python3 -m pip install --user yfinancecompleted end-to-end — PyPI over HTTPS, numpy/pandas/ lxml/curl_cffi and the rest downloaded through the routed cache, installed into the routed user-site,pip showreporting yfinance 1.7.0. With only the three distro-id files added instead, the same install got pastdebian_versionand failed onmime.types, which is what settled enumerate-vs-grant-/etc. The Debian side is fully provisioned (python3-venv,python3-pip, and the bundled pip/ setuptools wheels are installed), and the samevenvcreation succeeds outside the sandbox, so every failure here was Landlock, not the image. - Verification gotcha worth knowing for the next person: the launcher
consumes its
--configpolicy file (it is gone after one run), so a hand-staged policy is single-use — stage one copy per invocation, or the second run fails withSANDBOX_UNAVAILABLE: read Landlock policybefore doing anything. - Still pending after this ticket: the first real trading-workflow step that needs a package outside the run image is the production reverify.
Auto-synced from docs/ on main. Edit there, not here.