Skip to content

fix(mcp): warn loudly when X-Forwarded-For is present but use_x_forwarded_for is off - #31266

Merged
mateo-berri merged 2 commits into
litellm_internal_stagingfrom
litellm_mcp_xff_disabled_warning
Jun 25, 2026
Merged

fix(mcp): warn loudly when X-Forwarded-For is present but use_x_forwarded_for is off#31266
mateo-berri merged 2 commits into
litellm_internal_stagingfrom
litellm_mcp_xff_disabled_warning

Conversation

@mateo-berri

Copy link
Copy Markdown
Contributor

Relevant issues

Linear ticket

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • My PR passes all unit tests on make test-unit
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have requested a Greptile review by commenting @greptileai and received a Confidence Score of at least 4/5 before requesting a maintainer review

CI (LiteLLM team)

  • Branch creation CI run
    Link:

  • CI run for the last commit
    Link:

  • Merge / cherry-pick CI run
    Links:

Screenshots / Proof of Fix

Run a proxy with an internal-only MCP server and use_x_forwarded_for left off, then send a request carrying an X-Forwarded-For header and watch the logs.

litellm/proxy/dev_config.yaml (minimal):

mcp_servers:
  internal_only:
    url: https://mcp.deepwiki.com/mcp
    available_on_public_internet: false
python litellm/proxy/proxy_cli.py --config litellm/proxy/dev_config.yaml --detailed_debug --reload --use_v2_migration_resolver 2>&1 | tee litellm.log

curl -s http://localhost:4000/v1/mcp/server/health \
  -H "Authorization: Bearer sk-1234" \
  -H "X-Forwarded-For: 8.8.8.8"

Expected in litellm.log (fires once, request still served):

ERROR ... Received a request with an X-Forwarded-For header but use_x_forwarded_for is not enabled. The real client IP is being ignored and the direct peer's IP (typically your load balancer / reverse proxy) is used for MCP access control. Because that peer almost always falls within general_settings.mcp_internal_ip_ranges, every external caller is treated as internal and 'available_on_public_internet: false' MCP servers are effectively exposed. Set use_x_forwarded_for: true (and mcp_trusted_proxy_ranges to your proxy CIDRs) in general_settings to honor the real client IP. Not failing the request: if there is no load balancer, a crafted X-Forwarded-For header must not be able to take down the service.

Type

🐛 Bug Fix

Changes

When a request carries an X-Forwarded-For header but use_x_forwarded_for is unset, IPAddressUtils.get_mcp_client_ip silently falls back to the direct peer's IP (the load balancer / reverse proxy). That peer almost always sits inside general_settings.mcp_internal_ip_ranges, so the "Internal network only" (available_on_public_internet: false) restriction trusts every external caller as internal and effectively exposes those servers. Operators got no signal that their internal-only filtering was a no-op.

This emits a one-shot loud error pointing the operator at use_x_forwarded_for (and mcp_trusted_proxy_ranges) instead of hard-failing. Hard-failing would be a foot-gun on deployments with no load balancer: a malicious user could craft an X-Forwarded-For header to take the service down. Keeping it to a single log line also prevents a flood of crafted headers from spamming the logs, which would itself be a DoS vector.

Added regression tests in tests/test_litellm/proxy/auth/test_mcp_ip_filtering.py covering: the warning fires and the request is still served (falls back to the direct peer), the warning is one-shot, no warning is emitted without the header, and no warning is emitted when XFF is properly enabled.

…rded_for is off

When a request carries an X-Forwarded-For header but use_x_forwarded_for is
unset, get_mcp_client_ip silently falls back to the direct peer's IP (the load
balancer / reverse proxy). That peer almost always sits inside
mcp_internal_ip_ranges, so the 'Internal network only'
(available_on_public_internet: false) restriction trusts every external caller
as internal and effectively exposes those servers.

Emit a one-shot loud error pointing the operator at use_x_forwarded_for instead
of hard-failing: on a deployment with no load balancer, a crafted
X-Forwarded-For header must not be able to take the service down, and a one-shot
log keeps a flood of crafted headers from spamming the logs.
@greptile-apps

greptile-apps Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds a one-shot ERROR-level log when a request carries an X-Forwarded-For header but use_x_forwarded_for is disabled, making the silent access-control misconfiguration visible to operators without hard-failing the request. The warning flag re-arms whenever the setting is later observed as enabled, ensuring a subsequent rollback to disabled will warn again.

  • Introduces _warned_xff_present_but_disabled module flag in ip_address_utils.py with unconditional reset to False whenever use_xff is True, and a one-shot verbose_proxy_logger.error otherwise.
  • Adds five unit tests in test_mcp_ip_filtering.py covering: warning fires + fallback IP returned, one-shot suppression, re-arm after disable→enable→disable cycle, no-header path, and XFF-enabled path.

Confidence Score: 5/5

Safe to merge — the change only adds a one-shot log statement and leaves all existing request-handling behavior untouched.

The change is entirely additive: a module-level boolean flag and a conditional log call that never alters the IP returned to callers. The fallback path (direct peer IP) is identical to pre-PR behavior. The re-arming logic is simple and covered by a dedicated regression test. No authentication or access-control decisions are changed.

No files require special attention.

Important Files Changed

Filename Overview
litellm/proxy/auth/ip_address_utils.py Adds _warned_xff_present_but_disabled module-level flag with one-shot error logging when XFF header is present but use_x_forwarded_for is disabled; flag correctly re-arms when XFF is later observed as enabled.
tests/test_litellm/proxy/auth/test_mcp_ip_filtering.py Adds TestXffPresentButDisabledWarning class with five focused tests: warns-and-continues, one-shot suppression, re-arms after enabled→disabled cycle, no warning without header, and no warning when XFF is properly enabled. All mocked; no real network calls.

Reviews (2): Last reviewed commit: "fix(mcp): re-arm XFF-disabled warning on..." | Re-trigger Greptile

Comment thread litellm/proxy/auth/ip_address_utils.py Outdated
Comment thread tests/test_litellm/proxy/auth/test_mcp_ip_filtering.py Outdated
@codecov

codecov Bot commented Jun 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

…t assertion

Address PR review: tie the one-shot warning flag to the observed
use_x_forwarded_for value so it re-arms whenever the setting is seen enabled,
restoring the diagnostic on a later rollback to disabled. Also assert against
str(call_args) so the test survives a positional-to-keyword logger refactor.
@mateo-berri

Copy link
Copy Markdown
Contributor Author

@greptileai

@mateo-berri
mateo-berri requested a review from tin-berri June 25, 2026 03:28
@mateo-berri
mateo-berri merged commit 9c41077 into litellm_internal_staging Jun 25, 2026
123 checks passed
@mateo-berri
mateo-berri deleted the litellm_mcp_xff_disabled_warning branch June 25, 2026 03:49
ishaan-berri pushed a commit to ishaan-berri/litellm that referenced this pull request Jun 25, 2026
…rded_for is off (BerriAI#31266)

* fix(mcp): warn loudly when X-Forwarded-For is present but use_x_forwarded_for is off

When a request carries an X-Forwarded-For header but use_x_forwarded_for is
unset, get_mcp_client_ip silently falls back to the direct peer's IP (the load
balancer / reverse proxy). That peer almost always sits inside
mcp_internal_ip_ranges, so the 'Internal network only'
(available_on_public_internet: false) restriction trusts every external caller
as internal and effectively exposes those servers.

Emit a one-shot loud error pointing the operator at use_x_forwarded_for instead
of hard-failing: on a deployment with no load balancer, a crafted
X-Forwarded-For header must not be able to take the service down, and a one-shot
log keeps a flood of crafted headers from spamming the logs.

* fix(mcp): re-arm XFF-disabled warning on config change and harden test assertion

Address PR review: tie the one-shot warning flag to the observed
use_x_forwarded_for value so it re-arms whenever the setting is seen enabled,
restoring the diagnostic on a later rollback to disabled. Also assert against
str(call_args) so the test survives a positional-to-keyword logger refactor.
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