From b5e69ffba6afd49d147e9b5e88e2d7020c09142f Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Mon, 20 Jul 2026 07:50:50 +0200 Subject: [PATCH] fix(security): enforce token scope filtering on folder list endpoints (#10193) * fix(security): enforce token scope filtering on folder list endpoints * fix: apply folder scope filtering before pagination * fix: keep SQL pagination for unrestricted tokens on folder lists * fix: bound scoped folder-list scan with chunked pagination * fix: use keyset pagination for scoped folder-list scan * refactor: reduce folder scope fix to the minimal predicate filter * chore: drop folder list scope test --- backend/windmill-api-groups/src/folders.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/backend/windmill-api-groups/src/folders.rs b/backend/windmill-api-groups/src/folders.rs index 67997bc965..5e204a6f2c 100644 --- a/backend/windmill-api-groups/src/folders.rs +++ b/backend/windmill-api-groups/src/folders.rs @@ -15,7 +15,7 @@ use axum::{ }; use lazy_static::lazy_static; use regex::Regex; -use windmill_api_auth::{check_scopes, ApiAuthed, AuthCache, Tokened}; +use windmill_api_auth::{build_scope_path_predicate, check_scopes, ApiAuthed, AuthCache, Tokened}; use windmill_audit::audit_oss::{audit_log, AuditAuthorable}; use windmill_audit::ActionKind; use windmill_common::DB; @@ -109,6 +109,7 @@ async fn list_folders( let (per_page, offset) = paginate(pagination); let mut tx = user_db.begin(&authed).await?; + let allowed = build_scope_path_predicate(&authed, "folders", "read"); let rows = sqlx::query_as!( Folder, "SELECT workspace_id, name, display_name, owners, extra_perms, summary, created_by, edited_at, default_permissioned_as, labels FROM folder WHERE workspace_id = $1 ORDER BY name asc LIMIT $2 OFFSET $3", @@ -117,7 +118,10 @@ async fn list_folders( offset as i64 ) .fetch_all(&mut *tx) - .await?; + .await? + .into_iter() + .filter(|r| allowed(&format!("f/{}", r.name))) + .collect::>(); tx.commit().await?; Ok(Json(rows)) @@ -131,6 +135,7 @@ async fn list_foldernames( let (per_page, offset) = paginate(pagination); let mut tx = user_db.begin(&authed).await?; + let allowed = build_scope_path_predicate(&authed, "folders", "read"); let rows = sqlx::query_scalar!( "SELECT name FROM folder WHERE workspace_id = $1 ORDER BY name asc LIMIT $2 OFFSET $3", w_id, @@ -138,7 +143,10 @@ async fn list_foldernames( offset as i64 ) .fetch_all(&mut *tx) - .await?; + .await? + .into_iter() + .filter(|name| allowed(&format!("f/{}", name))) + .collect::>(); tx.commit().await?;