Summary
The per-user webhook delivery client (created in startClient) does not use the SSRF-protected transport that guards globalHTTPClient, and it disables TLS certificate verification. Filing as a hardening discussion before a PR, since the right fix is likely opt-in to avoid breaking existing setups.
Details
main.go builds globalHTTPClient with a custom dialer (newSafeHTTPClient / isPrivateOrLoopback) that refuses to connect to private/loopback IPs — good SSRF protection. But the per-user webhook client (used by callHookWithHmac / callHookFileWithHmac) is plain:
// wmiau.go (~452)
httpClient := resty.New()
httpClient.SetRedirectPolicy(resty.FlexibleRedirectPolicy(15))
...
httpClient.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}) // ~458
- SSRF: no IP guard, and it follows up to 15 redirects with no re-validation. On a multi-tenant instance, a user who controls their own webhook URL can point/redirect it to
http://169.254.169.254/... (cloud metadata) or other internal services. The global client's guard does not apply here.
- TLS
InsecureSkipVerify: true: cert verification is disabled for all webhook calls. As HMAC signing is the only webhook integrity mechanism, a MitM can intercept the signed payload + signature.
Threat model
Mainly relevant when operators don't fully trust the users who can configure webhooks (multi-tenant / resold instances); lower risk for single-operator deployments.
Why opt-in
Many deployments legitimately webhook to internal/Docker addresses (n8n, Chatwoot, etc.). A blanket private-IP block would break them, so the fix should be configurable, e.g.:
- apply the
newSafeHTTPClient transport (+ a redirect re-validation hook) to the per-user client, gated by WUZAPI_WEBHOOK_SSRF_PROTECT=true;
- default TLS verification on, with an opt-in per-user
tls_skip_verify for self-signed endpoints.
Locations
wmiau.go:452-458
main.go (newSafeHTTPClient / isPrivateOrLoopback)
Happy to open a PR along these lines — wanted to agree on defaults/flag names first.
Summary
The per-user webhook delivery client (created in
startClient) does not use the SSRF-protected transport that guardsglobalHTTPClient, and it disables TLS certificate verification. Filing as a hardening discussion before a PR, since the right fix is likely opt-in to avoid breaking existing setups.Details
main.gobuildsglobalHTTPClientwith a custom dialer (newSafeHTTPClient/isPrivateOrLoopback) that refuses to connect to private/loopback IPs — good SSRF protection. But the per-user webhook client (used bycallHookWithHmac/callHookFileWithHmac) is plain:http://169.254.169.254/...(cloud metadata) or other internal services. The global client's guard does not apply here.InsecureSkipVerify: true: cert verification is disabled for all webhook calls. As HMAC signing is the only webhook integrity mechanism, a MitM can intercept the signed payload + signature.Threat model
Mainly relevant when operators don't fully trust the users who can configure webhooks (multi-tenant / resold instances); lower risk for single-operator deployments.
Why opt-in
Many deployments legitimately webhook to internal/Docker addresses (n8n, Chatwoot, etc.). A blanket private-IP block would break them, so the fix should be configurable, e.g.:
newSafeHTTPClienttransport (+ a redirect re-validation hook) to the per-user client, gated byWUZAPI_WEBHOOK_SSRF_PROTECT=true;tls_skip_verifyfor self-signed endpoints.Locations
wmiau.go:452-458main.go(newSafeHTTPClient/isPrivateOrLoopback)Happy to open a PR along these lines — wanted to agree on defaults/flag names first.