fix(proxy): resolve os.environ/ refs for all AWS auth params in DB-sourced models - #32256
Conversation
…urced models PR #30867 removed request-time os.environ/ expansion in BaseAWSLLM.get_credentials to close LIT-3831. That relies on config-load paths pre-resolving os.environ/ refs, but the DB-load path (_resolve_db_litellm_param) only re-expands keys in _DB_LITELLM_PARAM_ENV_REF_KEYS, which covered api_key, aws_access_key_id, and aws_secret_access_key but not the other AWS auth fields. A model stored in Postgres with e.g. aws_role_name: os.environ/BEDROCK_ASSUME_ROLE_ARN lands on the router with the literal string, get_credentials no longer expands it, and STS returns ValidationError: os.environ/BEDROCK_ASSUME_ROLE_ARN is invalid Add the remaining AWS auth params to the allowlist so DB-sourced values resolve at model-load time (trusted, server-side), matching the YAML-config path. Team-scoped DB rows still get resolve_env_refs=False, so the LIT-3831 defense-in-depth path is unchanged and request-body injection is still blocked by _BANNED_REQUEST_BODY_PARAMS. Regression tests pin every added field as an os.environ/ DB value and assert it resolves on the router, plus a team-scoped pin that asserts env refs remain literal.
|
yucheng seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account. You have signed the CLA already but the status is still pending? Let us recheck it. |
Greptile SummaryThis PR fixes a bug where DB-stored AWS auth parameters containing
Confidence Score: 5/5Safe to merge — additive, narrow change to the DB model loading path with preserved team-scope bypass. The fix restores load-time resolution of AWS auth env refs in DB-stored models, matching YAML config behavior. LiteLLM_Params uses extra=allow so all new keys are preserved. Team-scoped bypass remains intact and is tested. No files require special attention.
|
| Filename | Overview |
|---|---|
| litellm/proxy/proxy_server.py | Extends _DB_LITELLM_PARAM_ENV_REF_KEYS with 12 additional AWS auth params; team-scoped model path is unchanged. |
| tests/test_litellm/proxy/proxy_server/test_proxy_config.py | Adds two targeted mock-only regression tests covering env-ref resolution and team-scoped bypass. |
Reviews (3): Last reviewed commit: "fix(proxy): also allow os.environ/ resol..." | Re-trigger Greptile
Greptile SummaryThis PR fixes a regression where DB-stored models with
Confidence Score: 5/5Safe to merge — minimal, surgical change with comprehensive regression tests covering both the fix and the security boundary it must not cross. The change is a single frozenset extension in proxy_server.py. Every added key is confirmed to flow directly into BaseAWSLLM.get_credentials or its callers (verified in base_aws_llm.py, converse_handler.py, batches/handler.py). The resolve_env_refs=False guard for team-scoped models is untouched and is explicitly tested by the new regression test. No existing tests are modified, and all new tests use mocks with no real network calls. No files require special attention.
|
| Filename | Overview |
|---|---|
| litellm/proxy/proxy_server.py | Extends _DB_LITELLM_PARAM_ENV_REF_KEYS with 9 missing AWS auth params so DB-stored os.environ/ refs resolve at load time, matching the YAML-config path |
| tests/test_litellm/proxy/proxy_server/test_proxy_config.py | Adds two regression tests: one pins that all 9 new AWS params resolve for global DB models, one pins that team-scoped DB models leave os.environ/ refs literal |
Reviews (2): Last reviewed commit: "fix(proxy): resolve os.environ/ refs for..." | Re-trigger Greptile
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
…_id, aws_batch_role_arn, aws_workspace_id Round out the AWS auth-field coverage of _DB_LITELLM_PARAM_ENV_REF_KEYS so every stringy aws_* field a deployment can pin in the DB resolves os.environ/ refs at load time: - aws_bedrock_project_id: Bedrock project/workspace association, banned from request bodies via _BANNED_REQUEST_BODY_PARAMS - aws_batch_role_arn: Bedrock batches role ARN (analog of aws_role_name) - aws_workspace_id: Claude Platform workspace ID Verified against three independent sources: - BaseAWSLLM.aws_authentication_params (all 11) - LiteLLM_Params-declared AWS fields (all 5) - every aws_* string read from litellm_params/kwargs/optional_params across litellm/ (all 14, excluding aws_bedrock_client which is a boto3 client object, not a string, and aws_polly which is a provider name) The regression test now pins all 12 newly-allowlisted fields.
Root cause: PR #30867 removed request-time os.environ/ expansion in BaseAWSLLM.get_credentials. That is only safe if config-load pre-resolves os.environ/ refs so the value reaching get_credentials is already the real secret. The YAML config path has always done this. The DB-load path (ProxyConfig._resolve_db_litellm_param) only re-expanded keys in a hardcoded whitelist (_DB_LITELLM_PARAM_ENV_REF_KEYS) plus short-circuited env-ref resolution entirely for team-scoped rows. PR #32256 extended that whitelist to 18 keys to unblock a customer whose Bedrock model with aws_role_name: os.environ/BEDROCK_ASSUME_ROLE_ARN broke on v1.90+, but the whitelist is structurally fragile: every future auth field breaks the same way until someone remembers to add it Fix: remove the whitelist and the team-scope short-circuit. The DB-load resolver now expands os.environ/ on every string field, matching the YAML path. Trust boundary stays on the write side: only PROXY_ADMIN can create team_id=None rows, only team admins of a team can create rows scoped to that team, and the request-body vector is still blocked by _BANNED_REQUEST_BODY_PARAMS. Team-scoped rows now resolve env refs — this is a deliberate LIT-3831 threat-model expansion trusting team admins for env-var reads Regression tests in tests/test_litellm/proxy/proxy_server/test_proxy_config.py: - test_ProxyConfig__add_deployment_resolves_env_refs_after_db_decrypt pins admin-scoped rows resolve every field (previously api_base stayed literal) - test_ProxyConfig__add_deployment_resolves_team_env_refs pins team rows resolve env refs (previously stayed literal) - test_ProxyConfig__add_deployment_resolves_env_refs_on_arbitrary_field pins the no-whitelist invariant against a made-up field name - test_ProxyConfig__add_deployment_resolves_env_refs_for_aws_bedrock_auth_params (from #32256) still passes - Path B counterparts (decrypt_model_list_from_db) mirror the above Left as followups (not fixed here): - /model/info and /v2/model/info still echo resolved values for fields not in the current pop-list (aws_role_name, aws_sts_endpoint, api_base, etc.). Fix is to extend remove_sensitive_info_from_deployment; separate PR - Master-key rotation reads DB rows via decrypt_model_list_from_db which now resolves universally, so rotation collapses env-refs into hardcoded values. Pre-existing bug for the 6 previously-whitelisted fields; wider surface after this PR. Separate PR
….1) (#1475) This PR contains the following updates: | Package | Update | Change | |---|---|---| | [ghcr.io/berriai/litellm](https://images.chainguard.dev/directory/image/wolfi-base/overview) ([source](https://github.com/BerriAI/litellm)) | patch | `v1.91.0` → `v1.91.1` | --- ### Release Notes <details> <summary>BerriAI/litellm (ghcr.io/berriai/litellm)</summary> ### [`v1.91.1`](https://github.com/BerriAI/litellm/releases/tag/v1.91.1) [Compare Source](BerriAI/litellm@v1.91.1...v1.91.1) ##### Verify Docker Image Signature All LiteLLM Docker images are signed with [cosign](https://docs.sigstore.dev/cosign/overview/). Every release is signed with the same key introduced in [commit `0112e53`](BerriAI/litellm@0112e53). **Verify using the pinned commit hash (recommended):** A commit hash is cryptographically immutable, so this is the strongest way to ensure you are using the original signing key: ```bash cosign verify \ --key https://raw.githubusercontent.com/BerriAI/litellm/0112e53046018d726492c814b3644b7d376029d0/cosign.pub \ ghcr.io/berriai/litellm:v1.91.1 ``` **Verify using the release tag (convenience):** Tags are protected in this repository and resolve to the same key. This option is easier to read but relies on tag protection rules: ```bash cosign verify \ --key https://raw.githubusercontent.com/BerriAI/litellm/v1.91.1/cosign.pub \ ghcr.io/berriai/litellm:v1.91.1 ``` Expected output: ``` The following checks were performed on each of these signatures: - The cosign claims were validated - The signatures were verified against the specified public key ``` *** ##### What's Changed - chore(release): backport [#​32256](BerriAI/litellm#32256), [#​32405](BerriAI/litellm#32405), [#​32524](BerriAI/litellm#32524) to stable/1.91.x and cut 1.91.1 by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​32552](BerriAI/litellm#32552) **Full Changelog**: <BerriAI/litellm@v1.91.0...v1.91.1> ### [`v1.91.1`](https://github.com/BerriAI/litellm/releases/tag/v1.91.1) [Compare Source](BerriAI/litellm@v1.91.0...v1.91.1) ##### Verify Docker Image Signature All LiteLLM Docker images are signed with [cosign](https://docs.sigstore.dev/cosign/overview/). Every release is signed with the same key introduced in [commit `0112e53`](BerriAI/litellm@0112e53). **Verify using the pinned commit hash (recommended):** A commit hash is cryptographically immutable, so this is the strongest way to ensure you are using the original signing key: ```bash cosign verify \ --key https://raw.githubusercontent.com/BerriAI/litellm/0112e53046018d726492c814b3644b7d376029d0/cosign.pub \ ghcr.io/berriai/litellm:v1.91.1 ``` **Verify using the release tag (convenience):** Tags are protected in this repository and resolve to the same key. This option is easier to read but relies on tag protection rules: ```bash cosign verify \ --key https://raw.githubusercontent.com/BerriAI/litellm/v1.91.1/cosign.pub \ ghcr.io/berriai/litellm:v1.91.1 ``` Expected output: ``` The following checks were performed on each of these signatures: - The cosign claims were validated - The signatures were verified against the specified public key ``` *** ##### What's Changed - chore(release): backport [#​32256](BerriAI/litellm#32256), [#​32405](BerriAI/litellm#32405), [#​32524](BerriAI/litellm#32524) to stable/1.91.x and cut 1.91.1 by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​32552](BerriAI/litellm#32552) **Full Changelog**: <BerriAI/litellm@v1.91.0...v1.91.1> </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about these updates again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xMDEuMSIsInVwZGF0ZWRJblZlciI6IjQzLjEwMS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZS9jb250YWluZXIiLCJ0eXBlL3BhdGNoIl19--> Reviewed-on: https://git.erwanleboucher.dev/eleboucher/homelab/pulls/1475
….1) (#488) This PR contains the following updates: | Package | Update | Change | |---|---|---| | [ghcr.io/berriai/litellm](https://images.chainguard.dev/directory/image/wolfi-base/overview) ([source](https://github.com/BerriAI/litellm)) | patch | `v1.91.0` → `v1.91.1` | --- ### Release Notes <details> <summary>BerriAI/litellm (ghcr.io/berriai/litellm)</summary> ### [`v1.91.1`](https://github.com/BerriAI/litellm/releases/tag/v1.91.1) [Compare Source](BerriAI/litellm@v1.91.1...v1.91.1) ##### Verify Docker Image Signature All LiteLLM Docker images are signed with [cosign](https://docs.sigstore.dev/cosign/overview/). Every release is signed with the same key introduced in [commit `0112e53`](BerriAI/litellm@0112e53). **Verify using the pinned commit hash (recommended):** A commit hash is cryptographically immutable, so this is the strongest way to ensure you are using the original signing key: ```bash cosign verify \ --key https://raw.githubusercontent.com/BerriAI/litellm/0112e53046018d726492c814b3644b7d376029d0/cosign.pub \ ghcr.io/berriai/litellm:v1.91.1 ``` **Verify using the release tag (convenience):** Tags are protected in this repository and resolve to the same key. This option is easier to read but relies on tag protection rules: ```bash cosign verify \ --key https://raw.githubusercontent.com/BerriAI/litellm/v1.91.1/cosign.pub \ ghcr.io/berriai/litellm:v1.91.1 ``` Expected output: ``` The following checks were performed on each of these signatures: - The cosign claims were validated - The signatures were verified against the specified public key ``` *** ##### What's Changed - chore(release): backport [#​32256](BerriAI/litellm#32256), [#​32405](BerriAI/litellm#32405), [#​32524](BerriAI/litellm#32524) to stable/1.91.x and cut 1.91.1 by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​32552](BerriAI/litellm#32552) **Full Changelog**: <BerriAI/litellm@v1.91.0...v1.91.1> ### [`v1.91.1`](https://github.com/BerriAI/litellm/releases/tag/v1.91.1) [Compare Source](BerriAI/litellm@v1.91.0...v1.91.1) ##### Verify Docker Image Signature All LiteLLM Docker images are signed with [cosign](https://docs.sigstore.dev/cosign/overview/). Every release is signed with the same key introduced in [commit `0112e53`](BerriAI/litellm@0112e53). **Verify using the pinned commit hash (recommended):** A commit hash is cryptographically immutable, so this is the strongest way to ensure you are using the original signing key: ```bash cosign verify \ --key https://raw.githubusercontent.com/BerriAI/litellm/0112e53046018d726492c814b3644b7d376029d0/cosign.pub \ ghcr.io/berriai/litellm:v1.91.1 ``` **Verify using the release tag (convenience):** Tags are protected in this repository and resolve to the same key. This option is easier to read but relies on tag protection rules: ```bash cosign verify \ --key https://raw.githubusercontent.com/BerriAI/litellm/v1.91.1/cosign.pub \ ghcr.io/berriai/litellm:v1.91.1 ``` Expected output: ``` The following checks were performed on each of these signatures: - The cosign claims were validated - The signatures were verified against the specified public key ``` *** ##### What's Changed - chore(release): backport [#​32256](BerriAI/litellm#32256), [#​32405](BerriAI/litellm#32405), [#​32524](BerriAI/litellm#32524) to stable/1.91.x and cut 1.91.1 by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​32552](BerriAI/litellm#32552) **Full Changelog**: <BerriAI/litellm@v1.91.0...v1.91.1> </details> --- ### Configuration 📅 **Schedule**: (in timezone America/New_York) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about these updates again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNTIuMSIsInVwZGF0ZWRJblZlciI6IjQzLjI1Mi4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZS9jb250YWluZXIiLCJ0eXBlL3BhdGNoIl19--> Reviewed-on: https://git.greyrock.io/greyrock-labs/home-ops/pulls/488
…expansion fix(proxy): resolve os.environ/ refs for all AWS auth params in DB-sourced models (cherry picked from commit 7d13f03)
Root cause: PR BerriAI#30867 removed request-time os.environ/ expansion in BaseAWSLLM.get_credentials. That is only safe if config-load pre-resolves os.environ/ refs so the value reaching get_credentials is already the real secret. The YAML config path has always done this. The DB-load path (ProxyConfig._resolve_db_litellm_param) only re-expanded keys in a hardcoded whitelist (_DB_LITELLM_PARAM_ENV_REF_KEYS) plus short-circuited env-ref resolution entirely for team-scoped rows. PR BerriAI#32256 extended that whitelist to 18 keys to unblock a customer whose Bedrock model with aws_role_name: os.environ/BEDROCK_ASSUME_ROLE_ARN broke on v1.90+, but the whitelist is structurally fragile: every future auth field breaks the same way until someone remembers to add it Fix: remove the whitelist and the team-scope short-circuit. The DB-load resolver now expands os.environ/ on every string field, matching the YAML path. Trust boundary stays on the write side: only PROXY_ADMIN can create team_id=None rows, only team admins of a team can create rows scoped to that team, and the request-body vector is still blocked by _BANNED_REQUEST_BODY_PARAMS. Team-scoped rows now resolve env refs — this is a deliberate LIT-3831 threat-model expansion trusting team admins for env-var reads Regression tests in tests/test_litellm/proxy/proxy_server/test_proxy_config.py: - test_ProxyConfig__add_deployment_resolves_env_refs_after_db_decrypt pins admin-scoped rows resolve every field (previously api_base stayed literal) - test_ProxyConfig__add_deployment_resolves_team_env_refs pins team rows resolve env refs (previously stayed literal) - test_ProxyConfig__add_deployment_resolves_env_refs_on_arbitrary_field pins the no-whitelist invariant against a made-up field name - test_ProxyConfig__add_deployment_resolves_env_refs_for_aws_bedrock_auth_params (from BerriAI#32256) still passes - Path B counterparts (decrypt_model_list_from_db) mirror the above Left as followups (not fixed here): - /model/info and /v2/model/info still echo resolved values for fields not in the current pop-list (aws_role_name, aws_sts_endpoint, api_base, etc.). Fix is to extend remove_sensitive_info_from_deployment; separate PR - Master-key rotation reads DB rows via decrypt_model_list_from_db which now resolves universally, so rotation collapses env-refs into hardcoded values. Pre-existing bug for the 6 previously-whitelisted fields; wider surface after this PR. Separate PR
Root cause: PR #30867 removed request-time os.environ/ expansion in BaseAWSLLM.get_credentials. That is only safe if config-load pre-resolves os.environ/ refs so the value reaching get_credentials is already the real secret. The YAML config path has always done this. The DB-load path (ProxyConfig._resolve_db_litellm_param) only re-expanded keys in a hardcoded whitelist (_DB_LITELLM_PARAM_ENV_REF_KEYS) plus short-circuited env-ref resolution entirely for team-scoped rows. PR #32256 extended that whitelist to 18 keys to unblock a customer whose Bedrock model with aws_role_name: os.environ/BEDROCK_ASSUME_ROLE_ARN broke on v1.90+, but the whitelist is structurally fragile: every future auth field breaks the same way until someone remembers to add it Fix: remove the whitelist and the team-scope short-circuit. The DB-load resolver now expands os.environ/ on every string field, matching the YAML path. Trust boundary stays on the write side: only PROXY_ADMIN can create team_id=None rows, only team admins of a team can create rows scoped to that team, and the request-body vector is still blocked by _BANNED_REQUEST_BODY_PARAMS. Team-scoped rows now resolve env refs — this is a deliberate LIT-3831 threat-model expansion trusting team admins for env-var reads Regression tests in tests/test_litellm/proxy/proxy_server/test_proxy_config.py: - test_ProxyConfig__add_deployment_resolves_env_refs_after_db_decrypt pins admin-scoped rows resolve every field (previously api_base stayed literal) - test_ProxyConfig__add_deployment_resolves_team_env_refs pins team rows resolve env refs (previously stayed literal) - test_ProxyConfig__add_deployment_resolves_env_refs_on_arbitrary_field pins the no-whitelist invariant against a made-up field name - test_ProxyConfig__add_deployment_resolves_env_refs_for_aws_bedrock_auth_params (from #32256) still passes - Path B counterparts (decrypt_model_list_from_db) mirror the above Left as followups (not fixed here): - /model/info and /v2/model/info still echo resolved values for fields not in the current pop-list (aws_role_name, aws_sts_endpoint, api_base, etc.). Fix is to extend remove_sensitive_info_from_deployment; separate PR - Master-key rotation reads DB rows via decrypt_model_list_from_db which now resolves universally, so rotation collapses env-refs into hardcoded values. Pre-existing bug for the 6 previously-whitelisted fields; wider surface after this PR. Separate PR (cherry picked from commit 5862be3)
Relevant issues
Linear ticket
Pre-Submission checklist
@greptileaiand received a Confidence Score of at least 4/5 before requesting a maintainer reviewScreenshots / Proof of Fix
The customer reported that after upgrading from 1.89.1 to 1.91.0, a model stored in the DB with
aws_role_name: os.environ/BEDROCK_ASSUME_ROLE_ARNstopped working. STS returnsValidationError: os.environ/BEDROCK_ASSUME_ROLE_ARN is invalidbecause the literal string reaches AssumeRole instead of the resolved ARN.The proof-of-fix curls a proxy connected to a real Postgres, with a DB row shaped like the customer's:
Before (unfixed 1.91.x): the router receives the literal
os.environ/...string and forwards it to STSThe traceback shows
_auth_with_aws_rolewas called with the literal string asRoleArn, sosts_client.assume_role(RoleArn="os.environ/BEDROCK_ASSUME_ROLE_ARN", ...)fires and STS rejects it. In this repro the fake AWS creds tripInvalidClientTokenIdfirst, but the customer's real AWS creds get past that check and hit the exactValidationError: ... is invalidthey reportedAfter (with this PR): the router receives the resolved ARN, and STS receives a well-formed value
LIT-3831 stays closed: an attacker sending
aws_role_name: os.environ/DATABASE_URLin the HTTP request body is still refused by the existing_BANNED_REQUEST_BODY_PARAMSgateTeam-scoped DB models still get
resolve_env_refs=False, matching the LIT-3831 defense-in-depth contract for the team-scoped DB path (see the regression testtest_ProxyConfig__add_deployment_keeps_team_aws_env_refs_literal)Type
🐛 Bug Fix
Changes
Root cause: PR #30867 (LIT-3831) removed the per-request
os.environ/expansion insideBaseAWSLLM.get_credentials. That is only safe if the config-load path pre-resolvesos.environ/refs, so the value reachingget_credentialsis already the real secret. The YAML config path has always done this. The DB-load path (ProxyConfig._resolve_db_litellm_paraminlitellm/proxy/proxy_server.py) only re-expands keys in_DB_LITELLM_PARAM_ENV_REF_KEYS, which coveredapi_key,client_secret,vertex_credentials,vertex_ai_credentials,aws_access_key_id, andaws_secret_access_key, but none of the other AWS auth fields. A model row likeaws_role_name: os.environ/BEDROCK_ASSUME_ROLE_ARNlands on the router with the literal string,get_credentialsno longer expands it, and STS rejects the AssumeRole callFix: extend
_DB_LITELLM_PARAM_ENV_REF_KEYSto cover the remaining nine AWS auth params (aws_session_token,aws_region_name,aws_session_name,aws_profile_name,aws_role_name,aws_web_identity_token,aws_sts_endpoint,aws_external_id,aws_bedrock_runtime_endpoint). Nowos.environ/refs resolve at DB-load time (trusted, server-owned), matching the YAML-config code path. Team-scoped DB rows still getresolve_env_refs=False, so the LIT-3831 team-scoped defense-in-depth path is unchanged and the request-body attack surface stays gated by_BANNED_REQUEST_BODY_PARAMSRegression tests in
tests/test_litellm/proxy/proxy_server/test_proxy_config.py:test_ProxyConfig__add_deployment_resolves_env_refs_for_aws_bedrock_auth_paramspins every added field as anos.environ/…DB value and asserts each resolves on the router (mutation-checked; fails on unfixed code with the exact "literal string not resolved" symptom)test_ProxyConfig__add_deployment_keeps_team_aws_env_refs_literalpins that team-scoped DB rows do NOT resolveaws_role_name, guarding the LIT-3831 team path