diff --git a/apps/purepoint-macos/purepoint-macos/Services/ManifestWatcher.swift b/apps/purepoint-macos/purepoint-macos/Services/ManifestWatcher.swift index 0a118a6..0464709 100644 --- a/apps/purepoint-macos/purepoint-macos/Services/ManifestWatcher.swift +++ b/apps/purepoint-macos/purepoint-macos/Services/ManifestWatcher.swift @@ -12,7 +12,7 @@ final class ManifestWatcher: @unchecked Sendable { private var debounceWork: DispatchWorkItem? private static let debounceInterval: TimeInterval = 0.05 - var isWatching: Bool { source != nil } + var isWatching: Bool { queue.sync { source != nil } } init(path: String, onChange: @escaping @MainActor @Sendable () -> Void) { self.path = path diff --git a/crates/pu-cli/src/commands/bench.rs b/crates/pu-cli/src/commands/bench.rs index 24055f8..db4a35b 100644 --- a/crates/pu-cli/src/commands/bench.rs +++ b/crates/pu-cli/src/commands/bench.rs @@ -57,7 +57,7 @@ pub async fn run_bench( } } - output::print_response(&resp, json); + output::print_response(&resp, json)?; Ok(()) } @@ -75,6 +75,6 @@ pub async fn run_play(socket: &Path, agent_id: &str, json: bool) -> Result<(), C ) .await?; let resp = output::check_response(resp, json)?; - output::print_response(&resp, json); + output::print_response(&resp, json)?; Ok(()) } diff --git a/crates/pu-cli/src/output.rs b/crates/pu-cli/src/output.rs index 5421c3e..24ee2af 100644 --- a/crates/pu-cli/src/output.rs +++ b/crates/pu-cli/src/output.rs @@ -719,7 +719,7 @@ mod tests { #[test] fn given_empty_suspend_result_should_not_panic() { let resp = Response::SuspendResult { suspended: vec![] }; - print_response(&resp, false); + print_response(&resp, false).unwrap(); } #[test] diff --git a/crates/pu-core/src/validation.rs b/crates/pu-core/src/validation.rs index c0557c7..8a82f1e 100644 --- a/crates/pu-core/src/validation.rs +++ b/crates/pu-core/src/validation.rs @@ -14,7 +14,7 @@ pub fn validate_name(name: &str) -> Result<(), std::io::Error> { format!("name must not start with '.': {name}"), )); } - if name.contains('/') || name.contains('\\') || name.contains("..") { + if trimmed.contains('/') || trimmed.contains('\\') || trimmed.contains("..") { return Err(std::io::Error::new( std::io::ErrorKind::InvalidInput, format!("name contains invalid characters: {name}"), @@ -54,6 +54,13 @@ mod tests { assert!(validate_name("..").is_err()); } + #[test] + fn given_whitespace_padded_traversal_should_reject() { + assert!(validate_name(" ../evil").is_err()); + assert!(validate_name("foo/../bar ").is_err()); + assert!(validate_name(" /root").is_err()); + } + #[test] fn given_empty_should_reject() { assert!(validate_name("").is_err());