7b0b9c7365
Solution + 23 src projects + 26 test projects renamed; folders, csproj, namespaces, and ScadaLinkDbContext/ScadaBridgeDbContext class updated. ActorSystem "scadalink" → "scadabridge", Akka seed-node URLs migrated. SQL roles/logins, LDAP domains, CLI command name, and CLI config dir (~/.scadalink → ~/.scadabridge) also renamed. Build green; 5 Host.Tests fail awaiting SQL login rename in next commit. Pre-existing StaleTagMonitor timing flakes unchanged. Rename script committed at tools/rename-to-scadabridge.sh.
106 lines
5.6 KiB
C#
106 lines
5.6 KiB
C#
using ZB.MOM.WW.ScadaBridge.Commons.Types;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.TemplateEngine.Validation;
|
|
|
|
/// <summary>
|
|
/// Validates script code by attempting to compile it using Roslyn.
|
|
/// In production, this would compile C# scripts against a stub ScriptApi assembly
|
|
/// that provides the allowed API surface (attribute read/write, CallScript, CallShared, etc.)
|
|
/// and enforces the forbidden API list (System.IO, Process, Threading, Reflection, raw network).
|
|
///
|
|
/// For now, this implementation performs basic syntax validation.
|
|
///
|
|
/// <para>
|
|
/// <b>SECURITY LIMITATION (TemplateEngine-006):</b> the forbidden-API check below
|
|
/// is an interim, <i>advisory</i> text scan — it is NOT an authoritative trust-model
|
|
/// boundary. <see cref="CSharpDelimiterScanner.ContainsInCode"/> removes the
|
|
/// false-positive half (forbidden text inside a string/comment is ignored), but a
|
|
/// determined script can still bypass the literal patterns via namespace aliases,
|
|
/// <c>using static</c>, or <c>global::</c>-qualified references. Authoritative
|
|
/// enforcement requires Roslyn semantic symbol analysis of the referenced
|
|
/// types/namespaces and is the responsibility of the real script compiler and the
|
|
/// Site Runtime sandbox. Do not rely on this class as the sole trust-model gate.
|
|
/// </para>
|
|
/// </summary>
|
|
public class ScriptCompiler
|
|
{
|
|
/// <summary>
|
|
/// Forbidden namespace patterns — scripts (and trigger expressions, via
|
|
/// <see cref="ValidationService"/>) must not use these. Trigger expressions run
|
|
/// under the same trust model as scripts, so the list is shared from here rather
|
|
/// than duplicated.
|
|
///
|
|
/// <para>
|
|
/// Matched with <see cref="CSharpDelimiterScanner.ContainsInCode"/> against code
|
|
/// regions only. This is advisory — see the class summary's SECURITY LIMITATION
|
|
/// note; the substring patterns are bypassable and the authoritative check is
|
|
/// deferred to Roslyn semantic analysis.
|
|
/// </para>
|
|
/// </summary>
|
|
internal static readonly string[] ForbiddenPatterns =
|
|
[
|
|
"System.IO.",
|
|
"System.Diagnostics.Process",
|
|
"System.Threading.",
|
|
"System.Reflection.",
|
|
"System.Net.Sockets.",
|
|
"System.Net.Http.",
|
|
];
|
|
|
|
/// <summary>
|
|
/// Attempts to compile a script and returns success or a compilation error.
|
|
/// </summary>
|
|
/// <param name="code">The C# script code.</param>
|
|
/// <param name="scriptName">The canonical name of the script (for error messages).</param>
|
|
/// <returns>Success if the script compiles, or Failure with the error message.</returns>
|
|
public Result<bool> TryCompile(string code, string scriptName)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(code))
|
|
return Result<bool>.Failure($"Script '{scriptName}' has empty code.");
|
|
|
|
// Check for forbidden APIs. Advisory only (see class summary): the scan is
|
|
// code-region-aware so forbidden text inside a string/comment is ignored,
|
|
// but it remains a substring match and is not an authoritative boundary.
|
|
foreach (var pattern in ForbiddenPatterns)
|
|
{
|
|
if (CSharpDelimiterScanner.ContainsInCode(code, pattern))
|
|
{
|
|
return Result<bool>.Failure(
|
|
$"Script '{scriptName}' uses forbidden API: '{pattern.TrimEnd('.')}'. " +
|
|
"Scripts cannot use System.IO, Process, Threading, Reflection, or raw network APIs.");
|
|
}
|
|
}
|
|
|
|
// Basic structural validation: balanced braces/brackets/parens. The scan
|
|
// is string- and comment-aware (see CSharpDelimiterScanner) so a delimiter
|
|
// inside a regular/verbatim/interpolated/raw string, a char literal, or a
|
|
// comment does not produce a false mismatch. This remains an interim check
|
|
// until the Roslyn-based compiler is wired in.
|
|
var mismatch = CSharpDelimiterScanner.Scan(code);
|
|
return mismatch switch
|
|
{
|
|
CSharpDelimiterScanner.Mismatch.None =>
|
|
Result<bool>.Success(true),
|
|
CSharpDelimiterScanner.Mismatch.UnexpectedCloseBrace =>
|
|
Result<bool>.Failure($"Script '{scriptName}' has mismatched braces (unexpected closing brace)."),
|
|
CSharpDelimiterScanner.Mismatch.UnclosedBrace =>
|
|
Result<bool>.Failure($"Script '{scriptName}' has mismatched braces (unclosed opening brace)."),
|
|
CSharpDelimiterScanner.Mismatch.UnexpectedCloseBracket =>
|
|
Result<bool>.Failure($"Script '{scriptName}' has mismatched brackets (unexpected closing bracket)."),
|
|
CSharpDelimiterScanner.Mismatch.UnclosedBracket =>
|
|
Result<bool>.Failure($"Script '{scriptName}' has mismatched brackets (unclosed opening bracket)."),
|
|
CSharpDelimiterScanner.Mismatch.UnexpectedCloseParen =>
|
|
Result<bool>.Failure($"Script '{scriptName}' has mismatched parentheses (unexpected closing parenthesis)."),
|
|
CSharpDelimiterScanner.Mismatch.UnclosedParen =>
|
|
Result<bool>.Failure($"Script '{scriptName}' has mismatched parentheses (unclosed opening parenthesis)."),
|
|
CSharpDelimiterScanner.Mismatch.UnclosedBlockComment =>
|
|
Result<bool>.Failure($"Script '{scriptName}' has an unclosed block comment."),
|
|
CSharpDelimiterScanner.Mismatch.UnterminatedString =>
|
|
Result<bool>.Failure($"Script '{scriptName}' has an unterminated string literal."),
|
|
CSharpDelimiterScanner.Mismatch.UnterminatedChar =>
|
|
Result<bool>.Failure($"Script '{scriptName}' has an unterminated character literal."),
|
|
_ => Result<bool>.Success(true),
|
|
};
|
|
}
|
|
}
|