using System; using System.Reflection; using UnityEngine; namespace io.github.hatayama.uLoopMCP { /// /// Security checker for Unity MCP tools - provides runtime tool blocking based on user settings /// /// Design document reference: Packages/src/Editor/Security/SECURITY.md /// /// Related classes: /// - ULoopSettings: Persistent security settings storage (.uloop/settings.permissions.json) /// - McpSecurityException: Custom exception for security violations /// - UnityToolRegistry: Tool registration and execution /// - McpBridgeServer: Server that executes tools /// public static class McpSecurityChecker { /// /// Checks if a tool is allowed based on current security settings /// /// The name of the tool to check /// True if tool is allowed, false otherwise public static bool IsToolAllowed(string toolName) { if (string.IsNullOrEmpty(toolName)) { return false; } // Get tool info from registry var toolInfo = GetToolSecurityInfoFromRegistry(toolName); if (!toolInfo.HasValue) { // Unknown tool - default to block for security return false; } return IsToolAllowedByAttribute(toolInfo.Value); } /// /// Checks if a command is allowed based on current security settings /// /// The name of the command to check /// True if command is allowed, false otherwise [System.Obsolete("Use IsToolAllowed instead. This method will be removed in a future version.")] public static bool IsCommandAllowed(string commandName) { return IsToolAllowed(commandName); } /// /// Gets tool security info from the tool registry /// /// The name of the tool /// Tool security info or null if not found private static ToolAttributeInfo? GetToolSecurityInfoFromRegistry(string toolName) { // Get the tool registry instance var registry = CustomToolManager.GetRegistry(); if (registry == null) { return null; } // Find the tool and get its attribute var toolType = registry.GetToolType(toolName); if (toolType == null) { return null; } var attribute = toolType.GetCustomAttribute(); if (attribute == null) { return new ToolAttributeInfo(toolName, SecuritySettings.None); } return new ToolAttributeInfo(toolName, attribute.RequiredSecuritySetting); } /// /// Checks if tool is allowed based on its attribute settings /// /// Tool attribute information /// True if tool is allowed private static bool IsToolAllowedByAttribute(ToolAttributeInfo toolInfo) { switch (toolInfo.RequiredSecuritySetting) { case SecuritySettings.None: return IsThirdPartyToolAllowed(toolInfo.ToolName); default: return false; // Unknown setting - block by default } } /// /// Validates tool execution and throws exception if blocked /// /// The name of the tool to validate /// If true, throws exception when blocked. If false, returns validation result. /// True if tool is allowed /// Thrown when tool is blocked and throwOnBlock is true public static bool ValidateTool(string toolName, bool throwOnBlock = true) { if (IsToolAllowed(toolName)) { return true; } string reason = GetBlockReason(toolName); if (throwOnBlock) { throw new McpSecurityException(toolName, reason); } return false; } /// /// Validates command execution and throws exception if blocked /// /// The name of the command to validate /// If true, throws exception when blocked. If false, returns validation result. /// True if command is allowed /// Thrown when command is blocked and throwOnBlock is true [System.Obsolete("Use ValidateTool instead. This method will be removed in a future version.")] public static bool ValidateCommand(string commandName, bool throwOnBlock = true) { return ValidateTool(commandName, throwOnBlock); } /// /// Gets the user-friendly reason why a tool is blocked /// /// The name of the tool /// Human-readable reason string public static string GetBlockReason(string toolName) { var toolInfo = GetToolSecurityInfoFromRegistry(toolName); if (!toolInfo.HasValue) { return $"Tool '{toolName}' is not allowed by security policy."; } switch (toolInfo.Value.RequiredSecuritySetting) { case SecuritySettings.None: if (IsThirdPartyTool(toolName)) { return "Third party tools execution is disabled. Enable 'Allow Third Party Tools' in uLoopMCP Security Settings."; } return $"Tool '{toolName}' is not allowed by security policy."; default: return $"Tool '{toolName}' is not allowed by security policy."; } } /// /// Gets security information for a tool /// /// The name of the tool /// Security information object public static ToolSecurityInfo GetToolSecurityInfo(string toolName) { bool isAllowed = IsToolAllowed(toolName); string reason = isAllowed ? "Tool is allowed" : GetBlockReason(toolName); return new ToolSecurityInfo(toolName, isAllowed, reason); } /// /// Checks if third party tools execution is allowed /// /// True if third party tools execution is allowed private static bool IsThirdPartyToolsAllowed() { return ULoopSettings.GetAllowThirdPartyTools(); } /// /// Checks if a specific tool is allowed (considers both explicit setting and third party status) /// /// The name of the tool to check /// True if tool is allowed private static bool IsThirdPartyToolAllowed(string toolName) { // If it's not a third party tool (i.e., official tool), allow it if (!IsThirdPartyTool(toolName)) { return true; } // If it's a third party tool, check the setting return IsThirdPartyToolsAllowed(); } /// /// Checks if a tool is a third party tool based on its assembly /// /// The name of the tool to check /// True if tool is from a third party assembly private static bool IsThirdPartyTool(string toolName) { // Get tool type from registry var registry = CustomToolManager.GetRegistry(); if (registry == null) { return true; // Default to third party if registry unavailable } var toolType = registry.GetToolType(toolName); if (toolType == null) { return true; // Default to third party if tool type not found } // Check assembly name string assemblyName = toolType.Assembly.GetName().Name; // Official uLoopMCP assembly return assemblyName != "uLoopMCP.Editor"; } } /// /// Security information for a tool /// public readonly struct ToolSecurityInfo { public readonly string ToolName; public readonly bool IsAllowed; public readonly string Reason; public ToolSecurityInfo(string toolName, bool isAllowed, string reason) { ToolName = toolName; IsAllowed = isAllowed; Reason = reason; } } /// /// Tool attribute information for security checking /// internal readonly struct ToolAttributeInfo { public readonly string ToolName; public readonly SecuritySettings RequiredSecuritySetting; public ToolAttributeInfo(string toolName, SecuritySettings requiredSecuritySetting) { ToolName = toolName; RequiredSecuritySetting = requiredSecuritySetting; } } /// /// Exception thrown when a tool is blocked by security settings /// public class McpSecurityException : Exception { public string ToolName { get; } public string SecurityReason { get; } /// /// Backwards compatibility property for CommandName /// [System.Obsolete("Use ToolName instead. This property will be removed in a future version.")] public string CommandName => ToolName; public McpSecurityException(string toolName, string reason) : base($"Tool '{toolName}' is blocked by security settings: {reason}") { ToolName = toolName; SecurityReason = reason; } public McpSecurityException(string toolName, string reason, Exception innerException) : base($"Tool '{toolName}' is blocked by security settings: {reason}", innerException) { ToolName = toolName; SecurityReason = reason; } } }