Tracking issue for the webhook hardening follow-ups identified in the round-2 review of #4564 that are not addressed by #5190 / #5191 / #5192.
Shipped
Remaining
Item #6: InsecureSkipVerify conflates cert-skip with plaintext-HTTP
pkg/webhook/types.go ValidateDefinition allows non-HTTPS URLs whenever InsecureSkipVerify=true. pkg/webhook/client.go buildTransport propagates the same flag into ValidatingTransport.InsecureAllowHTTP=true, which (per pkg/networking/http_client.go:55-73) skips ALL request validation when set: cert check, scheme check, and now also short-circuits after the dial-time SSRF guard fires. Flip one knob, lose multiple guarantees.
RFC THV-0017 says HTTPS-only. Suggested fix:
- Split
InsecureSkipVerify into two fields, or remove the plaintext escape hatch entirely.
- Add a CRD-level CEL rule on
MCPWebhookConfig.spec.*.url enforcing self.startsWith('https://') at admission time.
MCP parsing layer needs an inbound body cap
The webhook-layer cap shipped in #5192 is correct for the webhook package's own re-read buffer, but mcp.ParsingMiddleware (pkg/mcp/parser.go:85) reads the inbound HTTP body via unbounded io.ReadAll BEFORE the webhook middleware in the proxy chain. So an attacker can still buffer a 10 GB body into memory upstream of the webhook cap.
pkg/mcp/tool_filter.go:229 has the same exposure. Both should be wrapped with http.MaxBytesReader or an equivalent cap. The cap may need to be configurable per-server (some MCP tools/call payloads can legitimately be larger than the default).
pkg/webhook/internal/dialer subpackage refactor
The three exported test helpers in pkg/webhook/dialer_testing.go (SetDialerControlForTesting, SetDialerControlForTestMain, AllowAnyDialerControl) inflate pkg/webhook's public API surface in service of cross-package test injection. Moving them — and the dialerControl atomic.Pointer itself — into a new pkg/webhook/internal/dialer subpackage would keep them off the project's public API while still letting pkg/webhook, pkg/webhook/validating, and pkg/webhook/mutating import them via Go's internal rule.
An even more principled fix is to refactor webhook.NewClient to accept an optional *http.Client (or a WithHTTPClient(...) option), so production passes the SSRF-protected client and tests pass an unguarded one. The package-level dialerControl then disappears entirely.
Expanded SSRF integration coverage in pkg/networking
pkg/networking/utilities_test.go already covers IPv6 cases at the unit level. The webhook PR (#5190) adds IPv6 cases to TestClientSSRFGuardBlocksPrivateAddress, but it would be worth adding parallel integration coverage for any other consumer of ProtectedDialerControl (the OAuth/discovery clients, etc.).
Webhook response body in client error log fields
#5191 moves the body preview to slog.Debug with a body_preview field. If the project's log policy forbids untrusted bytes flowing into structured log fields at any level (not just info+), the preview should be redacted further or omitted. Re-evaluate when log-injection policy is formalized.
🤖 Generated with Claude Code
Tracking issue for the webhook hardening follow-ups identified in the round-2 review of #4564 that are not addressed by #5190 / #5191 / #5192.
Shipped
Remaining
Item #6:
InsecureSkipVerifyconflates cert-skip with plaintext-HTTPpkg/webhook/types.goValidateDefinitionallows non-HTTPS URLs wheneverInsecureSkipVerify=true.pkg/webhook/client.gobuildTransportpropagates the same flag intoValidatingTransport.InsecureAllowHTTP=true, which (perpkg/networking/http_client.go:55-73) skips ALL request validation when set: cert check, scheme check, and now also short-circuits after the dial-time SSRF guard fires. Flip one knob, lose multiple guarantees.RFC THV-0017 says HTTPS-only. Suggested fix:
InsecureSkipVerifyinto two fields, or remove the plaintext escape hatch entirely.MCPWebhookConfig.spec.*.urlenforcingself.startsWith('https://')at admission time.MCP parsing layer needs an inbound body cap
The webhook-layer cap shipped in #5192 is correct for the webhook package's own re-read buffer, but
mcp.ParsingMiddleware(pkg/mcp/parser.go:85) reads the inbound HTTP body via unboundedio.ReadAllBEFORE the webhook middleware in the proxy chain. So an attacker can still buffer a 10 GB body into memory upstream of the webhook cap.pkg/mcp/tool_filter.go:229has the same exposure. Both should be wrapped withhttp.MaxBytesReaderor an equivalent cap. The cap may need to be configurable per-server (some MCPtools/callpayloads can legitimately be larger than the default).pkg/webhook/internal/dialersubpackage refactorThe three exported test helpers in
pkg/webhook/dialer_testing.go(SetDialerControlForTesting,SetDialerControlForTestMain,AllowAnyDialerControl) inflatepkg/webhook's public API surface in service of cross-package test injection. Moving them — and thedialerControlatomic.Pointeritself — into a newpkg/webhook/internal/dialersubpackage would keep them off the project's public API while still lettingpkg/webhook,pkg/webhook/validating, andpkg/webhook/mutatingimport them via Go'sinternalrule.An even more principled fix is to refactor
webhook.NewClientto accept an optional*http.Client(or aWithHTTPClient(...)option), so production passes the SSRF-protected client and tests pass an unguarded one. The package-leveldialerControlthen disappears entirely.Expanded SSRF integration coverage in
pkg/networkingpkg/networking/utilities_test.goalready covers IPv6 cases at the unit level. The webhook PR (#5190) adds IPv6 cases toTestClientSSRFGuardBlocksPrivateAddress, but it would be worth adding parallel integration coverage for any other consumer ofProtectedDialerControl(the OAuth/discovery clients, etc.).Webhook response body in client error log fields
#5191 moves the body preview to
slog.Debugwith abody_previewfield. If the project's log policy forbids untrusted bytes flowing into structured log fields at any level (not just info+), the preview should be redacted further or omitted. Re-evaluate when log-injection policy is formalized.🤖 Generated with Claude Code