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) <noreply@anthropic.com>
This commit is contained in:
Ruben Fiszel
2026-06-15 18:53:30 +02:00
committed by GitHub
parent 36c9f8612b
commit 3bf6e102af
2 changed files with 10 additions and 2 deletions
+7 -1
View File
@@ -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::<Vec<_>>();
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))
}
+3 -1
View File
@@ -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;