You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Overview: Merchants control their own webhook destination, and the backend POSTs to that URL with no validation of the scheme, host, or resolved IP. UpdateMerchantSettingsDto declares webhookUrl with only @IsString() and @IsOptional() — there is no @IsUrl() and no allowlist. WebhooksService then calls axios.post(merchant.webhookUrl, ...) and axios.post(delivery.url, ...) directly. Any authenticated merchant can point the webhook at internal infrastructure and make the backend issue requests on their behalf, which is a server-side request forgery primitive.
Details:
A merchant can set webhookUrl to a loopback address (http://127.0.0.1:3001/...), a private range (http://10.0.0.5/...), a link-local metadata endpoint (http://169.254.169.254/latest/meta-data/), or a non-HTTP scheme, and the backend will call it.
The test-delivery endpoint returns the HTTP status and error classification to the caller, and delivery history records lastHttpStatus. That turns the SSRF into an oracle: a merchant can probe internal hosts and ports and read the outcome back through the API.
axios follows redirects by default. Even if the configured URL is validated at write time, a public endpoint can 302 the backend to an internal address at delivery time, so validation must also hold at request time.
DNS is resolved at request time, so a hostname that validates as public on write can later resolve to a private address (DNS rebinding). Write-time validation alone is not sufficient.
Scope:
Validate webhookUrl on write: require a valid absolute URL, restrict to https (with an explicit, configurable escape hatch for local development), and reject credentials embedded in the URL.
Block requests to loopback, private, link-local, unique-local, and reserved address ranges, enforced against the resolved IP at request time rather than only the hostname.
Disable or tightly bound redirect following on webhook delivery, and re-validate the destination on every hop that is allowed.
Ensure the test-delivery and delivery-history responses do not leak internal reachability details beyond what a merchant needs to debug their own endpoint.
Keep an explicit configuration switch so local and CI environments can still target localhost deliberately.
UpdateMerchantSettingsDtodeclareswebhookUrlwith only@IsString()and@IsOptional()— there is no@IsUrl()and no allowlist.WebhooksServicethen callsaxios.post(merchant.webhookUrl, ...)andaxios.post(delivery.url, ...)directly. Any authenticated merchant can point the webhook at internal infrastructure and make the backend issue requests on their behalf, which is a server-side request forgery primitive.webhookUrlto a loopback address (http://127.0.0.1:3001/...), a private range (http://10.0.0.5/...), a link-local metadata endpoint (http://169.254.169.254/latest/meta-data/), or a non-HTTP scheme, and the backend will call it.lastHttpStatus. That turns the SSRF into an oracle: a merchant can probe internal hosts and ports and read the outcome back through the API.axiosfollows redirects by default. Even if the configured URL is validated at write time, a public endpoint can 302 the backend to an internal address at delivery time, so validation must also hold at request time.webhookUrlon write: require a valid absolute URL, restrict tohttps(with an explicit, configurable escape hatch for local development), and reject credentials embedded in the URL.backend/src/merchant/dto/update-merchant-settings.dto.tsbackend/src/merchant/merchant.service.tsbackend/src/webhooks/webhooks.service.tsbackend/src/webhooks/webhooks.controller.tsbackend/src/config/app.config.ts