From 3bf6e102afbdad41e558617bc812012eaaaecd9b Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Mon, 15 Jun 2026 18:53:30 +0200 Subject: [PATCH] fix(apps): apply scope-path predicate to app list/search endpoints (#9581) The list_apps and list_search_apps endpoints did not filter returned rows against the calling token's resource-qualified scope. A token scoped to apps:read:u/foo/specific_app could list every app in the workspace, including full app_version.value definitions. Apply build_scope_path_predicate, mirroring the protection already in place for script, flow, resource and variable list endpoints. Fixes WIN-2046 Co-authored-by: Claude Opus 4.8 (1M context) --- backend/windmill-api/src/apps.rs | 8 +++++++- backend/windmill-api/src/utils.rs | 4 +++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/backend/windmill-api/src/apps.rs b/backend/windmill-api/src/apps.rs index 029f0e7d1f..3cb2a1aef6 100644 --- a/backend/windmill-api/src/apps.rs +++ b/backend/windmill-api/src/apps.rs @@ -12,7 +12,7 @@ use crate::{ db::{ApiAuthed, DB}, jobs::RunJobQuery, users::{require_owner_of_path, require_path_read_access_for_preview, OptAuthed}, - utils::check_scopes, + utils::{build_scope_path_predicate, check_scopes}, webhook_util::{WebhookMessage, WebhookShared}, HTTP_CLIENT, }; @@ -355,6 +355,8 @@ async fn list_search_apps( let n = 3; let mut tx = user_db.begin(&authed).await?; + let allowed = build_scope_path_predicate(&authed, "apps", "read"); + let rows = sqlx::query_as::<_, SearchApp>( "SELECT path, app_version.value from app LEFT JOIN app_version ON app_version.id = versions[array_upper(versions, 1)] WHERE workspace_id = $1 LIMIT $2", ) @@ -363,6 +365,7 @@ async fn list_search_apps( .fetch_all(&mut *tx) .await? .into_iter() + .filter(|r| allowed(&r.path)) .collect::>(); tx.commit().await?; Ok(Json(rows)) @@ -539,6 +542,9 @@ async fn list_apps( } } + let allowed = build_scope_path_predicate(&authed, "apps", "read"); + rows.retain(|r| allowed(&r.path)); + Ok(Json(rows)) } diff --git a/backend/windmill-api/src/utils.rs b/backend/windmill-api/src/utils.rs index 4f55877e62..5f472c49d3 100644 --- a/backend/windmill-api/src/utils.rs +++ b/backend/windmill-api/src/utils.rs @@ -9,7 +9,9 @@ use axum::{body::Body, response::Response}; use serde::{Deserialize, Deserializer}; -pub use windmill_api_auth::{check_scopes, require_devops_role, require_super_admin}; +pub use windmill_api_auth::{ + build_scope_path_predicate, check_scopes, require_devops_role, require_super_admin, +}; #[cfg(feature = "private")] pub use windmill_common::usernames::generate_instance_wide_unique_username;