security(proxy): keep team callback credentials out of the stored request body (#43217)

* security(proxy): keep team callback credentials out of the stored request body

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* ci: allow the security conventional commit type

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: oliver <oliver@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
devin-ai-integration[bot] 2026-09-28 11:16:58 -07:00 • committed by GitHub
parent 6f5ad78a1f
commit 703eb4fa68
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 34 additions and 3 deletions

View file

@ -42,7 +42,7 @@ case "$subject" in
;;
esac
ALLOWED_TYPES="feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert"
ALLOWED_TYPES="feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert|security"
# Description must not start with an uppercase letter — kept in sync with the
# subjectPattern in .github/workflows/conventional-commits.yml so the local
# hook is the strictly tighter of the two gates. (Without this guard, a commit
@ -61,7 +61,7 @@ cat >&2 <<EOF
Expected: <type>(<scope>)!: <description>
(description must start with a lowercase letter)
Allowed types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert
Allowed types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert, security
Examples:
feat(router): add weighted round-robin strategy
fix(bedrock): decouple STS region from aws_region_name

View file

@ -41,6 +41,7 @@ jobs:
ci
chore
revert
security
requireScope: false
subjectPattern: ^(?![A-Z]).+$
subjectPatternError: |

View file

@ -196,6 +196,9 @@ from litellm.types.utils import (
SupportedCacheControls,
)
_CALLBACK_CREDENTIAL_KEYS: Final = frozenset(StandardCallbackDynamicParams.__annotations__) | frozenset(
{TRUSTED_CALLBACK_VARS_FIELD}
)
service_logger_obj: Final = ServiceLogging() # used for tracking latency on OTEL
# Bounded dedup for stale-alias warnings (FIFO eviction when over cap).
_MAX_STALE_ALIAS_WARNING_KEYS: Final = 10_000
@ -1958,7 +1961,9 @@ def refresh_proxy_server_request_body_snapshot(
if not isinstance(proxy_server_request, dict):
return
_body_snapshot_exclude: Final = (
frozenset({"secret_fields", "proxy_server_request", "litellm_logging_obj"}) | _TRANSPORT_ONLY_CREDENTIAL_KEYS
frozenset({"secret_fields", "proxy_server_request", "litellm_logging_obj"})
| _TRANSPORT_ONLY_CREDENTIAL_KEYS
| _CALLBACK_CREDENTIAL_KEYS
)
body: Final = { # mutable-ok: audit JSON serialization requires a dict with shared nested messages
k: v for k, v in data.items() if k not in _body_snapshot_exclude

View file

@ -870,6 +870,31 @@ def test_initial_snapshot_refresh_clears_a_previous_guardrail_checkpoint() -> No
assert proxy_request == {"body": {"messages": [{"role": "user", "content": "new request"}]}}
def test_body_snapshot_excludes_team_callback_credentials() -> None:
from litellm.proxy.litellm_pre_call_utils import refresh_proxy_server_request_body_snapshot
from litellm.types.litellm_params import TRUSTED_CALLBACK_VARS_FIELD
callback_vars: Final = {
"langfuse_public_key": "pk-lf-team",
"langfuse_secret_key": "sk-lf-team-secret",
"langfuse_host": "https://cloud.langfuse.com",
}
proxy_request: Final = {"body": None}
data: Final = {
"messages": [{"role": "user", "content": "hi"}],
"proxy_server_request": proxy_request,
"success_callback": ["langfuse"],
**callback_vars,
TRUSTED_CALLBACK_VARS_FIELD: callback_vars,
}
refresh_proxy_server_request_body_snapshot(data)
assert proxy_request == {
"body": {"messages": [{"role": "user", "content": "hi"}], "success_callback": ["langfuse"]}
}, proxy_request
@pytest.mark.asyncio
@pytest.mark.parametrize("pre_call_ran", [False, True])
async def test_post_guardrail_snapshot_preserves_logging_only_masking_in_spend_logs(