lichen.page.review from the reviewed point 0b92cb61 to stack eb5a1653, as one PR per subsystem (review set)

product: 02 Permissions: route groups and spawn permissions #49

targeting oct/01-sandbox from oct/02-permissions
Closed opened by and assigned to notplants-bot.bsky.social
targeting oct/01-sandbox from oct/02-permissions

superseded by the full set from 09-23, #17–#37

description: Review set for lichen.page.review, 0b92cb61 (reviewed) → eb5a1653 (stack). PR 2 of 16. 8 files, +1252/−14. One commit, 9b0115271e91, which sets these files to their content at eb5a1653. Targets #1 (oct/01-sandbox). #3 stacks on this one. Tests and generated files are not in any PR; they are in main.

Who may do what is now declared in two places only, and both are checked by the compiler or by clippy:

  • On routers. Every HTTP route is added to a PermissionRouter group, which names the permissions its routes need on their target (or a named group, or the explicit public group). The group's check runs as a route_layer on that route alone. A route outside a group cannot be written: clippy.toml now forbids axum::Router::route, route_service, nest_service and fallback, and the group records every route with its group for the generated tables in docs/SECURITY.md.
  • On spawns. Every process lichen starts names a list of Permissions (lichen-site/src/permissions.rs), and its sandbox is compiled from that list. Nothing grants a spawn a site path or a descriptor otherwise.

There are no roles: a principal (owner, share guest, full token, safe token, …) holds permissions, and the policy for each server finds the principal and checks the group's needs. The front server's policy is lichen-server/src/access.rs; the site's is lichen-site/src/access.rs, which fails closed when the serving server put no admission in the request. The operator's answers to the L0 note (O1–O4) add RewriteHistory, ConfigureSite and ManageDeployKey as permissions, a desktop() group, and keep every refusal as it was: no behaviour change was intended by the move to groups.


info: Plans: LICHEN-SANDBOX-AND-PERMISSIONS-2026-10-13.md (L2: groups on routers; L3: permissions on spawns; L4: tables generated from the code; its header records the operator's answers O1–O4), design note LICHEN-SANDBOX-L0-DESIGN-2026-10-15.md §4.3, §6 and §7. Later plans put their new routes in these groups (ADMIN-EDIT-HOST-SPLIT's host roles, SITE-FRONT-BOUNDARY's routes).

Files

  • crates/lichen-sandbox/src/permission_router.rs — new: PermissionRouter, Group (Gate, Named, Public), Policy, Routes, and the GROUPS record. Read this first. It is in lichen-sandbox because the mechanism is generic; the policies are lichen's.
  • crates/lichen-sandbox/src/route_names.rs — moved from lichen-site: records each route's handler by name as it is added, so a route-table dump can say whether a move changed a handler. RouteNamed is now crate-private: routes are added through the groups.
  • crates/lichen-server/src/access.rs — new: the front server's Policy (Front): who is asking (session, token, share guest), their principal on the target site, and the answer statuses (401/403/404) the routes already gave.
  • crates/lichen-site/src/access.rs — new: the site's Policy (Site), with one Admitter per serving server: the front server's own, or a site process's ForwardedIdentity.
  • crates/lichen-site/src/permissions.rs — new: Permission, the per-spawn lists (SITE_PROCESS, BACKUP_READ, BACKUP_RESTORE, BACKUP_FETCH, …) and their compilation to a sandbox policy and child limits.
  • clippy.toml — the router rule above. Also renames the metadata-write rule to mutate_without_advance (N1; the rename itself is in #1).
  • crates/lichen-yrs/examples/code-mirror/main.rs, examples/webrtc-signaling-server/main.rs — an allow for the router rule: these examples run a plain axum server, not lichen's.

Moved in: every file here. The starting map spread them over Sandbox (permission_router.rs, route_names.rs), front server core (lichen-server/src/access.rs), site core (lichen-site/src/access.rs, permissions.rs), CI (clippy.toml) and iroh (the two lichen-yrs examples, by crate). They are one subject, and this is where a reviewer of "who may call this route" would look.

Lives elsewhere

  • Each crate's routes are put in their groups where they are defined: the dashboard API in #11 (api.rs), the front server's router in #10 (server.rs), the site's router in #13 (api.rs), git and publish routes in their own PRs.
  • The generated tables and the principals table are in docs/SECURITY.md, #16.
  • The snapshot of who may do what (permission_snapshot.txt), the dashboard boundary tables and their tests are in main.
Commits 0