mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-05-15 11:54:07 +00:00
Enhanced Markdown security by using SecurePipeline
This commit is contained in:
parent
685f95245b
commit
57f0f39f4d
@ -96,10 +96,10 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudMarkdown Value="@textContent.Text.RemoveThinkTags().Trim()" Props="Markdown.DefaultConfig" Styling="@this.MarkdownStyling" />
|
||||
<MudMarkdown Value="@this.NormalizeMarkdownForRendering(textContent.Text)" Props="Markdown.DefaultConfig" Styling="@this.MarkdownStyling" MarkdownPipeline="Markdown.SecurePipeline" />
|
||||
@if (textContent.Sources.Count > 0)
|
||||
{
|
||||
<MudMarkdown Value="@textContent.Sources.ToMarkdown()" Props="Markdown.DefaultConfig" Styling="@this.MarkdownStyling" />
|
||||
<MudMarkdown Value="@textContent.Sources.ToMarkdown()" Props="Markdown.DefaultConfig" Styling="@this.MarkdownStyling" MarkdownPipeline="Markdown.SecurePipeline" />
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -135,4 +135,4 @@
|
||||
}
|
||||
}
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudCard>
|
||||
|
||||
@ -10,6 +10,18 @@ namespace AIStudio.Chat;
|
||||
/// </summary>
|
||||
public partial class ContentBlockComponent : MSGComponentBase
|
||||
{
|
||||
private static readonly string[] HTML_TAG_MARKERS =
|
||||
[
|
||||
"<!doctype",
|
||||
"<html",
|
||||
"<head",
|
||||
"<body",
|
||||
"<style",
|
||||
"<script",
|
||||
"<iframe",
|
||||
"<svg",
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// The role of the chat content block.
|
||||
/// </summary>
|
||||
@ -68,18 +80,36 @@ public partial class ContentBlockComponent : MSGComponentBase
|
||||
private RustService RustService { get; init; } = null!;
|
||||
|
||||
private bool HideContent { get; set; }
|
||||
private bool hasRenderHash;
|
||||
private int lastRenderHash;
|
||||
|
||||
#region Overrides of ComponentBase
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
// Register the streaming events:
|
||||
this.Content.StreamingDone = this.AfterStreaming;
|
||||
this.Content.StreamingEvent = () => this.InvokeAsync(this.StateHasChanged);
|
||||
|
||||
this.RegisterStreamingEvents();
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
protected override Task OnParametersSetAsync()
|
||||
{
|
||||
this.RegisterStreamingEvents();
|
||||
return base.OnParametersSetAsync();
|
||||
}
|
||||
|
||||
protected override bool ShouldRender()
|
||||
{
|
||||
var currentRenderHash = this.CreateRenderHash();
|
||||
if (!this.hasRenderHash || currentRenderHash != this.lastRenderHash)
|
||||
{
|
||||
this.lastRenderHash = currentRenderHash;
|
||||
this.hasRenderHash = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets called when the content stream ended.
|
||||
/// </summary>
|
||||
@ -111,6 +141,47 @@ public partial class ContentBlockComponent : MSGComponentBase
|
||||
});
|
||||
}
|
||||
|
||||
private void RegisterStreamingEvents()
|
||||
{
|
||||
this.Content.StreamingDone = this.AfterStreaming;
|
||||
this.Content.StreamingEvent = () => this.InvokeAsync(this.StateHasChanged);
|
||||
}
|
||||
|
||||
private int CreateRenderHash()
|
||||
{
|
||||
var hash = new HashCode();
|
||||
hash.Add(this.Role);
|
||||
hash.Add(this.Type);
|
||||
hash.Add(this.Time);
|
||||
hash.Add(this.Class);
|
||||
hash.Add(this.IsLastContentBlock);
|
||||
hash.Add(this.IsSecondToLastBlock);
|
||||
hash.Add(this.HideContent);
|
||||
hash.Add(this.SettingsManager.IsDarkMode);
|
||||
hash.Add(this.RegenerateEnabled());
|
||||
hash.Add(this.Content.InitialRemoteWait);
|
||||
hash.Add(this.Content.IsStreaming);
|
||||
hash.Add(this.Content.FileAttachments.Count);
|
||||
hash.Add(this.Content.Sources.Count);
|
||||
|
||||
switch (this.Content)
|
||||
{
|
||||
case ContentText text:
|
||||
var textValue = text.Text ?? string.Empty;
|
||||
hash.Add(textValue.Length);
|
||||
hash.Add(textValue.GetHashCode(StringComparison.Ordinal));
|
||||
hash.Add(text.Sources.Count);
|
||||
break;
|
||||
|
||||
case ContentImage image:
|
||||
hash.Add(image.SourceType);
|
||||
hash.Add(image.Source);
|
||||
break;
|
||||
}
|
||||
|
||||
return hash.ToHashCode();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private string CardClasses => $"my-2 rounded-lg {this.Class}";
|
||||
@ -121,6 +192,34 @@ public partial class ContentBlockComponent : MSGComponentBase
|
||||
{
|
||||
CodeBlock = { Theme = this.CodeColorPalette },
|
||||
};
|
||||
|
||||
private string NormalizeMarkdownForRendering(string text)
|
||||
{
|
||||
var cleaned = text.RemoveThinkTags().Trim();
|
||||
if (string.IsNullOrWhiteSpace(cleaned))
|
||||
return string.Empty;
|
||||
|
||||
if (cleaned.Contains("```", StringComparison.Ordinal))
|
||||
return cleaned;
|
||||
|
||||
if (LooksLikeRawHtml(cleaned))
|
||||
return $"```html{Environment.NewLine}{cleaned}{Environment.NewLine}```";
|
||||
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
private static bool LooksLikeRawHtml(string text)
|
||||
{
|
||||
var content = text.TrimStart();
|
||||
if (!content.StartsWith("<", StringComparison.Ordinal))
|
||||
return false;
|
||||
|
||||
foreach (var marker in HTML_TAG_MARKERS)
|
||||
if (content.Contains(marker, StringComparison.OrdinalIgnoreCase))
|
||||
return true;
|
||||
|
||||
return content.Contains("</", StringComparison.Ordinal) || content.Contains("/>", StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private async Task RemoveBlock()
|
||||
{
|
||||
@ -194,4 +293,4 @@ public partial class ContentBlockComponent : MSGComponentBase
|
||||
var result = await ReviewAttachmentsDialog.OpenDialogAsync(this.DialogService, this.Content.FileAttachments.ToHashSet());
|
||||
this.Content.FileAttachments = result.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,4 +6,4 @@
|
||||
}
|
||||
</MudSelect>
|
||||
|
||||
<MudMarkdown Value="@this.LogContent" Props="Markdown.DefaultConfig"/>
|
||||
<MudMarkdown Value="@this.LogContent" Props="Markdown.DefaultConfig" MarkdownPipeline="Markdown.SecurePipeline"/>
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
@if (!block.HideFromUser)
|
||||
{
|
||||
<ContentBlockComponent
|
||||
@key="@block"
|
||||
Role="@block.Role"
|
||||
Type="@block.ContentType"
|
||||
Time="@block.Time"
|
||||
@ -126,4 +127,4 @@
|
||||
<MudIconButton />
|
||||
</MudToolBar>
|
||||
</FooterContent>
|
||||
</InnerScrolling>
|
||||
</InnerScrolling>
|
||||
|
||||
@ -28,7 +28,7 @@
|
||||
<MudText Typo="Typo.h6">
|
||||
@T("Description")
|
||||
</MudText>
|
||||
<MudMarkdown Value="@this.currentConfidence.Description"/>
|
||||
<MudMarkdown Value="@this.currentConfidence.Description" MarkdownPipeline="Markdown.SecurePipeline"/>
|
||||
|
||||
@if (this.currentConfidence.Sources.Count > 0)
|
||||
{
|
||||
@ -67,4 +67,4 @@
|
||||
</MudCardActions>
|
||||
</MudCard>
|
||||
</MudPopover>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -104,7 +104,7 @@
|
||||
@context.ToName()
|
||||
</MudTd>
|
||||
<MudTd>
|
||||
<MudMarkdown Value="@context.GetConfidence(this.SettingsManager).Description"/>
|
||||
<MudMarkdown Value="@context.GetConfidence(this.SettingsManager).Description" MarkdownPipeline="Markdown.SecurePipeline"/>
|
||||
</MudTd>
|
||||
<MudTd Style="vertical-align: top;">
|
||||
<MudMenu StartIcon="@Icons.Material.Filled.Security" EndIcon="@Icons.Material.Filled.KeyboardArrowDown" Label="@this.GetCurrentConfidenceLevelName(context)" Variant="Variant.Filled" Style="@this.SetCurrentConfidenceLevelColorStyle(context)">
|
||||
|
||||
@ -54,7 +54,7 @@
|
||||
Class="ma-2 pe-4"
|
||||
HelperText="@T("This is the content we loaded from your file — including headings, lists, and formatting. Use this to verify your file loads as expected.")">
|
||||
<div style="max-height: 40vh; overflow-y: auto;">
|
||||
<MudMarkdown Value="@this.FileContent" Props="Markdown.DefaultConfig" Styling="@this.MarkdownStyling"/>
|
||||
<MudMarkdown Value="@this.FileContent" Props="Markdown.DefaultConfig" Styling="@this.MarkdownStyling" MarkdownPipeline="Markdown.SecurePipeline"/>
|
||||
</div>
|
||||
</MudField>
|
||||
</MudTabPanel>
|
||||
@ -83,4 +83,4 @@
|
||||
@T("Close")
|
||||
</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
</MudDialog>
|
||||
|
||||
@ -30,7 +30,7 @@
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(this.licenseText))
|
||||
{
|
||||
<MudMarkdown Value="@this.licenseText" Props="Markdown.DefaultConfig"/>
|
||||
<MudMarkdown Value="@this.licenseText" Props="Markdown.DefaultConfig" MarkdownPipeline="Markdown.SecurePipeline"/>
|
||||
}
|
||||
</ExpansionPanel>
|
||||
|
||||
@ -226,4 +226,4 @@
|
||||
}
|
||||
}
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
</MudDialog>
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<MudIcon Icon="@Icons.Material.Filled.Update" Size="Size.Large" Class="mr-3"/>
|
||||
@this.HeaderText
|
||||
</MudText>
|
||||
<MudMarkdown Value="@this.UpdateResponse.Changelog" Props="Markdown.DefaultConfig"/>
|
||||
<MudMarkdown Value="@this.UpdateResponse.Changelog" Props="Markdown.DefaultConfig" MarkdownPipeline="Markdown.SecurePipeline"/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="@this.Cancel" Variant="Variant.Filled">
|
||||
@ -15,4 +15,4 @@
|
||||
@T("Install now")
|
||||
</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
</MudDialog>
|
||||
|
||||
@ -27,7 +27,7 @@
|
||||
</ExpansionPanel>
|
||||
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.EventNote" HeaderText="@T("Last Changelog")">
|
||||
<MudMarkdown Value="@this.LastChangeContent" Props="Markdown.DefaultConfig"/>
|
||||
<MudMarkdown Value="@this.LastChangeContent" Props="Markdown.DefaultConfig" MarkdownPipeline="Markdown.SecurePipeline"/>
|
||||
</ExpansionPanel>
|
||||
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Lightbulb" HeaderText="@T("Vision")">
|
||||
@ -35,9 +35,9 @@
|
||||
</ExpansionPanel>
|
||||
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.RocketLaunch" HeaderText="@T("Quick Start Guide")">
|
||||
<MudMarkdown Props="Markdown.DefaultConfig" Value="@QUICK_START_GUIDE"/>
|
||||
<MudMarkdown Props="Markdown.DefaultConfig" Value="@QUICK_START_GUIDE" MarkdownPipeline="Markdown.SecurePipeline"/>
|
||||
</ExpansionPanel>
|
||||
|
||||
</MudExpansionPanels>
|
||||
</InnerScrolling>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -297,7 +297,7 @@
|
||||
</MudGrid>
|
||||
</ExpansionPanel>
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Verified" HeaderText="License: FSL-1.1-MIT">
|
||||
<MudMarkdown Value="@LICENSE" Props="Markdown.DefaultConfig"/>
|
||||
<MudMarkdown Value="@LICENSE" Props="Markdown.DefaultConfig" MarkdownPipeline="Markdown.SecurePipeline"/>
|
||||
</ExpansionPanel>
|
||||
</MudExpansionPanels>
|
||||
</InnerScrolling>
|
||||
|
||||
@ -1,7 +1,16 @@
|
||||
using Markdig;
|
||||
|
||||
namespace AIStudio.Tools;
|
||||
|
||||
public static class Markdown
|
||||
{
|
||||
private static readonly MarkdownPipeline SAFE_MARKDOWN_PIPELINE = new MarkdownPipelineBuilder()
|
||||
.UseAdvancedExtensions()
|
||||
.DisableHtml()
|
||||
.Build();
|
||||
|
||||
public static MarkdownPipeline SecurePipeline => SAFE_MARKDOWN_PIPELINE;
|
||||
|
||||
public static MudMarkdownProps DefaultConfig => new()
|
||||
{
|
||||
Heading =
|
||||
@ -19,4 +28,4 @@ public static class Markdown
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,4 +2,5 @@
|
||||
- Improved the performance by caching the OS language detection and requesting the user language only once per app start.
|
||||
- Improved the user-language logging by limiting language detection logs to a single entry per app start.
|
||||
- Improved the logbook readability by removing non-readable special characters from log entries.
|
||||
- Improved the logbook reliability by significantly reducing duplicate log entries.
|
||||
- Improved the logbook reliability by significantly reducing duplicate log entries.
|
||||
- Fixed an issue where the app could turn white or appear invisible in certain chats after HTML-like content was shown.
|
||||
Loading…
Reference in New Issue
Block a user