From 24f5127785a595cc00f4aba526874283ea6ebe40 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Fri, 21 Mar 2025 20:34:01 +0100 Subject: [PATCH] Implemented a mechanism for excluding forbidden plugins --- .../Tools/PluginSystem/ForbiddenPlugins.cs | 99 +++++++++++++++++++ .../Tools/PluginSystem/PluginCheckResult.cs | 8 ++ .../Tools/PluginSystem/PluginFactory.cs | 3 + 3 files changed, 110 insertions(+) create mode 100644 app/MindWork AI Studio/Tools/PluginSystem/ForbiddenPlugins.cs create mode 100644 app/MindWork AI Studio/Tools/PluginSystem/PluginCheckResult.cs diff --git a/app/MindWork AI Studio/Tools/PluginSystem/ForbiddenPlugins.cs b/app/MindWork AI Studio/Tools/PluginSystem/ForbiddenPlugins.cs new file mode 100644 index 00000000..b38459d6 --- /dev/null +++ b/app/MindWork AI Studio/Tools/PluginSystem/ForbiddenPlugins.cs @@ -0,0 +1,99 @@ +namespace AIStudio.Tools.PluginSystem; + +/// +/// Checks if a plugin is forbidden. +/// +public static class ForbiddenPlugins +{ + private const string ID_PATTERN = "ID = \""; + private static readonly int ID_PATTERN_LEN = ID_PATTERN.Length; + + /// + /// Checks if the given code represents a forbidden plugin. + /// + /// The code to check. + /// The result of the check. + public static PluginCheckResult Check(ReadOnlySpan code) + { + var endIndex = 0; + var foundAnyId = false; + var id = ReadOnlySpan.Empty; + while (true) + { + // Create a slice of the code starting at the end index. + // This way we can search for all IDs in the code: + code = code[endIndex..]; + + // Read the next ID as a string: + if (!TryGetId(code, out id, out endIndex)) + { + // When no ID was found at all, we block this plugin. + // When another ID was found previously, we allow this plugin. + if(foundAnyId) + return new PluginCheckResult(false, null); + + return new PluginCheckResult(true, "No ID was found."); + } + + // Try to parse the ID as a GUID: + if (!Guid.TryParse(id, out var parsedGuid)) + { + // Again, when no ID was found at all, we block this plugin. + if(foundAnyId) + return new PluginCheckResult(false, null); + + return new PluginCheckResult(true, "The ID is not a valid GUID."); + } + + // Check if the GUID is forbidden: + if (FORBIDDEN_PLUGINS.TryGetValue(parsedGuid, out var reason)) + return new PluginCheckResult(true, reason); + + foundAnyId = true; + } + } + + private static bool TryGetId(ReadOnlySpan code, out ReadOnlySpan id, out int endIndex) + { + // + // Please note: the code variable is a slice of the original code. + // That means the indices are relative to the slice, not the original code. + // + + id = ReadOnlySpan.Empty; + endIndex = 0; + + // Find the next ID: + var idStartIndex = code.IndexOf(ID_PATTERN); + if (idStartIndex < 0) + return false; + + // Find the start index of the value (Guid): + var valueStartIndex = idStartIndex + ID_PATTERN_LEN; + + // Find the end index of the value. In order to do that, + // we create a slice of the code starting at the value + // start index. That means that the end index is relative + // to the inner slice, not the original code nor the outer slice. + var valueEndIndex = code[valueStartIndex..].IndexOf('"'); + if (valueEndIndex < 0) + return false; + + // From the perspective of the start index is the end index + // the length of the value: + endIndex = valueStartIndex + valueEndIndex; + id = code.Slice(valueStartIndex, valueEndIndex); + return true; + } + + /// + /// The forbidden plugins. + /// + /// + /// A dictionary that maps the GUID of a plugin to the reason why it is forbidden. + /// + // ReSharper disable once CollectionNeverUpdated.Local + private static readonly Dictionary FORBIDDEN_PLUGINS = + [ + ]; +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginCheckResult.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginCheckResult.cs new file mode 100644 index 00000000..f390a47d --- /dev/null +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginCheckResult.cs @@ -0,0 +1,8 @@ +namespace AIStudio.Tools.PluginSystem; + +/// +/// Represents the result of a plugin check. +/// +/// In case the plugin is forbidden, this is true. +/// The message that describes why the plugin is forbidden. +public readonly record struct PluginCheckResult(bool IsForbidden, string? Message); \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs index b9658154..e20d80f4 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs @@ -92,6 +92,9 @@ public static class PluginFactory public static async Task Load(string path, string code, CancellationToken cancellationToken = default) { + if(ForbiddenPlugins.Check(code) is { IsForbidden: true } forbiddenState) + return new NoPlugin($"This plugin is forbidden: {forbiddenState.Message}"); + var state = LuaState.Create(); try