CVE-2026-63466: Unleash: Global Mustache.escape override disables HTML escaping process-wide, enabling Slack/Teams link-injection via unrestricted username

Published Aug 21, 2026
·
Updated

Vulnerability Details

File: src/lib/addons/feature-event-formatter-md.ts Line: 355 (in v8.0.1; format() method)

Root Cause

FeatureEventFormatterMd.format() does:

ts Mustache.escape = (text) => text; const text = Mustache.render(action, context);

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.

This 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.

feature-event-formatter-md-events.ts (EVENTMAP) 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).

Slack'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.

Attack Scenario 1. Admin has a Webhook, Slack (legacy), Microsoft Teams, Datadog, or New Relic integration configured (a very common production setup for flag-change notifications). 2. 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>. 3. Attacker performs any ordinary write action (create/update/toggle a feature flag — routine, no special privilege beyond Editor on one project). 4. 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. 5. 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.

Vulnerable Code ts Mustache.escape = (text) => text;

const text = Mustache.render(action, context); const url = path ? ${this.unleashUrl}${Mustache.render(path, context)} : undefined;

Impact - 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. - 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). - 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.

Recommended Fix Never 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):

ts const renderConfig = { escape: (text: string) => text }; const text = Mustache.render(action, context, undefined, renderConfig); const url = path ? ${this.unleashUrl}${Mustache.render(path, context, undefined, renderConfig)} : undefined;

Verification Dynamically confirmed on v8.0.1 in a local Docker lab (official unleashorg/unleash-server:8.0.1 image + Postgres 15): - Created a Webhook addon with the addon UI's own placeholder bodyTemplate ({{event.createdBy}} etc.), pointed at a local listener. - Created an Editor-role user with username = evil2<https://attacker.example/urgent-rollback|Click here to view incident> (accepted with HTTP 201, no sanitization). - Logged in as that user and created a feature flag (ordinary Editor action). - Captured webhook payload: "createdBy": "evil2<https://attacker.example/urgent-rollback|Click here to view incident>" — <, >, | completely unescaped, live Slack link-injection syntax. - 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.

Other sources

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.

MITRE

Affected Software

2 affected componentsFixes available
Unleash Unleash<8.0.3
npm/unleash-server<8.0.3
8.0.3

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade npm/unleash-server to a version that resolves this vulnerability.

    Fixed in 8.0.3
  2. Upgrade

    Upgrade to a fixed release to a version that resolves this vulnerability.

    Fixed in 8.0.3
  3. Configuration

    Never mutate the shared, module-level Mustache.escape in FeatureEventFormatterMd.format; instead pass a per-call escape function via Mustache.render/render options (e.g., RenderOptions.escape with escape: (text: string) => text only for the specific template rendering, but do not disable HTML escaping process-wide).

    Mustache (mustache.js) Mustache.escape = must not be assigned globally

Event History

Aug 21, 2026
CVE Published
via MITRE·06:21 PM
Data Sourced
via MITRE·06:21 PM
DescriptionSeverityWeakness
Advisory Published
via GitHub·07:14 PM
Data Sourced
via GitHub·07:14 PM
DescriptionSeverityWeaknessAffected Software

Frequently Asked Questions

1

Who can exploit this issue?

An editor-level Unleash user can exploit it by using Slack or Microsoft Teams link syntax in an unrestricted username and triggering a feature event. Exploitation requires a trusted outbound notification channel that renders the affected content.

2

Which deployments are affected?

Unleash versions prior to 8.0.3 are affected. The vulnerable formatter can disable Mustache HTML escaping process-wide, affecting later rendering in email, webhook, Datadog, and New Relic notification paths until the process restarts.

3

What is the practical impact of exploitation?

An attacker can inject an attacker-labeled link into trusted Slack or Microsoft Teams notifications. This can make a malicious destination appear as a trusted notification link to recipients.

4

What should be done if patching cannot happen immediately?

Restrict editor-level access and avoid allowing untrusted or unrestricted usernames to trigger feature events sent to Slack or Microsoft Teams. Restarting the affected process restores Mustache escaping only until the vulnerable formatter is invoked again.

5

How can administrators determine whether they are exposed?

Check whether the Unleash instance is running a version earlier than 8.0.3 and sends feature-event notifications through email, webhooks, Datadog, New Relic, Slack, or Microsoft Teams-related channels. Review whether editor-level users can set usernames containing notification link syntax and trigger feature events.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203