Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,8 @@
<None Include="..\..\..\icon.png" Pack="true" PackagePath="icon.png" />
<None Include="README.md" Pack="true" PackagePath="README.md" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="HtmlSanitizer" Version="9.0.892" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
else
{
<div class="f-html-content">
@((MarkupString)Item.Content)
@((MarkupString)_sanitizedContent)
Comment on lines 12 to +13
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.
</div>
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
using Ganss.Xss;

namespace FluentCMS.Web.Plugins.TextHTML;

public partial class TextHTMLViewPlugin
{
private const string CONTENT_TYPE_NAME = nameof(TextHTMLContent);
private TextHTMLContent? Item { get; set; }
private static readonly HtmlSanitizer Sanitizer = new();

private string _sanitizedContent = string.Empty;

private async Task UpdateContent(string content)
{
if (Item is null)
return;

Item.Content = content;
_sanitizedContent = Sanitizer.Sanitize(Item.Content);
Comment on lines 18 to +19
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.
await ApiClient.PluginContent.UpdateAsync(CONTENT_TYPE_NAME, Plugin.Id, Item.Id, Item.ToDictionary());
}

Expand All @@ -27,7 +33,10 @@ protected override async Task OnInitializedAsync()
var response = await ApiClient.PluginContent.GetAllAsync(CONTENT_TYPE_NAME, Plugin.Id);

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;
Comment on lines 35 to +38
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.
}
}
}
}
Loading