Repository navigation
chore(release): backport #40989 to stable/1.101.x and cut 1.101.6 - #45212
Conversation
…_additional_tools fix(responses): hoist Codex additional_tools input items into the chat bridge tools (cherry picked from commit 1ce66e9)
|
| hoisted: Final = hoist_additional_tools(input, responses_api_request.get("tools")) | ||
| bridged_input: Final = hoisted.input | ||
| bridged_request: Final[ResponsesAPIOptionalRequestParams] = ( | ||
| {**responses_api_request, "tools": list(hoisted.tools)} if hoisted.hoisted else responses_api_request |
There was a problem hiding this comment.
With tool_policy configured for pre_call and exec blocked, a caller can put exec inside an additional_tools input item. The Responses guardrail checks only top-level tools. This bridge then forwards the unchecked tool, and the completion hook skips guardrails already marked as run. Hoist before guardrails and forward their edited tools.
How this was verified: The request path checks top-level tools before hoisting, then skips the already-run guardrail when calling completion
| if nested and tool_type == "custom": | ||
| return convert_custom_tool_to_function_tool({**namespace_tool, "description": description}) |
There was a problem hiding this comment.
Matching tool names become ambiguous
A top-level function exec and a nested custom tool exec now both become chat functions named exec. _validate_namespace_name_collisions ignores custom members. Both response paths then classify calls to the existing function as custom_tool_call, returning the wrong call type and arguments. Reject these collisions or preserve distinct names throughout conversion
| | BaseResponsesAPIStreamingIterator | ||
| | Coroutine[object, object, ResponsesAPIResponse | BaseResponsesAPIStreamingIterator] | ||
| ): | ||
| hoisted: Final = hoist_additional_tools(input, responses_api_request.get("tools")) |
There was a problem hiding this comment.
Medium: Late tool hoisting bypasses key and team allowlists
On a Responses-to-Chat-Completions deployment, a caller whose key/team allowed_tools permits only read can place a write function in an input item of type additional_tools, alongside a normal message, and have it forwarded to the provider. check_tools_allowlist() runs during authentication and its Responses extractor only reads top-level tools, so this newly supported declaration is never authorized; hoist before that check, or reapply it to the merged tools using the authenticated key/team before calling completion/acompletion.
PR overviewThis PR backports #40989 to stable/1.101.x and prepares release 1.101.6. The touched Responses-to-Chat-Completions handler adds support for merging tool declarations from additional_tools input items into the tools forwarded to the provider. One security issue remains open, with none addressed so far. On Responses-to-Chat-Completions deployments, callers can bypass key and team tool allowlists by supplying tool declarations through additional_tools, which are merged after authorization. This allows disallowed tool definitions, including write functions, to reach the provider, though execution of those functions is not established by the finding. Open issues (1)
Fixed/addressed: 0 · PR risk: 7/10 |
TLDR
Problem this solves:
additional_toolsinput item, which the bridge dropscustomtool nested in anamespace, is skipped tooHow it solves it:
User Flow
Before: a developer pointing Codex CLI at a bridged model on 1.101.x never gets a tool call back, so Codex cannot run a shell command
~/.codex/config.tomlathttps://litellm-domain/v1withwire_api = "responses", openscodex, and asks it to runlsexecshell tool nested in one{"type": "additional_tools", ...}input item and no top-leveltoolsreasoningandmessageoutput itemsAfter: the same session comes back with a shell tool call, so Codex runs the command
~/.codex/config.tomlat the same URL, openscodex, and asks it to runlscustom_tool_calloutput item namedexecwhoseinputis the shell commandlslocally and reports the filesRelevant issues
Backports #40989 onto
stable/1.101.xand cuts1.101.6. The line's tip1.101.5is already published on DockerHub and GHCR and tagged, so this PR bumps the version#40989 merged to main as a two-parent merge commit, so it is picked with
-m 1against its first parent. It has shipped since v1.103.0. Later main commits touching the same files are lint, typing and perf refactors only, so no follow-up is includedWhat is included
Adaptation notes
#40989 is adapted by context only: every added and removed line matches the upstream change byte for byte in all 13 files, and the line counts are identical (560 additions, 104 deletions). Three files needed conflict resolution because this line is missing code that main had near the hunks
In
streaming_iterator.py, main has aweb_search_callhandling block next to the removedtool_name, tool_namespace = ...line. That block comes from a different change that is not on this line, so it was not pulled in. Only the removed line was dropped, as upstream doesIn
test_litellm_completion_responses.py, a plain three-way merge would have pulled in six reasoning-summary tests that belong to other changes. The line's file was restored and only the PR's own hunks were added, so the added lines are identical to upstream (3 new tests plus one extra entry in an existing test's tool list)bedrock_mantle/responses/transformation.pymerged cleanly, and its changed lines are identical to upstreamKnown noise on this line
These tests fail on the untouched line tip and fail the same way after these commits:
tests/test_litellm/llms/bedrock_mantle/test_bedrock_mantle_transformation.py::TestBedrockMantlePricing::test_gpt_oss_20b_pricingtests/test_litellm/llms/bedrock/chat/invoke_transformations/test_bedrock_chat_invoke_transformations_anthropic_claude3_transformation.py::test_output_config_format_forwarded_for_bedrock_chat_invoke_requesttests/test_litellm/llms/bedrock/chat/test_converse_transformation.py::test_output_config_format_translated_to_native_output_config_conversetests/test_litellm/llms/bedrock/chat/test_converse_transformation.py::test_translate_response_format_json_schema_still_injects_toolScreenshots / Proof of Fix
Deep verification (gauntlet-mini, live-proxy replay) in progress; this body will be updated with the evidence
The targeted set (
tests/test_litellm/responses,tests/test_litellm/llms/bedrock_mantle,tests/test_litellm/llms/openai/responses) goes from 1039 passed and 1 failed to 1053 passed and the same 1 failed. 10 of the pick's own tests fail when its source changes are reverted on this line and pass with them. A wider set covering the callers (completion_extras,integrations/test_custom_guardrail.py, the Purview guardrail,proxy/response_api_endpoints,llms/bedrock) has the same 3 failures before and afterType
🐛 Bug Fix