From cd4b1b86297dd6400be23cf3955193b6cd61a738 Mon Sep 17 00:00:00 2001 From: gitlawr Date: Wed, 8 Jul 2026 17:54:04 +0800 Subject: [PATCH] fix(clusters): scope cluster pickers to the owning org Cluster pickers listed every visible cluster, which includes clusters shared with all authenticated users (e.g. the default org's "shared with everyone" clusters). A member of a custom org could then pick another org's cluster for a model deployment or a new worker. Request only the current org's own clusters (mine=true) for: - the model deploy picker (models page), - the deploy-from-model-file picker (model files page), and - the add-worker picker. These resources are owner-scoped on the backend, so the model list and the worker table (including its cluster-name column) only reference own-org clusters. The add-model-file worker cascader is already own-org via the owner-scoped worker list. Platform admin in the "All" view bypasses mine and is scoped instead by the org picker. --- .../services/use-query-cluster-list.tsx | 7 ++++++- .../llmodels/hooks/use-form-initial-values.ts | 16 ++++++++++++++-- src/pages/resources/components/workers.tsx | 8 +++++++- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/pages/cluster-management/services/use-query-cluster-list.tsx b/src/pages/cluster-management/services/use-query-cluster-list.tsx index 75b8679a..d91a6574 100644 --- a/src/pages/cluster-management/services/use-query-cluster-list.tsx +++ b/src/pages/cluster-management/services/use-query-cluster-list.tsx @@ -22,7 +22,12 @@ export const useQueryClusterList = (options?: { useStateData?: boolean }) => { loading, cancel } = useRequest( - async (params: { page: number; perPage?: number }) => { + async (params: { + page: number; + perPage?: number; + mine?: boolean; + gpu_instance_enabled?: boolean; + }) => { axiosTokenRef.current?.cancel(); axiosTokenRef.current = createAxiosToken(); const res = await queryClusterList(params, { diff --git a/src/pages/llmodels/hooks/use-form-initial-values.ts b/src/pages/llmodels/hooks/use-form-initial-values.ts index 805e1b49..211ad4fc 100644 --- a/src/pages/llmodels/hooks/use-form-initial-values.ts +++ b/src/pages/llmodels/hooks/use-form-initial-values.ts @@ -199,7 +199,13 @@ export const useGenerateWorkerOptions = () => { page: -1 }), queryClusterList({ - page: -1 + page: -1, + // Own-org clusters only. Feeds the deploy-from-model-file cluster + // picker, which must not offer another org's cluster (e.g. the + // Default org's "shared with everyone" clusters). The worker + // cascader on this page is already own-org via the owner-scoped + // worker list. + mine: true }) ]); const workerList = workerRes.items || ([] as WorkerListItem[]); @@ -272,7 +278,13 @@ export default function useFormInitialValues() { // Exclude clusters that opt in to GPU-instance handling // (k8s_options.gpu_instance_options set) — those are for the // GPU-service flow, not model deployment. - gpu_instance_enabled: false + gpu_instance_enabled: false, + // Only clusters owned by the current org. Drops cross-org grants + // (e.g. the Default org's "shared with everyone" clusters) so a + // tenant can't deploy onto another org's infrastructure. Platform + // admin in the "All" view bypasses this and is scoped instead by + // the org picker (see basic.tsx). + mine: true }); const list = response.items.map((item) => ({ label: item.name, diff --git a/src/pages/resources/components/workers.tsx b/src/pages/resources/components/workers.tsx index d7c1603e..1231b1f0 100644 --- a/src/pages/resources/components/workers.tsx +++ b/src/pages/resources/components/workers.tsx @@ -103,8 +103,14 @@ const Workers: React.FC = ({ clusterId, source }) => { const getClusterList = async () => { try { + // Own-org clusters only (mine=true). A worker can only join a cluster + // its org owns, so another org's cluster (e.g. the Default org's + // "shared with everyone" clusters) must not be offered in the picker. + // The worker list is owner-scoped too, so this list also covers every + // cluster the table's name column can reference. const params = { - page: -1 + page: -1, + mine: true }; const items = await fetchClusterList(params); const clusterMap = items?.reduce(