From 5ed6a0b8b7c2e0c3a2369a46151adfefcbc1bad4 Mon Sep 17 00:00:00 2001 From: OneNoted Date: Wed, 15 Apr 2026 11:02:12 +0200 Subject: [PATCH 1/2] fix: fail root setup before side effects Reject `whispers setup` at the start of the setup flow when it is launched as root so it cannot mutate config or starter files before reporting the privilege error. Constraint: Root-only setup runs must fail before any local state is written Rejected: Leave the root guard inside the later injection setup helper | config and starter files can already be mutated before that point Confidence: high Scope-risk: narrow Reversibility: clean Directive: Keep the root guard at the top of `run_setup()` so later helpers cannot become the first failing point again Tested: cargo test setup::tests:: -- --nocapture; cargo fmt --all -- --check; cargo check --all-targets; cargo clippy --all-targets -- -D warnings; cargo test Not-tested: Live sudo invocation --- src/setup.rs | 6 ++++++ src/setup/tests.rs | 11 +++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/setup.rs b/src/setup.rs index 3d7028d..047c7c4 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -54,7 +54,13 @@ impl CloudSetup { } } +fn validate_setup_invoker(uid: libc::uid_t) -> Result<()> { + side_effects::validate_setup_user(uid) +} + pub async fn run_setup(config_path_override: Option<&Path>) -> Result<()> { + validate_setup_invoker(unsafe { libc::geteuid() })?; + let ui = SetupUi::new(); ui.print_header("whispers setup"); ui.blank(); diff --git a/src/setup/tests.rs b/src/setup/tests.rs index 8c70f6f..2e25ed4 100644 --- a/src/setup/tests.rs +++ b/src/setup/tests.rs @@ -81,6 +81,17 @@ fn setup_rejects_root_before_uinput_readiness_short_circuit() { } } +#[test] +fn setup_rejects_root_before_any_setup_side_effects() { + let err = super::validate_setup_invoker(0).expect_err("root should be rejected up front"); + match err { + WhsprError::Config(message) => { + assert!(message.contains("run `whispers setup` as your normal user, not as root")); + } + other => panic!("unexpected error variant: {other:?}"), + } +} + #[test] fn group_membership_success_marks_logout_as_needed() { let mut outcome = side_effects::InjectionSetupOutcome::default(); From 9dc45b7f274ee8e9dd9bc75427ffb830a5c882b6 Mon Sep 17 00:00:00 2001 From: OneNoted Date: Wed, 15 Apr 2026 11:08:25 +0200 Subject: [PATCH 2/2] test: stabilize wl-copy non-zero exit coverage Make the non-zero clipboard test consume stdin before exiting so CI reliably exercises the intended non-zero exit path instead of racing into a broken-pipe write. Constraint: The test should still cover the non-zero exit path, not the broken-pipe write path Confidence: high Scope-risk: narrow Reversibility: clean Directive: Keep subprocess tests deterministic by making the child process consume the expected I/O before exiting Tested: cargo test inject::tests:: -- --nocapture; cargo fmt --all -- --check; cargo check --all-targets; cargo clippy --all-targets -- -D warnings; cargo test Not-tested: GitHub Actions rerun still pending --- src/inject/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/inject/tests.rs b/src/inject/tests.rs index 6044bd2..f1bc9d5 100644 --- a/src/inject/tests.rs +++ b/src/inject/tests.rs @@ -21,7 +21,7 @@ fn run_wl_copy_reports_spawn_failure() { fn run_wl_copy_reports_non_zero_exit() { let err = clipboard::run_wl_copy( "/bin/sh", - &[String::from("-c"), String::from("exit 7")], + &[String::from("-c"), String::from("cat >/dev/null; exit 7")], "hello", ) .expect_err("non-zero exit should fail");