superseded by the full set from 09-23, #17–#37
description:
Review set for lichen.page.review, 0b92cb61 (reviewed) → eb5a1653 (stack). PR 1 of 16. 30 files, +2987/−1262. One commit, c3bdd2b7dcb0, which sets these files to their content at eb5a1653. Targets main (the reviewed point plus tests and generated files). #2 stacks on this one. Tests and generated files are not in any PR; they are in main.
lichen's kernel confinement now lives in a crate of its own, lichen-sandbox. Most of it moved out of lichen-flat-file (Landlock rulesets, seccomp filters, the sandboxed process mode, confined threads, the egress filter, the child environment), and the site process's memory bound moved out of lichen-server. lichen-flat-file keeps the store, its transactions and its file operations, and is now built on lichen-sandbox, which it re-exports under the old names so callers did not change.
Two behaviours change, beyond the move:
- A call's processes are one process group (SITE-FRONT-BOUNDARY decision 6, unit 3). Every child lichen starts leads a group of its own. In a site process,
setsidandsetpgidare denied by seccomp inside that group, so nothing a call starts can leave it. When the call ends (success, error, cancel, timeout) the group is killed while its leader is still unreaped, then reaped. This replaces the old "register every child by pidfd and sweep" design (lichen-flat-file/src/subtree.rs, deleted). Interpreters and mdBook, whose spawns lichen does not control, join the call's group throughlichen sandbox call-join. - The front server sweeps a dead site process's orphans (
reaper.rs, moved here fromlichen-server): it is a subreaper, and tells a site's orphan from its own children by their seccomp filter count.
The crate also gets a start-up probe of the kernel (probe.rs): a mode that needs a feature the kernel lacks refuses to start, instead of running with less.
info:
Plans: LICHEN-SANDBOX-AND-PERMISSIONS-2026-10-13.md (L1: the crate; its design note LICHEN-SANDBOX-L0-DESIGN-2026-10-15.md), SITE-FRONT-BOUNDARY-2026-10-17.md (decision 6 and unit 3: call groups; unit 11d: in_confined_site_process), OPERATOR-REVIEW-REVISIONS-2026-10-13.md (N3, R5: one wrapper that reads use_sandbox; N5: keep lichen-flat-file generic). All in lichen-orchestrator machine-docs/plans/.
Files
The new crate:
crates/lichen-sandbox/Cargo.toml,README.md,src/lib.rs— the crate, its rules (restrict before running, fail closed, only narrow), and its module list. It depends on no lichen crate.src/beneath.rs—openat2/RESOLVE_BENEATHpath resolution and the confined file operations, taken out oflichen-flat-file/src/fsops.rs.src/call_group.rs— new: a call's process group, its seccomp deny filter, and the kill-then-reap order. Read this first.src/subtree.rs— new and small: "am I a site process", and whether it is confined.src/reaper.rs— the front server's sweep of a dead site process's orphans (waslichen-server/src/site_process/reaper.rs, deleted in #3; git sees it as a new file).src/probe.rs— new: what the kernel offers, and the refusal when a mode needs more.src/egress_unsupported.rs— new: the non-Linux stubs, now with the same item shapes as Linux.
Moved from lichen-flat-file (renames, with their changes):
src/sandbox.rs— the sandboxed process mode; addscall-hold/call-join, and thenetworkflag inSpec(only the front server's push of a hub has it, unit 6).src/confine.rs— confined threads and the runners of a tree's children, now on call groups.src/script_child.rs— Python's child; its subtree kill loop replaced byCallChild.src/egress.rs— addsprobe_listener, read by the probe.src/ruleset.rs— addsscope_only, a child's own nested domain (signals and abstract sockets).src/capstd.rs,src/config.rs,src/child_env.rs— small follow-ups of the move.src/memory.rs— moved fromlichen-server/src/site_process/memory.rs; items madepub.
lichen-flat-file, now on lichen-sandbox:
src/lib.rs,Cargo.toml— re-exports the moved modules; dropslibc,seccompiler,landlock,cap-stdas direct dependencies.src/fsops.rs— keeps the writes; resolution moved tolichen_sandbox::beneath(−528).src/subtree.rs,src/egress_unsupported.rs— deleted (replaced by call groups; moved).src/reserved.rs— newtree_private_area:<root>/.lichen/sites/<tree>/, the per-site area #4 uses; the marker text now says not to remove it.src/store.rs—read_limited;mutate_metadatarenamedmutate_without_advance(N1).src/lock.rs,src/txn.rs,README.md— wording.
lichen's side:
crates/lichen-site/src/sandbox.rs— the site's interface to confinement. A script worker thread has no confinement of its own any more (N3): in a site process the process's sandbox covers it; withuse_sandbox = falseit runs with the server's access.Cargo.toml(workspace) — addscrates/lichen-sandboxas a member.
Moved in: the workspace Cargo.toml (the starting map's CI bucket; its one change adds this crate) and lichen-site/src/sandbox.rs (site core; it is lichen's interface to this crate). Moved out: permission_router.rs and route_names.rs, to #2.
Lives elsewhere
permission_router.rsandroute_names.rsare inlichen-sandboxbut are about who may call a route, so they are in #2.- What each spawn is allowed (
lichen-site/src/permissions.rs) is in #2; the site process's own launch is in #3. - Tests (
crates/lichen-sandbox/tests/,src/tests/, fixtures) are inmain, not here. SANDBOX.mdis in #16.
Inline tests kept in this PR's files: beneath.rs, call_group.rs and lichen-site/src/sandbox.rs each have an inline #[cfg(test)] module.