ContextForge SSRF protection bypass via outbound HTTP redirects (Tool/Gateway/A2A invocation paths)
HighPublished Aug 25, 2026
ContextForge validates Tool, Gateway, and A2A URLs for SSRF at registration/update time, but the Python outbound clients used later for those URLs follow redirects by default. A user who can create and invoke one of those resources can submit a public URL that passes validation, then return a redirect to a blocked destination such as cloud metadata, localhost, or an RFC1918 service. The redirected Location is not revalidated. At commit f855e54d5b7bc1c91b977574a03b91eff6b86bb6. ## Source evidence - mcpgateway/common/validators.py:1289-1290 — _validate_ssrf() called only for the hostname parsed from the submitted URL. - mcpgateway/common/validators.py:1417-1451 — blocks configured metadata networks, localhost, and private ranges. - mcpgateway/schemas.py:563-579 — validates ToolCreate.url. - mcpgateway/schemas.py:2851-2862 — validates GatewayCreate.url. - mcpgateway/schemas.py:4601-4612, :4926-4937 — validates A2A endpoint URLs. - mcpgateway/services/http_client_service.py:120-126 — shared httpx.AsyncClient created with follow_redirects=True. - mcpgateway/services/http_client_service.py:304-367 — isolated clients default follow_redirects=True. - mcpgateway/services/tool_service.py:9...
Affected versions
| Package | Affected | Fixed in |
|---|---|---|
| mcp-context-forge Product | < v1.0.2 | v1.0.2 |
Details and references
ContextForge validates Tool, Gateway, and A2A URLs for SSRF at registration/update time, but the Python outbound clients used later for those URLs follow redirects by default. A user who can create and invoke one of those resources can submit a public URL that passes validation, then return a redirect to a blocked destination such as cloud metadata, localhost, or an RFC1918 service. The redirected Location is not revalidated. At commit f855e54d5b7bc1c91b977574a03b91eff6b86bb6. ## Source evidence - mcpgateway/common/validators.py:1289-1290 — _validate_ssrf() called only for the hostname parsed from the submitted URL. - mcpgateway/common/validators.py:1417-1451 — blocks configured metadata networks, localhost, and private ranges. - mcpgateway/schemas.py:563-579 — validates ToolCreate.url. - mcpgateway/schemas.py:2851-2862 — validates GatewayCreate.url. - mcpgateway/schemas.py:4601-4612, :4926-4937 — validates A2A endpoint URLs. - mcpgateway/services/http_client_service.py:120-126 — shared httpx.AsyncClient created with follow_redirects=True. - mcpgateway/services/http_client_service.py:304-367 — isolated clients default follow_redirects=True. - mcpgateway/services/tool_service.py:933-940 — REST tool HTTP client built without disabling redirects. - mcpgateway/services/tool_service.py:4957-4983 — invokes REST tools against stored URL. - mcpgateway/services/gateway_service.py:3719-3722, :3735, :3805, :5666-5680, :5833-5846 — gateway/SSE/StreamableHTTP clients keep redirects enabled. - mcpgateway/services/a2a_service.py:1942-1943, :2255, :2327 — Python A2A requests use shared client. ## Positive control (proves the maintainers know how to disable redirects) - mcpgateway/services/openapi_service.py:61-65 — explicitly disables redirects to prevent SSRF bypass. - mcpgateway/services/openapi_service.py:80-84 — uses get_isolated_http_client(..., follow_redirects=False). - mcpgateway/services/http_client_service.py:335-336 — documents that follow_redirects=False should be used for SSRF-sensitive requests. ## RBAC reachability - mcpgateway/main.py:4927-4935 — POST /tools behind tools.create. - mcpgateway/main.py:6496-6503 — POST /gateways behind gateways.create. - mcpgateway/main.py:4478-4487 — POST /a2a behind a2a.create. - mcpgateway/alembic/versions/v1a2b3c4d5e6_assign_default_viewer_role_to_existing_users.py:51-110 — gives team-admin/developer roles the relevant create + execute permissions. ## PoC Local non-destructive primitive imports the project validator, blocks a direct metadata URL, accepts a public-looking URL, then demonstrates that an httpx client configured with follow_redirects=True follows a 302 to metadata. ```text direct metadata blocked: direct metadata URL contains IP address blocked by SSRF protection (network: 169.254.169.254/32) attacker URL accepted by preflight validator: https://example.com/redirect final status: 200 requests made: - https://example.com/redirect - http://169.254.169.254/latest/meta-data/iam/security-credentials/ ``` ## Root cause Preflight URL validation runs once at registration/update time. The runtime httpx client uses default follow_redirects=True. Redirect Location is not revalidated. The maintainers' own openapi_service.py demonstrates the correct pattern (follow_redirects=False) — the issue is that pattern is not applied across REST tool, gateway health/discovery, SSE/StreamableHTTP, and A2A code paths. ## Auth boundary Boundary: server-side egress scope on ContextForge — outbound requests from the server should be limited to operator-authorized destinations, not attacker-redirected internal targets. Respected at: openapi_service.py:61-65 + :80-84 (disables redirects + revalidates). Violated at: tool_service.py:4957-4983, gateway_service.py:3719-3846, a2a_service.py:1942-2327. SSRF-sensitive outbound clients allow redirects without per-hop revalidation. ## Impact In a multi-user deployment where developer or team-admi
- Severity from
- GitHub (reviewed advisory)
- Weakness
- CWE-918
More IBM advisories
All IBM| Date | Advisory | Severity | Fixed in |
|---|---|---|---|
| Aug 25 | Parse-gap bypasses in mcp-context-forge: SQL Sanitizer (privesc/DROP/mass-DELETE) and resource_filter SSRF | High8.8 | v1.0.7 |
| Aug 25 | Scoped admin OAuth DCR endpoints ignore token_teams restrictions and expose global registered clients | Medium6.4 | v1.0.7 |
| Aug 25 | Server-Side Request Forgery (SSRF) in url_to_markdown_server via Unvalidated URL Fetching | Critical9.1 | v1.0.7 |
| Aug 25 | Server-Side Request Forgery (SSRF) in mcp-rss-search via Unvalidated URL Fetching | High8.6 | v1.0.7 |
| Aug 25 | Session auth-context reuse ignores JWT exp — expired JWT replayable within session_auth_reuse_ttl | Medium | v1.0.5 |
| Aug 25 | Cross-tenant read of plugin-binding policies (BOLA) in ContextForge MCP Gateway | High | v1.0.5 |