diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs
index 5f7f0df0..4b4f6a08 100644
--- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs
+++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs
@@ -25,13 +25,22 @@ public static partial class PluginFactory
///
/// Initializes the enterprise encryption service by reading the encryption secret
- /// from the Windows Registry or environment variables.
+ /// from the effective enterprise source.
///
/// The Rust service to use for reading the encryption secret.
public static async Task InitializeEnterpriseEncryption(Services.RustService rustService)
{
- LOG.LogInformation("Initializing enterprise encryption service...");
var encryptionSecret = await rustService.EnterpriseEnvConfigEncryptionSecret();
+ InitializeEnterpriseEncryption(encryptionSecret);
+ }
+
+ ///
+ /// Initializes the enterprise encryption service using a prefetched secret value.
+ ///
+ /// The base64-encoded enterprise encryption secret.
+ public static void InitializeEnterpriseEncryption(string? encryptionSecret)
+ {
+ LOG.LogInformation("Initializing enterprise encryption service...");
var enterpriseEncryptionLogger = Program.LOGGER_FACTORY.CreateLogger();
EnterpriseEncryption = new EnterpriseEncryption(enterpriseEncryptionLogger, encryptionSecret);
diff --git a/app/MindWork AI Studio/Tools/Services/EnterpriseEnvironmentService.cs b/app/MindWork AI Studio/Tools/Services/EnterpriseEnvironmentService.cs
index 4d38eb15..656d7358 100644
--- a/app/MindWork AI Studio/Tools/Services/EnterpriseEnvironmentService.cs
+++ b/app/MindWork AI Studio/Tools/Services/EnterpriseEnvironmentService.cs
@@ -1,4 +1,8 @@
using AIStudio.Tools.PluginSystem;
+using AIStudio.Settings;
+
+using System.Security.Cryptography;
+using System.Text;
namespace AIStudio.Tools.Services;
@@ -7,8 +11,14 @@ public sealed class EnterpriseEnvironmentService(ILogger CURRENT_ENVIRONMENTS = [];
public static bool HasValidEnterpriseSnapshot { get; private set; }
+
+ private static EnterpriseSecretSnapshot CURRENT_SECRET_SNAPSHOT;
private readonly record struct EnterpriseEnvironmentSnapshot(Guid ConfigurationId, string ConfigurationServerUrl, string? ETag);
+
+ private readonly record struct EnterpriseSecretSnapshot(bool HasSecret, string Fingerprint);
+
+ private readonly record struct EnterpriseSecretTarget(string SecretId, string SecretName, SecretStoreType StoreType) : ISecretId;
#if DEBUG
private static readonly TimeSpan CHECK_INTERVAL = TimeSpan.FromMinutes(6);
@@ -39,6 +49,7 @@ public sealed class EnterpriseEnvironmentService(ILogger(null, Event.ENTERPRISE_ENVIRONMENTS_CHANGED);
}
catch (Exception e)
@@ -193,8 +229,81 @@ public sealed class EnterpriseEnvironmentService(ILogger BuildSecretSnapshot(string secret)
+ {
+ if (string.IsNullOrWhiteSpace(secret))
+ return new EnterpriseSecretSnapshot(false, string.Empty);
+
+ return new EnterpriseSecretSnapshot(true, await ComputeSecretFingerprint(secret));
+ }
+
+ private static async Task ComputeSecretFingerprint(string secret)
+ {
+ using var secretStream = new MemoryStream(Encoding.UTF8.GetBytes(secret));
+ var hash = await SHA256.HashDataAsync(secretStream);
+ return Convert.ToHexString(hash);
+ }
+
private static string NormalizeServerUrl(string serverUrl)
{
return serverUrl.Trim().TrimEnd('/');
}
+
+ private async Task RemoveEnterpriseManagedApiKeysAsync()
+ {
+ var secretTargets = GetEnterpriseManagedSecretTargets();
+ if (secretTargets.Count == 0)
+ {
+ logger.LogInformation("No enterprise-managed API keys are currently known in the settings. No keyring cleanup is required.");
+ return;
+ }
+
+ logger.LogInformation("Removing {SecretCount} enterprise-managed API key(s) from the OS keyring after an enterprise encryption secret change.", secretTargets.Count);
+ foreach (var target in secretTargets)
+ {
+ try
+ {
+ var deleteResult = await rustService.DeleteAPIKey(target, target.StoreType);
+ if (deleteResult.Success)
+ {
+ if (deleteResult.WasEntryFound)
+ logger.LogInformation("Successfully deleted enterprise-managed API key '{SecretName}' from the OS keyring.", target.SecretName);
+ else
+ logger.LogInformation("Enterprise-managed API key '{SecretName}' was already absent from the OS keyring.", target.SecretName);
+ }
+ else
+ logger.LogWarning("Failed to delete enterprise-managed API key '{SecretName}' from the OS keyring: {Issue}", target.SecretName, deleteResult.Issue);
+ }
+ catch (Exception e)
+ {
+ logger.LogWarning(e, "Failed to delete enterprise-managed API key '{SecretName}' from the OS keyring.", target.SecretName);
+ }
+ }
+ }
+
+ private static List GetEnterpriseManagedSecretTargets()
+ {
+ var configurationData = Program.SERVICE_PROVIDER.GetRequiredService().ConfigurationData;
+ var secretTargets = new HashSet();
+
+ AddEnterpriseManagedSecretTargets(configurationData.Providers, SecretStoreType.LLM_PROVIDER, secretTargets);
+ AddEnterpriseManagedSecretTargets(configurationData.EmbeddingProviders, SecretStoreType.EMBEDDING_PROVIDER, secretTargets);
+ AddEnterpriseManagedSecretTargets(configurationData.TranscriptionProviders, SecretStoreType.TRANSCRIPTION_PROVIDER, secretTargets);
+
+ return secretTargets.ToList();
+ }
+
+ private static void AddEnterpriseManagedSecretTargets(
+ IEnumerable secrets,
+ SecretStoreType storeType,
+ ISet secretTargets) where TSecret : ISecretId, IConfigurationObject
+ {
+ foreach (var secret in secrets)
+ {
+ if (!secret.IsEnterpriseConfiguration || secret.EnterpriseConfigurationPluginId == Guid.Empty)
+ continue;
+
+ secretTargets.Add(new EnterpriseSecretTarget(secret.SecretId, secret.SecretName, storeType));
+ }
+ }
}
\ No newline at end of file