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