Skip to content

Forward only standard chat fields and function tools upstream - #23

Merged
Deep-Sixed merged 1 commit into
mainfrom
claude/sleepy-curie-g8429q-body-allowlist
Oct 5, 2026
Merged

Deep-Sixed merged 1 commit into
mainfrom
claude/sleepy-curie-g8429q-body-allowlist

Conversation

@Deep-Sixed

Copy link
Copy Markdown
Owner

Problem

build_upstream_request forwarded the caller's body verbatim; only model and the reasoning fields were rewritten. On OpenRouter-backed aliases, a caller on a free-only alias could therefore add:

  • models: OpenRouter's fallback list
  • provider / route
  • plugins, e.g. web search
  • server tools such as {"type": "openrouter:fusion", "parameters": {"analysis_models": [<paid>]}}

Any of these can bill models the alias never lists, under Cerberus's credential. The fusion path rejected tools, tool_choice and plugins, but still forwarded models, provider and route.

Change

  • New leaf module cerberus/caller_fields.py:
    • CALLER_FIELDS allow-list of OpenAI chat-completions fields, including reasoning_effort and chat_template_kwargs, which carry existing caller-precedence semantics.
    • caller_body() applies the allow-list.
    • non_function_tools() reports any tool type other than function.
  • The dispatch egress and OpenRouterFusionBackend.build_body both go through caller_body().
  • A non-fusion request naming a non-function tool in tools or tool_choice is refused with 400 / reason: unsupported_tool. Fusion keeps its existing rejection.
  • Unknown fields are dropped silently, as most gateways do. Say if you'd rather they be rejected.

Tests

  • New tests/test_caller_fields.py covers both paths.
  • Full suite: 324 passed. Ruff is clean.
  • Run locally on Python 3.13.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Lj4neZ1qzw7tpiCJ8oedxJ


Generated by Claude Code

The caller's body went to the provider verbatim apart from `model`. On
OpenRouter-backed aliases that let a caller pass `models` (fallback
list), `provider`, `route`, `plugins` or server tools such as
`openrouter:fusion` with paid analysis models, spending paid quota
through a free-only alias. The fusion path rejected tools and plugins
but still forwarded `models`, `provider` and `route`.

Both paths now forward an allow-list of OpenAI chat-completions fields
(cerberus/caller_fields.py), and a dispatch request naming a non-function
tool is refused with 400 unsupported_tool.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lj4neZ1qzw7tpiCJ8oedxJ

Copy link
Copy Markdown
Owner Author

validate failed at scripts/check-public-history.py, not in this PR's code. That script scans every fetched branch, and it flagged a private-IP literal in tests/test_loopback_host.py on #24's branch. This PR's own push run was green.

The fix is at the source: #24's single commit was amended to use 203.0.113.7 (a documentation address) and force-pushed, and the history scan now reports 0 findings. Failed jobs re-run once.


Generated by Claude Code

@Deep-Sixed
Deep-Sixed marked this pull request as ready for review October 5, 2026 12:52
@Deep-Sixed
Deep-Sixed merged commit 85a7315 into main Oct 5, 2026
2 of 3 checks passed
Deep-Sixed pushed a commit that referenced this pull request Oct 5, 2026
PR #23 refuses a non-function tool on dispatch-path aliases with a 400
before any upstream call, but emitted no routing event, so the refusal
never appeared in /admin/events or the telemetry sink. Fusion and
jev-router already record their own caller-field refusals.

The refusal now emits an invalid_request event (status 400, no
attempts) under the caller's identity and the request's revision,
and the 400 body carries the event's request_id so the two can be
matched. docs/architecture.md now states the dispatch-path caller-field
rule: the CALLER_FIELDS allowlist, what is dropped and why, the
function-tool refusal, and how fusion and jev-router differ.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TVM2AGUsSu6Fyr5s5LSvew
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants