Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 18 additions & 5 deletions litellm/proxy/guardrails/guardrail_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,23 @@ async def get_guardrail_by_name_from_db(
raise Exception(f"Error getting guardrail from DB: {str(e)}")


def _resolve_env_references(litellm_params: LitellmParams) -> LitellmParams:
"""Expand every ``os.environ/NAME`` reference in a guardrail's litellm_params.

Only ``api_key`` and ``api_base`` used to be expanded, so a guardrail
configured with e.g. ``aws_region_name: os.environ/AWS_REGION`` handed boto3
the literal string and failed with "Invalid AWS region format". A name that
resolves to nothing becomes None rather than the string "None", so the
downstream client falls back to its own credential chain.
"""
resolved = {
name: get_secret(value)
for name, value in litellm_params.model_dump().items()
if isinstance(value, str) and value.startswith("os.environ/")
}
return litellm_params.model_copy(update=resolved) if resolved else litellm_params


class InMemoryGuardrailHandler:
"""
Class that handles initializing guardrails and adding them to the CallbackManager
Expand Down Expand Up @@ -442,11 +459,7 @@ def initialize_guardrail(
lakera_category_thresholds = LakeraCategoryThresholds(**litellm_params_data["category_thresholds"])
litellm_params.category_thresholds = lakera_category_thresholds

if litellm_params.api_key and litellm_params.api_key.startswith("os.environ/"):
litellm_params.api_key = str(get_secret(litellm_params.api_key))

if litellm_params.api_base and litellm_params.api_base.startswith("os.environ/"):
litellm_params.api_base = str(get_secret(litellm_params.api_base))
litellm_params = _resolve_env_references(litellm_params)

guardrail_type = litellm_params.guardrail

Expand Down
34 changes: 34 additions & 0 deletions tests/test_litellm/proxy/guardrails/test_guardrail_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -367,3 +367,37 @@ def distinct_runner_instances() -> int:
finally:
for cb_list, snapshot in zip(lists, snapshots):
cb_list[:] = snapshot


def test_resolve_env_references_expands_every_string_field(monkeypatch):
"""Regression: only api_key/api_base used to be expanded, so a guardrail set up
with aws_region_name="os.environ/AWS_REGION" handed boto3 the literal string and
failed with "Invalid AWS region format"."""
from litellm.proxy.guardrails.guardrail_registry import _resolve_env_references

monkeypatch.setenv("TEST_GUARDRAIL_REGION", "us-east-1")
monkeypatch.setenv("TEST_GUARDRAIL_KEY", "sk-guardrail")
monkeypatch.setenv("TEST_GUARDRAIL_ANALYZER", "https://analyzer.example")
monkeypatch.delenv("TEST_GUARDRAIL_ABSENT", raising=False)

resolved = _resolve_env_references(
LitellmParams(
guardrail="bedrock",
mode="pre_call",
api_key="os.environ/TEST_GUARDRAIL_KEY",
aws_region_name="os.environ/TEST_GUARDRAIL_REGION",
presidio_analyzer_api_base="os.environ/TEST_GUARDRAIL_ANALYZER",
aws_secret_access_key="os.environ/TEST_GUARDRAIL_ABSENT",
aws_access_key_id="AKIA_LITERAL_VALUE",
)
)

assert resolved.aws_region_name == "us-east-1"
assert resolved.presidio_analyzer_api_base == "https://analyzer.example"
assert resolved.api_key == "sk-guardrail"
# An unset name must not become the string "None", which boto3 would treat as a credential.
assert resolved.aws_secret_access_key is None
# Literal values are left alone, and unrelated fields survive the copy.
assert resolved.aws_access_key_id == "AKIA_LITERAL_VALUE"
assert resolved.guardrail == "bedrock"
assert resolved.mode == "pre_call"
Loading