{"id":"GHSA-8gr3-5j6f-25gp","revision":null,"records":[{"revision":"617b6f54e2114a2d99f38624da5388d9","observedAt":"2026-10-23T15:48:27.903+00:00","statement":{"source":"cve-program","id":"CVE-2026-75510","aliases":[],"title":"Novu: Stored XSS in In-App Inbox via notification redirect.url javascript: scheme","description":"Novu provides an API for sending notifications through multiple channels. Prior to 3.18.0, Novu's @novu/js In-App Inbox and the @novu/react Inbox component accept a notification call-to-action redirect.url from the v1 cta.data object and pass it through apps/api/src/app/inbox/utils/notification-mapper.ts and packages/js/src/ui/components/Notification/DefaultNotification.tsx to the navigate function in packages/js/src/ui/context/InboxContext.tsx without validating its URL scheme. An authenticated organization member or environment API-key holder can store a javascript: redirect with target _self in an in-app workflow. When a recipient using a Chromium-based browser clicks the notification, window.open executes the redirect in the current inbox-hosting origin, which can expose session material and permit authenticated actions in a customer application or the self-hosted Novu dashboard. This issue is fixed in version 3.18.0.","state":0,"publishedAt":"2026-09-22T15:47:19.922+00:00","modifiedAt":"2026-09-22T15:59:27.761+00:00","affected":[{"ecosystem":"Vendor product","name":"novu","ranges":[],"versions":[],"rawVersionEvidence":"{\n                    \"vendor\": \"novuhq\",\n                    \"product\": \"novu\",\n                    \"versions\": [\n                        {\n                            \"version\": \"< 3.18.0\",\n                            \"status\": \"affected\"\n                        }\n                    ]\n                }"}],"metrics":[{"system":"CVSS 4.0","vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:P/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N","score":5.1,"severity":"MEDIUM"}],"references":["https://github.com/novuhq/novu/security/advisories/GHSA-8gr3-5j6f-25gp","https://github.com/novuhq/novu/pull/11453","https://github.com/novuhq/novu/commit/f105f3d41a4405a75f803634d49f15a967524d8a","https://github.com/novuhq/novu/releases/tag/v3.18.0"],"sourceUrl":"https://www.cve.org/CVERecord?id=CVE-2026-75510","license":"CVE Program Terms of Use","key":"cve-program:CVE-2026-75510","identifiers":["CVE-2026-75510"]},"retained":false,"historicalAffected":[]},{"revision":"f4ed507eeded4dceab069ac2f7c650f2","observedAt":"2026-11-11T16:06:53.942+00:00","statement":{"source":"euvd","id":"EUVD-2026-84535","aliases":["CVE-2026-75510","GHSA-8gr3-5j6f-25gp"],"title":"Novu provides an API for sending notifications through multiple channels. Prior to 3.18.0, Novu's @novu/js In-App Inbox and the @novu/react Inbox component accept a notification call-to-action redirect.url from the v1 cta.data object and pa…","description":"Novu provides an API for sending notifications through multiple channels. Prior to 3.18.0, Novu's @novu/js In-App Inbox and the @novu/react Inbox component accept a notification call-to-action redirect.url from the v1 cta.data object and pass it through apps/api/src/app/inbox/utils/notification-mapper.ts and packages/js/src/ui/components/Notification/DefaultNotification.tsx to the navigate function in packages/js/src/ui/context/InboxContext.tsx without validating its URL scheme. An authenticated organization member or environment API-key holder can store a javascript: redirect with target _self in an in-app workflow. When a recipient using a Chromium-based browser clicks the notification, window.open executes the redirect in the current inbox-hosting origin, which can expose session material and permit authenticated actions in a customer application or the self-hosted Novu dashboard. This issue is fixed in version 3.18.0.","state":0,"publishedAt":null,"modifiedAt":null,"affected":[],"metrics":[{"system":"EUVD CVSS 4.0","vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:P/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N","score":5.1,"severity":null}],"references":["https://github.com/novuhq/novu/security/advisories/GHSA-8gr3-5j6f-25gp","https://github.com/novuhq/novu/pull/11453","https://github.com/novuhq/novu/commit/f105f3d41a4405a75f803634d49f15a967524d8a","https://github.com/novuhq/novu/releases/tag/v3.18.0"],"sourceUrl":"https://euvd.enisa.europa.eu/vulnerability/EUVD-2026-84535","license":"ENISA EUVD · source terms and attribution apply","enrichment":{"sourceStatus":null,"weaknesses":[],"productCriteria":[],"sourceDates":{"datePublished":"Sep 22, 2026, 3:47:19 PM","dateUpdated":"Sep 22, 2026, 3:59:27 PM"},"relatedAdvisories":[],"note":"EUVD source date text has no stated timezone and is retained without inventing a UTC instant. Related bulletin membership is not an alias, common cause or applicability claim. A missing vector with a zero score is treated as unrated. Complete vendor/product and underlying source evidence remains in the original JSON."},"key":"euvd:EUVD-2026-84535","identifiers":["EUVD-2026-84535","CVE-2026-75510","GHSA-8gr3-5j6f-25gp"]},"retained":false,"historicalAffected":[]},{"revision":"0f8f4c069aac45da8dbbcee4483779b5","observedAt":"2026-10-25T15:19:09.409+00:00","statement":{"source":"nvd","id":"CVE-2026-75510","aliases":[],"title":"Novu provides an API for sending notifications through multiple channels. Prior to 3.18.0, Novu's @novu/js In-App Inbox and the @novu/react Inbox component accept a notification call-to-action redirect.url from the v1 cta.data object and pa…","description":"Novu provides an API for sending notifications through multiple channels. Prior to 3.18.0, Novu's @novu/js In-App Inbox and the @novu/react Inbox component accept a notification call-to-action redirect.url from the v1 cta.data object and pass it through apps/api/src/app/inbox/utils/notification-mapper.ts and packages/js/src/ui/components/Notification/DefaultNotification.tsx to the navigate function in packages/js/src/ui/context/InboxContext.tsx without validating its URL scheme. An authenticated organization member or environment API-key holder can store a javascript: redirect with target _self in an in-app workflow. When a recipient using a Chromium-based browser clicks the notification, window.open executes the redirect in the current inbox-hosting origin, which can expose session material and permit authenticated actions in a customer application or the self-hosted Novu dashboard. This issue is fixed in version 3.18.0.","state":0,"publishedAt":"2026-09-22T16:17:53.95+00:00","modifiedAt":"2026-09-24T21:25:27.05+00:00","affected":[],"metrics":[{"system":"CVSS 4.0 · security-advisories@github.com · Secondary","vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:P/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","score":5.1,"severity":"MEDIUM"}],"references":["https://github.com/novuhq/novu/commit/f105f3d41a4405a75f803634d49f15a967524d8a","https://github.com/novuhq/novu/pull/11453","https://github.com/novuhq/novu/releases/tag/v3.18.0","https://github.com/novuhq/novu/security/advisories/GHSA-8gr3-5j6f-25gp"],"sourceUrl":"https://nvd.nist.gov/vuln/detail/CVE-2026-75510","license":"NIST NVD usage terms; underlying source attribution retained","enrichment":{"sourceStatus":"Deferred","weaknesses":["CWE-79"],"productCriteria":[],"sourceDates":{"published":"2026-09-22T16:17:53.950","lastModified":"2026-09-24T21:25:27.050"},"relatedAdvisories":[],"note":"NVD analysis and CPE criteria are source context, not proof that a package or your environment is affected. Complete affected data, logical configurations and SSVC are retained in the original JSON."},"key":"nvd:CVE-2026-75510","identifiers":["CVE-2026-75510"]},"retained":false,"historicalAffected":[]},{"revision":"578c3aebf073484f9aa759347af140d7","observedAt":"2026-10-24T13:17:17.793+00:00","statement":{"source":"osv","id":"GHSA-8gr3-5j6f-25gp","aliases":["CVE-2026-75510"],"title":"Novu: Stored XSS in In-App Inbox via notification redirect.url javascript: scheme","description":"## Summary\n\nThe `@novu/js` In-App Inbox renderer passes a notification's `redirect.url` to `window.open()` with no URL-scheme validation. The value originates from a notification's call-to-action and is delivered to the recipient verbatim.\n\nAn authenticated organization member (or any holder of the environment API key) creates a v1 in-app workflow whose step CTA stores `cta.data = { url: \"javascript:<payload>\", target: \"_self\" }`. The v1 message-template `cta.data` field is a Mongoose `Mixed` type, so the arbitrary `target` key is accepted and persisted. The server-side inbox mapper copies `cta.data.url` and `cta.data.target` into the notification's `redirect` object with no scheme check. Novu's v2 control schema validates redirect URLs against `redirectUrlRegex` (which rejects `javascript:`), proving the intended invariant; the v1 path and the client renderer do not enforce it.\n\nWhen the recipient clicks the notification, the inbox calls `window.open(url, \"_self\", \"noopener noreferrer\")`. In Chromium browsers a `javascript:` URL opened with `target=\"_self\"` executes in the current document origin (the default `_blank` is browser-blocked, so the attacker sets `_self`).\n\nResult: a low-privilege content author runs arbitrary JavaScript in the browser of every recipient who clicks, in the origin that hosts the inbox (the customer application or the self-hosted Novu dashboard, neither of which sends a CSP).\n\n## Affected\n\nnovuhq/novu self-hosted and cloud, API <= v3.15.0; `@novu/js` <= 3.15.0 and `@novu/react` (Inbox component). Confirmed live-exploitable on v3.15.0 (Docker community compose, default config, default roles).\nCondition: an in-app (Inbox) channel is in use, the standard product configuration.\nRecipient must use a Chromium-based browser (Chrome, Edge); the `javascript:` execution does not occur where the browser blocks `javascript:` in `window.open`.\n\n## Root cause\n\npackages/js/src/ui/context/InboxContext.tsx:108: `window.open(url, target ?? DEFAULT_TARGET, DEFAULT_REFERRER)` is reached for any URL not starting with `/`, with no scheme allowlist, so `javascript:` is passed through.\npackages/js/src/ui/components/Notification/DefaultNotification.tsx:103: the notification click handler calls `navigate(redirect.url, redirect.target)`, feeding the stored values into the sink.\napps/api/src/app/inbox/utils/notification-mapper.ts:85-89: maps `cta.data.url` and `cta.data.target` into `redirect` with no validation of either field.\nlibs/dal/src/repositories/message-template/message-template.schema.ts:41: `data: Schema.Types.Mixed` accepts the arbitrary `target` key (not present in the typed interface).\nlibs/application-generic/src/usecases/compile-in-app-template/compile-in-app-template.usecase.ts:35-36: the v1 render path handlebars-compiles `cta.data.url` and performs no scheme check.\nlibs/application-generic/src/schemas/control/in-app-control.schema.ts:24-27: the v2 control schema enforces `url: z.string().regex(redirectUrlRegex)` which rejects `javascript:`, the guard absent from the v1 path and the renderer.\n\n## Reproduction\n\nnovuhq/novu v3.15.0 Docker community compose, default config. Attacker holds an environment API key (or a member session); victim opens the inbox in Chromium.\n\n1. Create a v1 in-app workflow with a redirect CTA carrying a `javascript:` URL and `target=_self`:\n```\nPOST /v1/workflows   Authorization: ApiKey <key>\n{\"name\":\"poc\",\"notificationGroupId\":\"<ng>\",\"active\":true,\n \"steps\":[{\"template\":{\"type\":\"in_app\",\"content\":\"click me\",\n   \"cta\":{\"type\":\"redirect\",\"data\":{\n     \"url\":\"javascript:window.top.__X=document.domain;void 0\",\"target\":\"_self\"}}}}]}\n```\n2. Trigger it to a subscriber, then read the feed with a subscriber token minted from the public application identifier (no secret):\n```\nPOST /v1/inbox/session {\"applicationIdentifier\":\"<appId>\",\"subscriberId\":\"<sub>\"}\nGET  /v1/inbox/notifications   Authorization: Bearer <subscriber-jwt>\n-> redirect: {\"url\":\"javascript:window.top.__X=document.domain;void 0\",\"target\":\"_self\"}\n```\n3. The subscriber clicks the notification in the `@novu/js` / `@novu/react` Inbox.\n\nLive-verified: the stored `javascript:` URL is returned verbatim by the inbox feed, and the exact shipped `navigate()` logic invoked from a real click runs `window.open(url,\"_self\",...)`, executing the payload in the `http://<dashboard>:4000` origin (no CSP); `window.top.__X` was set to the page origin. On the self-hosted dashboard the test inbox bell uses `subscriberId = user.externalId` (apps/dashboard/src/components/inbox-button.tsx:102), so a member targets another member's id and the executing payload reads `localStorage['self-hosted-jwt']` (apps/dashboard/src/utils/self-hosted/jwt-manager.tsx:4), the dashboard session token.\n\n## Impact\n\n- Stored XSS in the recipient's browser, cross-principal (content author to end-user subscriber), persistent, replicated to every recipient of the workflow.\n- On the self-hosted dashboard origin (no CSP), theft of `localStorage['self-hosted-jwt']` yields takeover of another member or admin account.\n- In a customer application embedding the inbox, session and token theft and authenticated actions in that origin.\n- Triggered by the lowest privilege that can author a workflow, default config, one recipient click.\n\n## Credit\n\nJan Kahmen, [turingpoint](https://turingpoint.de) (jan@turingpoint.de)","state":0,"publishedAt":"2026-09-22T20:34:35+00:00","modifiedAt":"2026-09-22T21:00:03.6515375+00:00","affected":[{"ecosystem":"npm","name":"@novu/js","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"0"},{"kind":"fixed","version":"3.18.0"}]}],"versions":[],"rawVersionEvidence":null}],"metrics":[{"system":"CVSS_V4","vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:P/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N","score":null,"severity":null},{"system":"Publisher rating","vector":null,"score":null,"severity":"MODERATE"}],"references":["https://github.com/novuhq/novu/security/advisories/GHSA-8gr3-5j6f-25gp","https://nvd.nist.gov/vuln/detail/CVE-2026-75510","https://github.com/novuhq/novu/pull/11453","https://github.com/novuhq/novu/commit/f105f3d41a4405a75f803634d49f15a967524d8a","https://github.com/novuhq/novu","https://github.com/novuhq/novu/releases/tag/v3.18.0"],"sourceUrl":"https://github.com/advisories/GHSA-8gr3-5j6f-25gp","license":"GitHub Advisory Database · CC-BY-4.0","key":"osv:GHSA-8gr3-5j6f-25gp","identifiers":["GHSA-8gr3-5j6f-25gp","CVE-2026-75510"]},"retained":false,"historicalAffected":[]}],"coverage":[{"source":"cisa-kev:global","scope":"Complete CISA KEV catalog; all vendors. Missing entries do not establish absence of exploitation.","generation":"2026-11-12T11:55:28.1860721+00:00","complete":true,"records":1779,"lastAttemptAt":"2026-11-12T11:55:28.1860721+00:00","lastSuccessAt":"2026-11-12T11:55:28.4130785+00:00","error":null,"lastVerifiedAt":"2026-11-12T11:55:28.4130791+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"cve-program","scope":"Official CVE Program baseline and cumulative hourly delta; all vendors, published and rejected records","generation":"cve-64752486c567b55b31fb0d7225f5222bcd0019564ac0ac5f0649e242c7b19299-4a4604796d7c0adea2c1c677026f25a1e039e4a95710b02ad846575329daaa79","complete":true,"records":424651,"lastAttemptAt":"2026-11-12T11:53:33.8199862+00:00","lastSuccessAt":"2026-11-12T11:53:33.7811873+00:00","error":null,"lastVerifiedAt":"2026-11-12T11:53:33.8199862+00:00","snapshotPublishedAt":"2026-11-12T11:27:22+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"euvd","scope":"Rolling EUVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global EUVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-0580dcc98aaa46a8935f8ff576f25037","complete":true,"records":13764,"lastAttemptAt":"2026-11-12T11:56:20.2141865+00:00","lastSuccessAt":"2026-11-12T11:56:20.209+00:00","error":null,"lastVerifiedAt":"2026-11-12T11:56:20.209+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13817,"expectedIdentifiers":13817,"failedIdentifiers":0},{"source":"first-epss:nuget","scope":"FIRST EPSS for explicit CVE aliases in the collected public NuGet archive. Not a global EPSS backfill.","generation":"2026-11-12T11:55:28.1860721+00:00","complete":true,"records":1097,"lastAttemptAt":"2026-11-12T11:55:28.1860721+00:00","lastSuccessAt":"2026-11-12T11:55:28.4142299+00:00","error":null,"lastVerifiedAt":"2026-11-12T11:55:28.4142304+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"nvd","scope":"Rolling NVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global NVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-ea8d0b9744d8407d9df5932458afeb6a","complete":false,"records":13764,"lastAttemptAt":"2026-11-12T11:55:53.6222651+00:00","lastSuccessAt":"2026-11-12T11:55:53.618+00:00","error":null,"lastVerifiedAt":"2026-11-12T11:55:53.618+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13313,"expectedIdentifiers":13817,"failedIdentifiers":0},{"source":"osv:Go","scope":"Complete OSV Go ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"go-3562b6c78fd1bd0f0b55daf66a0926ae24f65e462df7f6d928caad458fc696bf","complete":true,"records":9947,"lastAttemptAt":"2026-11-12T11:55:28.0344457+00:00","lastSuccessAt":"2026-11-11T04:27:43.6877707+00:00","error":null,"lastVerifiedAt":"2026-11-12T11:55:28.0344457+00:00","snapshotPublishedAt":"2026-11-11T03:32:46+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:NuGet","scope":"Complete OSV NuGet ecosystem archive, including GitHub Advisory Database records; NuGet version matching only","generation":"nuget-456002455e0ab94bb92c95476d4e6d175c104cd594749982afd96c893728ecbc","complete":true,"records":2010,"lastAttemptAt":"2026-11-12T11:53:33.8856695+00:00","lastSuccessAt":"2026-11-11T02:36:58.1276999+00:00","error":null,"lastVerifiedAt":"2026-11-12T11:53:33.8856695+00:00","snapshotPublishedAt":"2026-11-11T01:17:51+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:crates.io","scope":"Complete OSV crates.io ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"crates.io-f46f74c6a30a9b6e4cfb481719c6d35521ebec2d53ebf163223d093ae587eb9d","complete":true,"records":3106,"lastAttemptAt":"2026-11-12T11:55:28.1484531+00:00","lastSuccessAt":"2026-11-12T09:55:31.1176285+00:00","error":null,"lastVerifiedAt":"2026-11-12T11:55:28.1484531+00:00","snapshotPublishedAt":"2026-11-12T09:48:13+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:npm","scope":"Complete OSV npm ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"npm-e59fd13399d7070c31f5f25102a6bcd537ccba91d773c8427fcf0d07ea0fe2d5","complete":true,"records":231700,"lastAttemptAt":"2026-11-12T11:55:27.8998515+00:00","lastSuccessAt":"2026-11-12T11:55:26.4268762+00:00","error":null,"lastVerifiedAt":"2026-11-12T11:55:27.8998515+00:00","snapshotPublishedAt":"2026-11-12T11:33:13+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null}]}