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
Give Linux a real per-host egress fence — a network namespace + veth +
parent-run proxy — so the egress-proxy pattern (ADR 0016) is actual confinement
on Linux, not the honest-but-advisory posture it is today. This is the missing
backend behind Leg 4 (per-host net confinement for confined subprocesses:
shell + stdio MCP).
Context — why this is needed now. Landlock's network rules are port-based,
not address-based (ADR 0014/0015): apply() can deny all TCP egress
(net: none) but cannot admit only loopback for a net: Only{host…} grant.
So the loopback-fence-plus-proxy design (ADR 0016) has no Linux backend —
only macOS Seatbelt and Windows AppContainer can address-fence to loopback.
The companion fix (PR fix/egress-proxy-net-axis-gate — the #257 fail-open)
makes this honest: egress_proxy_plan now engages the proxy only where the
loopback-net fence is enforceable, so on Linux a remote-host net grant is advisory (not a walk-around-able proxy that falsely reports egress_proxied()). This issue supplies the enforcing Linux backend that flips it back to
confined.
Approach — netns + veth + proxy in the parent
Rootless, no external binary dependency for the core path:
Spawn the child in a fresh user + net namespace (unshare(CLONE_NEWUSER | CLONE_NEWNET), uid/gid mapped) — so no root is required and the child gets an
empty network stack.
Parent wires the netns (it holds CAP_NET_ADMINinside the child's new
userns): bring lo up; create a veth pair; move one end into the child
netns; assign a point-to-point subnet (e.g. 10.x.x.1/30 parent / 10.x.x.2/30 child, or an fd00::/link-local v6 pair); install NO default
route — the child has no path off-box.
The loopback egress proxy runs in the PARENT netns, bound to the
parent-side veth address. The child's *_PROXY env points at that address.
NetnsEgressFence: creates the userns+netns child, sets up veth/addr via netlink (rtnetlink/neli, not shelling out to ip), points the proxy,
applies alongside Landlock. Wired into ConfinedCommand::spawn/spawn_tokio
under a general remote-host grant on a netns-capable host.
Feature linux-netns (inert off Linux) + capability probe netns_egress_is_supported() — kernel supports unprivileged userns + veth
creation. Hardened hosts (sysctl kernel.unprivileged_userns_clone=0, AppArmor
userns restrictions) → honest advisory fallback (the Expose the loopback egress proxy for external + long-lived confined callers (enables newt Leg 4) #257 gate already handles
this: no capability ⇒ no proxy ⇒ advisory).
Acceptance criteria (with the reality-check bar)
Real-kernel integration test (Linux 6.x, #[ignore] integration tier): a
spawned curl (exec-scoped, no shell re-exec) under net: Only{api.example.com}
reaches an allow-listed host through the proxy; a direct dial to a
non-allow-listed host (child ignoring *_PROXY) fails at the kernel (no
route off the veth); an off-list CONNECT through the proxy is 403'd and
recorded in refused_hosts(). Proves non-bypassability, not just the proxy.
Rootless: works without root / CAP_SYS_ADMIN via the userns path; honest
advisory fallback where unprivileged userns is disabled.
Compose: Landlock fs/exec confinement stays in force in the netns child
(an out-of-scope write is still EACCES; an un-granted exec still refused).
No heavy new runtime deps beyond a netlink crate; no hard dependency on an
external ip/slirp4netns binary for the core path (slirp/pasta may be an optional connectivity backend for environments without veth).
Weekly combinatorial CI covers linux-netns on/off (the --feature-powerset sweep, per the standing "exhaustive weekly jobs" rule).
Alternatives considered
nftables per-netns — still needs a netns to scope rules; the veth+proxy
design already gives per-host at the proxy, so nftables adds little.
slirp4netns / pasta — userspace TCP/IP stack (what rootless podman uses);
robust but an external binary dependency. Keep as an optional connectivity
backend, not the core.
Netns + veth + a parent-run proxy is the standard, robust, rootless-capable
choice — the same shape rootless containers use, aimed here at a single confined
subprocess.
Summary
Give Linux a real per-host egress fence — a network namespace + veth +
parent-run proxy — so the egress-proxy pattern (ADR 0016) is actual confinement
on Linux, not the honest-but-advisory posture it is today. This is the missing
backend behind Leg 4 (per-host
netconfinement for confined subprocesses:shell + stdio MCP).
Context — why this is needed now. Landlock's network rules are port-based,
not address-based (ADR 0014/0015):
apply()can deny all TCP egress(
net: none) but cannot admit only loopback for anet: Only{host…}grant.So the loopback-fence-plus-proxy design (ADR 0016) has no Linux backend —
only macOS Seatbelt and Windows AppContainer can address-fence to loopback.
The companion fix (PR fix/egress-proxy-net-axis-gate — the #257 fail-open)
makes this honest:
egress_proxy_plannow engages the proxy only where theloopback-net fence is enforceable, so on Linux a remote-host
netgrant isadvisory (not a walk-around-able proxy that falsely reports
egress_proxied()).This issue supplies the enforcing Linux backend that flips it back to
confined.
Approach — netns + veth + proxy in the parent
Rootless, no external binary dependency for the core path:
unshare(CLONE_NEWUSER | CLONE_NEWNET), uid/gid mapped) — so no root is required and the child gets anempty network stack.
CAP_NET_ADMINinside the child's newuserns): bring
loup; create avethpair; move one end into the childnetns; assign a point-to-point subnet (e.g.
10.x.x.1/30parent /10.x.x.2/30child, or an fd00::/link-local v6 pair); install NO defaultroute — the child has no path off-box.
parent-side veth address. The child's
*_PROXYenv points at that address.every off-box connection MUST traverse the proxy, which enforces the host
allow-list (+ the existing SSRF-pivot guard, net_proxy / bridle-netmon hardening (post-hoc review): netmon UTF-8 crash, SSRF pivot, CONNECT byte-drop, audit coverage #138). A rogue child cannot
walk around it — there is no route off the veth. This is the address-fence
Seatbelt provides, now on Linux.
child; netns confines net.
Landlock (fs/exec) ∘ netns (net).Implementation surface
SandboxKind: a net-fenced Linux variant (e.g.LandlockNetns) soeffective_sandbox_kind,egress_proxy_plan_for, andloopback_net_enforceable(the seam the Expose the loopback egress proxy for external + long-lived confined callers (enables newt Leg 4) #257 fix introduced) recognize it →
loopback_net_enforceable(LandlockNetns) == true.NetnsEgressFence: creates the userns+netns child, sets up veth/addr vianetlink (
rtnetlink/neli, not shelling out toip), points the proxy,applies alongside Landlock. Wired into
ConfinedCommand::spawn/spawn_tokiounder a general remote-host grant on a netns-capable host.
linux-netns(inert off Linux) + capability probenetns_egress_is_supported()— kernel supports unprivileged userns + vethcreation. Hardened hosts (sysctl
kernel.unprivileged_userns_clone=0, AppArmoruserns restrictions) → honest advisory fallback (the Expose the loopback egress proxy for external + long-lived confined callers (enables newt Leg 4) #257 gate already handles
this: no capability ⇒ no proxy ⇒ advisory).
Acceptance criteria (with the reality-check bar)
#[ignore]integration tier): aspawned
curl(exec-scoped, no shell re-exec) undernet: Only{api.example.com}reaches an allow-listed host through the proxy; a direct dial to a
non-allow-listed host (child ignoring
*_PROXY) fails at the kernel (noroute off the veth); an off-list CONNECT through the proxy is
403'd andrecorded in
refused_hosts(). Proves non-bypassability, not just the proxy.loopback_net_enforceable(LandlockNetns) == true;egress_proxy_plan_for(LandlockNetns, remote_net_grant).is_some().CAP_SYS_ADMINvia the userns path; honestadvisory fallback where unprivileged userns is disabled.
(an out-of-scope write is still
EACCES; an un-granted exec still refused).external
ip/slirp4netnsbinary for the core path (slirp/pasta may be anoptional connectivity backend for environments without veth).
linux-netnson/off (the--feature-powersetsweep, per the standing "exhaustive weekly jobs" rule).Alternatives considered
design already gives per-host at the proxy, so nftables adds little.
robust but an external binary dependency. Keep as an optional connectivity
backend, not the core.
connect()filtering — cannot express hostnames, brittle againstIP-literal dials; rejected.
Netns + veth + a parent-run proxy is the standard, robust, rootless-capable
choice — the same shape rootless containers use, aimed here at a single confined
subprocess.
Refs
(
fix/egress-proxy-net-axis-gate).Gilamonster-Foundation/newt-agent#1267):it makes per-host MCP net confinement real on Linux.