From d37277d2341c83faf72efa0035cbf70e2cfbd596 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Fri, 8 May 2026 09:12:33 +0200 Subject: [PATCH] fix: reject root-rooted paths in ansible playbook validator on windows (#9081) --- .../windmill-worker/src/ansible_executor.rs | 38 +++++++++++++------ 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/backend/windmill-worker/src/ansible_executor.rs b/backend/windmill-worker/src/ansible_executor.rs index c8289b6934..2608cc6088 100644 --- a/backend/windmill-worker/src/ansible_executor.rs +++ b/backend/windmill-worker/src/ansible_executor.rs @@ -121,18 +121,24 @@ fn validate_relative_path(path: &str, field_name: &str) -> error::Result<()> { ))); } let p = std::path::Path::new(trimmed); - if p.is_absolute() { - return Err(error::Error::BadRequest(format!( - "`{}` must be a relative path inside the cloned repo, got: {}", - field_name, trimmed - ))); - } for component in p.components() { - if matches!(component, std::path::Component::ParentDir) { - return Err(error::Error::BadRequest(format!( - "`{}` must not contain `..` segments, got: {}", - field_name, trimmed - ))); + match component { + // RootDir catches leading `/` or `\`; Prefix catches Windows drive + // letters and UNC paths. `Path::is_absolute()` alone misses + // RootDir-only paths on Windows (e.g. `/etc/passwd`). + std::path::Component::RootDir | std::path::Component::Prefix(_) => { + return Err(error::Error::BadRequest(format!( + "`{}` must be a relative path inside the cloned repo, got: {}", + field_name, trimmed + ))); + } + std::path::Component::ParentDir => { + return Err(error::Error::BadRequest(format!( + "`{}` must not contain `..` segments, got: {}", + field_name, trimmed + ))); + } + _ => {} } } Ok(()) @@ -1765,9 +1771,19 @@ mod tests { #[test] fn test_validate_relative_path_rejects_absolute() { + // `/etc/passwd` isn't `is_absolute()` on Windows (no drive prefix), but + // its leading RootDir still escapes the cloned repo, so reject it on + // every platform. assert!(validate_relative_path("/etc/passwd", "playbook").is_err()); } + #[cfg(windows)] + #[test] + fn test_validate_relative_path_rejects_windows_absolute() { + assert!(validate_relative_path("\\etc\\passwd", "playbook").is_err()); + assert!(validate_relative_path("C:\\Windows\\System32", "playbook").is_err()); + } + #[test] fn test_validate_relative_path_rejects_parent_dir() { assert!(validate_relative_path("../escape.yml", "playbook").is_err());