Added a prompt injection detection (#857)

Co-authored-by: Thorsten Sommer <SommerEngineering@users.noreply.github.com>
This commit is contained in:
Sabrina-devopsandThorsten Sommer authored and GitHub committed 2026-08-23 11:09:11 +02:00
1 parent d3163badd9
commit 902a01a4d0
55 files changed
+5598 -70

No files matched your search

@@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Components;
using SharedTools;
using DialogOptions = AIStudio.Dialogs.DialogOptions;
using AIStudio.Tools.Security;
namespace AIStudio.Assistants.DocumentAnalysis;
@@ -704,6 +705,13 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<NoSettingsPan
""");
}
//
// One report for the whole batch: analysing twenty documents must produce one dialog
// listing all of them, not twenty dialogs in a row.
//
var guardService = Program.SERVICE_PROVIDER.GetRequiredService<PromptInjectionGuardService>();
await using var promptInjectionScope = guardService.BeginAction();
var numDocuments = 1;
foreach (var document in documents)
{
@@ -4069,6 +4069,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1471315821"]
-- AI Studio cannot install updates into its current installation location. Install new versions yourself.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T14786838"] = "AI Studio cannot install updates into its current installation location. Install new versions yourself."
-- A dialog lists what was removed and explains the attack pattern
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T148008546"] = "A dialog lists what was removed and explains the attack pattern"
-- Select the desired behavior for the navigation bar.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1555038969"] = "Select the desired behavior for the navigation bar."
@@ -4102,6 +4105,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1907446663"]
-- Your organization has disabled update checks and installations.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1909339369"] = "Your organization has disabled update checks and installations."
-- Shows a dialog listing the removed passages, together with an explanation and an external reference.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2005319120"] = "Shows a dialog listing the removed passages, together with an explanation and an external reference."
-- AI Studio cannot install updates when running as a Flatpak. Update it using the Flatpak source or bundle from which you installed it.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2009652585"] = "AI Studio cannot install updates when running as a Flatpak. Update it using the Flatpak source or bundle from which you installed it."
@@ -4189,9 +4195,15 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T4004501229"]
-- When enabled, spellchecking will be active in all input fields. Depending on your operating system, errors may not be visually highlighted, but right-clicking may still offer possible corrections.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T4067492921"] = "When enabled, spellchecking will be active in all input fields. Depending on your operating system, errors may not be visually highlighted, but right-clicking may still offer possible corrections."
-- Show details when suspicious content was removed?
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T4156872850"] = "Show details when suspicious content was removed?"
-- Select a transcription provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T4174666315"] = "Select a transcription provider"
-- Only a short notification is shown
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T4191930078"] = "Only a short notification is shown"
-- How long AI Studio waits for external HTTP requests, such as AI providers, embeddings, transcription, ERI data sources, and enterprise configuration downloads.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T4192032183"] = "How long AI Studio waits for external HTTP requests, such as AI providers, embeddings, transcription, ERI data sources, and enterprise configuration downloads."
@@ -6106,6 +6118,42 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINGGUIDELINEDIALOG::T384594633"] = "Th
-- Prompting Guideline
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINGGUIDELINEDIALOG::T4250996615"] = "Prompting Guideline"
-- AI Studio found instructions aimed at the AI inside your content and removed them. Everything around them was kept, so you can continue working with the content. Please review what was removed below.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T1100148261"] = "AI Studio found instructions aimed at the AI inside your content and removed them. Everything around them was kept, so you can continue working with the content. Please review what was removed below."
-- Content source
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T1129278507"] = "Content source"
-- Close and don't show again
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T1384522605"] = "Close and don't show again"
-- And {0} more passages of the same kind.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T2039738154"] = "And {0} more passages of the same kind."
-- Source type
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T280442848"] = "Source type"
-- Prompt injection is a method used to manipulate AI systems such as chatbots. An attacker places misleading instructions in content so that the AI treats them as legitimate. This can cause the AI to ignore safeguards, expose private information, or generate harmful content.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T3122726298"] = "Prompt injection is a method used to manipulate AI systems such as chatbots. An attacker places misleading instructions in content so that the AI treats them as legitimate. This can cause the AI to ignore safeguards, expose private information, or generate harmful content."
-- Close
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T3448155331"] = "Close"
-- Removed content
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T3549539878"] = "Removed content"
-- Typical attacks on AI systems (e.g. prompt injection) hide instructions within untrusted content to trick an AI model into ignoring its intended rules or performing unintended actions.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T4221674400"] = "Typical attacks on AI systems (e.g. prompt injection) hide instructions within untrusted content to trick an AI model into ignoring its intended rules or performing unintended actions."
-- More information
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T475337262"] = "More information"
-- Hide more information
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T808738984"] = "Hide more information"
-- Suspicious content was removed
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T871282530"] = "Suspicious content was removed"
-- Hugging Face Inference Provider
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1085481431"] = "Hugging Face Inference Provider"
@@ -7936,6 +7984,9 @@ UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T2979224202"] = "Writer"
-- Show details
UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T3692372066"] = "Show details"
-- Security notice
UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T4004397997"] = "Security notice"
-- Information
UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T4256323669"] = "Information"
@@ -8341,6 +8392,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2160280545"] = "This library is
-- For the secure communication between the user interface and the runtime, we need to create certificates. This Rust library is great for this purpose.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2174764529"] = "For the secure communication between the user interface and the runtime, we need to create certificates. This Rust library is great for this purpose."
-- The regex crate detects structural and obfuscated prompt-injection patterns in untrusted document content. Its linear-time matching without backtracking keeps these scans predictable, even for hostile input.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2196053547"] = "The regex crate detects structural and obfuscated prompt-injection patterns in untrusted document content. Its linear-time matching without backtracking keeps these scans predictable, even for hostile input."
-- OK
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2246359087"] = "OK"
@@ -8476,6 +8530,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2929232062"] = "Copies the confi
-- Copies the root certificate fingerprint to the clipboard
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2989678330"] = "Copies the root certificate fingerprint to the clipboard"
-- The toml crate parses the embedded prompt-injection phrase catalog when the runtime starts. This keeps the detection rules separate from the Rust implementation and easier to maintain.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2999154325"] = "The toml crate parses the embedded prompt-injection phrase catalog when the runtime starts. This keeps the detection rules separate from the Rust implementation and easier to maintain."
-- This library identifies files by their content. It is used for document streaming and as the first safety and media classification step before local audio processing.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3002755581"] = "This library identifies files by their content. It is used for document streaming and as the first safety and media classification step before local audio processing."
@@ -8545,6 +8602,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3433065373"] = "Information abou
-- Used Rust compiler
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3440211747"] = "Used Rust compiler"
-- The aho-corasick crate searches the fixed catalog of prompt-injection phrases in one pass, allowing AI Studio to scan large documents efficiently. We thank Alfred V. Aho and Margaret J. Corasick for publishing the algorithm in 1975, and Andrew Gallant and the Open Source Community for bringing it to Rust.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3444344506"] = "The aho-corasick crate searches the fixed catalog of prompt-injection phrases in one pass, allowing AI Studio to scan large documents efficiently. We thank Alfred V. Aho and Margaret J. Corasick for publishing the algorithm in 1975, and Andrew Gallant and the Open Source Community for bringing it to Rust."
-- AI Studio runs with an enterprise configuration using configuration plugins, without central configuration management.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3449345633"] = "AI Studio runs with an enterprise configuration using configuration plugins, without central configuration management."
@@ -10324,6 +10384,63 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T4165204724"] = "Document"
-- Plugin archive
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T927001356"] = "Plugin archive"
-- Attempt to override instructions
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONFINDINGCATEGORYEXTENSIONS::T161976090"] = "Attempt to override instructions"
-- Attempt to expose protected data
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONFINDINGCATEGORYEXTENSIONS::T2050274293"] = "Attempt to expose protected data"
-- Attempt to bypass safeguards
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONFINDINGCATEGORYEXTENSIONS::T2260642992"] = "Attempt to bypass safeguards"
-- Attempt to change the AI's role
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONFINDINGCATEGORYEXTENSIONS::T2340508370"] = "Attempt to change the AI's role"
-- Hidden instructions using markup
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONFINDINGCATEGORYEXTENSIONS::T2526538070"] = "Hidden instructions using markup"
-- Hidden instructions using delimiters
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONFINDINGCATEGORYEXTENSIONS::T329656456"] = "Hidden instructions using delimiters"
-- Unknown
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONFINDINGCATEGORYEXTENSIONS::T3424652889"] = "Unknown"
-- Attempt to manipulate an agent
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONFINDINGCATEGORYEXTENSIONS::T355252317"] = "Attempt to manipulate an agent"
-- Persistent or delayed instruction
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONFINDINGCATEGORYEXTENSIONS::T4169123215"] = "Persistent or delayed instruction"
-- Hidden instructions using encoding
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONFINDINGCATEGORYEXTENSIONS::T49495195"] = "Hidden instructions using encoding"
-- Obfuscated instruction
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONFINDINGCATEGORYEXTENSIONS::T87316699"] = "Obfuscated instruction"
-- AI Studio could not check '{0}' for prompt injections. The content is used as it is.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONGUARDSERVICE::T1026469976"] = "AI Studio could not check '{0}' for prompt injections. The content is used as it is."
-- AI Studio removed suspicious instructions from '{0}' before using it.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONGUARDSERVICE::T2460486335"] = "AI Studio removed suspicious instructions from '{0}' before using it."
-- AI Studio removed suspicious instructions from {0} sources before using them.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONGUARDSERVICE::T3489536228"] = "AI Studio removed suspicious instructions from {0} sources before using them."
-- Chat attachment
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONSOURCEKINDEXTENSIONS::T1071345316"] = "Chat attachment"
-- Web content
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONSOURCEKINDEXTENSIONS::T2626468388"] = "Web content"
-- Retrieved context
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONSOURCEKINDEXTENSIONS::T3347144620"] = "Retrieved context"
-- Unknown
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONSOURCEKINDEXTENSIONS::T3424652889"] = "Unknown"
-- File content
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONSOURCEKINDEXTENSIONS::T3788064862"] = "File content"
-- The Assistant Builder context could not be loaded.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGINGENERATIONSERVICE::T108292972"] = "The Assistant Builder context could not be loaded."
@@ -2,6 +2,7 @@
using AIStudio.Chat;
using AIStudio.Dialogs.Settings;
using AIStudio.Tools.AssistantSessions;
using AIStudio.Tools.Security;
namespace AIStudio.Assistants.SlideBuilder;
@@ -373,6 +374,13 @@ public partial class SlideAssistant : AssistantBaseCore<SettingsDialogSlideBuild
""");
}
//
// One report for the whole batch: reading twenty documents must produce one dialog
// listing all of them, not twenty dialogs in a row.
//
var guardService = Program.SERVICE_PROVIDER.GetRequiredService<PromptInjectionGuardService>();
await using var promptInjectionScope = guardService.BeginAction();
var numDocuments = 1;
foreach (var document in documents)
{
@@ -6,6 +6,7 @@ using AIStudio.Settings;
using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.RAG.RAGProcesses;
using AIStudio.Tools.Rust;
using AIStudio.Tools.Security;
namespace AIStudio.Chat;
@@ -300,6 +301,13 @@ public sealed class ContentText : IContent
LOGGER.LogWarning("File attachments which need Pandoc could not be processed because the Pandoc version check failed.");
}
//
// One report for the whole batch: attaching twenty documents must produce one
// dialog listing all of them, not twenty dialogs in a row.
//
var guardService = Program.SERVICE_PROVIDER.GetRequiredService<PromptInjectionGuardService>();
await using var promptInjectionScope = guardService.BeginAction();
//
// The document blocks are collected separately, so we only announce attached
// files when at least one of them could actually be read. Announcing files we
@@ -1,5 +1,6 @@
using AIStudio.Agents;
using AIStudio.Chat;
using AIStudio.Tools.Security;
using Microsoft.AspNetCore.Components;
@@ -13,6 +14,9 @@ public partial class ReadWebContent : MSGComponentBase
[Inject]
private AgentTextContentCleaner AgentTextContentCleaner { get; init; } = null!;
[Inject]
private PromptInjectionGuardService PromptInjectionGuardService { get; init; } = null!;
[Parameter]
public string Content { get; set; } = string.Empty;
@@ -87,6 +91,7 @@ public partial class ReadWebContent : MSGComponentBase
this.processStep = this.process[ReadWebContentSteps.PARSING];
this.StateHasChanged();
markdown = this.HTMLParser.ParseToMarkdown(html);
markdown = await this.PromptInjectionGuardService.SanitizeAsync(markdown, PromptInjectionSource.WebContent(this.providedURL));
if (this.PreselectContentCleanerAgent && this.providerSettings != AIStudio.Settings.Provider.NONE)
{
@@ -14,6 +14,7 @@
<ConfigurationSelect OptionDescription="@T("Color theme")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.PreferredTheme)" Data="@ConfigurationSelectDataFactory.GetThemesData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.PreferredTheme = selectedValue)" OptionHelp="@T("Choose the color theme that best suits for you.")"/>
<ConfigurationOption OptionDescription="@T("Save energy?")" LabelOn="@T("Energy saving is enabled")" LabelOff="@T("Energy saving is disabled")" State="@(() => this.SettingsManager.ConfigurationData.App.IsSavingEnergy)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.App.IsSavingEnergy = updatedState)" OptionHelp="@T("When enabled, streamed content from the AI is updated once every third second. When disabled, streamed content will be updated as soon as it is available.")"/>
<ConfigurationOption OptionDescription="@T("Enable spellchecking?")" LabelOn="@T("Spellchecking is enabled")" LabelOff="@T("Spellchecking is disabled")" State="@(() => this.SettingsManager.ConfigurationData.App.EnableSpellchecking)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.App.EnableSpellchecking = updatedState)" OptionHelp="@T("When enabled, spellchecking will be active in all input fields. Depending on your operating system, errors may not be visually highlighted, but right-clicking may still offer possible corrections.")"/>
<ConfigurationOption OptionDescription="@T("Show details when suspicious content was removed?")" LabelOn="@T("A dialog lists what was removed and explains the attack pattern")" LabelOff="@T("Only a short notification is shown")" State="@(() => this.SettingsManager.ConfigurationData.App.ShowPromptInjectionAlert)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.App.ShowPromptInjectionAlert = updatedState)" OptionHelp="@T("Shows a dialog listing the removed passages, together with an explanation and an external reference.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.App, x => x.ShowPromptInjectionAlert, out var meta) && meta.IsLocked"/>
<ConfigurationSlider T="int" OptionDescription="@T("Request timeout")" Min="@ExternalHttpClientTimeout.MIN_HTTP_CLIENT_TIMEOUT_SECONDS" Max="@ExternalHttpClientTimeout.MAX_HTTP_CLIENT_TIMEOUT_SECONDS" Step="60" Unit="@T("seconds")" Value="@(() => this.SettingsManager.ConfigurationData.App.HttpClientTimeoutSeconds)" ValueUpdate="@(updatedValue => this.SettingsManager.ConfigurationData.App.HttpClientTimeoutSeconds = updatedValue)" OptionHelp="@T("How long AI Studio waits for external HTTP requests, such as AI providers, embeddings, transcription, ERI data sources, and enterprise configuration downloads.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.App, x => x.HttpClientTimeoutSeconds, out var meta) && meta.IsLocked"/>
<ConfigurationSelect OptionDescription="@T("Check for updates")" SelectedValue="@(() => this.DisplayedUpdateInterval)" Data="@ConfigurationSelectDataFactory.GetManagedUpdateIntervalData(this.DisplayedUpdateInterval)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.UpdateInterval = selectedValue)" OptionHelp="@this.UpdateIntervalHelp" IsLocked="@this.IsUpdateIntervalLocked"/>
<ConfigurationSelect OptionDescription="@T("Update installation method")" SelectedValue="@(() => this.DisplayedUpdateInstallation)" Data="@ConfigurationSelectDataFactory.GetUpdateBehaviourData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.UpdateInstallation = selectedValue)" OptionHelp="@this.UpdateInstallationHelp" IsLocked="@this.IsUpdateInstallationLocked"/>
@@ -0,0 +1,128 @@
@using AIStudio.Tools.Security
@inherits MSGComponentBase
<MudDialog>
<DialogContent>
<MudPaper Class="pa-6 mb-4" Elevation="0" Outlined="true">
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="3">
<MudAvatar Size="Size.Large" Color="Color.Warning" Variant="Variant.Filled">
<MudIcon Icon="@Icons.Material.Filled.GppMaybe"/>
</MudAvatar>
<MudStack Spacing="0">
<MudJustifiedText Typo="Typo.h5">
@T("Suspicious content was removed")
</MudJustifiedText>
<MudJustifiedText Typo="Typo.body2">
@T("AI Studio found instructions aimed at the AI inside your content and removed them. Everything around them was kept, so you can continue working with the content. Please review what was removed below.")
</MudJustifiedText>
</MudStack>
</MudStack>
<MudAlert Severity="Severity.Warning" Variant="Variant.Outlined" Class="mt-4">
<MudJustifiedText Typo="Typo.body2">
@T("Typical attacks on AI systems (e.g. prompt injection) hide instructions within untrusted content to trick an AI model into ignoring its intended rules or performing unintended actions.")
</MudJustifiedText>
</MudAlert>
</MudPaper>
<MudDivider />
<MudStack Row="true" Justify="Justify.Center" Class="my-2">
<MudButton Variant="Variant.Text"
EndIcon="@(this.showPromptInjectionInformation ? Icons.Material.Filled.ExpandLess : Icons.Material.Filled.ExpandMore)"
OnClick="@this.TogglePromptInjectionInformation">
@(this.showPromptInjectionInformation ? T("Hide more information") : T("More information"))
</MudButton>
</MudStack>
<MudCollapse Expanded="@this.showPromptInjectionInformation">
<MudPaper Outlined="true"
Class="pa-4 mb-4">
@foreach (var result in this.Alert.Results)
{
<MudGrid Class="mb-2">
<MudItem xs="12" md="3">
<MudStack>
<MudIcon Icon="@Icons.Material.Filled.Source" />
<MudJustifiedText Typo="Typo.subtitle2">
@T("Source type")
</MudJustifiedText>
<MudJustifiedText Typo="Typo.body2">
@result.Source.Kind.GetDisplayName()
</MudJustifiedText>
</MudStack>
</MudItem>
<MudItem xs="12" md="4">
<MudStack>
<MudIcon Icon="@Icons.Material.Outlined.Description"/>
<MudJustifiedText Typo="Typo.subtitle2">
@T("Content source")
</MudJustifiedText>
<MudJustifiedText Typo="Typo.body2" Style="word-break:break-all;">
@result.Source.Label
</MudJustifiedText>
</MudStack>
</MudItem>
<MudItem xs="12" md="5">
<MudStack>
<MudIcon Icon="@Icons.Material.Outlined.WarningAmber"/>
<MudJustifiedText Typo="Typo.subtitle2">
@T("Removed content")
</MudJustifiedText>
@foreach (var finding in result.Findings)
{
<MudStack Spacing="0">
<MudJustifiedText Typo="Typo.body2">
<b>
@finding.Category.GetDisplayName()
</b>
</MudJustifiedText>
<MudJustifiedText Typo="Typo.body2" Class="ml-4 mt-1">
@finding.Snippet
</MudJustifiedText>
</MudStack>
}
@* The runtime caps how many passages it describes, while it removes every one of them. *@
@if (result.RedactedCount > result.Findings.Count)
{
<MudJustifiedText Typo="Typo.body2" Class="mt-1">
@string.Format(T("And {0} more passages of the same kind."), result.RedactedCount - result.Findings.Count)
</MudJustifiedText>
}
</MudStack>
</MudItem>
</MudGrid>
}
</MudPaper>
<MudPaper Class="pa-4 mt-2" Outlined="true">
<MudJustifiedText Typo="Typo.body2" Class="mb-3">
@T("Prompt injection is a method used to manipulate AI systems such as chatbots. An attacker places misleading instructions in content so that the AI treats them as legitimate. This can cause the AI to ignore safeguards, expose private information, or generate harmful content.")
</MudJustifiedText>
<MudLink Href="@PromptInjectionGuardService.WIKI_URL" Target="_blank">
@PromptInjectionGuardService.WIKI_URL
</MudLink>
</MudPaper>
</MudCollapse>
</DialogContent>
<DialogActions>
@if (CanDisableFutureAlerts)
{
<MudButton Variant="Variant.Text" Color="Color.Default" OnClick="@this.CloseAndDisableFutureAlertsAsync">
@T("Close and don't show again")
</MudButton>
}
<MudButton Variant="Variant.Filled" Color="Color.Default" OnClick="@this.Close">
@T("Close")
</MudButton>
</DialogActions>
</MudDialog>
@@ -0,0 +1,39 @@
using AIStudio.Components;
using AIStudio.Settings;
using AIStudio.Tools.Security;
using Microsoft.AspNetCore.Components;
namespace AIStudio.Dialogs;
public partial class PromptInjectionAlertDialog : MSGComponentBase
{
private bool showPromptInjectionInformation;
private static bool CanDisableFutureAlerts => !ManagedConfiguration.TryGet(x => x.App, x => x.ShowPromptInjectionAlert, out var meta) || !meta.IsLocked;
[CascadingParameter]
private IMudDialogInstance MudDialog { get; set; } = null!;
/// <summary>
/// What was filtered during the user action that triggered this dialog.
/// </summary>
/// <remarks>
/// Carries every affected source, because one action may involve many documents and the
/// user should acknowledge them together rather than one dialog at a time.
/// </remarks>
[Parameter, EditorRequired]
public PromptInjectionAlertMessage Alert { get; set; } = null!;
private void Close() => this.MudDialog.Close();
private async Task CloseAndDisableFutureAlertsAsync()
{
this.SettingsManager.ConfigurationData.App.ShowPromptInjectionAlert = false;
await this.SettingsManager.StoreSettings();
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
this.MudDialog.Close();
}
private void TogglePromptInjectionInformation() => this.showPromptInjectionInformation = !this.showPromptInjectionInformation;
}
@@ -14,7 +14,6 @@
<ConfigurationOption OptionDescription="@T("Show the latest message after loading?")" LabelOn="@T("Latest message is shown, after loading a chat")" LabelOff="@T("First (oldest) message is shown, after loading a chat")" State="@(() => this.SettingsManager.ConfigurationData.Chat.ShowLatestMessageAfterLoading)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.Chat.ShowLatestMessageAfterLoading = updatedState)" OptionHelp="@T("When enabled, the latest message is shown after loading a chat. When disabled, the first (oldest) message is shown.")"/>
<ConfigurationSelect OptionDescription="@T("Provider selection when creating new chats")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Chat.AddChatProviderBehavior)" Data="@ConfigurationSelectDataFactory.GetAddChatProviderBehavior()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Chat.AddChatProviderBehavior = selectedValue)" OptionHelp="@T("Control how the LLM provider for added chats is selected.")"/>
<ConfigurationSelect OptionDescription="@T("Provider selection when loading a chat and sending assistant results to chat")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Chat.LoadingProviderBehavior)" Data="@ConfigurationSelectDataFactory.GetLoadingChatProviderBehavior()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Chat.LoadingProviderBehavior = selectedValue)" OptionHelp="@T("Control how the LLM provider for loaded chats is selected and when assistant results are sent to chat.")"/>
<MudPaper Class="pa-3 mb-8 border-dashed border rounded-lg">
<ConfigurationOption OptionDescription="@T("Preselect chat options?")" LabelOn="@T("Chat options are preselected")" LabelOff="@T("No chat options are preselected")" State="@(() => this.SettingsManager.ConfigurationData.Chat.PreselectOptions)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.Chat.PreselectOptions = updatedState)" OptionHelp="@T("When enabled, you can preselect chat options. This is might be useful when you prefer a specific provider.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.Chat, x => x.PreselectOptions, out var meta) && meta.IsLocked"/>
<ConfigurationProviderSelection Component="Components.CHAT" Data="@this.AvailableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.Chat.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Chat.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Chat.PreselectedProvider = selectedValue)" IsLocked="() => ManagedConfiguration.TryGet(x => x.Chat, x => x.PreselectedProvider, out var meta) && meta.IsLocked"/>
@@ -5,6 +5,7 @@ using AIStudio.Tools.AIJobs;
using AIStudio.Tools.AssistantSessions;
using AIStudio.Tools.Media;
using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.Security;
using AIStudio.Tools.Rust;
using AIStudio.Tools.Services;
@@ -71,6 +72,7 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
private bool startupCompleted;
private bool settingsWriteProtectionWarningShown;
private readonly SemaphoreSlim mandatoryInfoDialogSemaphore = new(1, 1);
private readonly SemaphoreSlim promptInjectionDialogSemaphore = new(1, 1);
private IReadOnlyCollection<NavBarItem> navItems = [];
@@ -112,7 +114,7 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
this.MessageBus.ApplyFilters(this, [],
[
Event.UPDATE_AVAILABLE, Event.CONFIGURATION_CHANGED, Event.COLOR_THEME_CHANGED, Event.SHOW_ERROR,
Event.SHOW_WARNING, Event.SHOW_SUCCESS, Event.SHOW_INFO, Event.STARTUP_PLUGIN_SYSTEM, Event.PLUGINS_RELOADED,
Event.SHOW_WARNING, Event.SHOW_SUCCESS, Event.SHOW_INFO, Event.SHOW_PROMPT_INJECTION_ALERT, Event.STARTUP_PLUGIN_SYSTEM, Event.PLUGINS_RELOADED,
Event.INSTALL_UPDATE, Event.STARTUP_COMPLETED, Event.AI_JOB_CHANGED, Event.AI_JOB_FINISHED,
Event.CHAT_GENERATION_CHANGED, Event.ASSISTANT_SESSION_CHANGED, Event.ASSISTANT_SESSION_FINISHED,
]);
@@ -254,6 +256,12 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
break;
case Event.SHOW_PROMPT_INJECTION_ALERT:
if (data is PromptInjectionAlertMessage promptInjectionAlert)
await this.ShowPromptInjectionAlertAsync(promptInjectionAlert);
break;
case Event.SHOW_ERROR:
if (data is DataErrorMessage error)
error.Show(this.Snackbar);
@@ -348,6 +356,32 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
});
}
private async Task ShowPromptInjectionAlertAsync(PromptInjectionAlertMessage alert)
{
await this.promptInjectionDialogSemaphore.WaitAsync();
try
{
if (!this.SettingsManager.ConfigurationData.App.ShowPromptInjectionAlert)
return;
var dialogParameters = new DialogParameters<PromptInjectionAlertDialog>
{
{ x => x.Alert, alert },
};
var dialogReference = await this.DialogService.ShowAsync<PromptInjectionAlertDialog>(
T("Security notice"),
dialogParameters,
DialogOptions.FULLSCREEN);
await dialogReference.Result;
}
finally
{
this.promptInjectionDialogSemaphore.Release();
}
}
public Task<TResult?> ProcessMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data)
{
return Task.FromResult<TResult?>(default);
@@ -351,6 +351,9 @@
}
<ThirdPartyComponent Name="Qdrant Edge" Developer="Andrey Vasnetsov, Tim Visée, Arnaud Gourlay, Luis Cossío, Ivan Pleshkov, Roman Titov, xzfc, JojiiOfficial & Open Source Community" LicenseName="Apache-2.0" LicenseUrl="https://github.com/qdrant/qdrant/blob/master/LICENSE" RepositoryUrl="https://github.com/qdrant/qdrant" UseCase="@T("Qdrant Edge is an embedded vector database and vector similarity search engine. We use it to realize local RAG—retrieval-augmented generation—within AI Studio. Thanks for the effort and great work that has been and is being put into Qdrant.")"/>
<ThirdPartyComponent Name="regex" Developer="Andrew Gallant, Alex Crichton, The Rust Project Developers & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/rust-lang/regex/blob/master/LICENSE-MIT" RepositoryUrl="https://github.com/rust-lang/regex" UseCase="@T("The regex crate detects structural and obfuscated prompt-injection patterns in untrusted document content. Its linear-time matching without backtracking keeps these scans predictable, even for hostile input.")"/>
<ThirdPartyComponent Name="aho-corasick" Developer="Alfred V. Aho, Margaret J. Corasick, Andrew Gallant & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/BurntSushi/aho-corasick/blob/master/LICENSE-MIT" RepositoryUrl="https://github.com/BurntSushi/aho-corasick" UseCase="@T("The aho-corasick crate searches the fixed catalog of prompt-injection phrases in one pass, allowing AI Studio to scan large documents efficiently. We thank Alfred V. Aho and Margaret J. Corasick for publishing the algorithm in 1975, and Andrew Gallant and the Open Source Community for bringing it to Rust.")"/>
<ThirdPartyComponent Name="toml" Developer="Ed Page, Alex Crichton, ordian, Eric Huss & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/toml-rs/toml/blob/main/LICENSE-MIT" RepositoryUrl="https://github.com/toml-rs/toml" UseCase="@T("The toml crate parses the embedded prompt-injection phrase catalog when the runtime starts. This keeps the detection rules separate from the Rust implementation and easier to maintain.")"/>
<ThirdPartyComponent Name="axum" Developer="David Pedersen, Jonas Platte, tottoto, David Mládek, Yann Simon, Tobias Bieniek, Open Source Community & Tokio Project" LicenseName="MIT" LicenseUrl="https://github.com/tokio-rs/axum/blob/main/LICENSE" RepositoryUrl="https://github.com/tokio-rs/axum" UseCase="@T("Axum is used to provide the small internal service that connects the Rust runtime with the app's user interface. This lets both parts of AI Studio exchange information while the app is running.")"/>
<ThirdPartyComponent Name="axum-server" Developer="Eray Karatay, Adi Salimgereyev, daxpedda & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/programatik29/axum-server/blob/master/LICENSE" RepositoryUrl="https://github.com/programatik29/axum-server" UseCase="@T("Axum server runs the internal axum service over a secure local connection. This helps AI Studio protect the communication between the Rust runtime and the user interface.")"/>
<ThirdPartyComponent Name="Rustls" Developer="Joe Birr-Pixton, Dirkjan Ochtman, Daniel McCarney, Brian Smith, Jacob Hoffman-Andrews, Jorge Aparicio & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/rustls/rustls/blob/main/LICENSE-MIT" RepositoryUrl="https://github.com/rustls/rustls" UseCase="@T("Rustls helps secure the internal connection between the app's user interface and the Rust runtime. This protects the local communication that AI Studio needs while it is running.")"/>
@@ -312,6 +312,11 @@ CONFIG["SETTINGS"] = {}
-- Configure whether the vision panel is shown on the welcome page.
-- CONFIG["SETTINGS"]["DataApp.ShowVision"] = false
-- Configure whether AI Studio shows a dialog listing suspicious instructions it
-- removed from external content, together with an explanation of the attack pattern.
-- A short notification is still shown when this setting is disabled.
-- CONFIG["SETTINGS"]["DataApp.ShowPromptInjectionAlert"] = true
-- Configure the user permission to add providers:
-- CONFIG["SETTINGS"]["DataApp.AllowUserToAddProvider"] = false
@@ -4071,6 +4071,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1471315821"]
-- AI Studio cannot install updates into its current installation location. Install new versions yourself.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T14786838"] = "AI Studio kann Updates am aktuellen Installationsort nicht installieren. Installieren Sie neue Versionen bitte selbst."
-- A dialog lists what was removed and explains the attack pattern
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T148008546"] = "Ein Dialog führt auf, was entfernt wurde, und erklärt das Angriffsmuster"
-- Select the desired behavior for the navigation bar.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1555038969"] = "Wählen Sie das gewünschte Verhalten für die Navigationsleiste aus."
@@ -4104,6 +4107,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1907446663"]
-- Your organization has disabled update checks and installations.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1909339369"] = "Ihre Organisation hat die Suche nach Updates und deren Installation deaktiviert."
-- Shows a dialog listing the removed passages, together with an explanation and an external reference.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2005319120"] = "Zeigt einen Dialog mit den entfernten Textstellen sowie einer Erklärung und einer externen Referenz an."
-- AI Studio cannot install updates when running as a Flatpak. Update it using the Flatpak source or bundle from which you installed it.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2009652585"] = "AI Studio kann keine Updates installieren, wenn es als Flatpak ausgeführt wird. Aktualisieren Sie es über die Flatpak-Quelle oder das Bundle, über die bzw. das Sie es installiert haben."
@@ -4191,9 +4197,15 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T4004501229"]
-- When enabled, spellchecking will be active in all input fields. Depending on your operating system, errors may not be visually highlighted, but right-clicking may still offer possible corrections.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T4067492921"] = "Wenn aktiviert, ist die Rechtschreibprüfung in allen Eingabefeldern aktiv. Je nach Betriebssystem werden Fehler möglicherweise nicht visuell hervorgehoben, aber ein Rechtsklick kann dennoch Korrekturvorschläge anzeigen."
-- Show details when suspicious content was removed?
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T4156872850"] = "Details anzeigen, wenn verdächtige Inhalte entfernt wurden?"
-- Select a transcription provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T4174666315"] = "Wählen Sie einen Transkriptionsanbieter aus"
-- Only a short notification is shown
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T4191930078"] = "Es wird nur eine kurze Benachrichtigung angezeigt"
-- How long AI Studio waits for external HTTP requests, such as AI providers, embeddings, transcription, ERI data sources, and enterprise configuration downloads.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T4192032183"] = "Wie lange AI Studio auf externe HTTP-Anfragen wartet, z. B. an KI-Anbieter, Einbettungen, Transkription, ERI-Datenquellen und Downloads von Enterprise-Konfigurationen."
@@ -6108,6 +6120,42 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINGGUIDELINEDIALOG::T384594633"] = "De
-- Prompting Guideline
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINGGUIDELINEDIALOG::T4250996615"] = "Prompting-Leitfaden"
-- AI Studio found instructions aimed at the AI inside your content and removed them. Everything around them was kept, so you can continue working with the content. Please review what was removed below.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T1100148261"] = "AI Studio hat in Ihren Inhalten Anweisungen erkannt, die an die KI gerichtet waren, und sie entfernt. Alles andere wurde beibehalten, sodass Sie mit den Inhalten weiterarbeiten können. Bitte überprüfen Sie unten, was entfernt wurde."
-- Content source
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T1129278507"] = "Quelle des Inhalts"
-- Close and don't show again
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T1384522605"] = "Schließen und nicht mehr anzeigen"
-- And {0} more passages of the same kind.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T2039738154"] = "Und {0} weitere Passagen derselben Art."
-- Source type
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T280442848"] = "Typ der Quelle"
-- Prompt injection is a method used to manipulate AI systems such as chatbots. An attacker places misleading instructions in content so that the AI treats them as legitimate. This can cause the AI to ignore safeguards, expose private information, or generate harmful content.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T3122726298"] = "Prompt Injection ist eine Methode zur Manipulation von KI-Systemen wie Chatbots. Dabei platziert ein Angreifer irreführende Anweisungen in Inhalten, sodass die KI sie als legitim behandelt. Dies kann dazu führen, dass die KI Schutzmaßnahmen ignoriert, private Informationen preisgibt oder schädliche Inhalte erstellt."
-- Close
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T3448155331"] = "Schließen"
-- Removed content
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T3549539878"] = "Inhalt entfernt"
-- Typical attacks on AI systems (e.g. prompt injection) hide instructions within untrusted content to trick an AI model into ignoring its intended rules or performing unintended actions.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T4221674400"] = "Typische Angriffe auf KI-Systeme (z. B. Prompt-Injection) verbergen Anweisungen in nicht vertrauenswürdigen Inhalten, um ein KI-Modell dazu zu bringen, seine vorgesehenen Regeln zu ignorieren oder unbeabsichtigte Aktionen auszuführen."
-- More information
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T475337262"] = "Weitere Informationen"
-- Hide more information
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T808738984"] = "Weitere Informationen ausblenden"
-- Suspicious content was removed
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T871282530"] = "Verdächtige Inhalte wurden entfernt"
-- Hugging Face Inference Provider
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1085481431"] = "Hugging Face Inferenz-Anbieter"
@@ -7938,6 +7986,9 @@ UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T2979224202"] = "Schreiben"
-- Show details
UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T3692372066"] = "Details anzeigen"
-- Security notice
UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T4004397997"] = "Sicherheitshinweis"
-- Information
UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T4256323669"] = "Information"
@@ -8343,6 +8394,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2160280545"] = "Diese Bibliothek
-- For the secure communication between the user interface and the runtime, we need to create certificates. This Rust library is great for this purpose.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2174764529"] = "Für die sichere Kommunikation zwischen der Benutzeroberfläche und der Laufzeit müssen wir Zertifikate erstellen. Diese Rust-Bibliothek eignet sich hervorragend dafür."
-- The regex crate detects structural and obfuscated prompt-injection patterns in untrusted document content. Its linear-time matching without backtracking keeps these scans predictable, even for hostile input.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2196053547"] = "Das Crate „regex“ erkennt strukturelle und verschleierte Prompt-Injection-Muster in nicht vertrauenswürdigen Dokumentinhalten. Durch die lineare Abgleichzeit ohne Backtracking bleiben diese Prüfungen auch bei bösartigen Eingaben vorhersehbar."
-- OK
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2246359087"] = "OK"
@@ -8478,6 +8532,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2929232062"] = "Kopiert die Quel
-- Copies the root certificate fingerprint to the clipboard
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2989678330"] = "Kopiert den Fingerabdruck des Stammzertifikats in die Zwischenablage"
-- The toml crate parses the embedded prompt-injection phrase catalog when the runtime starts. This keeps the detection rules separate from the Rust implementation and easier to maintain.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2999154325"] = "Die TOML-Bibliothek analysiert beim Start der Laufzeitumgebung den eingebetteten Phrasenkatalog zur Erkennung von Prompt-Injection. Dadurch bleiben die Erkennungsregeln von der Rust-Implementierung getrennt und lassen sich leichter pflegen."
-- This library identifies files by their content. It is used for document streaming and as the first safety and media classification step before local audio processing.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3002755581"] = "Diese Bibliothek identifiziert Dateien anhand ihres Inhalts. Sie wird für das Streaming von Dokumenten sowie als erste Sicherheits- und Medienklassifizierungsstufe vor der lokalen Audioverarbeitung verwendet."
@@ -8547,6 +8604,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3433065373"] = "Informationen ü
-- Used Rust compiler
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3440211747"] = "Verwendeter Rust-Compiler"
-- The aho-corasick crate searches the fixed catalog of prompt-injection phrases in one pass, allowing AI Studio to scan large documents efficiently. We thank Alfred V. Aho and Margaret J. Corasick for publishing the algorithm in 1975, and Andrew Gallant and the Open Source Community for bringing it to Rust.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3444344506"] = "Das Crate „aho-corasick“ durchsucht den festgelegten Katalog von Prompt-Injection-Phrasen in einem Durchlauf. Dadurch kann AI Studio auch große Dokumente effizient prüfen. Wir danken Alfred V. Aho und Margaret J. Corasick für die Veröffentlichung des Algorithmus im Jahr 1975 sowie Andrew Gallant und der Open-Source-Community dafür, ihn nach Rust gebracht zu haben."
-- AI Studio runs with an enterprise configuration using configuration plugins, without central configuration management.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3449345633"] = "AI Studio wird mit Unternehmenskonfigurationen unter Verwendung von Konfigurations-Plugins betrieben. Eine zentrale Konfigurationsverwaltung wird nicht eingesetzt."
@@ -10326,6 +10386,63 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T4165204724"] = "Dokument"
-- Plugin archive
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T927001356"] = "Plugin-Archiv"
-- Attempt to override instructions
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONFINDINGCATEGORYEXTENSIONS::T161976090"] = "Versuch, Anweisungen zu überschreiben"
-- Attempt to expose protected data
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONFINDINGCATEGORYEXTENSIONS::T2050274293"] = "Versuch, geschützte Daten offenzulegen"
-- Attempt to bypass safeguards
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONFINDINGCATEGORYEXTENSIONS::T2260642992"] = "Versuch, Schutzvorkehrungen zu umgehen"
-- Attempt to change the AI's role
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONFINDINGCATEGORYEXTENSIONS::T2340508370"] = "Versuch, die Rolle der KI zu ändern"
-- Hidden instructions using markup
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONFINDINGCATEGORYEXTENSIONS::T2526538070"] = "Versteckte Anweisungen mit Markup"
-- Hidden instructions using delimiters
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONFINDINGCATEGORYEXTENSIONS::T329656456"] = "Versteckte Anweisungen mit Trennzeichen"
-- Unknown
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONFINDINGCATEGORYEXTENSIONS::T3424652889"] = "Unbekannt"
-- Attempt to manipulate an agent
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONFINDINGCATEGORYEXTENSIONS::T355252317"] = "Versuch, einen Agenten zu manipulieren"
-- Persistent or delayed instruction
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONFINDINGCATEGORYEXTENSIONS::T4169123215"] = "Dauerhafte oder verzögerte Anweisung"
-- Hidden instructions using encoding
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONFINDINGCATEGORYEXTENSIONS::T49495195"] = "Versteckte Anweisungen mithilfe von Kodierung"
-- Obfuscated instruction
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONFINDINGCATEGORYEXTENSIONS::T87316699"] = "Verschleierte Anweisung"
-- AI Studio could not check '{0}' for prompt injections. The content is used as it is.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONGUARDSERVICE::T1026469976"] = "AI Studio konnte „{0}“ nicht auf Prompt-Injections prüfen. Der Inhalt wird unverändert verwendet."
-- AI Studio removed suspicious instructions from '{0}' before using it.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONGUARDSERVICE::T2460486335"] = "AI Studio hat verdächtige Anweisungen aus „{0}“ entfernt, bevor es verwendet wurde."
-- AI Studio removed suspicious instructions from {0} sources before using them.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONGUARDSERVICE::T3489536228"] = "AI Studio hat verdächtige Anweisungen aus {0} Quellen entfernt, bevor es sie verwendet hat."
-- Chat attachment
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONSOURCEKINDEXTENSIONS::T1071345316"] = "Chat-Anhang"
-- Web content
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONSOURCEKINDEXTENSIONS::T2626468388"] = "Webinhalte"
-- Retrieved context
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONSOURCEKINDEXTENSIONS::T3347144620"] = "Abgerufener Kontext"
-- Unknown
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONSOURCEKINDEXTENSIONS::T3424652889"] = "Unbekannt"
-- File content
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONSOURCEKINDEXTENSIONS::T3788064862"] = "Dateiinhalt"
-- The Assistant Builder context could not be loaded.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGINGENERATIONSERVICE::T108292972"] = "Der Kontext des Assistenten-Builders konnte nicht geladen werden."
@@ -4071,6 +4071,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1471315821"]
-- AI Studio cannot install updates into its current installation location. Install new versions yourself.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T14786838"] = "AI Studio cannot install updates into its current installation location. Install new versions yourself."
-- A dialog lists what was removed and explains the attack pattern
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T148008546"] = "A dialog lists what was removed and explains the attack pattern"
-- Select the desired behavior for the navigation bar.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1555038969"] = "Select the desired behavior for the navigation bar."
@@ -4104,6 +4107,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1907446663"]
-- Your organization has disabled update checks and installations.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1909339369"] = "Your organization has disabled update checks and installations."
-- Shows a dialog listing the removed passages, together with an explanation and an external reference.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2005319120"] = "Shows a dialog listing the removed passages, together with an explanation and an external reference."
-- AI Studio cannot install updates when running as a Flatpak. Update it using the Flatpak source or bundle from which you installed it.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2009652585"] = "AI Studio cannot install updates when running as a Flatpak. Update it using the Flatpak source or bundle from which you installed it."
@@ -4191,9 +4197,15 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T4004501229"]
-- When enabled, spellchecking will be active in all input fields. Depending on your operating system, errors may not be visually highlighted, but right-clicking may still offer possible corrections.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T4067492921"] = "When enabled, spellchecking will be active in all input fields. Depending on your operating system, errors may not be visually highlighted, but right-clicking may still offer possible corrections."
-- Show details when suspicious content was removed?
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T4156872850"] = "Show details when suspicious content was removed?"
-- Select a transcription provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T4174666315"] = "Select a transcription provider"
-- Only a short notification is shown
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T4191930078"] = "Only a short notification is shown"
-- How long AI Studio waits for external HTTP requests, such as AI providers, embeddings, transcription, ERI data sources, and enterprise configuration downloads.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T4192032183"] = "How long AI Studio waits for external HTTP requests, such as AI providers, embeddings, transcription, ERI data sources, and enterprise configuration downloads."
@@ -6108,6 +6120,42 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINGGUIDELINEDIALOG::T384594633"] = "Th
-- Prompting Guideline
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINGGUIDELINEDIALOG::T4250996615"] = "Prompting Guideline"
-- AI Studio found instructions aimed at the AI inside your content and removed them. Everything around them was kept, so you can continue working with the content. Please review what was removed below.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T1100148261"] = "AI Studio found instructions aimed at the AI inside your content and removed them. Everything around them was kept, so you can continue working with the content. Please review what was removed below."
-- Content source
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T1129278507"] = "Content source"
-- Close and don't show again
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T1384522605"] = "Close and don't show again"
-- And {0} more passages of the same kind.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T2039738154"] = "And {0} more passages of the same kind."
-- Source type
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T280442848"] = "Source type"
-- Prompt injection is a method used to manipulate AI systems such as chatbots. An attacker places misleading instructions in content so that the AI treats them as legitimate. This can cause the AI to ignore safeguards, expose private information, or generate harmful content.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T3122726298"] = "Prompt injection is a method used to manipulate AI systems such as chatbots. An attacker places misleading instructions in content so that the AI treats them as legitimate. This can cause the AI to ignore safeguards, expose private information, or generate harmful content."
-- Close
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T3448155331"] = "Close"
-- Removed content
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T3549539878"] = "Removed content"
-- Typical attacks on AI systems (e.g. prompt injection) hide instructions within untrusted content to trick an AI model into ignoring its intended rules or performing unintended actions.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T4221674400"] = "Typical attacks on AI systems (e.g. prompt injection) hide instructions within untrusted content to trick an AI model into ignoring its intended rules or performing unintended actions."
-- More information
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T475337262"] = "More information"
-- Hide more information
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T808738984"] = "Hide more information"
-- Suspicious content was removed
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T871282530"] = "Suspicious content was removed"
-- Hugging Face Inference Provider
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1085481431"] = "Hugging Face Inference Provider"
@@ -7938,6 +7986,9 @@ UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T2979224202"] = "Writing"
-- Show details
UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T3692372066"] = "Show details"
-- Security notice
UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T4004397997"] = "Security notice"
-- Information
UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T4256323669"] = "Information"
@@ -8343,6 +8394,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2160280545"] = "This library is
-- For the secure communication between the user interface and the runtime, we need to create certificates. This Rust library is great for this purpose.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2174764529"] = "For the secure communication between the user interface and the runtime, we need to create certificates. This Rust library is great for this purpose."
-- The regex crate detects structural and obfuscated prompt-injection patterns in untrusted document content. Its linear-time matching without backtracking keeps these scans predictable, even for hostile input.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2196053547"] = "The regex crate detects structural and obfuscated prompt-injection patterns in untrusted document content. Its linear-time matching without backtracking keeps these scans predictable, even for hostile input."
-- OK
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2246359087"] = "OK"
@@ -8478,6 +8532,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2929232062"] = "Copies the confi
-- Copies the root certificate fingerprint to the clipboard
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2989678330"] = "Copies the root certificate fingerprint to the clipboard"
-- The toml crate parses the embedded prompt-injection phrase catalog when the runtime starts. This keeps the detection rules separate from the Rust implementation and easier to maintain.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2999154325"] = "The toml crate parses the embedded prompt-injection phrase catalog when the runtime starts. This keeps the detection rules separate from the Rust implementation and easier to maintain."
-- This library identifies files by their content. It is used for document streaming and as the first safety and media classification step before local audio processing.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3002755581"] = "This library identifies files by their content. It is used for document streaming and as the first safety and media classification step before local audio processing."
@@ -8547,6 +8604,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3433065373"] = "Information abou
-- Used Rust compiler
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3440211747"] = "Used Rust compiler"
-- The aho-corasick crate searches the fixed catalog of prompt-injection phrases in one pass, allowing AI Studio to scan large documents efficiently. We thank Alfred V. Aho and Margaret J. Corasick for publishing the algorithm in 1975, and Andrew Gallant and the Open Source Community for bringing it to Rust.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3444344506"] = "The aho-corasick crate searches the fixed catalog of prompt-injection phrases in one pass, allowing AI Studio to scan large documents efficiently. We thank Alfred V. Aho and Margaret J. Corasick for publishing the algorithm in 1975, and Andrew Gallant and the Open Source Community for bringing it to Rust."
-- AI Studio runs with an enterprise configuration using configuration plugins, without central configuration management.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3449345633"] = "AI Studio runs with an enterprise configuration using configuration plugins, without central configuration management."
@@ -10326,6 +10386,63 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T4165204724"] = "Document"
-- Plugin archive
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T927001356"] = "Plugin archive"
-- Attempt to override instructions
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONFINDINGCATEGORYEXTENSIONS::T161976090"] = "Attempt to override instructions"
-- Attempt to expose protected data
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONFINDINGCATEGORYEXTENSIONS::T2050274293"] = "Attempt to expose protected data"
-- Attempt to bypass safeguards
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONFINDINGCATEGORYEXTENSIONS::T2260642992"] = "Attempt to bypass safeguards"
-- Attempt to change the AI's role
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONFINDINGCATEGORYEXTENSIONS::T2340508370"] = "Attempt to change the AI's role"
-- Hidden instructions using markup
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONFINDINGCATEGORYEXTENSIONS::T2526538070"] = "Hidden instructions using markup"
-- Hidden instructions using delimiters
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONFINDINGCATEGORYEXTENSIONS::T329656456"] = "Hidden instructions using delimiters"
-- Unknown
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONFINDINGCATEGORYEXTENSIONS::T3424652889"] = "Unknown"
-- Attempt to manipulate an agent
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONFINDINGCATEGORYEXTENSIONS::T355252317"] = "Attempt to manipulate an agent"
-- Persistent or delayed instruction
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONFINDINGCATEGORYEXTENSIONS::T4169123215"] = "Persistent or delayed instruction"
-- Hidden instructions using encoding
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONFINDINGCATEGORYEXTENSIONS::T49495195"] = "Hidden instructions using encoding"
-- Obfuscated instruction
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONFINDINGCATEGORYEXTENSIONS::T87316699"] = "Obfuscated instruction"
-- AI Studio could not check '{0}' for prompt injections. The content is used as it is.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONGUARDSERVICE::T1026469976"] = "AI Studio could not check '{0}' for prompt injections. The content is used as it is."
-- AI Studio removed suspicious instructions from '{0}' before using it.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONGUARDSERVICE::T2460486335"] = "AI Studio removed suspicious instructions from '{0}' before using it."
-- AI Studio removed suspicious instructions from {0} sources before using them.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONGUARDSERVICE::T3489536228"] = "AI Studio removed suspicious instructions from {0} sources before using them."
-- Chat attachment
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONSOURCEKINDEXTENSIONS::T1071345316"] = "Chat attachment"
-- Web content
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONSOURCEKINDEXTENSIONS::T2626468388"] = "Web content"
-- Retrieved context
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONSOURCEKINDEXTENSIONS::T3347144620"] = "Retrieved context"
-- Unknown
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONSOURCEKINDEXTENSIONS::T3424652889"] = "Unknown"
-- File content
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONSOURCEKINDEXTENSIONS::T3788064862"] = "File content"
-- The Assistant Builder context could not be loaded.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGINGENERATIONSERVICE::T108292972"] = "The Assistant Builder context could not be loaded."
+3 -1
View File
@@ -9,6 +9,7 @@ using AIStudio.Tools.Media;
using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.PluginSystem.Assistants;
using AIStudio.Tools.Rust;
using AIStudio.Tools.Security;
using AIStudio.Tools.Services;
using Microsoft.AspNetCore.DataProtection;
@@ -161,6 +162,7 @@ internal sealed class Program
builder.Services.AddSingleton(typeof(RuntimeInfoResponse), runtimeInfo);
builder.Services.AddMudMarkdownClipboardService<MarkdownClipboardService>();
builder.Services.AddSingleton<SettingsManager>();
builder.Services.AddSingleton<PromptInjectionGuardService>();
builder.Services.AddSingleton<ThreadSafeRandom>();
builder.Services.AddSingleton<AIJobService>();
builder.Services.AddSingleton<AssistantSessionService>();
@@ -300,4 +302,4 @@ internal sealed class Program
PluginFactory.Dispose();
programLogger.LogInformation("The AI Studio server was stopped.");
}
}
}
+1 -1
View File
@@ -42,4 +42,4 @@ internal static class Redirect
await nextHandler();
}
}
}
@@ -57,6 +57,11 @@ public sealed class DataApp(Expression<Func<Data, DataApp>>? configSelection = n
/// </summary>
public StartPage StartPage { get; set; } = ManagedConfiguration.Register(configSelection, n => n.StartPage, StartPage.HOME);
/// <summary>
/// Whether an alert dialog should be shown when prompt-injection content is blocked.
/// </summary>
public bool ShowPromptInjectionAlert { get; set; } = ManagedConfiguration.Register(configSelection, n => n.ShowPromptInjectionAlert, true);
/// <summary>
/// Should the built-in introduction be visible on the home page?
/// </summary>
@@ -8,10 +8,7 @@ using AIStudio.Tools.RAG.RAGProcesses;
namespace AIStudio.Tools.AIJobs;
public sealed class AIJobService(
SettingsManager settingsManager,
MessageBus messageBus,
ILogger<AIJobService> logger)
public sealed class AIJobService(SettingsManager settingsManager, MessageBus messageBus, ILogger<AIJobService> logger)
{
private sealed class AIJobState
{
@@ -24,6 +24,7 @@ public sealed class ContentStreamMetadataJsonConverter : JsonConverter<ContentSt
"Image" => JsonSerializer.Deserialize<ContentStreamImageMetadata?>(rawText, options),
"Document" => JsonSerializer.Deserialize<ContentStreamDocumentMetadata?>(rawText, options),
"Error" => JsonSerializer.Deserialize<ContentStreamErrorMetadata?>(rawText, options),
"PromptInjection" => JsonSerializer.Deserialize<ContentStreamPromptInjectionMetadata?>(rawText, options),
_ => null
};
@@ -10,7 +10,8 @@ namespace AIStudio.Tools;
/// </remarks>
/// <param name="Content">The content to append, or null when this event carries none.</param>
/// <param name="Error">The reported failure, or null when the event was processed successfully.</param>
public readonly record struct ContentStreamProcessedEvent(string? Content, ContentStreamErrorDetails? Error)
/// <param name="PromptInjection">What the runtime filtered out of the content, or null when it filtered nothing.</param>
public readonly record struct ContentStreamProcessedEvent(string? Content, ContentStreamErrorDetails? Error, ContentStreamPromptInjectionDetails? PromptInjection = null)
{
/// <summary>
/// An event which neither produced content nor reported a failure.
@@ -20,4 +21,13 @@ public readonly record struct ContentStreamProcessedEvent(string? Content, Conte
public static ContentStreamProcessedEvent FromContent(string? content) => new(content, null);
public static ContentStreamProcessedEvent FromError(ContentStreamErrorDetails? error) => new(null, error);
/// <summary>
/// An event reporting that suspicious passages were filtered out of the content.
/// </summary>
/// <remarks>
/// Carries no content and no error: the content was delivered by the events before it, and
/// filtering is a notice rather than a failure.
/// </remarks>
public static ContentStreamProcessedEvent FromPromptInjection(ContentStreamPromptInjectionDetails? promptInjection) => new(null, null, promptInjection);
}
@@ -0,0 +1,28 @@
using System.Text.Json.Serialization;
using AIStudio.Tools.Security;
namespace AIStudio.Tools;
// ReSharper disable UnusedAutoPropertyAccessor.Global
// ReSharper disable ClassNeverInstantiated.Global
/// <summary>
/// Reports that the runtime filtered suspected prompt injections out of a file.
/// </summary>
/// <remarks>
/// This is a notice, not a failure: the file was read and everything around the filtered
/// passages is intact. It travels beside the content rather than as an error code, because the
/// app needs the findings themselves to tell the user what was removed.
/// </remarks>
public sealed class ContentStreamPromptInjectionDetails
{
[JsonPropertyName("findings")]
public List<PromptInjectionFinding>? Findings { get; init; }
/// <summary>
/// How many passages were filtered. Can exceed the number of findings, because the runtime
/// caps how many it reports in detail while it filters every single one.
/// </summary>
[JsonPropertyName("redacted_count")]
public int RedactedCount { get; init; }
}
@@ -0,0 +1,11 @@
using System.Text.Json.Serialization;
namespace AIStudio.Tools;
// ReSharper disable UnusedAutoPropertyAccessor.Global
// ReSharper disable ClassNeverInstantiated.Global
public sealed class ContentStreamPromptInjectionMetadata : ContentStreamSseMetadata
{
[JsonPropertyName("PromptInjection")]
public ContentStreamPromptInjectionDetails? PromptInjection { get; init; }
}
@@ -73,6 +73,14 @@ public static class ContentStreamSseHandler
case ContentStreamErrorMetadata errorMetadata:
return ContentStreamProcessedEvent.FromError(errorMetadata.Error);
//
// The runtime filtered suspected prompt injections out of the content. The
// content itself already arrived through the events before this one, so this
// only reports what was removed.
//
case ContentStreamPromptInjectionMetadata promptInjectionMetadata:
return ContentStreamProcessedEvent.FromPromptInjection(promptInjectionMetadata.PromptInjection);
default:
return ContentStreamProcessedEvent.FromContent(sseEvent.Content);
}
+5
View File
@@ -78,6 +78,11 @@ public enum Event
/// </summary>
SHOW_INFO,
/// <summary>
/// Requests display of a prompt-injection alert dialog.
/// </summary>
SHOW_PROMPT_INJECTION_ALERT,
/// <summary>
/// Carries an event received from the Tauri runtime.
/// </summary>
@@ -1,3 +1,5 @@
using AIStudio.Tools.Security;
namespace AIStudio.Tools;
/// <summary>
@@ -17,6 +19,34 @@ namespace AIStudio.Tools;
public readonly record struct FileExtractionResult(FileExtractionOutcome Outcome, string Content, FileExtractionErrorCode ErrorCode, string? ErrorMessage, IReadOnlyList<int> FailedPages, string? DetectedFormat)
{
private static readonly int[] NO_FAILED_PAGES = [];
private static readonly PromptInjectionFinding[] NO_FINDINGS = [];
private readonly IReadOnlyList<PromptInjectionFinding>? promptInjectionFindings;
/// <summary>
/// The prompt-injection attempts the runtime filtered out of the content, if any.
/// </summary>
/// <remarks>
/// This is a notice, not a failure: the passages were removed and the content around them
/// is intact, which is why it does not affect the outcome. The findings exist so the app
/// can tell the user what was removed from their document.
/// </remarks>
public IReadOnlyList<PromptInjectionFinding> PromptInjectionFindings
{
get => this.promptInjectionFindings ?? NO_FINDINGS;
init => this.promptInjectionFindings = value;
}
/// <summary>
/// How many passages were filtered out. May exceed the number of findings, because the
/// runtime caps how many it reports in detail while it filters every single one.
/// </summary>
public int PromptInjectionRedactedCount { get; init; }
/// <summary>
/// Gets a value indicating whether prompt injections were filtered out of the content.
/// </summary>
public bool HasFilteredPromptInjections => this.PromptInjectionRedactedCount > 0;
public static FileExtractionResult Success(string content, string? detectedFormat = null) => new(FileExtractionOutcome.SUCCESS, content, FileExtractionErrorCode.NONE, null, NO_FAILED_PAGES, detectedFormat);
@@ -216,6 +216,9 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
// Config: what should be the start page?
ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.StartPage, this.Id, settingsTable, dryRun);
// Config: show prompt-injection alert dialogs?
ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.ShowPromptInjectionAlert, this.Id, settingsTable, dryRun);
// Config: show built-in introduction on the home page?
ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.ShowIntroduction, this.Id, settingsTable, dryRun);
@@ -1,6 +1,7 @@
using System.Text;
using AIStudio.Chat;
using AIStudio.Tools.Security;
namespace AIStudio.Tools.RAG;
@@ -13,86 +14,117 @@ public static class IRetrievalContextExtensions
sb ??= new StringBuilder();
var index = 0;
//
// One report for the whole retrieval run: a query may pull in dozens of contexts, and
// the user wants to know that something was filtered, not to acknowledge it per context.
//
var guardService = Program.SERVICE_PROVIDER.GetRequiredService<PromptInjectionGuardService>();
await using var reportingScope = guardService.BeginAction();
foreach(var retrievalContext in retrievalContexts)
{
index++;
await retrievalContext.AsMarkdown(sb, index, retrievalContexts.Count, token);
}
return sb.ToString();
}
public static async Task<string> AsMarkdown(this IRetrievalContext retrievalContext, StringBuilder? sb = null, int index = -1, int numTotalRetrievalContexts = -1, CancellationToken token = default)
{
sb ??= new StringBuilder();
var contextBuilder = new StringBuilder();
switch (index)
{
case > 0 when numTotalRetrievalContexts is -1:
sb.AppendLine($"# Retrieval context {index}");
contextBuilder.AppendLine($"# Retrieval context {index}");
break;
case > 0 when numTotalRetrievalContexts > 0:
sb.AppendLine($"# Retrieval context {index} of {numTotalRetrievalContexts}");
contextBuilder.AppendLine($"# Retrieval context {index} of {numTotalRetrievalContexts}");
break;
default:
sb.AppendLine("# Retrieval context");
contextBuilder.AppendLine("# Retrieval context");
break;
}
sb.AppendLine($"Data source name: {retrievalContext.DataSourceName}");
sb.AppendLine($"Content category: {retrievalContext.Category}");
sb.AppendLine($"Content type: {retrievalContext.Type}");
sb.AppendLine($"Content path: {retrievalContext.Path}");
contextBuilder.AppendLine($"Data source name: {retrievalContext.DataSourceName}");
contextBuilder.AppendLine($"Content category: {retrievalContext.Category}");
contextBuilder.AppendLine($"Content type: {retrievalContext.Type}");
contextBuilder.AppendLine($"Content path: {retrievalContext.Path}");
if(retrievalContext.Links.Count > 0)
{
sb.AppendLine("Additional links:");
contextBuilder.AppendLine("Additional links:");
foreach(var link in retrievalContext.Links)
sb.AppendLine($"- {link}");
contextBuilder.AppendLine($"- {link}");
}
var guardService = Program.SERVICE_PROVIDER.GetRequiredService<PromptInjectionGuardService>();
var source = PromptInjectionSource.RetrievalContext(retrievalContext.DataSourceName, retrievalContext.Path);
switch(retrievalContext)
{
case RetrievalTextContext textContext:
sb.AppendLine();
sb.AppendLine("Matched text content:");
sb.AppendLine("````");
sb.AppendLine(textContext.MatchedText);
sb.AppendLine("````");
contextBuilder.AppendLine();
contextBuilder.AppendLine("Matched text content:");
contextBuilder.AppendLine("````");
contextBuilder.AppendLine(textContext.MatchedText);
contextBuilder.AppendLine("````");
if(textContext.SurroundingContent.Count > 0)
{
sb.AppendLine();
sb.AppendLine("Surrounding text content:");
contextBuilder.AppendLine();
contextBuilder.AppendLine("Surrounding text content:");
foreach(var surrounding in textContext.SurroundingContent)
{
sb.AppendLine();
sb.AppendLine("````");
sb.AppendLine(surrounding);
sb.AppendLine("````");
contextBuilder.AppendLine();
contextBuilder.AppendLine("````");
contextBuilder.AppendLine(surrounding);
contextBuilder.AppendLine("````");
}
}
await FilterWhatWeHaveSoFar();
break;
case RetrievalImageContext imageContext:
sb.AppendLine();
sb.AppendLine("Matched image content as base64-encoded data:");
sb.AppendLine("````");
sb.AppendLine(await imageContext.TryAsBase64(token) is (success: true, { } base64Image)
? base64Image
//
// Filtering happens before the image is appended, and only covers the text
// around it. Base64 image data is not prose, and running it through the filter
// would have it treated as one enormous encoded carrier.
//
await FilterWhatWeHaveSoFar();
contextBuilder.AppendLine();
contextBuilder.AppendLine("Matched image content as base64-encoded data:");
contextBuilder.AppendLine("````");
contextBuilder.AppendLine(await imageContext.TryAsBase64(token) is (success: true, { } base64Image)
? base64Image
: string.Empty);
sb.AppendLine("````");
contextBuilder.AppendLine("````");
break;
default:
await FilterWhatWeHaveSoFar();
LOGGER.LogWarning($"The retrieval content type '{retrievalContext.Type}' of data source '{retrievalContext.DataSourceName}' at location '{retrievalContext.Path}' is not supported yet.");
break;
}
sb.AppendLine();
contextBuilder.AppendLine();
sb.Append(contextBuilder);
return sb.ToString();
//
// Replaces what has been built so far with its filtered version. A data source is as
// untrusted as any other external content: it may serve text written to steer the model
// rather than to answer the query.
//
async Task FilterWhatWeHaveSoFar()
{
var sanitized = await guardService.SanitizeAsync(contextBuilder.ToString(), source);
contextBuilder.Clear();
contextBuilder.Append(sanitized);
}
}
}
@@ -0,0 +1,6 @@
using System.Text.Json.Serialization;
namespace AIStudio.Tools.Rust;
/// <param name="Text">The content to filter.</param>
public readonly record struct SanitizePromptInjectionsRequest([property: JsonPropertyName("text")] string Text);
@@ -0,0 +1,13 @@
using System.Text.Json.Serialization;
using AIStudio.Tools.Security;
namespace AIStudio.Tools.Rust;
/// <param name="SanitizedText">The content with the suspicious passages removed. Usable as it stands.</param>
/// <param name="Findings">The passages that were removed, capped by the runtime.</param>
/// <param name="RedactedCount">How many passages were removed in total, which may exceed the number of findings.</param>
public readonly record struct SanitizePromptInjectionsResponse(
[property: JsonPropertyName("sanitized_text")] string SanitizedText,
[property: JsonPropertyName("findings")] IReadOnlyList<PromptInjectionFinding> Findings,
[property: JsonPropertyName("redacted_count")] int RedactedCount);
@@ -0,0 +1,17 @@
namespace AIStudio.Tools.Security;
/// <summary>
/// Asks the UI to tell the user what was filtered out of the content they just used.
/// </summary>
/// <remarks>
/// Carries every result of one user action rather than a single one. Attaching twenty
/// documents at once must produce one dialog listing all of them, not twenty dialogs.
/// </remarks>
/// <param name="Results">What was filtered, per piece of content.</param>
public sealed record PromptInjectionAlertMessage(IReadOnlyList<PromptInjectionScanResult> Results)
{
/// <summary>
/// Gets the total number of filtered passages across all content.
/// </summary>
public int TotalRedactedCount => this.Results.Sum(result => result.RedactedCount);
}
@@ -0,0 +1,31 @@
using System.Text.Json.Serialization;
namespace AIStudio.Tools.Security;
/// <summary>
/// One passage the runtime identified as a prompt-injection attempt and filtered out.
/// </summary>
/// <remarks>
/// The property names are spelled out because the content stream is deserialized without a
/// naming policy, so the names have to match what the runtime sends verbatim.
/// </remarks>
public sealed record PromptInjectionFinding
{
/// <summary>
/// Which rule matched, e.g. "instruction_override".
/// </summary>
[JsonPropertyName("rule_id")]
public string RuleId { get; init; } = string.Empty;
/// <summary>
/// The rule's family, e.g. "exfiltration".
/// </summary>
[JsonPropertyName("category")]
public PromptInjectionFindingCategory Category { get; init; } = PromptInjectionFindingCategory.UNKNOWN;
/// <summary>
/// The passage as it appeared in the content, so the user can see what was removed.
/// </summary>
[JsonPropertyName("snippet")]
public string Snippet { get; init; } = string.Empty;
}
@@ -0,0 +1,19 @@
using System.Text.Json.Serialization;
namespace AIStudio.Tools.Security;
[JsonConverter(typeof(PromptInjectionFindingCategoryJsonConverter))]
public enum PromptInjectionFindingCategory
{
UNKNOWN = 0,
OVERRIDE,
ROLE_OVERRIDE,
EXFILTRATION,
JAILBREAK,
AGENT_MANIPULATION,
DELIMITER_EVASION,
MARKUP_EVASION,
ENCODING_EVASION,
PERSISTENCE,
EVASION,
}
@@ -0,0 +1,23 @@
using AIStudio.Tools.PluginSystem;
namespace AIStudio.Tools.Security;
public static class PromptInjectionFindingCategoryExtensions
{
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(PromptInjectionFindingCategoryExtensions).Namespace, nameof(PromptInjectionFindingCategoryExtensions));
public static string GetDisplayName(this PromptInjectionFindingCategory category) => category switch
{
PromptInjectionFindingCategory.OVERRIDE => TB("Attempt to override instructions"),
PromptInjectionFindingCategory.ROLE_OVERRIDE => TB("Attempt to change the AI's role"),
PromptInjectionFindingCategory.EXFILTRATION => TB("Attempt to expose protected data"),
PromptInjectionFindingCategory.JAILBREAK => TB("Attempt to bypass safeguards"),
PromptInjectionFindingCategory.AGENT_MANIPULATION => TB("Attempt to manipulate an agent"),
PromptInjectionFindingCategory.DELIMITER_EVASION => TB("Hidden instructions using delimiters"),
PromptInjectionFindingCategory.MARKUP_EVASION => TB("Hidden instructions using markup"),
PromptInjectionFindingCategory.ENCODING_EVASION => TB("Hidden instructions using encoding"),
PromptInjectionFindingCategory.PERSISTENCE => TB("Persistent or delayed instruction"),
PromptInjectionFindingCategory.EVASION => TB("Obfuscated instruction"),
_ => TB("Unknown"),
};
}
@@ -0,0 +1,51 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace AIStudio.Tools.Security;
/// <summary>
/// Reads the finding category in the snake_case spelling the Rust runtime sends.
/// </summary>
/// <remarks>
/// The converter sits on the enum itself because neither path that reads a finding passes
/// JsonSerializerOptions: the sanitize response is read by RustService.SanitizePromptInjections
/// and the content stream by RustService.ReadFileContent. The shared RustEnumConverter therefore
/// never applies here, and without a converter on the type only numbers would be accepted.
///
/// An unrecognized category falls back to UNKNOWN instead of throwing. Throwing would cost more
/// than the label: it fails the whole response, and the guard service then passes the content
/// through unfiltered rather than losing a single name.
/// </remarks>
public sealed class PromptInjectionFindingCategoryJsonConverter : JsonConverter<PromptInjectionFindingCategory>
{
private static readonly ILogger<PromptInjectionFindingCategoryJsonConverter> LOG = Program.LOGGER_FACTORY.CreateLogger<PromptInjectionFindingCategoryJsonConverter>();
public override PromptInjectionFindingCategory Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType is not JsonTokenType.String)
{
LOG.LogWarning("Cannot read a prompt injection finding category from a '{TokenType}' token. Using UNKNOWN.", reader.TokenType);
return PromptInjectionFindingCategory.UNKNOWN;
}
var text = reader.GetString();
if (string.IsNullOrWhiteSpace(text))
{
LOG.LogWarning("Read an empty prompt injection finding category. Using UNKNOWN.");
return PromptInjectionFindingCategory.UNKNOWN;
}
//
// The enum members are the wire value in upper case, so upper-casing replaces a naming
// policy. Values starting with a digit or sign are rejected up front, because Enum.TryParse
// would otherwise accept "0" or "-1" as a category:
//
if (!char.IsAsciiDigit(text[0]) && text[0] is not ('-' or '+') && Enum.TryParse<PromptInjectionFindingCategory>(text.ToUpperInvariant(), out var category))
return category;
LOG.LogWarning("The runtime reported the unknown prompt injection finding category '{Category}'. Using UNKNOWN.", text);
return PromptInjectionFindingCategory.UNKNOWN;
}
public override void Write(Utf8JsonWriter writer, PromptInjectionFindingCategory value, JsonSerializerOptions options) => writer.WriteStringValue(value.ToString().ToLowerInvariant());
}
@@ -0,0 +1,159 @@
using AIStudio.Settings;
using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.Services;
namespace AIStudio.Tools.Security;
/// <summary>
/// Filters prompt injections out of external content before it reaches a model.
/// </summary>
/// <remarks>
/// The detection itself lives in the Rust runtime. File content is filtered while the runtime
/// streams it, so it never passes through here; what this service adds is the path for content
/// the runtime does not read itself — web pages and retrieval contexts — and the reporting the
/// user sees.
/// </remarks>
public sealed class PromptInjectionGuardService(
RustService rustService,
SettingsManager settingsManager,
ILogger<PromptInjectionGuardService> logger,
ILoggerFactory loggerFactory)
{
public const string WIKI_URL = "https://en.wikipedia.org/wiki/Prompt_engineering#Prompt_injection";
private const string DETECTION_LOG_CATEGORY = "PromptInjectionProtection";
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(PromptInjectionGuardService).Namespace, nameof(PromptInjectionGuardService));
private readonly ILogger detectionLogger = loggerFactory.CreateLogger(DETECTION_LOG_CATEGORY);
private readonly Lock reportLock = new();
private readonly List<PromptInjectionScanResult> pendingResults = [];
private int openActions;
/// <summary>
/// Filters prompt injections out of a text the runtime did not read itself, such as a web
/// page or a retrieval context.
/// </summary>
/// <remarks>
/// Returns usable text in every case. When the runtime cannot be reached, the text is passed
/// through unchanged: refusing the user's content because a check could not run would cost
/// them their work over a check that is best-effort anyway. The failure is logged and shown,
/// so it does not pass silently.
/// </remarks>
/// <param name="text">The content to filter.</param>
/// <param name="source">Where the content came from, for the report shown to the user.</param>
/// <returns>The content with any suspicious passages removed.</returns>
public async Task<string> SanitizeAsync(string text, PromptInjectionSource source)
{
if (string.IsNullOrWhiteSpace(text))
return text;
if (await rustService.SanitizePromptInjections(text) is not { } response)
{
logger.LogError("Could not check {SourceKind} '{SourceLabel}' for prompt injections. The content is used unchanged.", source.Kind, source.Label);
await MessageBus.INSTANCE.SendWarning(new(
Icons.Material.Filled.GppMaybe,
string.Format(TB("AI Studio could not check '{0}' for prompt injections. The content is used as it is."), source.NotificationLabel)));
return text;
}
if (response.RedactedCount > 0)
await this.ReportAsync(new(source, response.Findings, response.RedactedCount));
return response.SanitizedText;
}
/// <summary>
/// Records what was filtered out of one piece of content and tells the user about it.
/// </summary>
/// <remarks>
/// Within a BeginAction scope the result is collected and reported together
/// with the rest of that action. Outside of one it is reported immediately: a result that
/// simply waited for the next scope would either never reach the user, or reach them as
/// part of an unrelated action later on.
/// </remarks>
public async Task ReportAsync(PromptInjectionScanResult result)
{
if (!result.WasFiltered)
return;
bool reportNow;
lock (this.reportLock)
{
this.pendingResults.Add(result);
reportNow = this.openActions is 0;
}
if (reportNow)
await this.ReportPendingAsync();
}
/// <summary>
/// Marks the start of one user action, such as attaching a batch of files or sending a
/// message.
/// </summary>
/// <remarks>
/// Results are collected until the action finishes, so the user gets one report about
/// twenty documents instead of twenty reports. Actions may nest: only the outermost one
/// reports.
/// </remarks>
/// <returns>A scope that reports what was filtered once it is disposed.</returns>
public ReportingScope BeginAction()
{
lock (this.reportLock)
this.openActions++;
return new(this);
}
private async Task EndActionAsync()
{
lock (this.reportLock)
{
this.openActions--;
// An inner scope reports nothing: the action the user started is still running.
if (this.openActions > 0)
return;
}
await this.ReportPendingAsync();
}
private async Task ReportPendingAsync()
{
List<PromptInjectionScanResult> results;
lock (this.reportLock)
{
if (this.pendingResults.Count is 0)
return;
results = [..this.pendingResults];
this.pendingResults.Clear();
}
var totalCount = results.Sum(result => result.RedactedCount);
this.detectionLogger.LogWarning(
"Detected and removed {PassageCount} potentially dangerous passage(s) in {SourceCount} content source(s).",
totalCount,
results.Count);
await MessageBus.INSTANCE.SendWarning(new(
Icons.Material.Filled.GppMaybe,
results.Count is 1
? string.Format(TB("AI Studio removed suspicious instructions from '{0}' before using it."), results[0].Source.NotificationLabel)
: string.Format(TB("AI Studio removed suspicious instructions from {0} sources before using them."), results.Count)));
if (settingsManager.ConfigurationData.App.ShowPromptInjectionAlert)
await MessageBus.INSTANCE.SendMessage<PromptInjectionAlertMessage>(null, Event.SHOW_PROMPT_INJECTION_ALERT, new(results));
}
/// <summary>
/// Reports everything filtered during one user action when it goes out of scope.
/// </summary>
public sealed class ReportingScope(PromptInjectionGuardService guardService) : IAsyncDisposable
{
public async ValueTask DisposeAsync() => await guardService.EndActionAsync();
}
}
@@ -0,0 +1,19 @@
namespace AIStudio.Tools.Security;
/// <summary>
/// What the runtime filtered out of one piece of external content.
/// </summary>
/// <param name="Source">Where the content came from, so the user can tell which file or page it was.</param>
/// <param name="Findings">The passages that were removed. Capped by the runtime.</param>
/// <param name="RedactedCount">How many passages were removed in total, which may exceed the number of findings.</param>
public sealed record PromptInjectionScanResult(PromptInjectionSource Source, IReadOnlyList<PromptInjectionFinding> Findings, int RedactedCount)
{
/// <summary>
/// Gets a value indicating whether anything was filtered out of this content.
/// </summary>
/// <remarks>
/// The content itself stays usable either way: passages are removed, the content around
/// them is not rejected.
/// </remarks>
public bool WasFiltered => this.RedactedCount > 0;
}
@@ -0,0 +1,16 @@
namespace AIStudio.Tools.Security;
public readonly record struct PromptInjectionSource(PromptInjectionSourceKind Kind, string Label)
{
public string NotificationLabel => this.Kind is PromptInjectionSourceKind.FILE_CONTENT or PromptInjectionSourceKind.CHAT_ATTACHMENT
? Path.GetFileName(this.Label)
: this.Label;
public static PromptInjectionSource WebContent(string url) => new(PromptInjectionSourceKind.WEB_CONTENT, url);
public static PromptInjectionSource FileContent(string filePath) => new(PromptInjectionSourceKind.FILE_CONTENT, filePath);
public static PromptInjectionSource ChatAttachment(string filePath) => new(PromptInjectionSourceKind.CHAT_ATTACHMENT, filePath);
public static PromptInjectionSource RetrievalContext(string dataSourceName, string path) => new(PromptInjectionSourceKind.RETRIEVAL_CONTEXT, $"{dataSourceName}: {path}");
}
@@ -0,0 +1,10 @@
namespace AIStudio.Tools.Security;
public enum PromptInjectionSourceKind
{
UNKNOWN = 0,
WEB_CONTENT,
FILE_CONTENT,
CHAT_ATTACHMENT,
RETRIEVAL_CONTEXT,
}
@@ -0,0 +1,17 @@
using AIStudio.Tools.PluginSystem;
namespace AIStudio.Tools.Security;
public static class PromptInjectionSourceKindExtensions
{
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(PromptInjectionSourceKindExtensions).Namespace, nameof(PromptInjectionSourceKindExtensions));
public static string GetDisplayName(this PromptInjectionSourceKind kind) => kind switch
{
PromptInjectionSourceKind.WEB_CONTENT => TB("Web content"),
PromptInjectionSourceKind.FILE_CONTENT => TB("File content"),
PromptInjectionSourceKind.CHAT_ATTACHMENT => TB("Chat attachment"),
PromptInjectionSourceKind.RETRIEVAL_CONTEXT => TB("Retrieved context"),
_ => TB("Unknown"),
};
}
@@ -1,5 +1,6 @@
using System.Text;
using System.Text.Json;
using AIStudio.Tools.Security;
namespace AIStudio.Tools.Services;
@@ -17,6 +18,13 @@ public sealed partial class RustService
public async Task<FileExtractionResult> ReadArbitraryFileData(string path, int maxChunks, bool extractImages = false)
{
//
// The runtime filters prompt injections while it streams the file. Doing it there rather
// than here means the whole document never has to exist in memory at once, which is what
// makes documents of a few thousand pages affordable.
//
var guardService = Program.SERVICE_PROVIDER.GetRequiredService<PromptInjectionGuardService>();
var streamId = Guid.NewGuid().ToString();
var requestUri = $"/retrieval/fs/extract?path={Uri.EscapeDataString(path)}&stream_id={streamId}&extract_images={extractImages}";
@@ -25,6 +33,8 @@ public sealed partial class RustService
var resultBuilder = new StringBuilder();
var failedPages = new List<int>();
var promptInjectionFindings = new List<PromptInjectionFinding>();
var promptInjectionRedactedCount = 0;
var hasPartialFailure = false;
var failureCode = FileExtractionErrorCode.NONE;
string? failureMessage = null;
@@ -124,6 +134,17 @@ public sealed partial class RustService
detectedFormat = error.DetectedFormat;
}
}
else if (processedEvent.PromptInjection is { } promptInjection)
{
//
// Not a failure: the passages were removed and the document around them is
// intact. It only needs to reach the user, so they know their document was
// changed before the AI saw it.
//
promptInjectionRedactedCount += promptInjection.RedactedCount;
if (promptInjection.Findings is { } findings)
promptInjectionFindings.AddRange(findings);
}
else if (processedEvent.Content is not null)
resultBuilder.AppendLine(processedEvent.Content);
@@ -174,8 +195,28 @@ public sealed partial class RustService
return FileExtractionResult.Failed(FileExtractionErrorCode.NO_CONTENT, "Reading the file produced no content.");
}
return hasPartialFailure
var result = hasPartialFailure
? FileExtractionResult.Partial(content, failedPages, detectedFormat)
: FileExtractionResult.Success(content, detectedFormat);
if (promptInjectionRedactedCount is 0)
return result;
//
// Reported from here rather than from the callers: every way of reading a file passes
// through this method, so this is the one place where no caller can forget it.
//
await guardService.ReportAsync(new(PromptInjectionSource.FileContent(path), promptInjectionFindings, promptInjectionRedactedCount));
//
// Filtering does not change the outcome: the passages were removed and the document
// around them is intact. The findings travel along so a caller can show them next to
// the document they belong to.
//
return result with
{
PromptInjectionFindings = promptInjectionFindings,
PromptInjectionRedactedCount = promptInjectionRedactedCount,
};
}
}
@@ -0,0 +1,49 @@
using AIStudio.Tools.Rust;
namespace AIStudio.Tools.Services;
public sealed partial class RustService
{
/// <summary>
/// How long one sanitize request may take.
/// </summary>
/// <remarks>
/// Web pages and retrieval contexts are small, so this only exists to keep a stuck runtime
/// from blocking the caller forever.
/// </remarks>
private static readonly TimeSpan SANITIZE_TIMEOUT = TimeSpan.FromSeconds(30);
/// <summary>
/// Asks the runtime to filter prompt injections out of a text.
/// </summary>
/// <remarks>
/// File content does not go through here: the runtime filters it while it streams the file.
/// This is the path for content the app fetched itself, i.e. web pages and retrieval contexts.
/// </remarks>
/// <param name="text">The content to filter.</param>
/// <returns>The filtered content and what was found or null when the runtime could not be reached.</returns>
public async Task<SanitizePromptInjectionsResponse?> SanitizePromptInjections(string text)
{
try
{
using var timeoutTokenSource = new CancellationTokenSource(SANITIZE_TIMEOUT);
using var response = await this.http.PostAsJsonAsync(
"/security/prompt-injection/sanitize",
new SanitizePromptInjectionsRequest(text),
cancellationToken: timeoutTokenSource.Token);
if (!response.IsSuccessStatusCode)
{
this.logger?.LogError("Failed to check a text for prompt injections. Status: {StatusCode}, reason: '{ReasonPhrase}'", response.StatusCode, response.ReasonPhrase);
return null;
}
return await response.Content.ReadFromJsonAsync<SanitizePromptInjectionsResponse>(timeoutTokenSource.Token);
}
catch (Exception exception)
{
this.logger?.LogError(exception, "Failed to check a text for prompt injections.");
return null;
}
}
}
@@ -1 +1,2 @@
# v26.8.2, build 255 (2026-08-xx xx:xx UTC)
- Added protection against prompt injection. Documents, web pages, and retrieved content can carry instructions written for the AI rather than for you, for example, text telling it to ignore its rules or to hand over its instructions. AI Studio now always removes such passages before the content reaches a model, while the rest of your document stays intact and usable. When something was removed, AI Studio tells you and can show you which passages it took out. You can turn off the detailed dialog in the app settings. For IT departments: the new setting `DataApp.ShowPromptInjectionAlert` lets you configure the detailed dialog for your organization. Many thanks to Sabrina `Sabrina-devops` for implementing this feature and to Simon `SimonBpunkt` for his work on the detection patterns and their translations.