{"id":"GHSA-w4mq-xh27-6xpx","revision":null,"records":[{"revision":"41227d82663a4f93adda00cdba6c161f","observedAt":"2026-10-23T15:48:27.903+00:00","statement":{"source":"cve-program","id":"CVE-2026-63466","aliases":[],"title":"Unleash: Global Mustache.escape override disables HTML escaping process-wide, enabling Slack/Teams link-injection via unrestricted username","description":"Unleash is an open-source feature management platform. Prior to 8.0.3, FeatureEventFormatterMd.format in src/lib/addons/feature-event-formatter-md.ts assigns Mustache.escape to an identity function before rendering action and path templates. Because Mustache.escape is process-wide, the assignment disables escaping for subsequent Mustache.render calls in email-service.ts, webhook.ts, datadog.ts, and new-relic.ts. An editor-level user can place Slack or Microsoft Teams link syntax in an unrestricted username, trigger a feature event, and inject an attacker-labeled link into a trusted outbound notification channel, while other Mustache sinks remain unescaped until restart. This issue is fixed in version 8.0.3.","state":0,"publishedAt":"2026-08-21T18:21:46.526+00:00","modifiedAt":"2026-08-21T21:43:35.526+00:00","affected":[{"ecosystem":"Vendor product","name":"unleash","ranges":[],"versions":[],"rawVersionEvidence":"{\n                    \"vendor\": \"Unleash\",\n                    \"product\": \"unleash\",\n                    \"versions\": [\n                        {\n                            \"version\": \"< 8.0.3\",\n                            \"status\": \"affected\"\n                        }\n                    ]\n                }"}],"metrics":[{"system":"CVSS 3.1","vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:N/I:L/A:N","score":4.1,"severity":"MEDIUM"}],"references":["https://github.com/Unleash/unleash/security/advisories/GHSA-w4mq-xh27-6xpx","https://github.com/Unleash/unleash/commit/002012cfdbedd2e9b7db9dc83b9f549f761db22e","https://github.com/Unleash/unleash/releases/tag/v8.0.3"],"sourceUrl":"https://www.cve.org/CVERecord?id=CVE-2026-63466","license":"CVE Program Terms of Use","key":"cve-program:CVE-2026-63466","identifiers":["CVE-2026-63466"]},"retained":false,"historicalAffected":[]},{"revision":"dda5e4da69b5441193a7014e393a5e05","observedAt":"2026-11-09T14:13:08.895+00:00","statement":{"source":"euvd","id":"EUVD-2026-64042","aliases":["CVE-2026-63466","GHSA-w4mq-xh27-6xpx"],"title":"Unleash is an open-source feature management platform. Prior to 8.0.3, FeatureEventFormatterMd.format in src/lib/addons/feature-event-formatter-md.ts assigns Mustache.escape to an identity function before rendering action and path templates…","description":"Unleash is an open-source feature management platform. Prior to 8.0.3, FeatureEventFormatterMd.format in src/lib/addons/feature-event-formatter-md.ts assigns Mustache.escape to an identity function before rendering action and path templates. Because Mustache.escape is process-wide, the assignment disables escaping for subsequent Mustache.render calls in email-service.ts, webhook.ts, datadog.ts, and new-relic.ts. An editor-level user can place Slack or Microsoft Teams link syntax in an unrestricted username, trigger a feature event, and inject an attacker-labeled link into a trusted outbound notification channel, while other Mustache sinks remain unescaped until restart. This issue is fixed in version 8.0.3.","state":0,"publishedAt":null,"modifiedAt":null,"affected":[],"metrics":[{"system":"EUVD CVSS 3.1","vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:N/I:L/A:N","score":4.1,"severity":null}],"references":["https://github.com/Unleash/unleash/security/advisories/GHSA-w4mq-xh27-6xpx","https://github.com/Unleash/unleash/commit/002012cfdbedd2e9b7db9dc83b9f549f761db22e","https://github.com/Unleash/unleash/releases/tag/v8.0.3"],"sourceUrl":"https://euvd.enisa.europa.eu/vulnerability/EUVD-2026-64042","license":"ENISA EUVD · source terms and attribution apply","enrichment":{"sourceStatus":null,"weaknesses":[],"productCriteria":[],"sourceDates":{"datePublished":"Aug 21, 2026, 6:21:46 PM","dateUpdated":"Aug 21, 2026, 9:43:35 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-64042","identifiers":["EUVD-2026-64042","CVE-2026-63466","GHSA-w4mq-xh27-6xpx"]},"retained":false,"historicalAffected":[]},{"revision":"9804953a304a4b6ca7afc482c6db21ec","observedAt":"2026-10-25T15:18:45.851+00:00","statement":{"source":"nvd","id":"CVE-2026-63466","aliases":[],"title":"Unleash is an open-source feature management platform. Prior to 8.0.3, FeatureEventFormatterMd.format in src/lib/addons/feature-event-formatter-md.ts assigns Mustache.escape to an identity function before rendering action and path templates…","description":"Unleash is an open-source feature management platform. Prior to 8.0.3, FeatureEventFormatterMd.format in src/lib/addons/feature-event-formatter-md.ts assigns Mustache.escape to an identity function before rendering action and path templates. Because Mustache.escape is process-wide, the assignment disables escaping for subsequent Mustache.render calls in email-service.ts, webhook.ts, datadog.ts, and new-relic.ts. An editor-level user can place Slack or Microsoft Teams link syntax in an unrestricted username, trigger a feature event, and inject an attacker-labeled link into a trusted outbound notification channel, while other Mustache sinks remain unescaped until restart. This issue is fixed in version 8.0.3.","state":0,"publishedAt":"2026-08-21T19:17:32.117+00:00","modifiedAt":"2026-10-14T19:10:00.297+00:00","affected":[],"metrics":[{"system":"CVSS 3.1 · security-advisories@github.com · Secondary","vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:N/I:L/A:N","score":4.1,"severity":"MEDIUM"}],"references":["https://github.com/Unleash/unleash/commit/002012cfdbedd2e9b7db9dc83b9f549f761db22e","https://github.com/Unleash/unleash/releases/tag/v8.0.3","https://github.com/Unleash/unleash/security/advisories/GHSA-w4mq-xh27-6xpx"],"sourceUrl":"https://nvd.nist.gov/vuln/detail/CVE-2026-63466","license":"NIST NVD usage terms; underlying source attribution retained","enrichment":{"sourceStatus":"Deferred","weaknesses":["CWE-116"],"productCriteria":[],"sourceDates":{"published":"2026-08-21T19:17:32.117","lastModified":"2026-10-14T19:10:00.297"},"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-63466","identifiers":["CVE-2026-63466"]},"retained":false,"historicalAffected":[]},{"revision":"3640a41287dd4a04b250b302e5f23367","observedAt":"2026-10-24T13:17:17.793+00:00","statement":{"source":"osv","id":"GHSA-w4mq-xh27-6xpx","aliases":["CVE-2026-63466"],"title":"Unleash: Global Mustache.escape override disables HTML escaping process-wide, enabling Slack/Teams link-injection via unrestricted username","description":"## Vulnerability Details\n\n**File**: `src/lib/addons/feature-event-formatter-md.ts`\n**Line**: 355 (in v8.0.1; `format()` method)\n\n### Root Cause\n\n`FeatureEventFormatterMd.format()` does:\n\n```ts\nMustache.escape = (text) => text;\nconst text = Mustache.render(action, context);\n```\n\n`mustache` (pinned `^4.2.0`, confirmed installed `4.2.0`) keeps `escape` as a **module-level singleton** (`mustache.js`: `mustache.escape = escapeHtml;`), read by every `Mustache.render()` call in the process unless a per-call `config.escape` override is passed (`var escape = this.getConfigEscape(config) || mustache.escape;`). Node's module cache guarantees every `import Mustache from 'mustache'` in the process — `feature-event-formatter-md.ts`, `email-service.ts`, `webhook.ts`, `datadog.ts`, `new-relic.ts` — shares the same object instance.\n\nThis assignment therefore **permanently disables HTML escaping for every other `Mustache.render()` call in the same Node process** (including `email-service.ts` templates) from the moment any single notification addon (Webhook, Slack legacy, Microsoft Teams, Datadog, New Relic) first formats any event, for the remaining lifetime of the process.\n\n`feature-event-formatter-md-events.ts` (`EVENT_MAP`) confirms the blast radius: nearly every event's `action` template interpolates attacker-controlled values with single-mustache (intended-to-be-escaped) syntax, most importantly `{{user}}`, which is `event.createdBy` — the acting account's `username` (or `email` if set; `src/lib/util/extract-user.ts`: `extractUsernameFromUser`). Neither `username` nor `name` have any charset/length validation anywhere in the codebase (`create-user-schema.ts`, `create-invited-user-schema.ts`, `user-service.ts:289` only does `Joi.assert(name, Joi.string(), 'Name')` — a type check, nothing more).\n\nSlack's own API docs require `&`, `<`, `>` to be replaced with `&amp;`, `&lt;`, `&gt;` before sending user-generated text, specifically so Slack's mrkdwn parser does not interpret it as `<url|label>` link syntax. Mustache's default `escapeHtml` happens to produce exactly those entities, so this was (likely unintentionally) the application's only defense against link-injection in chat notifications — and it is unconditionally switched off by the same code path that depends on it.\n\n### Attack Scenario\n1. Admin has a Webhook, Slack (legacy), Microsoft Teams, Datadog, or New Relic integration configured (a very common production setup for flag-change notifications).\n2. Attacker has (or self-registers, if public signup is enabled — `POST /invite/:token/signup` is `permission: NONE`) any **Editor**-level account and sets `username` to e.g. `evil<https://attacker.example/urgent-rollback|Click here to view incident>`.\n3. Attacker performs any ordinary write action (create/update/toggle a feature flag — routine, no special privilege beyond Editor on one project).\n4. The configured addon's `handleEvent()` calls `this.msgFormatter.format(event)`, which mutates the global escape function and immediately renders the `{{user}}`-containing template with escaping disabled.\n5. The resulting message — containing the attacker's raw `<url|label>` Slack link syntax — is POSTed to the team's Slack/Teams channel or webhook endpoint and rendered as a real, clickable, attacker-labeled hyperlink inside a trusted automated notification feed.\n\n### Vulnerable Code\n```ts\nMustache.escape = (text) => text;\n\nconst text = Mustache.render(action, context);\nconst url = path\n    ? `${this.unleashUrl}${Mustache.render(path, context)}`\n    : undefined;\n```\n\n### Impact\n- Stored markdown/link-injection (phishing-link injection) into any configured outbound notification channel (Slack legacy, MS Teams, Webhook default markdown, Datadog, New Relic), using an attacker-controlled username — no admin privilege required, only Editor on a single project, and potentially reachable through public self-signup.\n- Secondary: loss of HTML escaping for any other reachable Mustache single-mustache placeholder process-wide until restart (increases severity of any other currently-unreached or future Mustache sink, e.g. email templates).\n- Tertiary: a custom Webhook `bodyTemplate` that interpolates raw event/user fields directly into a JSON string literal (rather than the pre-escaped `eventJson` field the code already provides for this purpose) can have its JSON structure broken by an attacker-controlled `\"` character once the global escape function is neutered.\n\n### Recommended Fix\nNever mutate the shared `Mustache.escape` global. Pass a local escape function via Mustache's per-call render option instead (supported and typed in `@types/mustache@4.2.6`'s `RenderOptions.escape`):\n\n```ts\nconst renderConfig = { escape: (text: string) => text };\nconst text = Mustache.render(action, context, undefined, renderConfig);\nconst url = path\n    ? `${this.unleashUrl}${Mustache.render(path, context, undefined, renderConfig)}`\n    : undefined;\n```\n\n### Verification\nDynamically confirmed on v8.0.1 in a local Docker lab (official `unleashorg/unleash-server:8.0.1` image + Postgres 15):\n- Created a Webhook addon with the addon UI's own placeholder `bodyTemplate` (`{{event.createdBy}}` etc.), pointed at a local listener.\n- Created an Editor-role user with `username = evil2<https://attacker.example/urgent-rollback|Click here to view incident>` (accepted with HTTP 201, no sanitization).\n- Logged in as that user and created a feature flag (ordinary Editor action).\n- Captured webhook payload: `\"createdBy\": \"evil2<https://attacker.example/urgent-rollback|Click here to view incident>\"` — `<`, `>`, `|` completely unescaped, live Slack link-injection syntax.\n- Control test with the same pinned `mustache@4.2.0` package confirmed the default (pre-bug) output for the same string would have been `evil2&lt;https:&#x2F;&#x2F;attacker.example&#x2F;urgent-rollback|Click here to view incident&gt;` — i.e. the single global assignment is solely responsible for the unescaped output observed live.","state":0,"publishedAt":"2026-08-21T19:14:52+00:00","modifiedAt":"2026-08-21T19:40:58.377206+00:00","affected":[{"ecosystem":"npm","name":"unleash-server","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"0"},{"kind":"fixed","version":"8.0.3"}]}],"versions":[],"rawVersionEvidence":null}],"metrics":[{"system":"CVSS_V3","vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:N/I:L/A:N","score":null,"severity":null},{"system":"Publisher rating","vector":null,"score":null,"severity":"MODERATE"}],"references":["https://github.com/Unleash/unleash/security/advisories/GHSA-w4mq-xh27-6xpx","https://github.com/Unleash/unleash/commit/002012cfdbedd2e9b7db9dc83b9f549f761db22e","https://github.com/Unleash/unleash","https://github.com/Unleash/unleash/releases/tag/v8.0.3"],"sourceUrl":"https://github.com/advisories/GHSA-w4mq-xh27-6xpx","license":"GitHub Advisory Database · CC-BY-4.0","key":"osv:GHSA-w4mq-xh27-6xpx","identifiers":["GHSA-w4mq-xh27-6xpx","CVE-2026-63466"]},"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-10T00:04:20.9434546+00:00","complete":true,"records":1773,"lastAttemptAt":"2026-11-10T00:04:20.9434546+00:00","lastSuccessAt":"2026-11-10T00:04:21.6223341+00:00","error":null,"lastVerifiedAt":"2026-11-10T00:04:21.622335+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-24e6fde7b744113daefb8f6135035fb8a5c69c9617c9c0d40574a0376532ffd3-28cfe0e9a7fd540693c3c003e82e8794f6d50787cb7c9866f49d79161db1199f","complete":true,"records":421738,"lastAttemptAt":"2026-11-10T00:02:15.9105874+00:00","lastSuccessAt":"2026-11-10T00:02:15.7728836+00:00","error":null,"lastVerifiedAt":"2026-11-10T00:02:15.9105874+00:00","snapshotPublishedAt":"2026-11-09T23:23:26+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-712f6c1278224b899f49f24ee1b071dc","complete":false,"records":13689,"lastAttemptAt":"2026-11-10T00:17:10.7338694+00:00","lastSuccessAt":"2026-11-10T00:17:10.73+00:00","error":"42 identifier lookups failed; latest failures include HTTP 403. Other identifiers were checked independently.","lastVerifiedAt":"2026-11-10T00:17:10.73+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13662,"expectedIdentifiers":13742,"failedIdentifiers":42},{"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-10T00:04:20.9434546+00:00","complete":true,"records":1096,"lastAttemptAt":"2026-11-10T00:04:20.9434546+00:00","lastSuccessAt":"2026-11-10T00:04:21.6231234+00:00","error":null,"lastVerifiedAt":"2026-11-10T00:04:21.6231235+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-46931e0d84a04da7b4a608d083ac3d63","complete":false,"records":13695,"lastAttemptAt":"2026-11-10T00:04:49.7283608+00:00","lastSuccessAt":"2026-11-10T00:04:49.725+00:00","error":null,"lastVerifiedAt":"2026-11-10T00:04:49.725+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13704,"expectedIdentifiers":13742,"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-cdffb4589856378aa42e8d1bd4a6c74e27a180d8160bf764c44715ef760f0a62","complete":true,"records":9932,"lastAttemptAt":"2026-11-10T00:04:20.2100924+00:00","lastSuccessAt":"2026-11-10T00:04:19.8260565+00:00","error":null,"lastVerifiedAt":"2026-11-10T00:04:20.2100924+00:00","snapshotPublishedAt":"2026-11-09T23:48:01+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-26503967ae4379db8f374089589d6e03fb15bd31f820c9e992aaf93840c2236c","complete":true,"records":1971,"lastAttemptAt":"2026-11-10T00:02:15.9827084+00:00","lastSuccessAt":"2026-11-09T21:02:20.472473+00:00","error":null,"lastVerifiedAt":"2026-11-10T00:02:15.9827084+00:00","snapshotPublishedAt":"2026-11-09T20:18:18+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-761ecc9061239b0da572dc9254c6f384c1d19339eb08408c436647a2f26129e9","complete":true,"records":3064,"lastAttemptAt":"2026-11-10T00:04:20.8155249+00:00","lastSuccessAt":"2026-11-09T17:05:03.4044794+00:00","error":null,"lastVerifiedAt":"2026-11-10T00:04:20.8155249+00:00","snapshotPublishedAt":"2026-11-09T16:33:20+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-0238b66eb8569b8ca6bfb00e5ba7ca79562b7b754d90d8771d19caf3b72b4f1c","complete":true,"records":231580,"lastAttemptAt":"2026-11-10T00:04:10.424408+00:00","lastSuccessAt":"2026-11-10T00:04:09.9885069+00:00","error":null,"lastVerifiedAt":"2026-11-10T00:04:10.424408+00:00","snapshotPublishedAt":"2026-11-09T23:48:05+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null}]}