Summary
Two code paths in docker-agent issue outbound HTTP requests without the
SSRF guard provided by httpclient.NewSafeClient / NewSSRFSafeTransport.
Both were identified during the audit that led to PR #3031.
FINDING-005 -- pkg/tools/mcp/oauth_login.go OAuth metadata fetch (HIGH)
PerformOAuthLogin uses an unguarded &http.Client{Timeout: 5s} as
oauth.metadataClient for authorization server metadata discovery.
Attack path: the MCP server returns a /.well-known/oauth-protected-resource
response whose authorization_servers field is attacker-controlled. The
metadataClient follows that URL unconditionally, reaching any internal
host the attacker specifies (e.g. http://169.254.169.254/).
Fix: replace with httpclient.NewSafeClient(5s, false). PR pending.
FINDING-006 -- pkg/tools/mcp/remote.go data plane transport (LOW)
headerTransport() returns http.DefaultTransport unconditionally.
The allowPrivateIPs flag controls the OAuth helper client only -- it
has no effect on the data channel connection.
This is a contract inconsistency rather than a direct SSRF attack path:
the MCP server URL is operator-supplied (not attacker-supplied), so
pointing it at an internal address is self-inflicted. However, when
allowPrivateIPs: false is set, the documented behaviour is that private
addresses are blocked -- the data plane does not honour that contract.
Fix: make headerTransport() return httpclient.NewSSRFSafeTransport()
when allowPrivateIPs is false. Separate PR, lower urgency.
References
Summary
Two code paths in docker-agent issue outbound HTTP requests without the
SSRF guard provided by
httpclient.NewSafeClient/NewSSRFSafeTransport.Both were identified during the audit that led to PR #3031.
FINDING-005 --
pkg/tools/mcp/oauth_login.goOAuth metadata fetch (HIGH)PerformOAuthLoginuses an unguarded&http.Client{Timeout: 5s}asoauth.metadataClientfor authorization server metadata discovery.Attack path: the MCP server returns a
/.well-known/oauth-protected-resourceresponse whose
authorization_serversfield is attacker-controlled. ThemetadataClientfollows that URL unconditionally, reaching any internalhost the attacker specifies (e.g.
http://169.254.169.254/).Fix: replace with
httpclient.NewSafeClient(5s, false). PR pending.FINDING-006 --
pkg/tools/mcp/remote.godata plane transport (LOW)headerTransport()returnshttp.DefaultTransportunconditionally.The
allowPrivateIPsflag controls the OAuth helper client only -- ithas no effect on the data channel connection.
This is a contract inconsistency rather than a direct SSRF attack path:
the MCP server URL is operator-supplied (not attacker-supplied), so
pointing it at an internal address is self-inflicted. However, when
allowPrivateIPs: falseis set, the documented behaviour is that privateaddresses are blocked -- the data plane does not honour that contract.
Fix: make
headerTransport()returnhttpclient.NewSSRFSafeTransport()when
allowPrivateIPsis false. Separate PR, lower urgency.References
IsPublicIPfor IPv6 / CGNAT gaps (same audit)