Skip to content

fix: Prevent stored XSS in Tag Cloud#41

Open
Flashl3opard wants to merge 2 commits intoalphaonelabs:mainfrom
Flashl3opard:fix/xss-tag-inline-onclick
Open

fix: Prevent stored XSS in Tag Cloud#41
Flashl3opard wants to merge 2 commits intoalphaonelabs:mainfrom
Flashl3opard:fix/xss-tag-inline-onclick

Conversation

@Flashl3opard
Copy link
Copy Markdown
Contributor

@Flashl3opard Flashl3opard commented Apr 13, 2026

Overview

This PR fixes a stored XSS vulnerability caused by injecting user-controlled tag names directly into inline JavaScript event handlers. A crafted tag name could break out of the JavaScript string context and execute arbitrary code in other users’ browsers.

Root Cause

Tag names were inserted into inline handlers as follows:

onclick="toggleTag('${name}')"

Because HTML entity decoding happens before JavaScript execution, a tag such as:

x');alert(1);//

could break out of the string context and execute arbitrary JavaScript.

Fix

This PR removes inline event handlers and replaces them with safe DOM-based event binding:

  • Removed inline onclick="toggleTag('${...}')"
  • Store tag names safely using data-tag="..."
  • Bind click events via addEventListener() and retrieve values using .dataset.tag

This ensures user-controlled values are treated strictly as data and are never executed as code.

Changes

  • Updated buildTagCloud() in public/index.html
  • Replaced inline event handlers with programmatic event listeners
  • Tag values are now handled as plain text and never interpreted as JavaScript

Summary

This PR fixes a stored cross-site scripting (XSS) vulnerability in the tag cloud feature where user-controlled tag names were injected into inline JavaScript event handlers.

Vulnerability Details

User-controlled tag names were previously embedded directly into inline onclick attributes:

<button onclick="toggleTag('${esc(name)}')">

An attacker could craft a malicious tag name (e.g., x');alert(1);//) that breaks out of the JavaScript string context and executes arbitrary code in other users' browsers.

Solution Implemented

  • Removed inline event handlers: Eliminated onclick="toggleTag('${esc(name)}')" from HTML generation
  • Added data attributes: Tag names now stored in data-tag-name attributes, treated as plain text
  • Event binding via JavaScript: Added addEventListener() calls to programmatically bind click events, reading tag values from element.dataset.tagName
  • Safe event handling: The buildTagCloud() function now attaches event listeners after DOM generation, ensuring tag names are never interpreted as code

Files Modified

  • public/index.html: Updated tag cloud rendering logic in buildTagCloud() function to replace inline event handlers with data attributes and programmatic event binding

Impact

Tag values are now completely isolated from the JavaScript execution context, preventing any injection attacks regardless of tag name content. The fix maintains full functionality while eliminating the XSS vector.

Copilot AI review requested due to automatic review settings April 13, 2026 18:12
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 13, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: alphaonelabs/coderabbit/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a67a13c7-986c-4cb0-9292-fe58ad0a18cb

📥 Commits

Reviewing files that changed from the base of the PR and between fe7916b and 87f6397.

📒 Files selected for processing (1)
  • public/index.html

Walkthrough

This change adjusts end-of-file formatting in the HTML file by removing and re-adding the closing tag with modified trailing whitespace and newline structure. No functional or structural content changes are introduced.

Changes

Cohort / File(s) Summary
HTML Formatting
public/index.html
Adjusts the file ending: removes the original closing </html> tag and re-adds it with additional trailing newlines/whitespace structure (adds an extra blank line after the closing tag).

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~2 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title 'fix: Prevent stored XSS in Tag Cloud' accurately describes the main security fix in this PR, but the actual changeset only modifies end-of-file formatting in public/index.html with no substantive code changes. The title describes a significant security fix (removing inline onclick handlers and replacing with addEventListener), but the raw_summary shows only whitespace/formatting changes. Clarify whether the XSS fix is actually implemented in this changeset or if the summary is incomplete.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

Copilot AI left a comment

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 addresses a stored XSS risk in the activity tag cloud by removing inline JavaScript handlers and switching to DOM event listeners with data-* attributes. It also includes a set of front-end concurrency guards (action locks) and a back-end change to make /api/join idempotent when an enrollment already exists.

Changes:

  • Replace inline onclick tag-cloud handlers with data-tag + addEventListener binding to prevent stored XSS.
  • Add client-side “in-flight” action locks to reduce double-submit/double-click behavior across several pages.
  • Update api_join to detect existing enrollments and handle uniqueness conflicts explicitly.

Reviewed changes

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

Show a summary per file
File Description
public/index.html Removes inline tag-cloud handlers and binds clicks via addEventListener; expands escaping for tag/user strings.
public/teach.html UI refactor + adds withActionLock and load versioning; adjusts hosted activities rendering.
public/login.html Adds withActionLock around login/register submit flows; mostly formatting.
public/course.html Adds withActionLock for joining activities; mostly formatting/structure changes.
src/worker.py Makes /api/join return success when already enrolled and removes INSERT OR IGNORE in favor of explicit handling.

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

Comment thread public/teach.html Outdated
Comment thread src/worker.py Outdated
Comment thread public/index.html Outdated
@Flashl3opard Flashl3opard force-pushed the fix/xss-tag-inline-onclick branch from fe7916b to 9fecd98 Compare April 13, 2026 18:18
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@public/index.html`:
- Around line 121-139: The inputs lack accessible labels: add explicit labels
associated with the elements (use <label for="search">, <label
for="filter-type">, and <label for="filter-format"> or equivalent
aria-labelledby attributes) so screen readers and keyboard users get an
accessible name; if visual clutter is a concern use visually-hidden/sr-only
labels and ensure each label text describes the control (e.g., "Search
activities", "Filter by type", "Filter by format") and that the label 'for'
attribute matches the element ids search, filter-type, and filter-format
respectively.

In `@public/login.html`:
- Around line 89-97: Update the client-side password rule to match the server's
8-character minimum by changing the input with id "r-password": set minlength
from 6 to 8, update the placeholder from "min 6 characters" to "min 8
characters", and ensure any UI text (e.g., error container "register-err") or
client-side validation logic that references the 6-char rule is updated to
reflect the 8-character minimum so users are validated consistently before
submission.
- Around line 45-90: The labels next to inputs are not associated with their
controls; update each label element to include a matching for attribute pointing
to the corresponding input id (e.g., for="l-username", for="l-password",
for="r-name", for="r-username", for="r-email", for="r-password") so screen
readers and form focus work correctly; ensure each label exactly matches the
input id strings used in the form elements.

In `@public/teach.html`:
- Around line 72-173: Many labels are not associated with their controls which
breaks accessibility; update each <label> to include a matching for attribute
pointing to the corresponding control id (e.g., for="a-title" for the Title
input with id="a-title", for="a-desc" for textarea id="a-desc",
for="a-type"/"a-format"/"a-schedule"/"a-tags" for those selects/inputs, and for
the session form labels to match ids like s-activity, s-title, s-start, s-end,
s-location, s-desc); ensure any error/status paragraphs (act-err, act-ok) are
either referenced via aria-describedby on the form fields where applicable or
given role="status" to be announced by assistive tech.
- Around line 240-245: The code uses esc(preselect) (an HTML-entity escaper)
when building the selector for sel.querySelector, which can produce invalid CSS
selectors for certain inputs; replace esc(preselect) with CSS.escape(preselect)
when constructing the selector passed to document.querySelector/querySelectorAll
(i.e., in the block that reads params, preselect, sel, opt and calls
sel.querySelector('option[value="' + ... + '"]')), ensuring the selector
interpolation uses CSS.escape to safely escape any characters that would break
querySelector.

In `@src/worker.py`:
- Around line 991-993: The except block catching Exception as e currently checks
for the generic "UNIQUE" substring; update it to detect the specific D1
constraint for the enrollments table (e.g., check for "enrollments.activity_id,
enrollments.user_id" or the exact D1 format you observe such as
"enrollments(activity_id, user_id)") inside the except handling in src/worker.py
(the try/except around the enrollment insert). If that specific constraint is
present, return ok(None, "Already joined activity"); otherwise re-raise or
handle the error normally so other UNIQUE violations are not treated as
successful joins.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: alphaonelabs/coderabbit/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: e5fcf3d2-324c-404a-b5be-56f3ea5c0805

📥 Commits

Reviewing files that changed from the base of the PR and between 2b843c2 and fe7916b.

📒 Files selected for processing (5)
  • public/course.html
  • public/index.html
  • public/login.html
  • public/teach.html
  • src/worker.py

Comment thread public/index.html Outdated
Comment thread public/login.html Outdated
Comment thread public/login.html Outdated
Comment thread public/teach.html Outdated
Comment thread public/teach.html Outdated
Comment thread src/worker.py Outdated
@Flashl3opard Flashl3opard force-pushed the fix/xss-tag-inline-onclick branch from 9fecd98 to 52cca0e Compare April 13, 2026 18:24
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.

2 participants