fix(scriptanalysis): M3.6 — full-framework analysis refs close forbidden-type-in-allowed-ns blind spot; pin Process/Stopwatch; fix stale codec test; drop dead ContainsInCode

This commit is contained in:
Joseph Doherty
2026-06-16 20:00:28 -04:00
parent cf935d5744
commit 069757209a
6 changed files with 121 additions and 127 deletions
@@ -1,3 +1,4 @@
using System.IO;
using System.Reflection;
using Microsoft.CodeAnalysis;
using ZB.MOM.WW.ScadaBridge.Commons.Types;
@@ -121,6 +122,67 @@ public static class ScriptTrustPolicy
.Select(a => (MetadataReference)MetadataReference.CreateFromFile(a.Location))
.ToList();
/// <summary>
/// The full trusted-platform reference set used ONLY by
/// <see cref="ScriptTrustValidator"/>'s semantic analysis — NOT by
/// <see cref="RoslynScriptCompiler"/>. Unlike <see cref="DefaultReferences"/>
/// (the minimal, runtime-fidelity set used to decide script <i>validity</i>,
/// which must mirror exactly what the site runtime compiles against), the
/// trust validator references the entire framework so that EVERY type a
/// script names resolves to a real symbol and is judged by its true
/// namespace. Without this, a forbidden TYPE that sits inside an ALLOWED
/// namespace and is reached as a bare identifier — the only such case in the
/// policy being <c>System.Diagnostics.Process</c> via
/// <c>using System.Diagnostics;</c> — would not resolve against a minimal
/// reference set and would slip past the semantic pass (still blocked
/// downstream as an undefined-symbol compile error, but with a misleading
/// message). Referencing the full framework lets the validator flag it
/// authoritatively as a forbidden API. Enriching the analysis reference set
/// can only IMPROVE detection — the verdict is by namespace/type, so more
/// resolvable symbols means more correct verdicts, never a false allow.
/// </summary>
public static readonly IReadOnlyList<MetadataReference> AnalysisReferences = BuildAnalysisReferences();
private static IReadOnlyList<MetadataReference> BuildAnalysisReferences()
{
var byPath = new Dictionary<string, MetadataReference>(StringComparer.OrdinalIgnoreCase);
// Trusted platform assemblies = the full framework reference set the host
// started with; lets the semantic pass resolve any BCL type.
if (AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES") is string tpa)
{
foreach (var path in tpa.Split(Path.PathSeparator))
{
if (path.Length == 0 ||
!path.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) ||
byPath.ContainsKey(path) ||
!File.Exists(path))
{
continue;
}
try { byPath[path] = MetadataReference.CreateFromFile(path); }
catch { /* skip an unreadable assembly rather than fail validation */ }
}
}
// Ensure app assemblies the script API surface needs are present even if
// not in the TPA list (e.g. Commons / DynamicJsonElement).
foreach (var asm in DefaultAssemblies)
{
var loc = asm.Location;
if (loc.Length == 0 || byPath.ContainsKey(loc) || !File.Exists(loc))
continue;
try { byPath[loc] = MetadataReference.CreateFromFile(loc); }
catch { /* ignore */ }
}
// Fallback to the minimal set if the TPA list was unavailable (e.g. a
// single-file/AOT host) so validation still functions.
return byPath.Count > 0 ? byPath.Values.ToList() : DefaultReferences;
}
/// <summary>
/// Default namespace imports made available to compiled scripts.
/// </summary>
@@ -79,7 +79,12 @@ public static class ScriptTrustValidator
var violations = new SortedSet<string>(StringComparer.Ordinal);
// ---- Pass 1: semantic symbol analysis (ported from SiteRuntime) ----
var references = ScriptTrustPolicy.DefaultReferences.ToList();
// Use the full trusted-platform reference set (not the minimal
// runtime-fidelity DefaultReferences) so EVERY type a script names
// resolves and is judged by its true namespace — closing the
// forbidden-type-in-allowed-namespace blind spot (e.g. a bare
// System.Diagnostics.Process via `using System.Diagnostics;`).
var references = ScriptTrustPolicy.AnalysisReferences.ToList();
if (extraReferences != null)
references.AddRange(extraReferences);