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;
}
}
}