diff --git a/backend/windmill-api/src/runnables.rs b/backend/windmill-api/src/runnables.rs index a8a110d151..a0e8bb8562 100644 --- a/backend/windmill-api/src/runnables.rs +++ b/backend/windmill-api/src/runnables.rs @@ -1149,26 +1149,28 @@ struct RunnablesActivityResponse { /// The latest runs and the attached triggers of a batch of scripts and flows, for /// the homepage rows. Only paths the caller can see as a deployed script or flow /// are answered: the `visible` CTE runs under RLS, and `native_trigger`, which has -/// no RLS, is only reached through it. +/// no RLS, is only reached through it. RLS doesn't honor token scopes, so a path +/// outside the token's `scripts:read` / `flows:read` grant is dropped first. async fn get_runnables_activity( authed: ApiAuthed, Extension(user_db): Extension, Path(w_id): Path, Json(req): Json, ) -> JsonResult { - let mut paths: Vec = vec![]; - let mut is_flows: Vec = vec![]; - for (list, is_flow) in [(req.scripts, false), (req.flows, true)] { - for p in list { - paths.push(p); - is_flows.push(is_flow); - } - } - if paths.len() > ACTIVITY_MAX_PATHS { + if req.scripts.len() + req.flows.len() > ACTIVITY_MAX_PATHS { return Err(Error::BadRequest(format!( "at most {ACTIVITY_MAX_PATHS} paths per request" ))); } + let mut paths: Vec = vec![]; + let mut is_flows: Vec = vec![]; + for (list, is_flow, domain) in [(req.scripts, false, "scripts"), (req.flows, true, "flows")] { + let scope = build_scope_path_filter(&authed, domain, "read"); + for p in list.into_iter().filter(|p| scope.allows(p)) { + paths.push(p); + is_flows.push(is_flow); + } + } let mut res = RunnablesActivityResponse::default(); if paths.is_empty() { return Ok(Json(res)); diff --git a/frontend/src/lib/components/home/ItemsList.svelte b/frontend/src/lib/components/home/ItemsList.svelte index be706723bc..6938dcc670 100644 --- a/frontend/src/lib/components/home/ItemsList.svelte +++ b/frontend/src/lib/components/home/ItemsList.svelte @@ -2055,8 +2055,8 @@ {#if !contentActive && hasChips} -
@@ -2234,7 +2234,10 @@ variant="subtle" wrapperClasses="w-fit" disabled={filter !== ''} - btnClasses={twMerge('-ml-1.5 px-1.5 text-2xs font-medium whitespace-nowrap', active ? 'text-primary' : 'text-secondary')} + btnClasses={twMerge( + '-ml-1.5 px-1.5 text-2xs font-medium whitespace-nowrap', + active ? 'text-primary' : 'text-secondary' + )} endIcon={active ? { icon: sortOrder === asc ? ArrowUp : ArrowDown } : undefined} title={filter !== '' ? 'Sorting is disabled while searching (results are ranked by relevance)'