fix: validate message roles early to prevent 500 stack trace leakage#31409
fix: validate message roles early to prevent 500 stack trace leakage#31409xbrxr03 wants to merge 1 commit into
Conversation
When an invalid role (e.g. 'admin', 'xyz') is sent in a chat completion request, litellm currently returns a 500 with a full Python stack trace. This validates roles early in validate_and_fix_openai_messages() and raises BadRequestError(400) with a sanitized message instead. Fixes BerriAI#30948 This PR is submitted with AI assistance (OpenClaw DEV agent). Per the project's AI policy, this addresses a confirmed bug from an open issue.
Greptile SummaryThis PR adds early validation of message
Confidence Score: 3/5The change hardens a real error path but introduces a new hard rejection at the top of completion() that affects all providers — custom or provider-specific roles that worked before will now always return a 400 with no escape hatch. The validation is placed before provider-specific message transformation in the central completion() entry point, meaning any role outside the hard-coded set is permanently blocked for every provider integration, not just OpenAI. litellm/utils.py — the placement and scope of the role check needs a second look before merging.
|
| Filename | Overview |
|---|---|
| litellm/constants.py | Adds VALID_MESSAGE_ROLES set constant — straightforward and follows the existing pattern of centralizing constants. |
| litellm/utils.py | Adds early role validation in validate_and_fix_openai_messages; the check fires before any provider-specific transformation in completion(), which will now reject non-OpenAI roles that were previously forwarded to the provider. |
| tests/litellm_utils_tests/test_invalid_role_validation.py | New unit tests for role validation; tests use mocked/unit-level calls (no real network calls) and cover core invalid/valid/missing-role cases, though the valid-role assertion is minimal. |
Reviews (1): Last reviewed commit: "fix: validate message roles early to pre..." | Re-trigger Greptile
| elif message["role"] not in VALID_MESSAGE_ROLES: | ||
| raise BadRequestError( | ||
| message=f"Invalid role: '{message['role']}'. Supported roles are: {sorted(VALID_MESSAGE_ROLES)}", | ||
| model="", | ||
| llm_provider="", | ||
| ) |
There was a problem hiding this comment.
Early rejection blocks provider-specific roles
validate_and_fix_openai_messages is called in litellm/main.py at line 5055 — very early in completion(), before any provider-specific message transformation runs. Some open-source/self-hosted backends (e.g., Ollama serving Llama 3.x) accept roles beyond the OpenAI set (like "ipython" for tool-call results). Those messages are now blocked with a 400 before they ever reach the provider transformation layer, whereas before they were forwarded and the provider decided. Users currently passing such custom roles will see a new failure they cannot work around without litellm changes.
| raise BadRequestError( | ||
| message=f"Invalid role: '{message['role']}'. Supported roles are: {sorted(VALID_MESSAGE_ROLES)}", | ||
| model="", | ||
| llm_provider="", | ||
| ) |
There was a problem hiding this comment.
The error message interpolates
sorted(VALID_MESSAGE_ROLES) which produces a Python list literal (e.g., ['assistant', 'developer', ...]). A comma-separated string is cleaner for a user-facing error.
| raise BadRequestError( | |
| message=f"Invalid role: '{message['role']}'. Supported roles are: {sorted(VALID_MESSAGE_ROLES)}", | |
| model="", | |
| llm_provider="", | |
| ) | |
| raise BadRequestError( | |
| message=f"Invalid role: '{message['role']}'. Supported roles are: {', '.join(sorted(VALID_MESSAGE_ROLES))}", | |
| model="", | |
| llm_provider="", | |
| ) |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| for role in valid_roles: | ||
| messages = [{"role": role, "content": "test"}] | ||
| result = validate_and_fix_openai_messages(messages=messages) | ||
| assert len(result) == 1 | ||
|
|
||
| def test_invalid_role_error_no_stack_trace(self): | ||
| """The error response body must not contain Python internal paths or tracebacks.""" | ||
| from litellm.utils import validate_and_fix_openai_messages | ||
|
|
||
| messages = [{"role": "hacker", "content": "pwned"}] |
There was a problem hiding this comment.
tool and function role test assertions are too weak
validate_and_fix_openai_messages calls validate_chat_completion_user_messages on the result. The test only asserts len(result) == 1 and does not verify the role is preserved, so it would pass even if the role were silently mutated. Additionally, tool messages typically require a tool_call_id field; the bare {"role": "tool", "content": "test"} fixture may silently pass today but break if stricter downstream validation is added.
|
|
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
|
Closing due to lack of maintainer response. Happy to reopen if there's interest. Thanks for the review opportunity. |
Problem
When a chat completion request is sent with an invalid
rolevalue (e.g."admin","xyz"), litellm leaks a full Python stack trace in the error response body, including internal file paths and exception chains. The response also returns HTTP 500 instead of the correct 400.Example error response from the bug:
{ "error": { "message": "litellm.APIConnectionError: Invalid Message passed in - {\"role\": \"xyz\", ...}. File an issue...\nTraceback (most recent call last):\n ...", "type": "500" } }Fix
Adds early role validation in
validate_and_fix_openai_messages()that raisesBadRequestError(HTTP 400) with a sanitized message for invalid roles. Valid roles (system,user,assistant,tool,function,developer) continue to work. Missing roles still default toassistant(existing behavior preserved).3 files changed, 89 insertions:
litellm/constants.py— addVALID_MESSAGE_ROLESsetlitellm/utils.py— validate role invalidate_and_fix_openai_messages()tests/litellm_utils_tests/test_invalid_role_validation.py— 5 testsFixes #30948
This PR was created with AI assistance (OpenClaw DEV agent). Per the project's AI contribution guidelines, this addresses a confirmed bug from an open issue.