CVE-2026-125114: Novu has SSRF via conditions filter webhook bypasses validateUrlSsrf() protection
Summary
The conditions filter webhook at libs/application-generic/src/usecases/conditions-filter/conditions-filter.usecase.ts line 261 sends POST requests to user-configured URLs using raw axios.post() with no SSRF validation. The HTTP Request workflow step in the same codebase correctly uses validateUrlSsrf() which blocks private IP ranges. The conditions webhook was not included in this protection.
Root Cause
conditions-filter.usecase.ts line 261: typescript return await axios.post(child.webhookUrl, payload, config).then((response) => { return response.data as Record<string, unknown>; });
No call to validateUrlSsrf(). The webhookUrl comes from the workflow condition configuration with zero validation.
Protected Code (for contrast)
execute-http-request-step.usecase.ts line 130: typescript const ssrfValidationError = await validateUrlSsrf(url); if (ssrfValidationError) { // blocked }
This function resolves DNS and checks against private ranges (127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16). It exists in the codebase but is not applied to the conditions webhook path.
Proof of Concept
1. Create a workflow with a condition step 2. Configure the condition's webhook URL to http://169.254.169.254/latest/meta-data/iam/security-credentials/ 3. Trigger the workflow by sending a notification event 4. The worker evaluates the condition and calls axios.post() to the metadata endpoint 5. The response data is stored in execution details and accessible via the execution details API
Impact
Full-read SSRF. The response body is returned as Record<string, unknown> for condition evaluation and stored in the execution details raw field. The GET /execution-details API returns this data.
The POST method limits some metadata endpoints (GCP requires GET, Azure requires GET), but AWS IMDSv1 accepts POST and returns credentials. Internal services accepting POST are also reachable.
Suggested Fix
Extract validateUrlSsrf() to a shared utility and call it before the axios.post in conditions-filter.usecase.ts:
typescript const ssrfError = await validateUrlSsrf(child.webhookUrl); if (ssrfError) { throw new Error('Webhook URL blocked by SSRF protection'); } return await axios.post(child.webhookUrl, payload, config)...
Other sources
Novu provides an API for sending notifications through multiple channels. Prior to 3.15.0, the conditions filter webhook in libs/application-generic/src/usecases/conditions-filter/conditions-filter.usecase.ts passes the user-configured child.webhookUrl directly to axios.post without calling validateUrlSsrf. Evaluating a workflow condition can therefore make the worker send a POST request to a private or link-local service and return the response data for condition evaluation. The response is stored in execution details and exposed through GET /execution-details, which can disclose credentials from POST-capable metadata services or data from internal services, although metadata endpoints that require GET are not reachable through this path. This issue is fixed in version 3.15.0.
— MITRE
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
npm/@novu/apito a version that resolves this vulnerability.Fixed in 3.15.0 - Upgrade
Upgrade
Novuto a version that resolves this vulnerability.Fixed in 3.15.0
Event History
Frequently Asked Questions
Who is exposed to this issue?
Deployments are exposed when users can configure workflow condition webhooks and the Novu instance can reach internal or private network addresses. The affected path sends POST requests to the configured webhook URL without SSRF validation.
What access does an attacker need?
An attacker needs privileges to set a workflow condition's webhook URL. No user interaction is required after the malicious URL is configured.
What can be targeted through the vulnerable request path?
The missing validation permits requests to private address ranges, including loopback, RFC1918 ranges, and link-local addresses. The existing validateUrlSsrf() function is designed to block 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, and 169.254.0.0/16, but it is not used for condition webhooks.
What can be done if a fix cannot be applied immediately?
Restrict who can create or modify workflow condition webhook configurations and avoid using condition webhooks with untrusted URLs. Where the workflow design permits it, use the HTTP Request workflow step instead, because that path invokes validateUrlSsrf().
How can I determine whether my deployment uses the affected path?
Review workflow condition configurations for webhook URLs, especially URLs resolving to internal, loopback, private, or link-local destinations. The affected code path is the conditions-filter use case that calls axios.post(child.webhookUrl, payload, config) without a preceding validateUrlSsrf() check.