using UnityEngine; namespace MCPForUnity.Editor.Security { /// /// Entry point for at-rest provider key storage. Selects the best OS secure store for /// the current platform and layers the read-only environment-variable override on top. /// Keys never transit the MCP bridge; only the C# editor side reads them, at call time. /// public static class SecureKeyStore { private static ISecureKeyStore _current; public static ISecureKeyStore Current => _current ??= Build(); /// Test seam: substitute an in-memory or temp-dir store. internal static void OverrideForTests(ISecureKeyStore store) => _current = store; /// Test seam: clear the cached instance. internal static void ResetForTests() => _current = null; private static ISecureKeyStore Build() => new EnvOverlayKeyStore(SelectPlatform()); private static ISecureKeyStore SelectPlatform() { switch (Application.platform) { case RuntimePlatform.OSXEditor: return new MacKeychainKeyStore(); case RuntimePlatform.WindowsEditor: return new WindowsCredentialKeyStore(); case RuntimePlatform.LinuxEditor: return LinuxSecretToolKeyStore.IsAvailable() ? (ISecureKeyStore)new LinuxSecretToolKeyStore() : new EncryptedFileKeyStore(); default: return new EncryptedFileKeyStore(); } } } /// /// Wraps a platform store so an MCPFORUNITY_<PROVIDER>_API_KEY environment /// variable takes precedence on reads. Writes always go to the underlying store. /// internal sealed class EnvOverlayKeyStore : ISecureKeyStore { private readonly ISecureKeyStore _inner; public EnvOverlayKeyStore(ISecureKeyStore inner) { _inner = inner; } public bool TryGet(string providerId, out string apiKey) { if (EnvKeyOverride.TryGet(providerId, out apiKey)) return true; return _inner.TryGet(providerId, out apiKey); } public bool Has(string providerId) => EnvKeyOverride.TryGet(providerId, out _) || _inner.Has(providerId); public void Set(string providerId, string apiKey) => _inner.Set(providerId, apiKey); public void Delete(string providerId) => _inner.Delete(providerId); } }