merge main#25524
Conversation
…hardening Port security fixes from litellm_v1.82.3.dev.6: - Use secureStorage (sessionStorage wrapper) instead of raw storage for tokens - Add URL validation for stored worker URLs to prevent open redirects - Add same-origin checks before redirecting to stored return URLs - Harden Dockerfile.health_check with non-root user and exec-form HEALTHCHECK
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Dockerfile.health_check: HEALTHCHECK now verifies the script is intact instead of unconditionally exiting 0 - secureStorage.ts: replace deprecated escape/unescape with encodeURIComponent/decodeURIComponent; don't delete legacy values on decode failure so in-flight flows can time out naturally - OAuth callback: add same-origin check before redirecting to stored return URL
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Remove the silent try/catch from setSecureItem so OAuth hooks can surface actionable "enable storage" guidance instead of a cryptic "state lost" error after the round-trip. Add a local try/catch in ChatUI where the storage write is non-critical.
…loyment best practices (#25439) - New doc page covering all signed image variants, verification commands, CI/CD enforcement (K8s Sigstore Policy Controller, GCP Binary Authorization, AWS/EKS, GitHub Actions), digest pinning, and safe upgrade patterns - Added to sidebar under Setup & Deployment - Cross-linked from the existing deploy.md cosign section Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Krrish Dholakia <krrish-berri-2@users.noreply.github.com>
…_model_test fix(test): mock headers in test_completion_fine_tuned_model
feat(mcp): add per-user OAuth token storage for interactive MCP flows
[Fix] UI: improve storage handling and Dockerfile consistency
…st overrides Raise vitest testTimeout from 10s to 30s and drop per-test timeout overrides across UI unit tests. Group CreateUserButton and TeamInfo tests under nested describe blocks to make the most flaky suites easier to scan.
…et_model_query_param fix(responses-ws): append ?model= to backend WebSocket URL
Remove leftover 10000ms per-test timeout in add_model_tab.test.tsx that was missed in the initial sweep. The test now inherits the 30000ms global.
MCP_PER_USER_TOKEN_DEFAULT_TTL and MCP_PER_USER_TOKEN_EXPIRY_BUFFER_SECONDS were added in #25441 but not documented, causing test_env_keys.py to fail.
…_env_vars [Docs] Add missing MCP per-user token env vars to config_settings
[Test] UI - Unit tests: raise global vitest timeout and remove per-test overrides
Unify UI and API token authorization through the shared RBAC path and backfill missing routes in role-based route lists.
refactor: consolidate route auth for UI and API tokens
|
You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool. What Enabling Code Scanning Means:
For more information about GitHub Code Scanning, check out the documentation. |
|
|
bec448d
into
litellm_fix-websearch-interception-logging
Greptile SummaryThis PR merges Confidence Score: 5/5Safe to merge; all findings are P2 UI inconsistencies and naming clarity with no correctness or data-integrity regressions. The RBAC refactor correctly migrates all previously allowed routes through ui/litellm-dashboard/src/components/mcp_tools/OAuthFormFields.tsx — token validation fields shown for M2M OAuth in create flow (inconsistent with edit form)
|
| Filename | Overview |
|---|---|
| litellm/proxy/_types.py | RBAC route consolidation: UI tokens now use the same _is_api_route_allowed path; global_spend_tracking_routes properly added to internal_user_routes, ui_routes kept for JWT backwards-compat but no longer drives auth logic. |
| litellm/proxy/auth/auth_checks.py | Removes _is_ui_route / _is_allowed_route shims; common_checks now calls _is_api_route_allowed directly for all token types including UI dashboard tokens. |
| litellm/proxy/_experimental/mcp_server/db.py | Adds refresh_user_oauth_token (POSTs to token endpoint) and skip_byok_guard parameter to store_user_oauth_credential to save the redundant DB lookup during refresh. |
| litellm/proxy/_experimental/mcp_server/oauth2_token_cache.py | Adds MCPPerUserTokenCache (Redis-backed, NaCl-encrypted via user_api_key_cache) and helper _compute_per_user_token_ttl; singleton mcp_per_user_token_cache exported for use in server/mcp_server_manager. |
| litellm/proxy/_experimental/mcp_server/server.py | Extends _get_user_oauth_extra_headers_from_db with Redis fast-path, auto-refresh on expiry, and Redis warm-up after DB lookup; also skips pre-emptive 401 for needs_user_oauth_token servers. |
| litellm/proxy/_experimental/mcp_server/discoverable_endpoints.py | Adds _validate_token_response (dot-notation field checks), _extract_user_id_from_request (cache-only user_id lookup), _store_per_user_token_server_side, and wires them into exchange_token_with_server. |
| ui/litellm-dashboard/src/utils/secureStorage.ts | New shared utility that base64-encodes values before sessionStorage write; centralises storage access and eliminates duplicated inline setItem/getItem calls across OAuth hooks. |
| ui/litellm-dashboard/src/components/mcp_tools/OAuthFormFields.tsx | Adds Token Validation Rules (JSON textarea) and Token Storage TTL (number input) fields unconditionally in the OAuth section — shown for both M2M and interactive flows, inconsistent with the edit form. |
Sequence Diagram
sequenceDiagram
participant Client
participant MCP as MCP Server Endpoint
participant Redis as Redis Cache
participant DB as Prisma DB
participant TokenEP as OAuth Token Endpoint
Client->>MCP: list_tools or call_tool
MCP->>Redis: get per-user token
alt Cache hit
Redis-->>MCP: decrypted access_token
MCP-->>Client: forward with Authorization header
else Cache miss
MCP->>DB: get_user_oauth_credential
DB-->>MCP: credential payload
alt Valid token
MCP->>Redis: store encrypted token with TTL
MCP-->>Client: forward with Authorization header
else Token expired with refresh_token
MCP->>TokenEP: POST refresh_token grant
TokenEP-->>MCP: new access_token
MCP->>DB: store refreshed credential
MCP->>Redis: update cache
MCP-->>Client: forward with new Authorization header
else No valid token
MCP->>Redis: delete stale entry
MCP-->>Client: null - re-auth required
end
end
Reviews (1): Last reviewed commit: "Merge pull request #25473 from BerriAI/l..." | Re-trigger Greptile
| <TextInput placeholder="https://example.com/oauth/register" className={fieldClassName} /> | ||
| </Form.Item> | ||
| <Form.Item | ||
| label={ | ||
| <FieldLabel | ||
| label="Token Validation Rules (optional)" | ||
| tooltip='JSON object of key-value rules checked against the OAuth token response before storing. Supports dot-notation for nested fields (e.g. {"organization": "my-org", "team.id": "123"}). Tokens that fail validation are rejected with HTTP 403.' | ||
| /> | ||
| } | ||
| name="token_validation_json" | ||
| rules={[ | ||
| { | ||
| validator: (_: any, value: string) => { | ||
| if (!value || value.trim() === "") return Promise.resolve(); | ||
| try { | ||
| JSON.parse(value); | ||
| return Promise.resolve(); | ||
| } catch { | ||
| return Promise.reject(new Error("Must be valid JSON")); | ||
| } | ||
| }, | ||
| }, | ||
| ]} | ||
| > | ||
| <Input.TextArea | ||
| placeholder={'{\n "organization": "my-org",\n "team.id": "123"\n}'} | ||
| rows={4} | ||
| className="font-mono text-sm rounded-lg border-gray-300 focus:border-blue-500 focus:ring-blue-500" | ||
| /> | ||
| </Form.Item> | ||
| <Form.Item | ||
| label={ | ||
| <FieldLabel | ||
| label="Token Storage TTL (seconds, optional)" | ||
| tooltip="How long to cache each user's OAuth access token in Redis before evicting it (regardless of the token's own expires_in). Leave blank to derive the TTL from the token's expires_in, or fall back to the 12-hour default." | ||
| /> | ||
| } | ||
| name="token_storage_ttl_seconds" | ||
| > | ||
| <InputNumber | ||
| min={1} | ||
| placeholder="e.g. 3600" | ||
| className="w-full rounded-lg" | ||
| style={{ width: "100%" }} | ||
| /> | ||
| </Form.Item> | ||
| {oauthFlow && ( | ||
| <div className="rounded-lg border border-dashed border-gray-300 p-4 space-y-2"> |
There was a problem hiding this comment.
Token validation fields shown for M2M OAuth in create flow
token_validation_json and token_storage_ttl_seconds are rendered unconditionally in OAuthFormFields (outside the {oauthFlow && ...} block), so they appear for both M2M and interactive flows. The edit form correctly wraps them in {!isM2MFlow && ...}. For M2M servers needs_user_oauth_token is false so the backend ignores these values, but showing them in the create UI is misleading.
| function encode(value: string): string { | ||
| // btoa cannot handle characters outside Latin-1, so we percent-encode first. | ||
| return btoa( | ||
| encodeURIComponent(value).replace( | ||
| /%([0-9A-F]{2})/g, | ||
| (_, p1) => String.fromCharCode(parseInt(p1, 16)) | ||
| ) | ||
| ); | ||
| } | ||
|
|
||
| function decode(encoded: string): string { | ||
| return decodeURIComponent( | ||
| atob(encoded) | ||
| .split("") | ||
| .map((c) => "%" + c.charCodeAt(0).toString(16).padStart(2, "0")) | ||
| .join("") | ||
| ); | ||
| } | ||
|
|
||
| export function setSecureItem(key: string, value: string): void { | ||
| window.sessionStorage.setItem(key, encode(value)); | ||
| } | ||
|
|
||
| export function getSecureItem(key: string): string | null { | ||
| try { | ||
| const raw = window.sessionStorage.getItem(key); | ||
| if (raw === null) return null; | ||
| return decode(raw); | ||
| } catch { | ||
| // Corrupted or non-encoded legacy value — return null without deleting | ||
| // so that in-flight flows (e.g. OAuth) can time out naturally. | ||
| return null; | ||
| } | ||
| } |
There was a problem hiding this comment.
Module name implies encryption but only provides base64 encoding
encode/decode use btoa/atob — standard base64, trivially reversible by any script with access to sessionStorage. The name secureStorage and function names setSecureItem/getSecureItem may lead future callers to assume at-rest encryption. The real security gain here is the correct use of sessionStorage over localStorage. A short clarifying comment at the top of the file (or a rename to sessionStorageUtils) would prevent future misuse.
Relevant issues
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
tests/test_litellm/directory, Adding at least 1 test is a hard requirement - see detailsmake test-unit@greptileaiand received a Confidence Score of at least 4/5 before requesting a maintainer reviewDelays in PR merge?
If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).
CI (LiteLLM team)
Branch creation CI run
Link:
CI run for the last commit
Link:
Merge / cherry-pick CI run
Links:
Type
🆕 New Feature
🐛 Bug Fix
🧹 Refactoring
📖 Documentation
🚄 Infrastructure
✅ Test
Changes