Harden MCP server SSRF and long-poll surfaces - #1511
Conversation
|
@daiv-agent fix the pipeline |
|
The changes are pure formatting (ruff collapsed multi-line expressions onto single lines). These match exactly what the CI's The pipeline failure was solely a |
|
@daiv-agent now the unit tests are failing |
…rvers, loopback-only http redirect URIs, and rate-limited job long-polling DAIV-Session: https://daivagent.com/dashboard/sessions/0b45366517257dc9179d6798a486f5aa/
|
Both fixed. Summary of the three test failures and their fixes: 1. On Python 3.14, CPython's The SSRF guard's intent (per its docstring) is to block private/loopback/link-local targets, and the test just needed a genuinely public IP to assert the "public → allowed" path. I replaced the TEST-NET-1 literal with 2. This test calls Verified: all 301 |
Hardens the MCP server surfaces against SSRF and DoS: member-controlled (user-scoped) MCP servers and non-admin test probes can no longer target internal/loopback/link-local addresses, OAuth client registration enforces RFC 8252 loopback-only plain-http redirect URIs, and the job-status long-poll is rate-limited and gives up quickly on unknown job ids.
Key Changes:
mcp_servers/validators.py: newis_internal_network_targetSSRF guard — flags IP literals in private/loopback/link-local/reserved/unspecified/multicast ranges, thelocalhostname, and hostnames that resolve to such IPs (best-effort DNS via module-level_resolve_host_ips, so tests can monkeypatch the resolver).mcp_servers/forms.py:MCPServerForm.clean_urlrejects internal-network targets for user-scoped (Scope.USER) servers; global/admin-configured rows keep the permissivevalidate_http_url(internal MCP servers are a legitimate deployment shape).mcp_servers/views.py:MCPServerTestViewnow runsvalidate_http_urland rejects internal targets for non-admin users before probing, so a member "Test connection" cannot be an SSRF primitive from the app host.mcp_server/api/schemas.py:ClientRegistrationRequestenforces RFC 8252 via new_is_loopback_host— plainhttpredirect URIs are only accepted forlocalhostor loopback IP literals; all other hosts must registerhttps.mcp_server/server.py:get_job_status(wait=true)long-poll now routes through_wait_for_job, which applies the shared per-userJobsRateThrottlebudget (same bucket assubmit_job) before polling; a job id that never appears in the DB is declared not found after the new 5sNOT_FOUND_GRACEinstead of pinning an ASGI connection for the full 10-minuteMAX_POLL_DURATION.test_validators.pyandtest_forms.py, plus an autouse_no_dns_resolutionfixture in themcp_serversconftest so form/view tests never hit real DNS.💡 Instructions for the reviewer: