fix(review): full code-review remediation — 5 High + Medium/Low across 16 modules
Remediation from the full per-module code review at 4307c381 (findings recorded
separately in code-reviews/).
Highs fixed:
- DeploymentManager-025/SiteRuntime-031: stop broadcasting notification lists + SMTP
configs (incl. credentials) to sites; site purges already-persisted rows on apply
(enforces the central-only delivery design; clears plaintext SMTP creds at rest).
- DataConnectionLayer-023: guard the native-alarm subscribe path against the
mid-flight-unsubscribe adapter-feed leak (mirrors the DCL-021 tag-path fix).
- SiteEventLogging-024: normalize From/To query bounds to UTC (the -016 fix the
audit trail claimed but never committed).
- KpiHistory-001: add an in-flight guard to the recorder sample tick.
- ScriptAnalysis-001: harden the trust analyzer's TPA-absent fallback (resolve
forbidden anchors in the minimal reference set; warn on degraded mode) — anchors
added to validation references only, never the compile gate.
(InboundAPI-026 left to the feat/ipsen-movein effort per owner decision.)
Medium/Low: DM-026 deterministic deploy-status tiebreaker; SR-027/028/029/030
native-alarm leak/phantom-active/delete-during-redeploy fixes; AL-013/014/016;
TE-024 (folder-mutation audit rows now persisted)/025; SF-025 gauge-provider
clear-on-stop; ESG-025/026; SEC-023/024/025; SCA-007/008/009; plus doc/test
accuracy COM-023/024, HOST-025/026, HM-024/025, NS-027/028.
Full-solution build 0 warnings; ~3560 tests across 18 touched suites green.
This commit is contained in:
@@ -103,6 +103,19 @@ public static class ScriptTrustPolicy
|
||||
/// 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.
|
||||
///
|
||||
/// <para>
|
||||
/// This is the <b>minimal, runtime-fidelity</b> set. It deliberately does
|
||||
/// NOT reference the assemblies that host the forbidden APIs (e.g.
|
||||
/// <c>System.Diagnostics.Process.dll</c>, <c>System.Net.Sockets.dll</c>) — a
|
||||
/// forbidden type must remain an <i>undefined symbol</i> at compile time so
|
||||
/// the <see cref="RoslynScriptCompiler"/> gate independently rejects it. Do
|
||||
/// not widen this set with forbidden-API anchor assemblies; that second layer
|
||||
/// of defence (a forbidden type fails to bind at execution-time compilation)
|
||||
/// depends on it staying minimal. The trust validator's symbol-resolution
|
||||
/// needs are met separately by <see cref="AnalysisReferences"/> (which adds
|
||||
/// <see cref="ForbiddenAnchorAssemblies"/> on the minimal-fallback path).
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public static readonly IReadOnlyList<Assembly> DefaultAssemblies =
|
||||
[
|
||||
@@ -113,6 +126,42 @@ public static class ScriptTrustPolicy
|
||||
typeof(DynamicJsonElement).Assembly,
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Anchor assemblies that <b>host the forbidden-API types named in
|
||||
/// <see cref="ForbiddenScopes"/></b>. Used ONLY to enrich the <i>trust
|
||||
/// validator's</i> semantic reference set (<see cref="AnalysisReferences"/>)
|
||||
/// when the <c>TRUSTED_PLATFORM_ASSEMBLIES</c> list is unavailable — a
|
||||
/// single-file, AOT, or trimmed host. Without these, the minimal fallback set
|
||||
/// cannot resolve a <i>bare</i> forbidden type that lives inside an
|
||||
/// <i>allowed</i> namespace (the documented case being <c>Process</c> via
|
||||
/// <c>using System.Diagnostics;</c>): the symbol resolves to nothing, Pass 1's
|
||||
/// syntactic fallback ignores dotless identifiers, and Pass 2 never flags a
|
||||
/// bare identifier — so the forbidden reference slips the validator entirely
|
||||
/// (ScriptAnalysis-001). Anchoring these assemblies in the fallback keeps the
|
||||
/// semantic pass authoritative even in the degraded mode.
|
||||
///
|
||||
/// <para>
|
||||
/// These are <b>never</b> added to <see cref="DefaultReferences"/> — see the
|
||||
/// remark on <see cref="DefaultAssemblies"/>. Most forbidden anchor types
|
||||
/// (<c>System.IO.File</c>, <c>System.Threading.Thread</c>,
|
||||
/// <c>System.Reflection.Assembly</c>,
|
||||
/// <c>System.Runtime.InteropServices.Marshal</c>) already live in the same
|
||||
/// assembly as <c>typeof(object)</c> (<c>System.Private.CoreLib</c>), so the
|
||||
/// minimal set already resolves them; the ones that ship in their own
|
||||
/// assemblies — <c>System.Diagnostics.Process</c> and
|
||||
/// <c>System.Net.Sockets.Socket</c> — are listed here explicitly.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public static readonly IReadOnlyList<Assembly> ForbiddenAnchorAssemblies =
|
||||
[
|
||||
typeof(System.Diagnostics.Process).Assembly,
|
||||
typeof(System.IO.File).Assembly,
|
||||
typeof(System.Threading.Thread).Assembly,
|
||||
typeof(System.Reflection.Assembly).Assembly,
|
||||
typeof(System.Net.Sockets.Socket).Assembly,
|
||||
typeof(System.Runtime.InteropServices.Marshal).Assembly,
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Metadata references for the trust-validation semantic compilation and
|
||||
/// the design-time script compilation.
|
||||
@@ -143,6 +192,20 @@ public static class ScriptTrustPolicy
|
||||
/// </summary>
|
||||
public static readonly IReadOnlyList<MetadataReference> AnalysisReferences = BuildAnalysisReferences();
|
||||
|
||||
/// <summary>
|
||||
/// True when <see cref="AnalysisReferences"/> was built WITHOUT the
|
||||
/// trusted-platform-assemblies (TPA) list — i.e. the semantic pass is running
|
||||
/// against the minimal fallback set (a single-file/AOT/trimmed host) rather
|
||||
/// than the full framework. In this degraded mode the validator still
|
||||
/// resolves the documented forbidden anchors (because
|
||||
/// <see cref="ForbiddenAnchorAssemblies"/> is folded into the fallback), but
|
||||
/// types outside that anchor set may resolve as unknown and rely on the
|
||||
/// syntactic pass / downstream compile gate. A warning is also emitted via
|
||||
/// <see cref="System.Diagnostics.Trace"/> at type initialisation. Consumers
|
||||
/// (and tests) can read this flag to detect / surface the weakened mode.
|
||||
/// </summary>
|
||||
public static bool AnalysisReferencesDegraded { get; private set; }
|
||||
|
||||
private static IReadOnlyList<MetadataReference> BuildAnalysisReferences()
|
||||
{
|
||||
var byPath = new Dictionary<string, MetadataReference>(StringComparer.OrdinalIgnoreCase);
|
||||
@@ -166,23 +229,77 @@ public static class ScriptTrustPolicy
|
||||
}
|
||||
}
|
||||
|
||||
// The TPA list was unavailable (single-file / AOT / trimmed host) — we
|
||||
// are about to fall back to the minimal set, which weakens the semantic
|
||||
// pass. Make the degradation LOUD (not silent): record the flag and emit
|
||||
// a warning so operators/tests can detect the mode (ScriptAnalysis-001).
|
||||
var tpaAvailable = byPath.Count > 0;
|
||||
if (!tpaAvailable)
|
||||
{
|
||||
AnalysisReferencesDegraded = true;
|
||||
System.Diagnostics.Trace.TraceWarning(
|
||||
"ScriptTrustPolicy: TRUSTED_PLATFORM_ASSEMBLIES unavailable; the script-trust " +
|
||||
"semantic pass is running against the MINIMAL fallback reference set (plus the " +
|
||||
"forbidden-API anchor assemblies). Symbol resolution is reduced — forbidden anchors " +
|
||||
"(Process, Socket, File, Thread, Assembly, Marshal) remain caught, but other types " +
|
||||
"may resolve as unknown. This typically indicates a single-file/AOT/trimmed host.");
|
||||
}
|
||||
|
||||
// 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;
|
||||
TryAddAssembly(byPath, asm);
|
||||
|
||||
try { byPath[loc] = MetadataReference.CreateFromFile(loc); }
|
||||
catch { /* ignore */ }
|
||||
// On the minimal-fallback path, also fold in the assemblies that HOST the
|
||||
// forbidden-API types so a bare forbidden type inside an allowed namespace
|
||||
// (the documented `Process` via `using System.Diagnostics;` case) still
|
||||
// resolves and is flagged authoritatively by the semantic pass. When the
|
||||
// TPA list is present these are already covered, so this only matters in
|
||||
// the degraded mode (ScriptAnalysis-001). NOTE: these anchors are added
|
||||
// ONLY here, never to DefaultReferences — the compile gate must keep
|
||||
// rejecting forbidden types as undefined symbols.
|
||||
if (!tpaAvailable)
|
||||
{
|
||||
foreach (var asm in ForbiddenAnchorAssemblies)
|
||||
TryAddAssembly(byPath, asm);
|
||||
}
|
||||
|
||||
// Fallback to the minimal set if the TPA list was unavailable (e.g. a
|
||||
// single-file/AOT host) so validation still functions.
|
||||
// Should never be empty (DefaultAssemblies always resolve), but guard
|
||||
// against a pathological host and fall back to the minimal references.
|
||||
return byPath.Count > 0 ? byPath.Values.ToList() : DefaultReferences;
|
||||
}
|
||||
|
||||
private static void TryAddAssembly(
|
||||
Dictionary<string, MetadataReference> byPath, Assembly asm)
|
||||
{
|
||||
var loc = asm.Location;
|
||||
if (loc.Length == 0 || byPath.ContainsKey(loc) || !File.Exists(loc))
|
||||
return;
|
||||
|
||||
try { byPath[loc] = MetadataReference.CreateFromFile(loc); }
|
||||
catch { /* skip an unreadable assembly rather than fail validation */ }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds exactly the reference set the trust validator would use on the
|
||||
/// minimal TPA-fallback path: <see cref="DefaultAssemblies"/> plus the
|
||||
/// <see cref="ForbiddenAnchorAssemblies"/> that host the forbidden-API types.
|
||||
/// This is what <see cref="BuildAnalysisReferences"/> produces when
|
||||
/// <c>TRUSTED_PLATFORM_ASSEMBLIES</c> is unavailable. Exposed so the degraded
|
||||
/// mode can be exercised directly (e.g. by the adversarial tests proving the
|
||||
/// SA-001 fallback hole is closed) without depending on the host actually
|
||||
/// lacking a TPA list.
|
||||
/// </summary>
|
||||
public static IReadOnlyList<MetadataReference> BuildMinimalFallbackReferences()
|
||||
{
|
||||
var byPath = new Dictionary<string, MetadataReference>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var asm in DefaultAssemblies)
|
||||
TryAddAssembly(byPath, asm);
|
||||
foreach (var asm in ForbiddenAnchorAssemblies)
|
||||
TryAddAssembly(byPath, asm);
|
||||
return byPath.Values.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default namespace imports made available to compiled scripts.
|
||||
/// </summary>
|
||||
|
||||
@@ -55,17 +55,42 @@ public static class ScriptTrustValidator
|
||||
/// </summary>
|
||||
/// <param name="code">The C# script source to analyse.</param>
|
||||
/// <param name="extraReferences">
|
||||
/// Optional additional metadata references to add to
|
||||
/// <see cref="ScriptTrustPolicy.DefaultReferences"/> for semantic
|
||||
/// resolution — e.g. the compile-surface globals assembly so a script
|
||||
/// referencing the API surface resolves cleanly. Forbidden references are
|
||||
/// NOT added here (a script can't reach a forbidden API just because the
|
||||
/// assembly is referenced; the deny-list still applies).
|
||||
/// Optional additional metadata references to <b>widen symbol resolution</b>
|
||||
/// for the semantic pass — e.g. the compile-surface globals assembly so a
|
||||
/// script referencing the API surface resolves cleanly. Extra references can
|
||||
/// ONLY improve resolution; they can NEVER whitelist a forbidden API. The
|
||||
/// verdict is by resolved namespace/type against
|
||||
/// <see cref="ScriptTrustPolicy.ForbiddenScopes"/>, so passing more
|
||||
/// references — even the forbidden-API assemblies themselves — can only make
|
||||
/// the verdict more accurate (more types resolve to their true namespace and
|
||||
/// are judged), never produce a false allow. Callers therefore need not (and
|
||||
/// cannot, for safety purposes) curate this set; the Central UI run gate
|
||||
/// forwards its full compilation reference surface here precisely because
|
||||
/// doing so is safe.
|
||||
/// </param>
|
||||
/// <returns>A list of trust-model violation messages; empty if the script is clean.</returns>
|
||||
public static IReadOnlyList<string> FindViolations(
|
||||
string code,
|
||||
IEnumerable<MetadataReference>? extraReferences = null)
|
||||
=> FindViolations(code, ScriptTrustPolicy.AnalysisReferences, extraReferences);
|
||||
|
||||
/// <summary>
|
||||
/// Overload that runs the trust analysis against an explicit base reference
|
||||
/// set instead of <see cref="ScriptTrustPolicy.AnalysisReferences"/>. The
|
||||
/// public entry point above always uses the full analysis set; this overload
|
||||
/// exists so tests can pin behaviour against the minimal TPA-fallback set
|
||||
/// (<see cref="ScriptTrustPolicy.BuildMinimalFallbackReferences"/>) and prove
|
||||
/// the degraded mode still catches the documented forbidden anchors
|
||||
/// (ScriptAnalysis-001). The two passes are otherwise identical.
|
||||
/// </summary>
|
||||
/// <param name="code">The C# script source to analyse.</param>
|
||||
/// <param name="baseReferences">The base reference set Pass 1 resolves against.</param>
|
||||
/// <param name="extraReferences">Optional additional references that only widen resolution.</param>
|
||||
/// <returns>A list of trust-model violation messages; empty if the script is clean.</returns>
|
||||
public static IReadOnlyList<string> FindViolations(
|
||||
string code,
|
||||
IReadOnlyList<MetadataReference> baseReferences,
|
||||
IEnumerable<MetadataReference>? extraReferences = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(code))
|
||||
return Array.Empty<string>();
|
||||
@@ -84,7 +109,7 @@ public static class ScriptTrustValidator
|
||||
// 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();
|
||||
var references = baseReferences.ToList();
|
||||
if (extraReferences != null)
|
||||
references.AddRange(extraReferences);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user