Skip to content

Replaced unsafe innerHTML with safe DOM APIs in popup.js#526

Open
diyasharma12 wants to merge 5 commits intofossasia:mainfrom
diyasharma12:securityScan
Open

Replaced unsafe innerHTML with safe DOM APIs in popup.js#526
diyasharma12 wants to merge 5 commits intofossasia:mainfrom
diyasharma12:securityScan

Conversation

@diyasharma12
Copy link
Copy Markdown
Contributor

@diyasharma12 diyasharma12 commented Apr 8, 2026

📌 Fixes

Fixes #524


📝 Summary of Changes

This PR addresses 18 blocking security findings identified by the sourcery security scan. The original implementation used innerHTML to render API data and user-controlled strings in src/scripts/popup.js, creating potential Cross-Site Scripting (XSS) vulnerabilities.


Changes Made

  • Replaced all flagged innerHTML usages with textContent, createElement, and appendChild.
  • Updated repoDropdown, repoTags, scrumReport, and various button success/loading states.
  • Created clearElementChildren and setButtonIconAndText to standardize safe DOM manipulation.

✅ Checklist

  • I’ve tested my changes locally
  • I’ve added tests (if applicable)
  • I’ve updated documentation (if applicable)
  • My code follows the project’s code style guidelines

Summary by Sourcery

Harden popup UI rendering by replacing unsafe HTML injection with structured DOM updates and centralizing button/icon handling.

Bug Fixes:

  • Eliminate XSS risk in popup UI by removing innerHTML usage for repository dropdowns, repo tags, scrum report messages, and various button states.

Enhancements:

  • Introduce clearElementChildren and button helper utilities to standardize safe DOM manipulation and icon/text rendering across the popup.
  • Refine repository dropdown and tag rendering to use programmatic DOM construction for names, metadata, and empty states.
  • Initialize versioned report configuration storage migration during popup load to prepare for future configuration handling.

@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai bot commented Apr 8, 2026

Reviewer's Guide

Refactors popup.js to eliminate unsafe innerHTML usage by introducing reusable DOM helper functions and replacing string-based HTML construction with explicit DOM node creation for buttons, repo dropdown, repo tags, and scrum report UI, while also adding a one-time reportConfig migration call on DOMContentLoaded.

Sequence diagram for refreshed cache and scrum report update in popup.js

sequenceDiagram
  actor User
  participant PopupUI
  participant RefreshCacheButton
  participant BrowserStorage as BrowserAPI
  participant ScrumReport
  participant RepoState as RepoUIState

  User ->> RefreshCacheButton: click
  RefreshCacheButton ->> RefreshCacheButton: store originalChildren
  RefreshCacheButton ->> RefreshCacheButton: add class loading
  RefreshCacheButton ->> RefreshCacheButton: setButtonIconWithSpanText(spinner, refreshingButton)
  RefreshCacheButton ->> RefreshCacheButton: disabled = true

  RefreshCacheButton ->> BrowserStorage: clear or refresh cache
  BrowserStorage -->> RefreshCacheButton: resolve or reject

  alt cache clear success
    RefreshCacheButton ->> RepoState: reset availableRepos, selectedRepos
    RefreshCacheButton ->> RepoState: updateRepoDisplay()
    RefreshCacheButton ->> ScrumReport: replaceChildren(p cacheClearedMessage)
    RefreshCacheButton ->> RefreshCacheButton: setButtonIconWithSpanText(check, cacheClearedButton)
    RefreshCacheButton ->> RefreshCacheButton: remove class loading
    RefreshCacheButton ->> RefreshCacheButton: schedule timeout 2s
    PopupUI ->> RefreshCacheButton: timeout fires
    RefreshCacheButton ->> RefreshCacheButton: restore originalChildren
    RefreshCacheButton ->> RefreshCacheButton: disabled = false
  else cache clear failure
    RefreshCacheButton ->> RefreshCacheButton: setButtonIconWithSpanText(exclamation, cacheClearFailed)
    RefreshCacheButton ->> RefreshCacheButton: remove class loading
    RefreshCacheButton ->> RefreshCacheButton: schedule timeout 3s
    PopupUI ->> RefreshCacheButton: timeout fires
    RefreshCacheButton ->> RefreshCacheButton: restore originalChildren
    RefreshCacheButton ->> RefreshCacheButton: disabled = false
  end
Loading

Class diagram for new DOM helper functions in popup.js

classDiagram

class PopupDOMHelpers {
  +clearElementChildren(element)
  +setButtonIconAndText(button, iconClasses, text)
  +setButtonIconWithSpanText(button, iconClasses, text)
}

class RepoDropdownUI {
  +filterAndDisplayRepos(query)
}

class RepoTagsUI {
  +updateRepoDisplay()
}

class PlatformDropdownUI {
  +setPlatformDropdown(value)
}

class RefreshCacheButtonUI {
  +onClick()
}

class ScrumReportUI {
  +showCacheClearedMessage()
}

RepoDropdownUI ..> PopupDOMHelpers : uses
RepoTagsUI ..> PopupDOMHelpers : uses
PlatformDropdownUI ..> PopupDOMHelpers : uses
RefreshCacheButtonUI ..> PopupDOMHelpers : uses
ScrumReportUI ..> PopupDOMHelpers : uses
Loading

File-Level Changes

Change Details Files
Replace innerHTML-based button label/icon updates with helper functions that safely construct and replace DOM children.
  • Introduce setButtonIconAndText and setButtonIconWithSpanText helpers to create icons and text/text spans via DOM APIs and replace button contents in one operation
  • Update generate button, copy-report button, platform dropdown control, and refresh-cache button to use the new helpers instead of setting innerHTML strings
  • Preserve and restore original refresh-cache button contents by cloning and reattaching child nodes instead of storing innerHTML
src/scripts/popup.js
Refactor repo dropdown and selected repo tags rendering to use explicit DOM construction instead of HTML string templates.
  • Add clearElementChildren helper to safely clear containers using replaceChildren
  • Rewrite filterAndDisplayRepos to build empty-state, no-results, and repo item entries using createElement, textContent, className, style, dataset, and appendChild instead of innerHTML with interpolated values
  • Rewrite updateRepoDisplay to construct repo tag elements and remove buttons via DOM APIs, then attach click handlers after construction rather than injecting HTML strings
src/scripts/popup.js
Remove remaining innerHTML usage for scrum report status messaging and minor initialization adjustments.
  • Update scrumReport cache-cleared message rendering to create a

    element, set styles and textContent, and replace existing children instead of assigning innerHTML

  • Remove unused global chrome comment and adjust DOMContentLoaded handler to trigger ReportConfigStorage.migrateLegacyIfNeeded with defensive optional chaining and error logging, dropping the previous setupButtonTooltips call
src/scripts/popup.js

Assessment against linked issues

Issue Objective Addressed Explanation
#524 Refactor all listed unsafe innerHTML assignments in src/scripts/popup.js (at/around the reported locations) to use safe DOM APIs such as textContent, createElement, appendChild, and replaceChildren.
#524 Ensure popup.js no longer injects user-controlled data into the DOM using innerHTML/outerHTML/document.write so that the XSS findings from the security scan are resolved.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@github-actions github-actions bot added the javascript Pull requests that update javascript code label Apr 8, 2026
Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 2 issues, and left some high level feedback:

  • The two helpers setButtonIconAndText and setButtonIconWithSpanText are very similar; consider consolidating them (e.g., with an optional wrapper element for the text) to reduce duplication and keep button updates consistent.
  • In the refreshCache click handler, originalChildren are cloned twice (once when captured and again when restoring); you could store a DocumentFragment or reuse the original nodes to avoid the extra cloning and make the restore logic simpler.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The two helpers `setButtonIconAndText` and `setButtonIconWithSpanText` are very similar; consider consolidating them (e.g., with an optional wrapper element for the text) to reduce duplication and keep button updates consistent.
- In the `refreshCache` click handler, `originalChildren` are cloned twice (once when captured and again when restoring); you could store a `DocumentFragment` or reuse the original nodes to avoid the extra cloning and make the restore logic simpler.

## Individual Comments

### Comment 1
<location path="src/scripts/popup.js" line_range="97-102" />
<code_context>
+	}
+}
+
+function setButtonIconAndText(button, iconClasses, text) {
+	if (!button) return;
+	const icon = document.createElement('i');
+	icon.className = iconClasses;
+	const label = document.createTextNode(` ${text || ''}`);
+	button.replaceChildren(icon, label);
+}
+
</code_context>
<issue_to_address>
**suggestion:** Avoid treating valid falsy text values (like '0') as empty in the shared button helpers.

`setButtonIconAndText` (and `setButtonIconWithSpanText`) use `text || ''`, which converts valid falsy values like `0` or `false` to an empty string. If localized strings or counts can be `'0'`, they’ll disappear in the UI. Use nullish coalescing (`text ?? ''`) instead so only `null`/`undefined` are treated as empty.

Suggested implementation:

```javascript
function setButtonIconAndText(button, iconClasses, text) {
	if (!button) return;
	const icon = document.createElement('i');
	icon.className = iconClasses;
	const label = document.createTextNode(` ${text ?? ''}`);
	button.replaceChildren(icon, label);
}

```

There’s another helper mentioned in your comment: `setButtonIconWithSpanText`. To fully apply your suggestion, update that function similarly by replacing any `text || ''` usage with `text ?? ''` so that only `null`/`undefined` result in an empty string, preserving values like `0` or `false`.
</issue_to_address>

### Comment 2
<location path="src/scripts/popup.js" line_range="2022-2028" />
<code_context>

 document.getElementById('refreshCache').addEventListener('click', async function () {
-	const originalText = this.innerHTML;
+	const originalChildren = Array.from(this.childNodes).map((node) => node.cloneNode(true));

 	this.classList.add('loading');
</code_context>
<issue_to_address>
**issue (bug_risk):** Restoring button content via double-cloned children can drop existing event listeners and is more complex than necessary.

You can avoid this by snapshotting the existing nodes instead of cloning them twice, e.g.:

```js
const originalChildren = Array.from(this.childNodes);
// ... later
this.replaceChildren(...originalChildren);
```

This preserves the original nodes (and any attached listeners) and simplifies the implementation.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/scripts/popup.js
Comment thread src/scripts/popup.js Outdated
Copy link
Copy Markdown
Contributor

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 aims to harden the extension popup UI (src/scripts/popup.js) against XSS by replacing innerHTML-based rendering of API/user-controlled content with safer DOM construction patterns.

Changes:

  • Introduces helper utilities (clearElementChildren, setButtonIconAndText, setButtonIconWithSpanText) to standardize safe DOM updates.
  • Refactors repository dropdown/tag rendering and several button/report UI states to use textContent/createElement/replaceChildren instead of innerHTML.
  • Adds a ReportConfigStorage legacy migration call during popup initialization.

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

Comment thread src/scripts/popup.js
Comment thread src/scripts/popup.js Outdated
Comment thread src/scripts/popup.js
Comment thread src/scripts/popup.js
Comment thread src/scripts/popup.js
diyasharma12 and others added 3 commits April 9, 2026 18:12
Copy link
Copy Markdown
Member

@mariobehling mariobehling left a comment

Choose a reason for hiding this comment

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

Please address AI reviews or comment if not relevant.

@github-actions github-actions bot added the core label Apr 11, 2026
@diyasharma12
Copy link
Copy Markdown
Contributor Author

I've completed a full security audit. All mentioned innerHTML usage is removed. The PR now meets all security acceptance criteria.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core javascript Pull requests that update javascript code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Critical: Multiple XSS vulnerabilities due to unsafe innerHTML usage in popup.js

3 participants