feat(scriptanalysis): M3.1 shared trust validator + compiler + compile surfaces + tests

This commit is contained in:
Joseph Doherty
2026-06-16 19:18:39 -04:00
parent 0cc8642cfa
commit 4f2b17ce6d
10 changed files with 1100 additions and 0 deletions
@@ -0,0 +1,134 @@
using System.Reflection;
using Microsoft.CodeAnalysis;
using ZB.MOM.WW.ScadaBridge.Commons.Types;
namespace ZB.MOM.WW.ScadaBridge.ScriptAnalysis;
/// <summary>
/// M3.1: the single authoritative source of truth for the ScadaBridge script
/// trust model. Previously the forbidden-API deny-list, allowed exceptions,
/// reflection-gateway member names, default metadata references, and default
/// imports were duplicated (and disagreed) across four call sites — the
/// SiteRuntime <c>ScriptCompilationService</c>, the InboundAPI
/// <c>ForbiddenApiChecker</c>, and the design-time deploy gate. This class
/// fuses them into one collection set that <see cref="ScriptTrustValidator"/>
/// and <see cref="RoslynScriptCompiler"/> consume; the four consumers delegate
/// here in later tasks (M3.2M3.5).
///
/// <para>
/// The deny-list is intentionally the UNION of the two existing
/// implementations — it forbids <c>System.Diagnostics.Process</c> (not all of
/// <c>System.Diagnostics</c>, so <c>Stopwatch</c> stays allowed), all of
/// <c>System.Net</c>, all of <c>System.Threading</c> except Tasks /
/// CancellationToken(Source), plus <c>System.Reflection</c>,
/// <c>System.Runtime.InteropServices</c>, and <c>Microsoft.Win32</c>.
/// </para>
/// </summary>
public static class ScriptTrustPolicy
{
/// <summary>
/// Forbidden API roots. Each entry is matched as a prefix against the
/// resolved symbol's containing namespace and fully-qualified containing
/// type — an entry may name a whole namespace ("System.IO") or a single
/// type ("System.Diagnostics.Process").
/// </summary>
public static readonly string[] ForbiddenScopes =
[
"System.IO",
"System.Diagnostics.Process",
"System.Threading",
"System.Reflection",
"System.Net",
"System.Runtime.InteropServices",
"Microsoft.Win32",
];
/// <summary>
/// Specific namespaces/types allowed even though they sit under a forbidden
/// root. async/await and cancellation tokens are OK despite
/// <c>System.Threading</c> being blocked.
/// </summary>
public static readonly string[] AllowedExceptions =
[
"System.Threading.Tasks",
"System.Threading.CancellationToken",
"System.Threading.CancellationTokenSource",
];
/// <summary>
/// Member names that are reflection gateways. Reaching any of these — even
/// off a permitted type such as <c>typeof(string)</c> — lets a script
/// escape the namespace deny-list (obtain an arbitrary <c>Type</c>, load an
/// assembly, late-bind a method). They are rejected regardless of the
/// receiver expression.
/// </summary>
public static readonly HashSet<string> ReflectionGatewayMembers = new(StringComparer.Ordinal)
{
"GetType",
"GetTypeInfo",
"Assembly",
"Module",
"CreateInstance",
"InvokeMember",
"GetMethod",
"GetMethods",
"GetConstructor",
"GetConstructors",
"GetField",
"GetFields",
"GetProperty",
"GetProperties",
"GetMember",
"GetMembers",
"GetRuntimeMethod",
"GetRuntimeMethods",
"MethodHandle",
"TypeHandle",
};
/// <summary>
/// Bare identifiers that are forbidden outright. <c>dynamic</c> widens
/// late-bound member access the static walker cannot see through;
/// <c>Activator</c> has no non-reflection use.
/// </summary>
public static readonly HashSet<string> ForbiddenIdentifiers = new(StringComparer.Ordinal)
{
"dynamic",
"Activator",
};
/// <summary>
/// Assemblies referenced by compiled scripts. Shared between the Roslyn
/// scripting options and the semantic-analysis compilation built for trust
/// validation, so the validator resolves symbols against exactly the same
/// metadata the script is compiled against.
/// </summary>
public static readonly IReadOnlyList<Assembly> DefaultAssemblies =
[
typeof(object).Assembly,
typeof(System.Linq.Enumerable).Assembly,
typeof(System.Math).Assembly,
typeof(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo).Assembly,
typeof(DynamicJsonElement).Assembly,
];
/// <summary>
/// Metadata references for the trust-validation semantic compilation and
/// the design-time script compilation.
/// </summary>
public static readonly IReadOnlyList<MetadataReference> DefaultReferences =
DefaultAssemblies
.Select(a => (MetadataReference)MetadataReference.CreateFromFile(a.Location))
.ToList();
/// <summary>
/// Default namespace imports made available to compiled scripts.
/// </summary>
public static readonly string[] DefaultImports =
[
"System",
"System.Collections.Generic",
"System.Linq",
"System.Threading.Tasks",
];
}