From 71048a862d4721ca7f57c0344a2e1c1b367b42b0 Mon Sep 17 00:00:00 2001 From: Sam & Claude Date: Sun, 14 Jun 2026 15:07:17 +0200 Subject: [PATCH] test(daemon): cover staged-env shell-quoting helpers; rescope ISO priority 1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add focused unit tests for the staged-jail env payload helpers in spawner.rs (shell_single_quote, valid_env_key, build_env_script) — they generate shell injected into the jail launcher and were previously only exercised indirectly. Tests assert single-quote escaping, metachar neutralization, env-key validation, and sorted/quoted script generation. Re-scope PRIORITY-HANDOFF-ISO-SPAWN-COST.md Priority 1: the clawdie-iso build now stages binaries, installs rc.d, creates the colibri user, and enables the service (build.sh::install_colibri_service). Remaining work is boot/runtime validation on FreeBSD, not build wiring. P2/P3 unchanged. colibri-daemon: 70 tests pass (was 62); fmt + clippy clean; md gate clean. Co-Authored-By: Claude Opus 4.8 --- crates/colibri-daemon/src/spawner.rs | 70 ++++++++++++++++++++ docs/PRIORITY-HANDOFF-ISO-SPAWN-COST.md | 88 +++++++++++-------------- 2 files changed, 109 insertions(+), 49 deletions(-) diff --git a/crates/colibri-daemon/src/spawner.rs b/crates/colibri-daemon/src/spawner.rs index cae51a0..7685403 100644 --- a/crates/colibri-daemon/src/spawner.rs +++ b/crates/colibri-daemon/src/spawner.rs @@ -966,3 +966,73 @@ mod jail_tests { assert!(err.to_string().contains("requires JailConfig.root_path")); } } + +#[cfg(test)] +mod staged_env_tests { + use super::*; + use std::collections::HashMap; + + #[test] + fn shell_single_quote_plain() { + assert_eq!(shell_single_quote("bar"), "'bar'"); + } + + #[test] + fn shell_single_quote_escapes_single_quote() { + // a'b -> 'a'"'"'b' : close quote, escaped quote, reopen quote + assert_eq!(shell_single_quote("a'b"), r#"'a'"'"'b'"#); + } + + #[test] + fn shell_single_quote_neutralizes_metachars() { + // Shell metacharacters stay literal inside the single quotes. + let payload = "$(rm -rf /); `id`; a&&b || c; $HOME"; + let quoted = shell_single_quote(payload); + assert!(quoted.starts_with('\'')); + assert!(quoted.ends_with('\'')); + // No unescaped single quote ever appears, so the shell cannot break out. + assert_eq!(quoted, format!("'{payload}'")); + } + + #[test] + fn valid_env_key_accepts_good_keys() { + for key in ["PATH", "_X", "A1", "COLIBRI_FOO", "_"] { + assert!(valid_env_key(key), "expected {key} to be valid"); + } + } + + #[test] + fn valid_env_key_rejects_bad_keys() { + for key in ["", "1A", "A-B", "A B", "A.B", "A=B"] { + assert!(!valid_env_key(key), "expected {key:?} to be rejected"); + } + } + + #[test] + fn build_env_script_sorts_and_quotes() { + let mut env = HashMap::new(); + env.insert("FOO".to_string(), "bar".to_string()); + env.insert("BAZ".to_string(), "x y".to_string()); + let script = build_env_script(&env).unwrap(); + assert_eq!(script, "export BAZ='x y'\nexport FOO='bar'\n"); + } + + #[test] + fn build_env_script_escapes_values() { + let mut env = HashMap::new(); + env.insert("TOKEN".to_string(), "a'b".to_string()); + let script = build_env_script(&env).unwrap(); + assert_eq!( + script, + format!("export TOKEN={}\n", shell_single_quote("a'b")) + ); + } + + #[test] + fn build_env_script_rejects_invalid_key() { + let mut env = HashMap::new(); + env.insert("BAD KEY".to_string(), "v".to_string()); + let err = build_env_script(&env).unwrap_err(); + assert!(err.to_string().contains("invalid env key")); + } +} diff --git a/docs/PRIORITY-HANDOFF-ISO-SPAWN-COST.md b/docs/PRIORITY-HANDOFF-ISO-SPAWN-COST.md index 61ff730..d516480 100644 --- a/docs/PRIORITY-HANDOFF-ISO-SPAWN-COST.md +++ b/docs/PRIORITY-HANDOFF-ISO-SPAWN-COST.md @@ -14,53 +14,39 @@ the final step. Items can be worked in parallel by different agents. --- -## Priority 1: Wire the ISO staging script into the clawdie-iso build +## Priority 1: Validate the staged colibri_daemon boots and runs on the ISO ### Why this is #1 -`scripts/stage-colibri-iso.sh` already exists and copies binaries, rc.d, -directories, and a sample rc.conf into a DESTDIR. But it has **never been run -against the actual ISO image root**, and the clawdie-iso build process does -not call it. Without this wiring, colibri_daemon cannot boot on the ISO, which -blocks Gate 1 (passive service) entirely. +The build-side wiring is **done**. The clawdie-iso build now stages the +Colibri binaries, installs the rc.d script, creates the `colibri` user, and +enables the service. What has **not** happened yet is booting a freshly built +image and confirming `colibri_daemon` actually starts and the acceptance +runbook passes on real FreeBSD. Until that boot/runtime validation is done, +Gate 1 (passive service) is unproven. -### What exists +### What's done (build wiring) -| Artifact | Location | Status | -| ------------------ | ------------------------------------------ | ----------------------------------------------------------------------------------------------- | -| staging script | `scripts/stage-colibri-iso.sh` | done — copies `colibri-daemon`, `colibri`, `colibri-smoke-agent`, rc.d, newsyslog, creates dirs | -| rc.d script | `packaging/freebsd/colibri_daemon.in` | done — `start_precmd`, pidfile, daemon(8) wrapper, `COLIBRI_COST_MODE` propagation | -| newsyslog config | `packaging/freebsd/newsyslog-colibri.conf` | done | -| rc.conf.sample | generated by staging script | done | -| acceptance runbook | `docs/ISO-ACCEPTANCE-RUNBOOK.md` | done | +| Artifact / step | Location | Status | +| -------------------- | ------------------------------------------------ | ----------------------------------------------------------------------------------------------- | +| staging script | `scripts/stage-colibri-iso.sh` | done — copies `colibri-daemon`, `colibri`, `colibri-smoke-agent`, rc.d, newsyslog, creates dirs | +| rc.d script | `packaging/freebsd/colibri_daemon.in` | done — `start_precmd`, pidfile, daemon(8) wrapper, `COLIBRI_COST_MODE` propagation | +| newsyslog config | `packaging/freebsd/newsyslog-colibri.conf` | done | +| rc.conf.sample | generated by staging script | done | +| acceptance runbook | `docs/ISO-ACCEPTANCE-RUNBOOK.md` | done | +| build integration | clawdie-iso `build.sh::install_colibri_service` | done — calls `stage-colibri-iso.sh` against the image root | +| `colibri` user/group | clawdie-iso `build.sh` (`pw useradd colibri`) | done — created in the image during build | +| service enable | clawdie-iso `build.sh` (`colibri_daemon_enable`) | done — written into image rc.conf | +| prebuilt binaries | build-host Rust toolchain (preflight-gated) | done — `build.sh` stages prebuilt release binaries and fails preflight if missing | -### What's missing +### What's missing (boot/runtime validation) -1. **`colibri` user/group creation in the image.** - The staging script documents this as a manual step in - `ETC_DIR/README.iso` but does not create the user. Options: - - Add `pw useradd`/`pw groupadd` to the staging script (requires root, or - runs during image build which has root) - - Add it to the clawdie-iso firstboot wizard - - Add it as an `etc/passwd`/`etc/group` entry in the image root +1. **Boot a freshly built image on FreeBSD** (bhyve VM or hardware) and confirm + the `colibri` user, binaries, rc.d script, and rc.conf entry are present in + the running system. -2. **clawdie-iso build integration.** - The clawdie-iso build process needs to: - - Cross-compile (or pre-build) `colibri-daemon` for `x86_64-unknown-freebsd` - - Call `stage-colibri-iso.sh` against the image root - - Merge `rc.conf.sample` into `/etc/rc.conf.d/colibri_daemon` or `/etc/rc.conf` - - Ensure the `colibri` user exists in `/etc/passwd` and `/etc/group` in the image +2. **Run the acceptance runbook on the booted image:** -3. **Pre-built FreeBSD binaries.** - Linux agents cannot produce FreeBSD binaries directly (no cross-compile - target in the workspace). Either: - - The FreeBSD agent (Codex) builds `cargo build --workspace --release` on - FreeBSD and the ISO build consumes `target/release/*.bin` - - A CI step on a FreeBSD runner produces the artifacts - - The ISO build host has Rust installed and builds in-place - -4. **Verification on FreeBSD.** - After staging, run the acceptance runbook commands on the booted image: ```sh service colibri_daemon start colibri status @@ -72,19 +58,23 @@ blocks Gate 1 (passive service) entirely. service colibri_daemon stop ``` +3. **Confirm logging + lifecycle:** pidfile is created, newsyslog rotation + config is in place, and `service colibri_daemon stop` cleanly stops the + daemon and removes the pidfile. + ### Key files -- `scripts/stage-colibri-iso.sh` — the staging script (line 44-93: dir creation, bin copy, rc.d install, rc.conf.sample generation) +- `scripts/stage-colibri-iso.sh` — the staging script (dir creation, bin copy, rc.d install, rc.conf.sample generation) - `packaging/freebsd/colibri_daemon.in` — rc.d script -- `docs/ISO-ACCEPTANCE-RUNBOOK.md` — acceptance commands +- `docs/ISO-ACCEPTANCE-RUNBOOK.md` — acceptance commands to run on the booted image - `docs/ISO-INTEGRATION-PLAN.md` §Lane A — full plan with gap audit -- clawdie-iso repo — image build scripts that need to call the staging script +- clawdie-iso `build.sh` — `install_colibri_service()` already wires staging, user creation, and service enable ### Suggested owner -ISO/build lane (Codex on FreeBSD for binary production + Sam for build -integration). Linux agents can prepare the staging script changes and -clawdie-iso build wiring. +ISO/build lane — FreeBSD agent (Codex) or Sam boots a built image and runs the +acceptance runbook. No Linux-side code change is required; this is a +runtime-proof step. --- @@ -284,11 +274,11 @@ wiring, no platform-specific behavior. ## Summary table -| # | Item | Blocks | Linux-doable | Effort | -| --- | --------------------- | ------------------- | ---------------------------------- | ------ | -| 1 | ISO staging wiring | Gate 1 | partially (needs FreeBSD binaries) | medium | -| 2 | Pi spawn end-to-end | Gate 2 | yes (with fake-pi-agent.py) | medium | -| 3 | Cost mode enforcement | core design promise | yes (pure logic) | medium | +| # | Item | Blocks | Linux-doable | Effort | +| --- | --------------------------- | ------------------- | --------------------------- | ------ | +| 1 | ISO boot/runtime validation | Gate 1 | no (needs FreeBSD boot) | small | +| 2 | Pi spawn end-to-end | Gate 2 | yes (with fake-pi-agent.py) | medium | +| 3 | Cost mode enforcement | core design promise | yes (pure logic) | medium | All three are medium effort and can be worked in parallel. None require FreeBSD to implement — only to validate the final result. -- 2.45.3