Skip to content

fix(ui): escape attacker-controlled values in transaction titles#2236

Merged
wa0x6e merged 5 commits into
masterfrom
security/innerhtml-xss-audit
Jul 20, 2026
Merged

fix(ui): escape attacker-controlled values in transaction titles#2236
wa0x6e merged 5 commits into
masterfrom
security/innerhtml-xss-audit

Conversation

@tony8713

Copy link
Copy Markdown
Collaborator

Follow-up to #2233. Chaitu asked us to sweep the rest of the frontend for the same class of XSS after the Markdown language-label fix. I audited every innerHTML / outerHTML / insertAdjacentHTML / document.write / v-html / dangerouslySetInnerHTML site in apps/ui.

One real issue: TransactionsListItem.vue builds a transaction summary string with intentional <b> markup and renders it via v-html. Three of the interpolated values are attacker-controlled and were going in unescaped:

  • token.symbol - an ERC20's on-chain symbol(); anyone can deploy a token whose symbol is an <img onerror=...> payload
  • contract-call method name - comes from an ABI the proposal author supplies
  • the resolved recipient name - an ENS reverse record the recipient controls

Any of these renders when a viewer opens a proposal that carries such an execution transaction, so it's stored XSS reachable by a proposal author. Fix escapes just the dynamic pieces with a small escapeHtml helper and keeps the static formatting markup.

Everything else came back clean (see audit table below), so no other code changes.

Audit

Site Sink Provenance Verdict
components/TransactionsListItem.vue:164 v-html parsedTitle token symbol / method / ENS name FIXED (was exploitable)
components/Ui/Markdown.vue:124 v-html parsed proposal/space markdown SAFE - Remarkable html:false, htmltag/htmlblock rules disabled
components/Ui/Markdown.vue:95,100,103 innerHTML static iconify svg SAFE - constant
views/User.vue:179, SpaceUser.vue:249 v-html autoLinkText(user.about) user profile SAFE - Autolinker sanitizeHtml:true
views/Space/Overview.vue:150, Organization/Overview.vue:176 v-html autoLinkText(about) space about SAFE - Autolinker sanitizeHtml:true
components/Modal/VoteReason.vue:22 v-html autoLinkText(reason) vote reason SAFE - Autolinker sanitizeHtml:true
views/Space/Editor.vue:585,611 v-html of <b>${name}</b> strategy/network names SAFE - values only ever members of hardcoded allowlists (DISABLED/DEPRECATED/OVERRIDING_STRATEGIES) or curated network config
helpers/turndownService.ts:21,32,83,99,107 reads .innerHTML n/a SAFE - read (source), not an assignment sink
docs/icons.mdx:16,28 dangerouslySetInnerHTML static iconify body SAFE - internal icon docs, constant

Test plan

  • Added escapeHtml unit tests in utils.test.ts covering the <img onerror> payload, all HTML-sensitive chars, plain text, and nullish input.
  • vitest run src/helpers/utils.test.ts -> 36 passed.
  • eslint + prettier clean on touched files.
  • Typecheck: no new errors from touched files (pre-existing codegen ./gql errors unrelated).

Transaction summaries render via v-html so the intentional <b> markup
shows. The token symbol, contract method name, and resolved recipient
name were interpolated unescaped. A token deployed with an XSS payload
as its symbol(), a crafted ABI method name, or an ENS reverse record
could execute script when a proposal's execution transactions render.

Add an escapeHtml helper and escape those dynamic values, keeping the
static formatting markup.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens the UI against stored XSS by ensuring attacker-controlled values interpolated into transaction title HTML (rendered via v-html) are HTML-escaped while preserving intended static <b> formatting.

Changes:

  • Added a shared escapeHtml helper in apps/ui/src/helpers/utils.ts.
  • Escaped token symbols, contract method names, and ENS-resolved recipient names in TransactionsListItem.vue before rendering via v-html.
  • Added unit tests for escapeHtml in utils.test.ts.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
apps/ui/src/helpers/utils.ts Introduces escapeHtml helper for safely escaping dynamic strings used in HTML contexts.
apps/ui/src/helpers/utils.test.ts Adds unit tests validating escapeHtml behavior against XSS-like payloads and special characters.
apps/ui/src/components/TransactionsListItem.vue Applies escapeHtml to attacker-controlled fields before composing v-html transaction titles.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread apps/ui/src/helpers/utils.ts Outdated
Comment thread apps/ui/src/helpers/utils.test.ts
Comment thread apps/ui/src/components/TransactionsListItem.vue Outdated

@chai3-bot chai3-bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the XSS fix and the surrounding transaction-title rendering path. No blocking findings.

The token symbol, ABI-derived method name, and resolved recipient name are now escaped before reaching v-html, while the intentional static <b> markup is preserved. The fallback recipient path is escaped as well, and the corresponding expanded transaction details continue to use Vue text interpolation. The helper covers all HTML-sensitive characters and nullish input.

CI is green (lint-build-test and E2E test), and the patch passes git diff --check.

Widen escapeHtml to accept nullish input so callers/tests don't cast,
and replace the full <b>_NAME_</b> marker to avoid collisions when an
escaped value contains the literal _NAME_ token.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread apps/ui/src/components/TransactionsListItem.vue Outdated
tony8713 and others added 3 commits July 20, 2026 11:50
String.prototype.replace treats $ sequences in a string replacement as
special patterns even with a plain-string search. escapeHtml turns & into
&amp;, so a recipient name containing $& or $$ would splice the matched
<b>_NAME_</b> marker into the output or silently drop characters. Use a
function replacer so the escaped value is inserted literally.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extract the <b>_NAME_</b> substitution into a testable replaceNamePlaceholder
helper and add regression cases for names containing $&, $$, $` and $'.
These fail against a plain string replacement (the marker gets spliced in or
characters are dropped) and pass with the function replacer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@wa0x6e wa0x6e left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tAck

@wa0x6e
wa0x6e enabled auto-merge (squash) July 20, 2026 04:58
@wa0x6e
wa0x6e merged commit 99da610 into master Jul 20, 2026
10 checks passed
@wa0x6e
wa0x6e deleted the security/innerhtml-xss-audit branch July 20, 2026 05:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants