Skip to content

Security: Remote-code-capable XSS via unsanitized playlist metadata rendered with innerHTML#420

Open
tuanaiseo wants to merge 1 commit into
aandrew-me:mainfrom
tuanaiseo:contribai/fix/security/remote-code-capable-xss-via-unsanitized-
Open

Security: Remote-code-capable XSS via unsanitized playlist metadata rendered with innerHTML#420
tuanaiseo wants to merge 1 commit into
aandrew-me:mainfrom
tuanaiseo:contribai/fix/security/remote-code-capable-xss-via-unsanitized-

Conversation

@tuanaiseo
Copy link
Copy Markdown

@tuanaiseo tuanaiseo commented Apr 21, 2026

Problem

Playlist entry fields (e.g., entry.title, thumbnail URL, entry URL) from yt-dlp JSON are concatenated into HTML and assigned with innerHTML. Metadata from remote sites can include HTML/JS payloads. In this app, renderers have Node integration enabled and context isolation disabled, so DOM XSS can become full RCE.

Severity: critical
File: src/playlist_new.js

Solution

Never inject untrusted content with innerHTML. Build DOM nodes with createElement, assign textContent/setAttribute after strict validation, and sanitize URLs. Additionally harden Electron (disable Node integration, enable context isolation).

Changes

  • src/playlist_new.js (modified)

Testing

  • Existing tests pass
  • Manual review completed
  • No new warnings/errors introduced

Summary by CodeRabbit

  • Bug Fixes
    • Enhanced security for playlist items through URL protocol validation and improved text rendering methods that prevent HTML injection attacks.
    • Thumbnail URLs are now validated to restrict loading to HTTP and HTTPS protocols only, preventing unsafe or malicious content from displaying.
    • Error messages now display using safer rendering techniques.

…ed playlist m

Playlist entry fields (e.g., `entry.title`, thumbnail URL, entry URL) from yt-dlp JSON are concatenated into HTML and assigned with `innerHTML`. Metadata from remote sites can include HTML/JS payloads. In this app, renderers have Node integration enabled and context isolation disabled, so DOM XSS can become full RCE.

Affected files: playlist_new.js

Signed-off-by: tuanaiseo <221258316+tuanaiseo@users.noreply.github.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 21, 2026

Walkthrough

The pull request adds URL sanitization and refactors playlist item rendering in src/playlist_new.js from HTML string concatenation to DOM node construction. Error message display shifts from innerHTML to textContent, and thumbnail URLs are validated before assignment. Item properties are set via DOM attributes rather than HTML interpolation.

Changes

Cohort / File(s) Summary
URL Sanitization
src/playlist_new.js
Added sanitizeHttpUrl() function that validates URLs to permit only http: or https: protocols, preventing injection of arbitrary schemes.
Error Handling
src/playlist_new.js
Replaced errorDetails.innerHTML with errorDetails.textContent in both execution error and non-playlist error paths, eliminating HTML injection risk for error display.
DOM Construction & Rendering
src/playlist_new.js
Refactored playlist item rendering from building HTML strings assigned via innerHTML to constructing actual DOM elements via document.createDocumentFragment() and document.createElement(), then appending fragments to the container.
Attribute Assignment
src/playlist_new.js
Changed playlist item properties from HTML interpolation to safe attribute assignment: thumbnail URLs are conditionally set after sanitization, and text/values are assigned via .textContent, .value, and .id properties.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
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 (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main security issue addressed in the changeset: an XSS vulnerability from unsanitized playlist metadata rendered via innerHTML, which is the primary focus of all changes in the PR.
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

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/playlist_new.js (2)

86-148: ⚠️ Potential issue | 🟡 Minor

DOM construction approach is correct and closes the XSS sink.

All untrusted fields (entry.title, entry.url, entry.thumbnails[3].url) are now assigned via textContent, .value (property, not attribute), or a sanitized src, so no value reaches an HTML parser. Clearing data via data.textContent = "" before appending the fragment and batching appends into a DocumentFragment are both good choices.

Two minor, non-blocking observations in this block:

  • Line 92: console.log(entry) is a debug artifact and logs full remote metadata per render — consider removing before merge.
  • Line 115: image.alt = "No thumbnail"; is hardcoded, whereas surrounding user-facing strings go through i18n.__(). Consider image.alt = i18n.__("No thumbnail"); for consistency.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/playlist_new.js` around lines 86 - 148, Remove the debug console output
and internationalize the hardcoded alt text: delete the console.log(entry) call
inside the parsed.entries.forEach loop and replace the image.alt = "No
thumbnail"; assignment with image.alt = i18n.__("No thumbnail"); (ensure i18n is
in scope). These changes touch the forEach handler that processes entry objects
and the image element creation code where console.log(entry) and image.alt are
set.

1-229: ⚠️ Potential issue | 🟠 Major

PR description vs. diff mismatch: Electron hardening not implemented.

The PR description states the solution hardens Electron by setting nodeIntegration: false and contextIsolation: true. However, main.js still has both set to their unsafe defaults (nodeIntegration: true, contextIsolation: false), and no changes to the BrowserWindow webPreferences appear in this diff.

Given the stated threat model (DOM XSS escalates to RCE due to Node integration being enabled and context isolation being disabled), the renderer-side sanitization in src/playlist_new.js alone is only defense-in-depth. The actual RCE mitigation requires the BrowserWindow hardening. Please either:

  1. Include the required webPreferences changes in main.js, or
  2. Update the PR description to accurately reflect what this change addresses (preventing DOM injection in the renderer, not eliminating the RCE risk).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/playlist_new.js` around lines 1 - 229, The PR claims Electron hardening
(nodeIntegration: false, contextIsolation: true) but the diff only changes
renderer code (functions like sanitizeHttpUrl and pasteLink in playlist_new.js)
and does not update the BrowserWindow creation in main.js; either modify main.js
to set webPreferences on the BrowserWindow (set nodeIntegration: false and
contextIsolation: true) so the stated RCE mitigation is actually implemented, or
update the PR description to state this change is defense-in-depth for renderer
DOM sanitization only (mentioning sanitizeHttpUrl/pasteLink) and does not remove
the elevated-risk configuration in BrowserWindow (BrowserWindow, webPreferences,
nodeIntegration, contextIsolation).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/playlist_new.js`:
- Line 80: Multiple sibling locations still assign untrusted URL values into
innerHTML (e.g., errorDetails.innerHTML, this.ui.errorDetails.innerHTML) causing
DOM XSS; change those assignments to use textContent (or properly
escape/sanitize the interpolated values) instead of innerHTML so the URL/url
variables are not interpreted as HTML. Locate the occurrences that use
errorDetails.innerHTML and this.ui.errorDetails.innerHTML and replace the HTML
interpolation with plain text assignments (or call a safe-escape helper) to
ensure the untrusted this.state.url / url values are rendered as text.

---

Outside diff comments:
In `@src/playlist_new.js`:
- Around line 86-148: Remove the debug console output and internationalize the
hardcoded alt text: delete the console.log(entry) call inside the
parsed.entries.forEach loop and replace the image.alt = "No thumbnail";
assignment with image.alt = i18n.__("No thumbnail"); (ensure i18n is in scope).
These changes touch the forEach handler that processes entry objects and the
image element creation code where console.log(entry) and image.alt are set.
- Around line 1-229: The PR claims Electron hardening (nodeIntegration: false,
contextIsolation: true) but the diff only changes renderer code (functions like
sanitizeHttpUrl and pasteLink in playlist_new.js) and does not update the
BrowserWindow creation in main.js; either modify main.js to set webPreferences
on the BrowserWindow (set nodeIntegration: false and contextIsolation: true) so
the stated RCE mitigation is actually implemented, or update the PR description
to state this change is defense-in-depth for renderer DOM sanitization only
(mentioning sanitizeHttpUrl/pasteLink) and does not remove the elevated-risk
configuration in BrowserWindow (BrowserWindow, webPreferences, nodeIntegration,
contextIsolation).
🪄 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 UI

Review profile: CHILL

Plan: Pro

Run ID: 9d781b14-a293-4369-baeb-4a355aac17c8

📥 Commits

Reviewing files that changed from the base of the PR and between 72cb49d and 94ac05b.

📒 Files selected for processing (1)
  • src/playlist_new.js

Comment thread src/playlist_new.js
<br><br>
${error}
`;
getId("errorDetails").textContent = `URL: ${clipboardText}\n\n${error}`;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm the innerHTML-with-URL sink still exists in the sibling files and find any other instances.
rg -nP --type=js -C2 'innerHTML\s*=\s*`[^`]*\$\{[^}]*\b(url|URL)\b'
rg -nP --type=js -C2 '\.innerHTML\s*=' -g '!**/node_modules/**'

Repository: aandrew-me/ytDownloader

Length of output: 3730


Sibling files still contain the same DOM XSS vulnerability.

This PR fixes the vulnerability in src/playlist_new.js by switching from innerHTML to textContent, but the identical vulnerable pattern remains in:

  • src/renderer.js:1557: errorDetails.innerHTML = \URL: ${url}...``
  • src/playlist.js:584–586: this.ui.errorDetails.innerHTML = \URL: ${this.state.url}...``

Both files directly interpolate untrusted URL/url variables into HTML. If the PR treats this as critical (DOM XSS → RCE via Node integration), the fix should extend to these sibling locations or be tracked as a follow-up issue linked to this PR.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/playlist_new.js` at line 80, Multiple sibling locations still assign
untrusted URL values into innerHTML (e.g., errorDetails.innerHTML,
this.ui.errorDetails.innerHTML) causing DOM XSS; change those assignments to use
textContent (or properly escape/sanitize the interpolated values) instead of
innerHTML so the URL/url variables are not interpreted as HTML. Locate the
occurrences that use errorDetails.innerHTML and this.ui.errorDetails.innerHTML
and replace the HTML interpolation with plain text assignments (or call a
safe-escape helper) to ensure the untrusted this.state.url / url values are
rendered as text.

@aandrew-me
Copy link
Copy Markdown
Owner

playlist_new.js file isn't even used in the app

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