SSRF via Unvalidated DCR Registration Endpoint
Summary
I found an SSRF issue in the OAuth Dynamic Client Registration flow of MissionSquad/mcp-api.
The project validates the external MCP transport URL with validateExternalMcpUrl(), but the DCR registrationEndpoint later used for client provisioning is not validated with the same SSRF protections. That endpoint is ultimately used in a direct fetch() call, which allows a caller-controlled external OAuth template to cause the server to send a POST request to a loopback or internal address.
Affected Version
@missionsquad/mcp-api 1.11.6
Relevant Code
src/services/mcp.ts
validateExternalOAuthTemplate(...) requires oauthTemplate.registrationEndpoint when registrationMode === 'dcr', but does not validate it with validateExternalMcpUrl()
addServer(...) passes normalizedOauthTemplate.registrationEndpoint into this.dcrClients.getOrRegisterClient(...)
src/services/dcrClients.ts
registerNewClient(...) performs:
fetch(input.registrationEndpoint, { method: 'POST', ... })
Impact
A caller able to add or resolve an external OAuth DCR-backed server can trigger server-side HTTP requests to internal or loopback targets reachable from the mcp-api host.
Recommended Fix
- Validate
oauthTemplate.registrationEndpoint with the same SSRF protections used for external MCP transport URLs.
- Re-validate any discovered
registration_endpoint values before storing or using them.
- Reject loopback, private, link-local, and other unsafe destinations for DCR registration fetches.
I have a minimal local reproduction and will add it in a follow-up comment with terminal output and screenshot.
SSRF via Unvalidated DCR Registration Endpoint
Summary
I found an SSRF issue in the OAuth Dynamic Client Registration flow of
MissionSquad/mcp-api.The project validates the external MCP transport URL with
validateExternalMcpUrl(), but the DCRregistrationEndpointlater used for client provisioning is not validated with the same SSRF protections. That endpoint is ultimately used in a directfetch()call, which allows a caller-controlled external OAuth template to cause the server to send aPOSTrequest to a loopback or internal address.Affected Version
@missionsquad/mcp-api1.11.6Relevant Code
src/services/mcp.tsvalidateExternalOAuthTemplate(...)requiresoauthTemplate.registrationEndpointwhenregistrationMode === 'dcr', but does not validate it withvalidateExternalMcpUrl()addServer(...)passesnormalizedOauthTemplate.registrationEndpointintothis.dcrClients.getOrRegisterClient(...)src/services/dcrClients.tsregisterNewClient(...)performs:fetch(input.registrationEndpoint, { method: 'POST', ... })Impact
A caller able to add or resolve an external OAuth DCR-backed server can trigger server-side HTTP requests to internal or loopback targets reachable from the
mcp-apihost.Recommended Fix
oauthTemplate.registrationEndpointwith the same SSRF protections used for external MCP transport URLs.registration_endpointvalues before storing or using them.I have a minimal local reproduction and will add it in a follow-up comment with terminal output and screenshot.