Skip to content

Fix stored XSS in TextHTML plugin via HTML sanitization#2406

Draft
Copilot wants to merge 3 commits intodevfrom
copilot/fix-xss-vulnerability-texthtml-plugin
Draft

Fix stored XSS in TextHTML plugin via HTML sanitization#2406
Copilot wants to merge 3 commits intodevfrom
copilot/fix-xss-vulnerability-texthtml-plugin

Conversation

Copy link
Copy Markdown

Copilot AI commented Mar 30, 2026

TextHTMLViewPlugin rendered stored content directly as MarkupString with no sanitization, allowing arbitrary script injection (e.g., <img src=1 onerror=alert('XSS')>) that executed on page load.

Changes

  • FluentCMS.Web.Plugins.TextHTML.csproj — Added HtmlSanitizer (v9.0.892, Ganss.Xss) package dependency.
  • TextHTMLViewPlugin.razor.cs — Added a static HtmlSanitizer instance; compute and cache sanitized output into _sanitizedContent on initial load and on content update — not on every render cycle.
  • TextHTMLViewPlugin.razor — Render _sanitizedContent instead of raw Item.Content.
- @((MarkupString)Item.Content)
+ @((MarkupString)_sanitizedContent)

HtmlSanitizer strips <script> tags and event handler attributes (onerror, onclick, etc.) while preserving valid HTML markup.

Copilot AI and others added 2 commits March 30, 2026 13:45
Agent-Logs-Url: https://github.com/fluentcms/FluentCMS/sessions/3dec0a96-d3c2-4c65-b9ba-866ae059056a

Co-authored-by: pournasserian <24959477+pournasserian@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix XSS vulnerability in TextHTML plugin Fix stored XSS in TextHTML plugin via HTML sanitization Mar 30, 2026
Copilot AI requested a review from pournasserian March 30, 2026 13:52
@pournasserian pournasserian added the bug Something isn't working label Mar 30, 2026
@pournasserian pournasserian added this to the MVP milestone Mar 30, 2026
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 vulnerability in the TextHTMLViewPlugin by sanitizing stored HTML before rendering it as a MarkupString in the Blazor UI.

Changes:

  • Add the HtmlSanitizer (Ganss.Xss) NuGet dependency to the TextHTML plugin project.
  • Sanitize and cache HTML content in TextHTMLViewPlugin on initialization and on updates.
  • Render sanitized HTML (_sanitizedContent) instead of raw Item.Content.

Reviewed changes

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

File Description
src/Frontend/Plugins/FluentCMS.Web.Plugins.TextHTML/FluentCMS.Web.Plugins.TextHTML.csproj Adds the HtmlSanitizer package dependency.
src/Frontend/Plugins/FluentCMS.Web.Plugins.TextHTML/TextHTMLViewPlugin.razor.cs Introduces sanitizer usage and caches sanitized output.
src/Frontend/Plugins/FluentCMS.Web.Plugins.TextHTML/TextHTMLViewPlugin.razor Switches rendering to use the cached sanitized HTML.

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

Comment on lines 18 to +19
Item.Content = content;
_sanitizedContent = Sanitizer.Sanitize(Item.Content);
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

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

UpdateContent sanitizes into _sanitizedContent, but it still persists the unsanitized content via Item.Content/Item.ToDictionary(). That means the stored value remains dangerous for any other rendering paths (including admin preview/InlineEditor) and future code that might render Item.Content directly. Consider sanitizing before assigning to Item.Content/before calling UpdateAsync (or store both raw+sanitized explicitly if you need raw for editing).

Suggested change
Item.Content = content;
_sanitizedContent = Sanitizer.Sanitize(Item.Content);
var sanitized = Sanitizer.Sanitize(content);
Item.Content = sanitized;
_sanitizedContent = sanitized;

Copilot uses AI. Check for mistakes.
Comment on lines 12 to +13
<div class="f-html-content">
@((MarkupString)Item.Content)
@((MarkupString)_sanitizedContent)
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

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

This change sanitizes the non-admin render path, but the admin preview path still passes Item.Content into InlineEditor. InlineEditor initializes by assigning innerHTML = content (see InlineEditor.razor.js), so stored XSS will still execute for admins when previewing/editing. Consider feeding _sanitizedContent to InlineEditor as well (or sanitizing inside InlineEditor before setting innerHTML).

Copilot uses AI. Check for mistakes.
Comment on lines 35 to +38
if (response?.Data != null && response.Data.ToContentList<TextHTMLContent>().Count != 0)
{
Item = response.Data.ToContentList<TextHTMLContent>().FirstOrDefault() ?? default!;
_sanitizedContent = Item is not null ? Sanitizer.Sanitize(Item.Content) : string.Empty;
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

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

response.Data.ToContentList<TextHTMLContent>() is computed multiple times in this block. Consider materializing it once into a local variable (e.g., var items = ...) and then using items.Count/items.FirstOrDefault() to reduce repeated work and simplify the conditionals.

Suggested change
if (response?.Data != null && response.Data.ToContentList<TextHTMLContent>().Count != 0)
{
Item = response.Data.ToContentList<TextHTMLContent>().FirstOrDefault() ?? default!;
_sanitizedContent = Item is not null ? Sanitizer.Sanitize(Item.Content) : string.Empty;
if (response?.Data != null)
{
var items = response.Data.ToContentList<TextHTMLContent>();
if (items.Count != 0)
{
Item = items.FirstOrDefault() ?? default!;
_sanitizedContent = Item is not null ? Sanitizer.Sanitize(Item.Content) : string.Empty;
}

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

XSS vulnerability in TextHTML plugin

3 participants