You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Roslyn限制用户C#脚本的可执行操作范围

限制C#用户脚本的方法调用范围解决方案

核心思路

你的需求本质是完全禁止实例方法调用,仅放行基础数学运算和你可控的静态方法。用Roslyn的CSharpSyntaxVisitor配合语义分析(SemanticModel)就能精准实现,无需复杂的标记接口——先从语法层面拦截所有实例调用,再通过语义验证静态方法是否属于你的白名单范围。

具体实现步骤

  1. 语法扫描与实例调用拦截
    自定义CSharpSyntaxVisitor,重写VisitInvocationExpression方法:

    • 识别调用节点的结构:如果是x.DoSomething()这类形式(即InvocationExpressionSyntax的Expression是MemberAccessExpressionSyntax,且其左节点不是类型名),直接判定为非法实例调用。
    • 若要更精准,结合SemanticModel获取方法符号IMethodSymbol,通过methodSymbol.IsStatic直接判断是否为实例方法,这种方式能避免变量名与类型名冲突导致的误判。
  2. 静态方法白名单验证

    • 维护一个允许的静态类/方法列表(比如你的自定义MyString、MyMath)。
    • 通过SemanticModel获取静态方法所属的类型,检查该类型是否在白名单内;如果需要用标记接口,也可以通过methodSymbol.ContainingType.GetInterfaces()判断是否实现了你定义的授权接口(如IAuthorizedStaticClass)。
  3. 基础数学运算放行
    加减乘除等基础运算属于BinaryExpressionSyntax,无需额外拦截,默认允许即可,只需递归检查运算表达式的参数是否合法。

示例代码片段

public class ScriptValidator : CSharpSyntaxVisitor<ValidationResult>
{
    private readonly SemanticModel _semanticModel;
    // 白名单:你可控的静态类
    private readonly HashSet<string> _allowedStaticTypeNames = new()
    {
        "MyNamespace.MyString",
        "MyNamespace.MyMath"
    };

    public ScriptValidator(SemanticModel semanticModel)
    {
        _semanticModel = semanticModel;
    }

    public override ValidationResult VisitInvocationExpression(InvocationExpressionSyntax node)
    {
        var symbolInfo = _semanticModel.GetSymbolInfo(node);
        if (symbolInfo.Symbol is not IMethodSymbol methodSymbol)
        {
            return ValidationResult.Fail("无法识别的方法调用", node.Span);
        }

        // 禁止所有实例方法调用
        if (!methodSymbol.IsStatic)
        {
            return ValidationResult.Fail("不允许调用实例方法", node.Span);
        }

        // 验证静态方法所属类是否在白名单
        var typeFullName = methodSymbol.ContainingType.ToDisplayString();
        if (!_allowedStaticTypeNames.Contains(typeFullName))
        {
            return ValidationResult.Fail($"禁止调用未授权类 {typeFullName} 的方法", node.Span);
        }

        // 递归检查参数合法性
        foreach (var arg in node.ArgumentList.Arguments)
        {
            var argResult = Visit(arg.Expression);
            if (!argResult.IsValid) return argResult;
        }

        return ValidationResult.Success();
    }

    // 放行基础数学运算,递归检查操作数
    public override ValidationResult VisitBinaryExpression(BinaryExpressionSyntax node)
    {
        var leftResult = Visit(node.Left);
        if (!leftResult.IsValid) return leftResult;
        var rightResult = Visit(node.Right);
        if (!rightResult.IsValid) return rightResult;
        return ValidationResult.Success();
    }

    // 其他语法节点默认放行(需根据需求调整,比如禁止变量声明等)
    public override ValidationResult DefaultVisit(SyntaxNode node)
    {
        return ValidationResult.Success();
    }
}

// 辅助验证结果类
public class ValidationResult
{
    public bool IsValid { get; }
    public string ErrorMessage { get; }
    public TextSpan ErrorSpan { get; }

    private ValidationResult(bool isValid, string errorMessage, TextSpan errorSpan)
    {
        IsValid = isValid;
        ErrorMessage = errorMessage;
        ErrorSpan = errorSpan;
    }

    public static ValidationResult Success() => new(true, string.Empty, default);
    public static ValidationResult Fail(string message, TextSpan span) => new(false, message, span);
}

补充说明

  • 如果不需要语义分析(比如不想提前编译脚本),仅通过语法树判断的话,可以直接检查MemberAccessExpressionSyntax.Expression的类型:若不是IdentifierNameSyntax(类型名)或QualifiedNameSyntax(带命名空间的类型名),则判定为实例调用,但这种方式可能存在变量名与类型名冲突的误判风险。
  • 若要进一步限制静态方法的具体名称,可以在白名单中存储类型名.方法名的组合,直接匹配methodSymbol.ToDisplayString()。

内容的提问来源于stack exchange,提问作者Will Comeaux

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 01:00:26