Skip to content

fix(ui): disable /config/list fetch for non-admin users in useProxyConfig hook#24313

Open
xykong wants to merge 1 commit intoBerriAI:mainfrom
xykong:fix/use-proxy-config-admin-role-check
Open

fix(ui): disable /config/list fetch for non-admin users in useProxyConfig hook#24313
xykong wants to merge 1 commit intoBerriAI:mainfrom
xykong:fix/use-proxy-config-admin-role-check

Conversation

@xykong
Copy link
Contributor

@xykong xykong commented Mar 21, 2026

Summary

Fixes useProxyConfig hook to skip the GET /config/list request for non-admin users, eliminating repeated 400 errors in proxy logs on every page load.

Fixes #24309

Root Cause

useProxyConfig calls GET /config/list unconditionally on component mount. Since /config/list is restricted to PROXY_ADMIN (returns 400 Bad Request for other roles), every page load for internal_user sessions triggers:

400 {"error": "Not allowed access, your role=internal_user"}

Components using useProxyConfig (ModelSettingsModal, SpendLogsSettingsModal) are rendered during initial page load, so this fires immediately and on every navigation.

Change

+ import { isProxyAdminRole } from "@/utils/roles";

  export const useProxyConfig = (configType: ConfigType) => {
-   const { accessToken } = useAuthorized();
+   const { accessToken, userRole } = useAuthorized();
    return useQuery<ProxyConfigResponse>({
      ...
-     enabled: Boolean(accessToken),
+     enabled: Boolean(accessToken) && isProxyAdminRole(userRole ?? ""),
    });
  };

The isProxyAdminRole utility already exists in @/utils/roles — no new dependencies.

Testing

  • internal_user sessions no longer produce 400 errors on page load
  • ✅ Admin users still fetch and display proxy config normally
  • ✅ Components that consume useProxyConfig handle the undefined data state (they already do via optional chaining)

Impact

  • Minimal: 3-line change to one hook
  • No breaking changes
  • Affected components: any consumer of useProxyConfig (ModelSettingsModal, SpendLogsSettingsModal, and future consumers)

…sers

The useProxyConfig hook called GET /config/list unconditionally on mount,
regardless of user role. Since /config/list is admin-only (returns 400
for non-admin), every page load for internal_user sessions generated a
400 error log entry.

Add isProxyAdminRole check to the query's enabled flag so the fetch
is skipped entirely for non-admin users. The isProxyAdminRole utility
already exists in @/utils/roles.

Fixes BerriAI#24309
@vercel
Copy link

vercel bot commented Mar 21, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
litellm Ready Ready Preview, Comment Mar 21, 2026 6:39pm

Request Review

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 21, 2026

Greptile Summary

This PR fixes repeated 400 Bad Request errors in proxy logs caused by useProxyConfig unconditionally calling GET /config/list on every page load, even for non-admin users who lack permission to access that endpoint. The fix gates the React Query fetch behind an isProxyAdminRole check so only proxy_admin / Admin-equivalent users trigger the network call.

  • Root cause addressed: useQuery's enabled flag was only checking for accessToken, not the user's role — causing every internal_user page load to fire a doomed 400 request.
  • Fix approach: Destructures userRole from useAuthorized and adds isProxyAdminRole(userRole ?? "") to the enabled condition — a minimal, targeted change.
  • Subtle coupling: useAuthorized returns userRole as a formatted display string (e.g., "Admin") via formatUserRole, not the raw backend value (e.g., "proxy_admin"). isProxyAdminRole happens to check for both "proxy_admin" (raw) and "Admin" (formatted), so the guard works correctly — but the role === "proxy_admin" branch is a dead path in this call site. If either function is refactored independently, this silent dependency could break the guard.
  • No tests added: The PR describes manual verification but does not include automated tests for the new role-gating behaviour in useProxyConfig.

Confidence Score: 4/5

  • Safe to merge — the fix correctly eliminates spurious 400 errors for non-admin users with no risk of breaking admin workflows.
  • The change is minimal (3 lines), targets a well-understood bug, and the role check works correctly given current implementations of formatUserRole and isProxyAdminRole. Score is 4 rather than 5 because of the implicit coupling between the formatted role string returned by useAuthorized and the dual-format check inside isProxyAdminRole, and the absence of automated tests for the new gate.
  • No files require special attention beyond the noted style concern in useProxyConfig.ts.

Important Files Changed

Filename Overview
ui/litellm-dashboard/src/app/(dashboard)/hooks/proxyConfig/useProxyConfig.ts Adds role-based gate to skip the /config/list fetch for non-admin users; works correctly for the fix scenario but relies on isProxyAdminRole matching formatted role strings ("Admin") rather than the raw backend values the function was designed for ("proxy_admin")

Sequence Diagram

sequenceDiagram
    participant C as Component (mount)
    participant H as useProxyConfig hook
    participant A as useAuthorized
    participant RQ as React Query
    participant API as GET /config/list

    C->>H: useProxyConfig(configType)
    H->>A: useAuthorized()
    A-->>H: { accessToken, userRole (formatted) }
    H->>H: isProxyAdminRole(userRole ?? "")

    alt userRole === "Admin" (proxy_admin / app_admin)
        H->>RQ: enabled: true
        RQ->>API: GET /config/list?config_type=...
        API-->>RQ: 200 OK + config data
        RQ-->>C: { data: ProxyConfigResponse }
    else userRole !== "Admin" (internal_user, viewer, etc.)
        H->>RQ: enabled: false
        RQ-->>C: { data: undefined, isFetching: false }
        note over C,API: No network request made — no 400 errors
    end
Loading

Last reviewed commit: "fix(ui): disable /co..."

}),
queryFn: async () => await getProxyConfigCall(accessToken!, configType),
enabled: Boolean(accessToken),
enabled: Boolean(accessToken) && isProxyAdminRole(userRole ?? ""),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Implicit coupling between formatted and raw role values

useAuthorized exposes userRole as the formatted display string (e.g., "Admin") via formatUserRole(decoded?.user_role), not the raw backend role (e.g., "proxy_admin"). This means isProxyAdminRole is being called with a formatted value here.

isProxyAdminRole is defined as:

export const isProxyAdminRole = (role: string): boolean => {
  return role === "proxy_admin" || role === "Admin";
};

The role === "proxy_admin" branch will never match in this call because userRole is always pre-formatted. The guard works only because the function also checks for "Admin" (the formatted form). This creates a silent dependency: if formatUserRole were updated to no longer map proxy_admin"Admin", or if isProxyAdminRole dropped the "Admin" branch, the guard would silently stop working for proxy_admin users.

Consider using useAuthorized's raw token instead, or explicitly documenting that isProxyAdminRole accepts formatted role strings:

Suggested change
enabled: Boolean(accessToken) && isProxyAdminRole(userRole ?? ""),
enabled: Boolean(accessToken) && isProxyAdminRole(userRole ?? ""),

Alternatively, expose a rawUserRole from useAuthorized (e.g., decoded?.user_role) so isProxyAdminRole can be called with the raw backend role it was designed for.

@codspeed-hq
Copy link
Contributor

codspeed-hq bot commented Mar 21, 2026

Merging this PR will not alter performance

✅ 16 untouched benchmarks


Comparing xykong:fix/use-proxy-config-admin-role-check (5fc83d9) with main (b64b0d4)

Open in CodSpeed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: useProxyConfig hook fetches /config/list for non-admin users — causes 400 errors on page load

1 participant