From 3deed7f6037ab841a6887f364cd2992a36f0f32d Mon Sep 17 00:00:00 2001
From: Thorsten Sommer <mail@tsommer.org>
Date: Thu, 27 Mar 2025 20:52:30 +0100
Subject: [PATCH] Made internal plugin detection secure

---
 app/MindWork AI Studio/Tools/PluginSystem/NoPlugin.cs  |  2 +-
 .../Tools/PluginSystem/PluginBase.cs                   | 10 ++--------
 .../Tools/PluginSystem/PluginFactory.Internal.cs       | 10 +++++-----
 .../Tools/PluginSystem/PluginFactory.cs                |  9 ++++++---
 .../Tools/PluginSystem/PluginLanguage.cs               |  2 +-
 5 files changed, 15 insertions(+), 18 deletions(-)

diff --git a/app/MindWork AI Studio/Tools/PluginSystem/NoPlugin.cs b/app/MindWork AI Studio/Tools/PluginSystem/NoPlugin.cs
index 88c745ae..3d9b74d1 100644
--- a/app/MindWork AI Studio/Tools/PluginSystem/NoPlugin.cs	
+++ b/app/MindWork AI Studio/Tools/PluginSystem/NoPlugin.cs	
@@ -6,4 +6,4 @@ namespace AIStudio.Tools.PluginSystem;
 /// Represents a plugin that could not be loaded.
 /// </summary>
 /// <param name="parsingError">The error message that occurred while parsing the plugin.</param>
-public sealed class NoPlugin(string parsingError) : PluginBase(LuaState.Create(), PluginType.NONE, parsingError);
\ No newline at end of file
+public sealed class NoPlugin(string parsingError) : PluginBase(false, LuaState.Create(), PluginType.NONE, parsingError);
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginBase.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginBase.cs
index daa7a16d..2674d4db 100644
--- a/app/MindWork AI Studio/Tools/PluginSystem/PluginBase.cs	
+++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginBase.cs	
@@ -8,12 +8,6 @@ namespace AIStudio.Tools.PluginSystem;
 /// </summary>
 public abstract partial class PluginBase : IPluginMetadata
 {
-    private static readonly Guid[] MANDATORY_INTERNAL_PLUGINS =
-    [
-        new("97dfb1ba-50c4-4440-8dfa-6575daf543c8"), // Language EN-US (base language)
-        new("43065dbc-78d0-45b7-92be-f14c2926e2dc"), // Language DE-DE
-    ];
-    
     private readonly IReadOnlyCollection<string> baseIssues;
     protected readonly LuaState state;
 
@@ -75,7 +69,7 @@ public abstract partial class PluginBase : IPluginMetadata
     /// </remarks>
     public bool IsValid => this is not NoPlugin && this.baseIssues.Count == 0 && this.pluginIssues.Count == 0;
 
-    protected PluginBase(LuaState state, PluginType type, string parseError = "")
+    protected PluginBase(bool isInternal, LuaState state, PluginType type, string parseError = "")
     {
         this.state = state;
         this.Type = type;
@@ -91,7 +85,7 @@ public abstract partial class PluginBase : IPluginMetadata
         if(this.TryInitId(out var issue, out var id))
         {
             this.Id = id;
-            this.IsInternal = MANDATORY_INTERNAL_PLUGINS.Contains(id);
+            this.IsInternal = isInternal;
         }
         else if(this is not NoPlugin)
             issues.Add(issue);
diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Internal.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Internal.cs
index cb9419bd..316376f6 100644
--- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Internal.cs	
+++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Internal.cs	
@@ -46,7 +46,7 @@ public static partial class PluginFactory
                     continue;
                 }
                 
-                await CopyPluginFile(content, metaData);
+                await CopyInternalPluginFile(content, metaData);
             }
         }
         catch
@@ -55,14 +55,14 @@ public static partial class PluginFactory
         }
     }
 
-    private static async Task CopyPluginFile(IFileInfo resourceInfo, InternalPluginData metaData)
+    private static async Task CopyInternalPluginFile(IFileInfo resourceInfo, InternalPluginData metaData)
     {
         await using var inputStream = resourceInfo.CreateReadStream();
         
-        var pluginTypeBasePath = Path.Join(PLUGINS_ROOT, metaData.Type.GetDirectory());
+        var pluginTypeBasePath = Path.Join(INTERNAL_PLUGINS_ROOT, metaData.Type.GetDirectory());
         
-        if (!Directory.Exists(PLUGINS_ROOT))
-            Directory.CreateDirectory(PLUGINS_ROOT);
+        if (!Directory.Exists(INTERNAL_PLUGINS_ROOT))
+            Directory.CreateDirectory(INTERNAL_PLUGINS_ROOT);
         
         if (!Directory.Exists(pluginTypeBasePath))
             Directory.CreateDirectory(pluginTypeBasePath);
diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs
index 73a91b52..81837f73 100644
--- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs	
+++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs	
@@ -14,6 +14,8 @@ public static partial class PluginFactory
     private static readonly string DATA_DIR = SettingsManager.DataDirectory!;
     
     private static readonly string PLUGINS_ROOT = Path.Join(DATA_DIR, "plugins");
+    
+    private static readonly string INTERNAL_PLUGINS_ROOT = Path.Join(PLUGINS_ROOT, ".internal");
 
     private static readonly List<IPluginMetadata> AVAILABLE_PLUGINS = [];
     
@@ -85,8 +87,8 @@ public static partial class PluginFactory
             AVAILABLE_PLUGINS.Add(new PluginMetadata(plugin));
         }
     }
-    
-    public static async Task<PluginBase> Load(string pluginPath, string code, CancellationToken cancellationToken = default)
+
+    private static async Task<PluginBase> Load(string pluginPath, string code, CancellationToken cancellationToken = default)
     {
         if(ForbiddenPlugins.Check(code) is { IsForbidden: true } forbiddenState)
             return new NoPlugin($"This plugin is forbidden: {forbiddenState.Message}");
@@ -126,9 +128,10 @@ public static partial class PluginFactory
         if(type is PluginType.NONE)
             return new NoPlugin($"TYPE is not a valid plugin type. Valid types are: {CommonTools.GetAllEnumValues<PluginType>()}");
         
+        var isInternal = pluginPath.StartsWith(INTERNAL_PLUGINS_ROOT, StringComparison.OrdinalIgnoreCase);
         return type switch
         {
-            PluginType.LANGUAGE => new PluginLanguage(state, type),
+            PluginType.LANGUAGE => new PluginLanguage(isInternal, state, type),
             
             _ => new NoPlugin("This plugin type is not supported yet. Please try again with a future version of AI Studio.")
         };
diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginLanguage.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginLanguage.cs
index a4297d60..4c8cf30a 100644
--- a/app/MindWork AI Studio/Tools/PluginSystem/PluginLanguage.cs	
+++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginLanguage.cs	
@@ -8,7 +8,7 @@ public sealed class PluginLanguage : PluginBase, ILanguagePlugin
     
     private ILanguagePlugin? baseLanguage;
     
-    public PluginLanguage(LuaState state, PluginType type) : base(state, type)
+    public PluginLanguage(bool isInternal, LuaState state, PluginType type) : base(isInternal, state, type)
     {
         if (this.TryInitUITextContent(out var issue, out var readContent))
             this.content = readContent;