From 827fff101c0e669539a7a3c5c6121c51c108f0a0 Mon Sep 17 00:00:00 2001 From: hart_s3 Date: Wed, 15 Jul 2026 16:31:29 +0200 Subject: [PATCH] Refactored prompt injection handling logic with enhanced blocking patterns. --- .../Assistants/I18N/allTexts.lua | 67 +++++++- .../Dialogs/PromptInjectionAlertDialog.razor | 154 ++++++++++++++---- .../PromptInjectionAlertDialog.razor.cs | 28 ++++ .../plugin.lua | 102 ++++++++++++ .../plugin.lua | 66 ++++++++ .../Tools/Security/PromptInjectionFinding.cs | 2 +- .../Security/PromptInjectionGuardService.cs | 2 +- .../Tools/Security/PromptInjectionPatterns.cs | 2 +- .../Tools/Security/PromptInjectionScanner.cs | 99 +++++++++-- 9 files changed, 464 insertions(+), 58 deletions(-) diff --git a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua index 357b03b4..4d108090 100644 --- a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua +++ b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua @@ -4738,26 +4738,77 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINGGUIDELINEDIALOG::T384594633"] = "Th -- Prompting Guideline UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINGGUIDELINEDIALOG::T4250996615"] = "Prompting Guideline" --- Source -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T1642243064"] = "Source" +-- Chat attachment +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T1071345316"] = "Chat attachment" + +-- Danger detected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T1081126836"] = "Danger detected" + +-- Content source +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T1129278507"] = "Content source" + +-- Attempt to override instructions +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T161976090"] = "Attempt to override instructions" + +-- Attempt to expose protected data +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T2050274293"] = "Attempt to expose protected data" + +-- Attempt to bypass safeguards +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T2260642992"] = "Attempt to bypass safeguards" + +-- Attempt to change the AI's role +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T2340508370"] = "Attempt to change the AI's role" -- AI Studio blocked this content before it reached a model or agent. UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T2378027194"] = "AI Studio blocked this content before it reached a model or agent." +-- Hidden instructions using markup +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T2526538070"] = "Hidden instructions using markup" + +-- Web content +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T2626468388"] = "Web content" + +-- 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." + +-- Hidden instructions using delimiters +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T329656456"] = "Hidden instructions using delimiters" + +-- Retrieved context +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T3347144620"] = "Retrieved context" + -- Close UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T3448155331"] = "Close" --- Prompt Injection Detected -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T3580322580"] = "Prompt Injection Detected" +-- Attempt to manipulate an agent +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T355252317"] = "Attempt to manipulate an agent" --- Source kind -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T47437466"] = "Source kind" +-- File content +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T3788064862"] = "File content" + +-- Persistent or delayed instruction +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T4169123215"] = "Persistent or delayed instruction" + +-- 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." + +-- Detected content +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T4223810750"] = "Detected content" -- More information UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T475337262"] = "More information" --- Detected signals -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T556618402"] = "Detected signals" +-- Hidden instructions using encoding +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T49495195"] = "Hidden instructions using encoding" + +-- Hide more information +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T808738984"] = "Hide more information" + +-- Obfuscated instruction +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T87316699"] = "Obfuscated instruction" -- Hugging Face Inference Provider UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1085481431"] = "Hugging Face Inference Provider" diff --git a/app/MindWork AI Studio/Dialogs/PromptInjectionAlertDialog.razor b/app/MindWork AI Studio/Dialogs/PromptInjectionAlertDialog.razor index e7dbe490..75011dad 100644 --- a/app/MindWork AI Studio/Dialogs/PromptInjectionAlertDialog.razor +++ b/app/MindWork AI Studio/Dialogs/PromptInjectionAlertDialog.razor @@ -1,50 +1,132 @@ @using AIStudio.Tools.Security @inherits MSGComponentBase - - - - - @T("Prompt Injection Detected") - - + + - @if (this.Result is not null) + + @if (Result is not null) { - - @T("AI Studio blocked this content before it reached a model or agent.") - + + + + + - - @T("Source kind"): @this.Result.Source.Kind - - - @T("Source"): @this.Result.Source.Label - + + + @T("Danger detected") + - - @T("Detected signals") - - - @foreach (var finding in this.Result.Findings) - { - - @($"{finding.Category} / {finding.DetectionStage}: {finding.Snippet}") - - } - + + @T("AI Studio blocked this content before it reached a model or agent.") + + + + - - @T("More information"): - - @PromptInjectionGuardService.WIKI_URL - - + + + + + @(showPromptInjectionInformation + ? T("Hide more information") + : T("More information")) + + + + + + + @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.") + + + + + + + + + + + @T("Source type") + + + + @this.GetSourceKindLabel(Result.Source.Kind) + + + + + + + + + + @T("Content source") + + + + @Result.Source.Label + + + + + + + + + + @T("Detected content") + + + @foreach (var finding in Result.Findings) + { + + + + @($"{this.GetFindingCategoryLabel(finding.Category)}") + + + + + @finding.Snippet + + + } + + + + + + + + + @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.") + + + + @PromptInjectionGuardService.WIKI_URL + + + } - + + @T("Close") - \ No newline at end of file + diff --git a/app/MindWork AI Studio/Dialogs/PromptInjectionAlertDialog.razor.cs b/app/MindWork AI Studio/Dialogs/PromptInjectionAlertDialog.razor.cs index 0ddd0c1b..433ed566 100644 --- a/app/MindWork AI Studio/Dialogs/PromptInjectionAlertDialog.razor.cs +++ b/app/MindWork AI Studio/Dialogs/PromptInjectionAlertDialog.razor.cs @@ -7,6 +7,8 @@ namespace AIStudio.Dialogs; public partial class PromptInjectionAlertDialog : MSGComponentBase { + private bool showPromptInjectionInformation; + [CascadingParameter] private IMudDialogInstance MudDialog { get; set; } = null!; @@ -14,4 +16,30 @@ public partial class PromptInjectionAlertDialog : MSGComponentBase public PromptInjectionScanResult Result { get; set; } = null!; private void Close() => this.MudDialog.Close(); + + private void TogglePromptInjectionInformation() => this.showPromptInjectionInformation = !this.showPromptInjectionInformation; + + private string GetSourceKindLabel(string sourceKind) => sourceKind switch + { + "Web content" => T("Web content"), + "File content" => T("File content"), + "Chat attachment" => T("Chat attachment"), + "Retrieval context" => T("Retrieved context"), + _ => sourceKind, + }; + + private string GetFindingCategoryLabel(string category) => category switch + { + "override" => T("Attempt to override instructions"), + "role_override" => T("Attempt to change the AI's role"), + "exfiltration" => T("Attempt to expose protected data"), + "jailbreak" => T("Attempt to bypass safeguards"), + "agent_manipulation" => T("Attempt to manipulate an agent"), + "delimiter_evasion" => T("Hidden instructions using delimiters"), + "markup_evasion" => T("Hidden instructions using markup"), + "encoding_evasion" => T("Hidden instructions using encoding"), + "persistence" => T("Persistent or delayed instruction"), + "evasion" => T("Obfuscated instruction"), + _ => category, + }; } \ No newline at end of file diff --git a/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua b/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua index 5af93419..d42ac69c 100644 --- a/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua +++ b/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua @@ -4740,6 +4740,78 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINGGUIDELINEDIALOG::T384594633"] = "De -- Prompting Guideline UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINGGUIDELINEDIALOG::T4250996615"] = "Prompting-Leitfaden" +-- Chat attachment +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T1071345316"] = "Dateianhang" + +-- Danger detected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T1081126836"] = "Gefahr erkannt" + +-- Content source +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T1129278507"] = "Datei" + +-- Prompt injection hides instructions in untrusted content to make an AI model ignore its intended rules or perform 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 ungewollte Aktionen auszuführen." + +-- Attempt to override instructions +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T161976090"] = "Versuch, Anweisungen zu überschreiben" + +-- Attempt to expose protected data +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T2050274293"] = "Versuch, geschützte Daten offenzulegen" + +-- Attempt to bypass safeguards +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T2260642992"] = "Versuch, Sicherheitsvorkehrungen zu umgehen" + +-- Attempt to change the AI's role +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T2340508370"] = "Versuch, die Rolle der KI zu ändern" + +-- AI Studio blocked this content before it reached a model or agent. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T2378027194"] = "AI Studio hat diesen Inhalt blockiert, bevor er ein Modell oder einen Agenten erreicht hat." + +-- Hidden instructions using markup +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T2526538070"] = "Versteckte Anweisungen mit Markup" + +-- Web content +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T2626468388"] = "Web-Inhalt" + +-- Source type +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T280442848"] = "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, um KI-Systeme wie Chatbots zu manipulieren. Dabei platziert ein Angreifer irreführende Anweisungen in Inhalten, sodass die KI diese als legitim betrachtet. Dadurch kann die KI Sicherheitsvorkehrungen ignorieren, private Informationen preisgeben oder schädliche Inhalte erzeugen." + +-- Hidden instructions using delimiters +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T329656456"] = "Versteckte Anweisungen mit Trennzeichen" + +-- Retrieved context +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T3347144620"] = "Es scheint, dass der Kontext bereits bereitgestellt wurde. Bitte geben Sie den Text an, den Sie aus dem Englischen (US) ins Deutsche (Deutschland) übersetzen möchten. Ich werde sicherstellen, dass die Übersetzung präzise, natürlich klingt und für die Zielgruppe leicht verständlich ist." + +-- Close +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T3448155331"] = "Schließen" + +-- Attempt to manipulate an agent +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T355252317"] = "Versuch, einen Agenten zu manipulieren" + +-- File content +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T3788064862"] = "Inhalt der Datei" + +-- Persistent or delayed instruction +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T4169123215"] = "Dauerhafte oder verzögerte Anweisung" + +-- Detected content +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T4223810750"] = "Erkannter Inhalt" + +-- More information +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T475337262"] = "Weitere Informationen" + +-- Hidden instructions using encoding +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T49495195"] = "Versteckte Anweisungen mit Kodierung" + +-- Hide more information +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T808738984"] = "Weniger Informationen anzeigen" + +-- Obfuscated instruction +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T87316699"] = "Verschleierte Anweisung" + -- Hugging Face Inference Provider UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1085481431"] = "Hugging Face Inferenz-Anbieter" @@ -5220,6 +5292,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T6790 -- When enabled, you can preselect options. This is might be useful when you prefer a specific language or LLM model. UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T711745239"] = "Wenn diese Option aktiviert ist, können Sie Voreinstellungen vornehmen. Das kann nützlich sein, wenn Sie eine bestimmte Sprache oder ein bestimmtes LLM-Modell bevorzugen." +-- Protect against prompt injection in external content? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T1040385578"] = "Soll der Zugriff auf externe Inhalte vor Prompt-Injection geschützt werden?" + -- Preselect one of your chat templates? UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T1402022556"] = "Eine ihrer Chat-Vorlagen vorab auswählen?" @@ -5241,12 +5316,18 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T1915793195"] -- Preselect a profile UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T2322771068"] = "Profil vorauswählen" +-- A blocking alert explains the detected attack pattern +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T2341755932"] = "Eine blockierende Warnung erklärt das erkannte Angriffsmuster." + -- Apply default data source option when sending assistant results to chat UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T2510376349"] = "Standarddatenquelle verwenden, wenn Assistentenergebnisse in den Chat gesendet werden" -- Control how the LLM provider for added chats is selected. UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T263621180"] = "Steuern Sie, wie der LLM-Anbieter für hinzugefügte Chats ausgewählt wird." +-- Show a learning alert when prompt injection is detected? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T2754886869"] = "Lernwarnung anzeigen, wenn Prompt-Injection erkannt wird?" + -- Provider selection when loading a chat and sending assistant results to chat UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T2868379953"] = "Anbieterauswahl beim Laden eines Chats und beim Senden von Assistentenergebnissen in den Chat" @@ -5256,6 +5337,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T2913693228"] -- Do you want to use any shortcut to send your input? UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T2936560092"] = "Möchten Sie eine Tastenkombination verwenden, um ihre Eingabe zu senden?" +-- Shows an explanation dialog with an external reference when AI Studio blocks suspicious content. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T3129787554"] = "Zeigt einen Erklärungsdialog mit einem externen Verweis an, wenn AI Studio verdächtige Inhalte blockiert." + -- Would you like to set one of your chat templates as the default for chats? UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T3234927721"] = "Möchten Sie eine ihrer Chat-Vorlagen als Standard für alle Chats festlegen?" @@ -5265,6 +5349,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T3383186996"] -- Close UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T3448155331"] = "Schließen" +-- Only the block notification is shown +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T3491678837"] = "Nur die Blockbenachrichtigung wird angezeigt" + -- First (oldest) message is shown, after loading a chat UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T3507181366"] = "Die erste (älteste) Nachricht wird nach dem Laden eines Chats angezeigt." @@ -5277,9 +5364,15 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T3730599555"] -- Latest message is shown, after loading a chat UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T3755993611"] = "Die neueste Nachricht wird nach dem Laden eines Chats angezeigt." +-- Potential prompt injections are blocked before they reach an LLM +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T3982766677"] = "Potenzielle Prompt-Injections werden blockiert, bevor sie ein LLM erreichen." + -- Do you want to apply the default data source options when sending assistant results to chat? UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T4033153439"] = "Möchten Sie die Standardoptionen für Datenquellen verwenden, wenn die Ergebnisse des Assistenten an den Chat gesendet werden?" +-- External content is passed through without prompt-injection checks +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T4245463281"] = "Externer Inhalt wird ohne Prompt-Injection-Prüfungen durchgereicht" + -- When enabled, you can preselect chat options. This is might be useful when you prefer a specific provider. UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T477675197"] = "Wenn aktiviert, können Sie Chat-Optionen im Voraus auswählen. Das kann nützlich sein, wenn Sie einen bestimmten Anbieter bevorzugen." @@ -5289,6 +5382,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T492357592"] = -- When enabled, the latest message is shown after loading a chat. When disabled, the first (oldest) message is shown. UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T582516016"] = "Wenn diese Option aktiviert ist, wird nach dem Laden eines Chats die neueste Nachricht angezeigt. Wenn sie deaktiviert ist, wird die erste (älteste) Nachricht angezeigt." +-- Checks web content, file attachments, retrieval context, and similar external input for prompt-injection patterns before it is sent to a model or agent. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T69192586"] = "Prüft Webinhalte, Dateianhänge, Abrufkontext und ähnliche externe Eingaben auf Prompt-Injection-Muster, bevor sie an ein Modell oder einen Agenten gesendet werden." + -- Customize your AI experience with chat templates. Whether you want to experiment with prompt engineering, simply use a custom system prompt in the standard chat interface, or create a specialized assistant, chat templates give you full control. Similar to common AI companies' playgrounds, you can define your own system prompts and leverage assistant prompts for providers that support them. UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T1172171653"] = "Passen Sie ihre KI-Erfahrung mit Chat-Vorlagen an. Egal, ob Sie mit Prompt-Engineering experimentieren, einfach einen eigenen System-Prompt im normalen Chat verwenden oder einen spezialisierten Assistenten erstellen möchten – mit Chat-Vorlagen haben Sie die volle Kontrolle. Ähnlich wie in den Playgrounds gängiger KI-Anbieter können Sie eigene System-Prompts festlegen und bei unterstützenden Anbietern auch Assistenten-Prompts nutzen." @@ -6360,6 +6456,9 @@ UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T2936083926"] = "AI Studio konnte -- Writing UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T2979224202"] = "Schreiben" +-- Prompt Injection Detected +UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T3580322580"] = "Prompt-Injection erkannt" + -- Show details UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T3692372066"] = "Details anzeigen" @@ -8457,6 +8556,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T378481461"] = "Source Code -- Document UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T4165204724"] = "Dokument" +-- AI Studio blocked content from '{0}' because it looks like a prompt-injection attempt. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONGUARDSERVICE::T3560909296"] = "AI Studio hat Inhalte von '{0}' blockiert, da es sich um einen möglichen Prompt-Injection-Versuch handelt." + -- Pandoc Installation UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PANDOCAVAILABILITYSERVICE::T185447014"] = "Pandoc-Installation" diff --git a/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua b/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua index 39d4bec7..d8c54938 100644 --- a/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua +++ b/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua @@ -4740,6 +4740,42 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINGGUIDELINEDIALOG::T384594633"] = "Th -- Prompting Guideline UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINGGUIDELINEDIALOG::T4250996615"] = "Prompting Guideline" +-- Danger detected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T1081126836"] = "Danger detected" + +-- More Information +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T1266371726"] = "More Information" + +-- Prompt injection is an attempt to hide instructions in untrusted content so that an AI model ignores its intended rules or performs unintended actions. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T163682410"] = "Prompt injection is an attempt to hide instructions in untrusted content so that an AI model ignores its intended rules or performs unintended actions." + +-- Source +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T1642243064"] = "Source" + +-- Hide More Information +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T1648537896"] = "Hide More Information" + +-- AI Studio blocked this content before it reached a model or agent. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T2378027194"] = "AI Studio blocked this content before it reached a model or agent." + +-- Prompt Injection detected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T3052247348"] = "Prompt Injection detected" + +-- Close +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T3448155331"] = "Close" + +-- Prompt injection is a trick used to manipulate AI systems like chatbots. Normally, these systems follow the rules set by their developers—such as being helpful and safe. But with prompt injection, an attacker crafts a clever input that makes the AI think it’s receiving a new, legitimate instruction. This can cause the AI to ignore its usual safeguards and do something unintended, like sharing private information or generating harmful content. The issue arises because the AI can’t always tell the difference between a trusted command and a deceptive one. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T3548442822"] = "Prompt injection is a trick used to manipulate AI systems like chatbots. Normally, these systems follow the rules set by their developers—such as being helpful and safe. But with prompt injection, an attacker crafts a clever input that makes the AI think it’s receiving a new, legitimate instruction. This can cause the AI to ignore its usual safeguards and do something unintended, like sharing private information or generating harmful content. The issue arises because the AI can’t always tell the difference between a trusted command and a deceptive one." + +-- Source kind +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T47437466"] = "Source kind" + +-- More information +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T475337262"] = "More information" + +-- Detected signals +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROMPTINJECTIONALERTDIALOG::T556618402"] = "Detected signals" + -- Hugging Face Inference Provider UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1085481431"] = "Hugging Face Inference Provider" @@ -5220,6 +5256,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T6790 -- When enabled, you can preselect options. This is might be useful when you prefer a specific language or LLM model. UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T711745239"] = "When enabled, you can preselect options. This is might be useful when you prefer a specific language or LLM model." +-- Protect against prompt injection in external content? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T1040385578"] = "Protect against prompt injection in external content?" + -- Preselect one of your chat templates? UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T1402022556"] = "Preselect one of your chat templates?" @@ -5241,12 +5280,18 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T1915793195"] -- Preselect a profile UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T2322771068"] = "Preselect a profile" +-- A blocking alert explains the detected attack pattern +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T2341755932"] = "A blocking alert explains the detected attack pattern" + -- Apply default data source option when sending assistant results to chat UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T2510376349"] = "Apply default data source option when sending assistant results to chat" -- Control how the LLM provider for added chats is selected. UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T263621180"] = "Control how the LLM provider for added chats is selected." +-- Show a learning alert when prompt injection is detected? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T2754886869"] = "Show a learning alert when prompt injection is detected?" + -- Provider selection when loading a chat and sending assistant results to chat UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T2868379953"] = "Provider selection when loading a chat and sending assistant results to chat" @@ -5256,6 +5301,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T2913693228"] -- Do you want to use any shortcut to send your input? UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T2936560092"] = "Do you want to use any shortcut to send your input?" +-- Shows an explanation dialog with an external reference when AI Studio blocks suspicious content. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T3129787554"] = "Shows an explanation dialog with an external reference when AI Studio blocks suspicious content." + -- Would you like to set one of your chat templates as the default for chats? UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T3234927721"] = "Would you like to set one of your chat templates as the default for chats?" @@ -5265,6 +5313,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T3383186996"] -- Close UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T3448155331"] = "Close" +-- Only the block notification is shown +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T3491678837"] = "Only the block notification is shown" + -- First (oldest) message is shown, after loading a chat UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T3507181366"] = "First (oldest) message is shown, after loading a chat" @@ -5277,9 +5328,15 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T3730599555"] -- Latest message is shown, after loading a chat UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T3755993611"] = "Latest message is shown, after loading a chat" +-- Potential prompt injections are blocked before they reach an LLM +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T3982766677"] = "Potential prompt injections are blocked before they reach an LLM" + -- Do you want to apply the default data source options when sending assistant results to chat? UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T4033153439"] = "Do you want to apply the default data source options when sending assistant results to chat?" +-- External content is passed through without prompt-injection checks +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T4245463281"] = "External content is passed through without prompt-injection checks" + -- When enabled, you can preselect chat options. This is might be useful when you prefer a specific provider. UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T477675197"] = "When enabled, you can preselect chat options. This is might be useful when you prefer a specific provider." @@ -5289,6 +5346,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T492357592"] = -- When enabled, the latest message is shown after loading a chat. When disabled, the first (oldest) message is shown. UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T582516016"] = "When enabled, the latest message is shown after loading a chat. When disabled, the first (oldest) message is shown." +-- Checks web content, file attachments, retrieval context, and similar external input for prompt-injection patterns before it is sent to a model or agent. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T69192586"] = "Checks web content, file attachments, retrieval context, and similar external input for prompt-injection patterns before it is sent to a model or agent." + -- Customize your AI experience with chat templates. Whether you want to experiment with prompt engineering, simply use a custom system prompt in the standard chat interface, or create a specialized assistant, chat templates give you full control. Similar to common AI companies' playgrounds, you can define your own system prompts and leverage assistant prompts for providers that support them. UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T1172171653"] = "Customize your AI experience with chat templates. Whether you want to experiment with prompt engineering, simply use a custom system prompt in the standard chat interface, or create a specialized assistant, chat templates give you full control. Similar to common AI companies' playgrounds, you can define your own system prompts and leverage assistant prompts for providers that support them." @@ -6360,6 +6420,9 @@ UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T2936083926"] = "AI Studio could -- Writing UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T2979224202"] = "Writing" +-- Prompt Injection Detected +UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T3580322580"] = "Prompt Injection Detected" + -- Show details UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T3692372066"] = "Show details" @@ -8457,6 +8520,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T378481461"] = "Source like p -- Document UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T4165204724"] = "Document" +-- AI Studio blocked content from '{0}' because it looks like a prompt-injection attempt. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONGUARDSERVICE::T3560909296"] = "AI Studio blocked content from '{0}' because it looks like a prompt-injection attempt." + -- Pandoc Installation UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PANDOCAVAILABILITYSERVICE::T185447014"] = "Pandoc Installation" diff --git a/app/MindWork AI Studio/Tools/Security/PromptInjectionFinding.cs b/app/MindWork AI Studio/Tools/Security/PromptInjectionFinding.cs index 738ccb18..27e2388e 100644 --- a/app/MindWork AI Studio/Tools/Security/PromptInjectionFinding.cs +++ b/app/MindWork AI Studio/Tools/Security/PromptInjectionFinding.cs @@ -1,3 +1,3 @@ namespace AIStudio.Tools.Security; -public sealed record PromptInjectionFinding(string RuleId, string Category, string DetectionStage, string Snippet); \ No newline at end of file +public sealed record PromptInjectionFinding(string RuleId, string Category, string Snippet); \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/Security/PromptInjectionGuardService.cs b/app/MindWork AI Studio/Tools/Security/PromptInjectionGuardService.cs index 22bc90f8..1f83da88 100644 --- a/app/MindWork AI Studio/Tools/Security/PromptInjectionGuardService.cs +++ b/app/MindWork AI Studio/Tools/Security/PromptInjectionGuardService.cs @@ -8,7 +8,7 @@ public sealed class PromptInjectionGuardService( SettingsManager settingsManager, ILogger logger) { - public const string WIKI_URL = "https://de.wikipedia.org/wiki/Prompt-Engineering#Prompt_Injection"; + public const string WIKI_URL = "https://en.wikipedia.org/wiki/Prompt_engineering#Prompt_injection"; private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(PromptInjectionGuardService).Namespace, nameof(PromptInjectionGuardService)); diff --git a/app/MindWork AI Studio/Tools/Security/PromptInjectionPatterns.cs b/app/MindWork AI Studio/Tools/Security/PromptInjectionPatterns.cs index 97ae69c8..314f307f 100644 --- a/app/MindWork AI Studio/Tools/Security/PromptInjectionPatterns.cs +++ b/app/MindWork AI Studio/Tools/Security/PromptInjectionPatterns.cs @@ -40,7 +40,7 @@ internal static partial class PromptInjectionPatterns private const string POLICY_BYPASS_PATTERN = """(?:do\s+not|don't|stop\s+to|never)\s+(?:follow|obey|respect|apply|enforce)\s+(?:the\s+)?(?:system|developer|safety|security|content|usage)\s+(?:prompt|message|instructions?|policy|policies|guardrails?|restrictions?)"""; private const string ROLE_REASSIGNMENT_PATTERN = """(?:you\s+are\s+now|you\s+are\s+no\s+longer|act\s+as|pretend\s+to\s+be|simulate\s+being|assume\s+the\s+role\s+of|from\s+now\s+on\s+you\s+are)\s+(?:an\s+)?(?:unfiltered|unrestricted|developer|system|root|admin|jailbroken|evil|dan|do\s+anything\s+now)"""; private const string PRIVILEGED_PERSONA_ACTIVATION_PATTERN = """\b(?:developer\s+mode|debug\s+mode|admin\s+mode|root\s+mode|god\s+mode|maintenance\s+mode|dan\s*(?:mode)?|do\s+anything\s+now|grandmother\s+trick)\b"""; - private const string TOOL_OR_SECRET_EXFILTRATION_PATTERN = """(?:export|send|return|reveal|show|print|list|dump|exfiltrate)\s+(?:all\s+)?(?:tools?|functions?|plugins?|api\s*keys?|keys?|tokens?|credentials?|secrets?|passwords?|hidden\s+instructions?|environment\s+variables?|system\s+information)"""; + private const string TOOL_OR_SECRET_EXFILTRATION_PATTERN = """(?:export|send|return|reveal|show|print|output|list|dump|exfiltrate)\s+(?:all\s+)?(?:tools?|functions?|plugins?|api\s*keys?|keys?|tokens?|credentials?|secrets?|passwords?|hidden\s+instructions?|environment\s+variables?|system\s+information|internal\s+data)"""; private const string CONVERSATION_MEMORY_EXFILTRATION_PATTERN = """(?:(?:show|print|reveal|return|dump|list)\s+(?:the\s+)?(?:conversation\s+history|chat\s+history|memory|scratchpad|chain\s+of\s+thought|reasoning|previous\s+user\s+messages?|prior\s+messages?)|(?:what\s+did\s+(?:the\s+)?previous\s+user\s+say))"""; private const string TOOL_CALL_MANIPULATION_PATTERN = """(?:(?:call|invoke|execute|run|use|trigger)\s+(?:the\s+)?(?:tool|function|plugin|api|browser|web|shell|terminal|command)[^\n]{0,120}(?:with|using|to)\s+(?:these\s+)?(?:arguments|params?|parameters)|(?:do\s+not|don't)\s+ask\s+for\s+(?:confirmation|approval|permission)|(?:silently|secretly|without\s+asking)\s+(?:call|invoke|execute|run|use))"""; private const string AGENT_THOUGHT_INJECTION_PATTERN = """(?:(?:thought|observation|reasoning|scratchpad|tool\s+output|assistant|system|developer)\s*[:=]\s*(?:ignore|bypass|override|reveal|call|execute)|forge\s+(?:an\s+)?(?:observation|tool\s+output|assistant\s+message)|pretend\s+(?:the\s+)?tool\s+(?:returned|said))"""; diff --git a/app/MindWork AI Studio/Tools/Security/PromptInjectionScanner.cs b/app/MindWork AI Studio/Tools/Security/PromptInjectionScanner.cs index ae425b24..bdf1711c 100644 --- a/app/MindWork AI Studio/Tools/Security/PromptInjectionScanner.cs +++ b/app/MindWork AI Studio/Tools/Security/PromptInjectionScanner.cs @@ -9,7 +9,7 @@ public sealed class PromptInjectionScanner(ILogger logge private const int MAX_DECODED_CANDIDATES_PER_ENCODING = 12; private const int MAX_DECODED_TEXT_LENGTH = 12_000; private const int MAX_FINDINGS = 8; - private const int SNIPPET_RADIUS = 80; + private const int MAX_SNIPPET_LENGTH = 240; private static readonly IReadOnlyDictionary<(int Length, char First, char Last), string[]> TYPOGLYCEMIA_KEYWORDS = CreateTypoglycemiaKeywordIndex(); @@ -70,8 +70,8 @@ public sealed class PromptInjectionScanner(ILogger logge if (!match.Success) continue; - var snippet = ExtractSnippet(text, match.Index, match.Length, SNIPPET_RADIUS); - AddFinding(findings, findingKeys, new(rule.Id, rule.Category, stage, snippet)); + var snippet = ExtractSnippet(text, match.Index, match.Length); + AddFinding(findings, findingKeys, new(rule.Id, rule.Category, snippet)); } } @@ -131,8 +131,8 @@ public sealed class PromptInjectionScanner(ILogger logge if (!IsTypoglycemiaVariant(token, keyword)) continue; - var snippet = ExtractSnippet(text, match.Index, match.Length, SNIPPET_RADIUS); - AddFinding(findings, findingKeys, new($"typoglycemia:{keyword}", "evasion", "typoglycemia", snippet)); + var snippet = ExtractSnippet(text, match.Index, match.Length); + AddFinding(findings, findingKeys, new($"typoglycemia:{keyword}", "evasion", snippet)); break; } } @@ -265,17 +265,94 @@ public sealed class PromptInjectionScanner(ILogger logge private static void AddFinding(List findings, HashSet findingKeys, PromptInjectionFinding finding) { - var key = $"{finding.RuleId}|{finding.DetectionStage}|{finding.Snippet}"; + var key = $"{finding.Category}|{finding.Snippet}"; if (findingKeys.Add(key)) findings.Add(finding); } - private static string ExtractSnippet(string text, int index, int length, int radius) + private static string ExtractSnippet(string text, int index, int length) { - var start = Math.Max(0, index - radius); - var end = Math.Min(text.Length, index + length + radius); - var snippet = text[start..end].Replace('\r', ' ').Replace('\n', ' ').Trim(); - return snippet.Length > radius * 2 ? $"{snippet[..(radius * 2)]}..." : snippet; + var matchStart = Math.Clamp(index, 0, text.Length); + var matchEnd = Math.Clamp(index + length, matchStart, text.Length); + var sentenceStart = FindSentenceStart(text, matchStart); + var sentenceEnd = FindSentenceEnd(text, matchEnd); + + while (sentenceStart < matchStart && char.IsWhiteSpace(text[sentenceStart])) + sentenceStart++; + + while (sentenceEnd > matchEnd && char.IsWhiteSpace(text[sentenceEnd - 1])) + sentenceEnd--; + + if (sentenceEnd - sentenceStart <= MAX_SNIPPET_LENGTH) + return NormalizeSnippet(text[sentenceStart..sentenceEnd]); + + var matchLength = matchEnd - matchStart; + if (matchLength >= MAX_SNIPPET_LENGTH - 6) + return NormalizeSnippet(text[matchStart..matchEnd]); + + var contextBudget = MAX_SNIPPET_LENGTH - 6 - matchLength; + var leftAvailable = matchStart - sentenceStart; + var rightAvailable = sentenceEnd - matchEnd; + var leftLength = Math.Min(leftAvailable, contextBudget / 2); + var rightLength = Math.Min(rightAvailable, contextBudget - leftLength); + var remainingBudget = contextBudget - leftLength - rightLength; + + leftLength += Math.Min(leftAvailable - leftLength, remainingBudget); + remainingBudget = contextBudget - leftLength - rightLength; + rightLength += Math.Min(rightAvailable - rightLength, remainingBudget); + + var snippetStart = matchStart - leftLength; + var snippetEnd = matchEnd + rightLength; + var snippet = NormalizeSnippet(text[snippetStart..snippetEnd]); + var prefix = snippetStart > sentenceStart ? "..." : string.Empty; + var suffix = snippetEnd < sentenceEnd ? "..." : string.Empty; + return $"{prefix}{snippet}{suffix}"; + } + + private static int FindSentenceStart(string text, int matchStart) + { + for (var index = matchStart - 1; index >= 0; index--) + { + if (IsSentenceBoundary(text[index])) + return index + 1; + } + + return matchStart; + } + + private static int FindSentenceEnd(string text, int matchEnd) + { + for (var index = matchEnd; index < text.Length; index++) + { + if (IsSentenceBoundary(text[index])) + return index + 1; + } + + return matchEnd; + } + + private static bool IsSentenceBoundary(char character) => character is '.' or '!' or '?' or '\r' or '\n'; + + private static string NormalizeSnippet(ReadOnlySpan snippet) + { + var normalized = new StringBuilder(snippet.Length); + var previousCharacterWasWhitespace = false; + foreach (var character in snippet) + { + if (char.IsWhiteSpace(character)) + { + if (normalized.Length > 0 && !previousCharacterWasWhitespace) + normalized.Append(' '); + + previousCharacterWasWhitespace = true; + continue; + } + + normalized.Append(character); + previousCharacterWasWhitespace = false; + } + + return normalized.ToString().Trim(); } private static IReadOnlyDictionary<(int Length, char First, char Last), string[]> CreateTypoglycemiaKeywordIndex()