You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
All proxy-mode sandboxes (any sandbox created with network_policies) crash immediately on startup with:
Error: × Permission denied (os error 13)
Block-mode sandboxes (no network_policies) are unaffected.
Root Cause
PR #151 (feat(sandbox): upgrade Landlock to ABI V2 and fix sandbox venv PATH) introduced two changes that combine to break proxy-mode sandboxes:
LandlockCompatibility default changed from BestEffort → HardRequirement (crates/navigator-sandbox/src/policy.rs:87-91)
Landlock ABI upgraded from V1 → V2 (crates/navigator-sandbox/src/sandbox/linux/landlock.rs:33)
Why proxy mode is affected but block mode is not
In the Landlock apply() function (landlock.rs:28-30), if both read_only and read_write are empty, it returns early without activating Landlock:
if read_only.is_empty() && read_write.is_empty(){returnOk(());}
Block mode: No TLS directory is created, no extra paths are added. FilesystemPolicy::default() has empty read_only/read_write. With include_workdir: true, only /sandbox is in read_write. But crucially, no read_only paths exist, and /sandbox alone triggers Landlock which then blocks access to everything else — but block mode sandboxes discovered from disk (via dev-sandbox-policy.yaml) explicitly set compatibility: best_effort, so Landlock errors are silently ignored.
Proxy mode (via gRPC): The TLS directory /etc/navigator-tls is dynamically added to read_only (lib.rs:184). This causes Landlock to activate with HardRequirement. After restrict_self(), the child process can only access /etc/navigator-tls (read) and /sandbox (read-write). It cannot access /usr, /lib, /etc/passwd, shared libraries, Python, or anything else needed to run. The child fails in pre_exec → sandbox::apply() → Permission denied.
The full chain
User creates sandbox with --policy policy.yaml containing only network_policies
Policy YAML has no landlock: field → proto landlock is None
Landlock activates with only 2 paths: /etc/navigator-tls (RO), /sandbox (RW)
restrict_self() succeeds, but the child process is now locked to those 2 paths
The child's exec() of sleep (or Python) fails with EACCES because the binary is in /usr/bin which is not in the allowlist
pre_exec returns Err(io::Error) → parent sees Permission denied
Why E2E tests pass
The E2E tests in test_sandbox_policy.py explicitly set landlock=LandlockPolicy(compatibility="best_effort") and include comprehensive filesystem paths (read_only=["/usr", "/lib", "/etc", "/app", "/var/log"]). This avoids both issues.
Change the LandlockCompatibility default back to BestEffort:
// crates/navigator-sandbox/src/policy.rs#[derive(Debug,Clone,Default)]pubenumLandlockCompatibility{#[default]BestEffort,// ← was HardRequirement after #151HardRequirement,}
This restores backward compatibility: policies that omit the landlock field gracefully degrade rather than hard-failing. Users who want strict enforcement can explicitly set landlock: { compatibility: hard_requirement }.
Summary
All proxy-mode sandboxes (any sandbox created with
network_policies) crash immediately on startup with:Block-mode sandboxes (no
network_policies) are unaffected.Root Cause
PR #151 (
feat(sandbox): upgrade Landlock to ABI V2 and fix sandbox venv PATH) introduced two changes that combine to break proxy-mode sandboxes:LandlockCompatibilitydefault changed fromBestEffort→HardRequirement(crates/navigator-sandbox/src/policy.rs:87-91)crates/navigator-sandbox/src/sandbox/linux/landlock.rs:33)Why proxy mode is affected but block mode is not
In the Landlock
apply()function (landlock.rs:28-30), if bothread_onlyandread_writeare empty, it returns early without activating Landlock:Block mode: No TLS directory is created, no extra paths are added.
FilesystemPolicy::default()has emptyread_only/read_write. Withinclude_workdir: true, only/sandboxis inread_write. But crucially, noread_onlypaths exist, and/sandboxalone triggers Landlock which then blocks access to everything else — but block mode sandboxes discovered from disk (viadev-sandbox-policy.yaml) explicitly setcompatibility: best_effort, so Landlock errors are silently ignored.Proxy mode (via gRPC): The TLS directory
/etc/navigator-tlsis dynamically added toread_only(lib.rs:184). This causes Landlock to activate withHardRequirement. Afterrestrict_self(), the child process can only access/etc/navigator-tls(read) and/sandbox(read-write). It cannot access/usr,/lib,/etc/passwd, shared libraries, Python, or anything else needed to run. The child fails inpre_exec→sandbox::apply()→Permission denied.The full chain
--policy policy.yamlcontaining onlynetwork_policieslandlock:field → protolandlockisNoneSandboxPolicy::try_from(proto)calls.unwrap_or_default()→LandlockCompatibility::HardRequirement(changed in feat(sandbox): upgrade Landlock to ABI V2 and fix sandbox venv PATH #151)/etc/navigator-tlstoread_only/etc/navigator-tls(RO),/sandbox(RW)restrict_self()succeeds, but the child process is now locked to those 2 pathsexec()ofsleep(or Python) fails withEACCESbecause the binary is in/usr/binwhich is not in the allowlistpre_execreturnsErr(io::Error)→ parent seesPermission deniedWhy E2E tests pass
The E2E tests in
test_sandbox_policy.pyexplicitly setlandlock=LandlockPolicy(compatibility="best_effort")and include comprehensive filesystem paths (read_only=["/usr", "/lib", "/etc", "/app", "/var/log"]). This avoids both issues.Reproduction
Proposed Fix
Change the
LandlockCompatibilitydefault back toBestEffort:This restores backward compatibility: policies that omit the
landlockfield gracefully degrade rather than hard-failing. Users who want strict enforcement can explicitly setlandlock: { compatibility: hard_requirement }.Affected Version
Current
main(commit e3ea796, after #151 merge)