Skip to content

[SECURITY] Stored XSS via unsanitized "Full Name" field in registration form #237

Description

@Tamcodes4

Description

The registration form's Full Name field accepts arbitrary free text with no sanitization or length limit. That value is persisted into users.json/overall.json/changes.json and later inserted into the DOM via raw innerHTML string interpolation in three places, instead of textContent (which is used correctly elsewhere, e.g. render.js).

Steps to Reproduce

  1. On /registration, submit a valid LeetCode username with Full Name set to <img src=x onerror=alert(document.cookie)>.
  2. Once the entry syncs into the leaderboard data, visit /leaderboard.
  3. Enable "compare" mode and select that user, or open the "recent changes" panel after that user shows up in a rank-change/new-user entry.
  4. The payload executes.

Expected Behavior

Any user-supplied name should be rendered as plain text, with no HTML/script execution, regardless of content.

Actual Behavior

The name is interpolated unescaped into innerHTML and executes as HTML/JS.

Root Cause

frontend/registration.html restricts the LeetCode username to [A-Za-z0-9_-] but applies no equivalent restriction to name:

body: JSON.stringify({ name: name.trim(), id: leetcodeId.trim() }),

That unsanitized value then hits three unsafe sinks:

frontend/js/leaderboard/compare.js (~line 237):

const namesStr = window.selectedUsers.map((u) => u.name).join(", ");
bar.innerHTML = `... ${namesStr} ...`;

frontend/js/leaderboard/compare.js (~lines 431, 501-508):

const headers = ["Metric", ...window.selectedUsers.map((u) => u.name)];
headers.forEach((h) => { html += `<th>${h}</th>`; });

frontend/js/leaderboard/sync-changes.js (~line 55):

html += `<strong style="color:#fff;">${rc.username}</strong> moved #${rc.old_rank} → ...`;

Suggested Fix

Replace these three innerHTML sites with safe DOM construction (textContent / document.createTextNode), matching the pattern already used in render.js. As defense in depth, also consider a length cap + character allowlist on name at registration time.

Affected Files

  • frontend/js/leaderboard/compare.js
  • frontend/js/leaderboard/sync-changes.js
  • frontend/registration.html

Metadata

Metadata

Assignees

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions