From 820c9e2596a21bf9c560af1818dba008a4e6ccc0 Mon Sep 17 00:00:00 2001 From: krut_ni Date: Tue, 7 Apr 2026 17:59:43 +0200 Subject: [PATCH] making the audit fully automatic --- .../Pages/Assistants.razor.cs | 72 ++++++++++++++++- app/MindWork AI Studio/Pages/Plugins.razor.cs | 80 +++++++++++++------ .../PluginAssistantSecurityResolver.cs | 6 +- 3 files changed, 125 insertions(+), 33 deletions(-) diff --git a/app/MindWork AI Studio/Pages/Assistants.razor.cs b/app/MindWork AI Studio/Pages/Assistants.razor.cs index b2f7ddd4..f7668a1d 100644 --- a/app/MindWork AI Studio/Pages/Assistants.razor.cs +++ b/app/MindWork AI Studio/Pages/Assistants.razor.cs @@ -1,4 +1,5 @@ using AIStudio.Components; +using AIStudio.Agents.AssistantAudit; using AIStudio.Tools.PluginSystem; using AIStudio.Tools.PluginSystem.Assistants; using Microsoft.AspNetCore.Components; @@ -7,22 +8,85 @@ namespace AIStudio.Pages; public partial class Assistants : MSGComponentBase { + private bool isAutoAuditing; + + [Inject] + private AssistantPluginAuditService AssistantPluginAuditService { get; init; } = null!; + protected override async Task OnInitializedAsync() { this.ApplyFilters([], [ Event.CONFIGURATION_CHANGED, Event.PLUGINS_RELOADED ]); await base.OnInitializedAsync(); } + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + await this.TryAutoAuditAssistantsAsync(); + } + private IReadOnlyCollection AssistantPlugins => PluginFactory.RunningPlugins.OfType() .Where(plugin => this.SettingsManager.IsPluginEnabled(plugin)) .ToList(); - protected override Task ProcessIncomingMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default + private async Task TryAutoAuditAssistantsAsync() { - if (triggeredEvent is Event.CONFIGURATION_CHANGED or Event.PLUGINS_RELOADED) - return this.InvokeAsync(this.StateHasChanged); + if (this.isAutoAuditing || !this.SettingsManager.ConfigurationData.AssistantPluginAudit.AutomaticallyAuditAssistants) + return; - return Task.CompletedTask; + this.isAutoAuditing = true; + + try + { + var wasConfigurationChanged = false; + var assistantPlugins = PluginFactory.RunningPlugins.OfType().ToList(); + foreach (var assistantPlugin in assistantPlugins) + { + var securityState = PluginAssistantSecurityResolver.Resolve(this.SettingsManager, assistantPlugin); + if (!securityState.RequiresAudit) + continue; + + var audit = await this.AssistantPluginAuditService.RunAuditAsync(assistantPlugin); + if (audit.Level is AssistantAuditLevel.UNKNOWN) + { + await MessageBus.INSTANCE.SendError(new (Icons.Material.Filled.SettingsSuggest, string.Format(this.T("The automatic security audit for the assistant plugin '{0}' failed. Please run it manually from the plugins page."), assistantPlugin.Name))); + continue; + } + + this.UpsertAuditCard(audit); + wasConfigurationChanged = true; + } + + if (!wasConfigurationChanged) + return; + + await this.SettingsManager.StoreSettings(); + await this.MessageBus.SendMessage(this, Event.CONFIGURATION_CHANGED); + } + finally + { + this.isAutoAuditing = false; + await this.InvokeAsync(this.StateHasChanged); + } + } + + private void UpsertAuditCard(PluginAssistantAudit audit) + { + var audits = this.SettingsManager.ConfigurationData.AssistantPluginAudits; + var existingIndex = audits.FindIndex(x => x.PluginId == audit.PluginId); + if (existingIndex >= 0) + audits[existingIndex] = audit; + else + audits.Add(audit); + } + + protected override async Task ProcessIncomingMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default + { + if (triggeredEvent is Event.PLUGINS_RELOADED) + await this.TryAutoAuditAssistantsAsync(); + + if (triggeredEvent is Event.CONFIGURATION_CHANGED or Event.PLUGINS_RELOADED) + await this.InvokeAsync(this.StateHasChanged); } } diff --git a/app/MindWork AI Studio/Pages/Plugins.razor.cs b/app/MindWork AI Studio/Pages/Plugins.razor.cs index 213ea603..914a13b7 100644 --- a/app/MindWork AI Studio/Pages/Plugins.razor.cs +++ b/app/MindWork AI Studio/Pages/Plugins.razor.cs @@ -15,6 +15,7 @@ public partial class Plugins : MSGComponentBase private const string GROUP_ENABLED = "Enabled"; private const string GROUP_DISABLED = "Disabled"; private const string GROUP_INTERNAL = "Internal"; + private bool isAutoAuditing; private DataAssistantPluginAudit AssistantPluginAuditSettings => this.SettingsManager.ConfigurationData.AssistantPluginAudit; @@ -50,6 +51,12 @@ public partial class Plugins : MSGComponentBase await base.OnInitializedAsync(); } + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + await this.TryAutoAuditAssistantsAsync(); + } + #endregion private async Task PluginActivationStateChanged(IPluginMetadata pluginMeta) @@ -75,31 +82,6 @@ public partial class Plugins : MSGComponentBase return; var securityState = PluginAssistantSecurityResolver.Resolve(this.SettingsManager, assistantPlugin); - if (securityState.RequiresAudit) - { - if (this.AssistantPluginAuditSettings.AutomaticallyAuditAssistants) - { - var audit = await this.AssistantPluginAuditService.RunAuditAsync(assistantPlugin); - if (audit.Level is AssistantAuditLevel.UNKNOWN) - { - await MessageBus.INSTANCE.SendError(new (Icons.Material.Filled.SettingsSuggest, string.Format(T("The Security Audit returned an invalid result, please try again manually.")))); - await this.OpenAssistantAuditDialogAsync(pluginMeta.Id); - return; - } - - this.UpsertAuditCard(audit); - await this.SettingsManager.StoreSettings(); - await this.MessageBus.SendMessage(this, Event.CONFIGURATION_CHANGED); - - securityState = PluginAssistantSecurityResolver.Resolve(this.SettingsManager, assistantPlugin); - } - else - { - await this.OpenAssistantAuditDialogAsync(pluginMeta.Id); - return; - } - } - if (securityState.RequiresAudit) { await this.OpenAssistantAuditDialogAsync(pluginMeta.Id); @@ -204,6 +186,47 @@ public partial class Plugins : MSGComponentBase private PluginAssistants? TryGetAssistantPlugin(Guid pluginId) => PluginFactory.RunningPlugins.OfType().FirstOrDefault(x => x.Id == pluginId); + private async Task TryAutoAuditAssistantsAsync() + { + if (this.isAutoAuditing || !this.AssistantPluginAuditSettings.AutomaticallyAuditAssistants) + return; + + this.isAutoAuditing = true; + + try + { + var wasConfigurationChanged = false; + var assistantPlugins = PluginFactory.RunningPlugins.OfType().ToList(); + foreach (var assistantPlugin in assistantPlugins) + { + var securityState = PluginAssistantSecurityResolver.Resolve(this.SettingsManager, assistantPlugin); + if (!securityState.RequiresAudit) + continue; + + var audit = await this.AssistantPluginAuditService.RunAuditAsync(assistantPlugin); + if (audit.Level is AssistantAuditLevel.UNKNOWN) + { + await MessageBus.INSTANCE.SendError(new (Icons.Material.Filled.SettingsSuggest, string.Format(this.T("The automatic security audit for the assistant plugin '{0}' failed. Please run it manually."), assistantPlugin.Name))); + continue; + } + + this.UpsertAuditCard(audit); + wasConfigurationChanged = true; + } + + if (!wasConfigurationChanged) + return; + + await this.SettingsManager.StoreSettings(); + await this.MessageBus.SendMessage(this, Event.CONFIGURATION_CHANGED); + } + finally + { + this.isAutoAuditing = false; + await this.InvokeAsync(this.StateHasChanged); + } + } + private void UpsertAuditCard(PluginAssistantAudit audit) { var audits = this.SettingsManager.ConfigurationData.AssistantPluginAudits; @@ -220,7 +243,12 @@ public partial class Plugins : MSGComponentBase { switch (triggeredEvent) { - case Event.PLUGINS_RELOADED or Event.CONFIGURATION_CHANGED: + case Event.PLUGINS_RELOADED: + await this.TryAutoAuditAssistantsAsync(); + await this.InvokeAsync(this.StateHasChanged); + break; + + case Event.CONFIGURATION_CHANGED: await this.InvokeAsync(this.StateHasChanged); break; } diff --git a/app/MindWork AI Studio/Tools/PluginSystem/Assistants/PluginAssistantSecurityResolver.cs b/app/MindWork AI Studio/Tools/PluginSystem/Assistants/PluginAssistantSecurityResolver.cs index ac7e7e79..50097165 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/Assistants/PluginAssistantSecurityResolver.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/Assistants/PluginAssistantSecurityResolver.cs @@ -181,8 +181,8 @@ public static class PluginAssistantSecurityResolver BadgeIcon = GetSecurityBadgeIcon(requiresAudit: false, hasAudit, hasHashMismatch: false, isBlockedByMinimum, canOverride), Headline = isBlockedByMinimum ? TB("This assistant is currently locked.") : TB("This assistant can still be used because your settings allow it."), Description = isBlockedByMinimum - ? string.Format(TB("The current audit result \"{0}\" is below your required minimum level \"{1}\". Your security settings therefore block this assistant plugin."), auditLevel.GetName(), auditSettings.MinimumLevel.GetName()) - : string.Format(TB("The current audit result is \"{0}\", which is below your required minimum level \"{1}\". Your settings still allow manual activation, but the assistant keeps this security status and should be reviewed carefully."), auditLevel.GetName(), auditSettings.MinimumLevel.GetName()), + ? string.Format(TB("The current audit result '{0}' is below your required minimum level '{1}'. Your security settings therefore block this assistant plugin."), auditLevel.GetName(), auditSettings.MinimumLevel.GetName()) + : string.Format(TB("The current audit result is '{0}', which is below your required minimum level '{1}'. Your settings still allow manual activation, but the assistant keeps this security status and should be reviewed carefully."), auditLevel.GetName(), auditSettings.MinimumLevel.GetName()), StatusColor = GetAvailabilityColor(requiresAudit: false, hasAudit, hasHashMismatch: false, isBlockedByMinimum, canOverride), StatusIcon = GetAvailabilityIcon(requiresAudit: false, hasAudit, hasHashMismatch: false, isBlockedByMinimum, canOverride), ActionLabel = TB("Open Security Check"), @@ -215,7 +215,7 @@ public static class PluginAssistantSecurityResolver StatusLabel = GetAvailabilityLabel(requiresAudit: false, hasAudit, hasHashMismatch: false, isBlocked: false, canOverride: false), BadgeIcon = GetSecurityBadgeIcon(requiresAudit: false, hasAudit, hasHashMismatch: false, isBlocked: false, canOverride: false), Headline = TB("This assistant is currently unlocked."), - Description = string.Format(TB("The stored audit matches the current plugin code and meets your required minimum level \"{0}\"."), auditSettings.MinimumLevel.GetName()), + Description = string.Format(TB("The stored audit matches the current plugin code and meets your required minimum level '{0}'."), auditSettings.MinimumLevel.GetName()), StatusColor = GetAvailabilityColor(requiresAudit: false, hasAudit, hasHashMismatch: false, isBlocked: false, canOverride: false), StatusIcon = GetAvailabilityIcon(requiresAudit: false, hasAudit, hasHashMismatch: false, isBlocked: false, canOverride: false), ActionLabel = TB("Open Security Check"),