Commit graph

2 commits

Author SHA1 Message Date
Breadway
50ff425d2a Fix test harness process leak: graceful SIGTERM shutdown + disable podman adapter in test fixtures
All checks were successful
dev release / build (push) Successful in 1m3s
The integration test harness (ipc_integration.rs and
module_host_sandbox.rs) tore down spawned breadd instances via
Child::kill() (SIGKILL). Since breadd's PodmanAdapter is enabled by
default and the test breadd.toml fixtures didn't disable it, every
test-spawned daemon on a machine with podman installed ran a real
`podman events --format json` child. SIGKILL gives breadd zero chance
to run its own graceful shutdown path (wait_for_shutdown -> adapter
tasks dropping -> kill_on_drop firing on that child), so it was
orphaned and reparented to init on every single test run instead.

A day of repeated `cargo test --workspace` runs during today's
workstream development left 1,559 such orphaned processes consuming
76.8GB of RSS system-wide, found and cleaned up separately.

Fix is two-layered: disable the podman adapter in both harnesses'
breadd.toml fixtures (tests don't need real container-lifecycle
watching, matching how hyprland/udev/power/network are already
disabled there), and change TestHarness::drop to send SIGTERM with a
bounded wait before falling back to SIGKILL, so breadd's own cleanup
gets a real chance to run - defense in depth against any future
adapter that spawns a subprocess.

Verified: two full `cargo test --workspace` runs (282 tests) produce
zero new podman events processes and zero leftover breadd/
bread-module-host processes.
2026-08-05 06:45:20 +08:00
Breadway
1e2817537b Add Workstream G: out-of-process, Landlock-sandboxed module runtime
Closes the gap Workstream D's in-process capability scoping left open:
build_scoped_env only gated presence of bread.* bindings, but os.execute/
io.open/debug.* remained fully reachable since a module's Lua still ran
inside breadd's own process. A module that declares [[permissions]] in
bread.module.toml (including an explicit empty list) is now spawned as a
separate bread-module-host process instead, restricted by a Landlock
ruleset breadd builds from that module's granted permissions and applies
via Command::pre_exec before the child executes any Lua at all. A module
with no manifest at all keeps today's in-process, ungated behavior for
backward compatibility.

- bread-module-host: new minimal binary (mlua + tokio + serde_json) that
  connects to breadd's existing IPC socket, presents a one-time spawn
  token, and proxies bread.* calls as RPC instead of direct bindings.
- breadd/src/module_host.rs: spawn + token registry + apply_sandbox
  (Landlock ruleset construction), with unit tests that spawn a real
  child and verify denial at the OS level, not a Lua-level check.
- breadd/src/ipc/module_host_bridge.rs: the module_host.* RPC bridge
  (on/once/off/emit/after/every/cancel, fs.read/write, exec/exec_capture,
  state.get, log/warn/error, status) plus the hello handshake. Bumped
  API_VERSION to 1.6.0.
- breadd/tests/module_host_sandbox.rs: end-to-end acceptance tests going
  through a real spawned breadd + bread-module-host + IPC handshake —
  os.execute/io.open denied outside a module's granted fs.read scope, and
  kill -9 on a module-host child leaving breadd and other modules intact
  while breadd reports bread.module.crashed.
- bread-shared/src/module_host_ipc.rs: shared wire types (hello result,
  tagged event/timer push envelope) so breadd and bread-module-host can't
  drift on the handshake/push shape.

Deferred (documented in Documentation.md's Workstream G section): the
trust="in-process" opt-out, remaining bread.* namespaces over RPC
(hyprland/widget/machine/bluetooth/notify/state.watch), network
sandboxing, and a fully static build that would remove the Execute grant
Landlock's dynamic-linker requirement forces on system library dirs.
2026-08-05 04:02:05 +08:00