v1.0.10 sécu item 7. The SSRF audit flagged callbacks on Hyperswitch +
distribution submissions ; investigating those revealed a different
risk class on the user-supplied return_url fields :
* sell_handler.ConnectOnboard accepts return_url + refresh_url and
forwards them to Stripe Connect.
* kyc_handler.StartVerification accepts return_url and forwards it
to Stripe Identity.
Stripe doesn't fetch these URLs server-side (so SSRF is not the
risk), but it redirects the user's browser there after the flow
completes. Without an allow-list, an attacker can craft an onboarding
or verification link with `return_url=https://attacker.com/phishing`
and a victim who clicks the resulting Stripe URL lands on the
attacker's page after Stripe finishes — open-redirect attack
disguised as a legitimate Stripe flow.
Hyperswitch + distribution were already protected :
* Webhook URLs go through validators.ValidateWebhookURL
(services/webhook_service.go:54) which blocks private IPs +
requires HTTPS — pre-existing SSRF guard from SEC-07.
* Hyperswitch's own callback URL is configured server-side, not
user-supplied (cf. hyperswitch/client.go) — no SSRF surface.
* Distribution submissions don't carry user-supplied callbacks —
the destination platforms are hard-coded.
What's added :
validators/url_validator.go
* ValidateRedirectURL(rawURL, allowedHosts) — accepts http or
https (since Stripe-redirect targets may be local dev hosts),
requires hostname to match one of allowedHosts exactly OR be
a subdomain of one. Empty allowedHosts ⇒ permissive (used in
dev / unconfigured envs ; only checks for non-internal IPs).
* Reuses the existing IsInternalOrPrivateURL guard so SSRF
protection still applies for the permissive branch.
handlers/sell_handler.go + handlers/kyc_handler.go
* Both handlers now take an allowedRedirectHosts []string param
at construction. Validation runs after the URL defaults are
applied so the caller's submitted URL is checked, not the
backend-derived fallback.
* Validation failure → 400 with a clear message ("invalid
return_url: <reason>") so the SPA can render the right error.
api/routes_marketplace.go
* Both handlers receive the existing
cfg.OAuthAllowedRedirectDomains list at construction. Same
list as the OAuth callback validation, same operator config,
single source of truth.
Tests pass : go test ./internal/{handlers,validators} -short.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>