From 2bf7746cdd299d99da7f4b48c4e24891efb42ae5 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sun, 26 Jul 2026 11:36:33 +0200 Subject: [PATCH] fix: operators cannot archive or delete flows and apps (#10322) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `create_flow`/`update_flow` and `create_app`/`update_app` reject operators, but `archive_flow_by_path`, `delete_flow_by_path` and `delete_app` did not — so an operator with folder write could delete a flow or app they were not allowed to edit. Scripts already get this right (archive is guarded, delete is admin-only). Verified on a live instance: all three returned 2xx for an operator before, 401 after, and a non-operator member with the same folder write is unaffected. --- backend/windmill-api-flows/src/flows.rs | 10 ++++++++++ backend/windmill-api/src/apps.rs | 5 +++++ 2 files changed, 15 insertions(+) diff --git a/backend/windmill-api-flows/src/flows.rs b/backend/windmill-api-flows/src/flows.rs index 97e6999e23..ca3d65b2ce 100644 --- a/backend/windmill-api-flows/src/flows.rs +++ b/backend/windmill-api-flows/src/flows.rs @@ -1622,6 +1622,11 @@ async fn archive_flow_by_path( Path((w_id, path)): Path<(String, StripPath)>, Json(archived): Json, ) -> Result { + if authed.is_operator { + return Err(Error::NotAuthorized( + "Operators cannot archive flows for security reasons".to_string(), + )); + } let path = path.to_path(); check_scopes(&authed, || format!("flows:write:{}", path))?; if let RuleCheckResult::Blocked(msg) = check_deploy_rules( @@ -1762,6 +1767,11 @@ async fn delete_flow_by_path( Path((w_id, path)): Path<(String, StripPath)>, Query(query): Query, ) -> Result { + if authed.is_operator { + return Err(Error::NotAuthorized( + "Operators cannot delete flows for security reasons".to_string(), + )); + } let path = path.to_path(); check_scopes(&authed, || format!("flows:write:{}", path))?; if let RuleCheckResult::Blocked(msg) = check_deploy_rules( diff --git a/backend/windmill-api/src/apps.rs b/backend/windmill-api/src/apps.rs index 4a6d5134d2..acd3a5eabf 100644 --- a/backend/windmill-api/src/apps.rs +++ b/backend/windmill-api/src/apps.rs @@ -2121,6 +2121,11 @@ async fn delete_app( Extension(webhook): Extension, Path((w_id, path)): Path<(String, StripPath)>, ) -> Result { + if authed.is_operator { + return Err(Error::NotAuthorized( + "Operators cannot delete apps for security reasons".to_string(), + )); + } let path = path.to_path(); check_scopes(&authed, || format!("apps:write:{}", path))?;