Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
- Added WineCspHttpProvider class to interface with Wine-hosted CryptoPro CSP. - Implemented ICryptoProvider, ICryptoProviderDiagnostics, and IDisposable interfaces. - Introduced WineCspHttpSigner and WineCspHttpHasher for signing and hashing operations. - Created WineCspProviderOptions for configuration settings including service URL and key options. - Developed CryptoProGostSigningService to handle GOST signing operations and key management. - Implemented HTTP service for the Wine CSP with endpoints for signing, verification, and hashing. - Added Swagger documentation for API endpoints. - Included health checks and error handling for service availability. - Established DTOs for request and response models in the service.
807 lines
26 KiB
C#
807 lines
26 KiB
C#
using StellaOps.Scanner.Analyzers.Lang.DotNet.Internal.Capabilities;
|
|
|
|
namespace StellaOps.Scanner.Analyzers.Lang.DotNet.Tests.Internal;
|
|
|
|
/// <summary>
|
|
/// Tests for <see cref="DotNetCapabilityScanner"/>.
|
|
/// </summary>
|
|
public sealed class DotNetCapabilityScannerTests
|
|
{
|
|
private const string TestFile = "Test.cs";
|
|
|
|
#region ScanFile - General Tests
|
|
|
|
[Fact]
|
|
public void ScanFile_NullContent_ReturnsEmpty()
|
|
{
|
|
var result = DotNetCapabilityScanner.ScanFile(null!, TestFile);
|
|
|
|
Assert.Empty(result);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_EmptyContent_ReturnsEmpty()
|
|
{
|
|
var result = DotNetCapabilityScanner.ScanFile("", TestFile);
|
|
|
|
Assert.Empty(result);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_WhitespaceContent_ReturnsEmpty()
|
|
{
|
|
var result = DotNetCapabilityScanner.ScanFile(" \n\t\n ", TestFile);
|
|
|
|
Assert.Empty(result);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_NoPatterns_ReturnsEmpty()
|
|
{
|
|
const string code = @"
|
|
namespace Test
|
|
{
|
|
public class Program
|
|
{
|
|
public static void Main() => Console.WriteLine(""Hello"");
|
|
}
|
|
}";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Empty(result);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_NormalizesBackslashesInPath()
|
|
{
|
|
const string code = @"Process.Start(""notepad.exe"");";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, @"C:\src\Test.cs");
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal("C:/src/Test.cs", result[0].SourceFile);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_DeduplicatesSamePatternOnSameLine()
|
|
{
|
|
const string code = @"Process.Start(""cmd""); Process.Start(""notepad"");";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
// Same pattern on same line should be deduplicated
|
|
Assert.Single(result);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ScanFile - Comment Stripping
|
|
|
|
[Fact]
|
|
public void ScanFile_IgnoresSingleLineComments()
|
|
{
|
|
const string code = @"
|
|
// Process.Start(""cmd"");
|
|
public void Method() { }";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Empty(result);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_IgnoresMultiLineComments()
|
|
{
|
|
const string code = @"
|
|
/*
|
|
Process.Start(""cmd"");
|
|
File.Delete(""file.txt"");
|
|
*/
|
|
public void Method() { }";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Empty(result);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ScanFile - Exec Patterns
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsProcessStart()
|
|
{
|
|
const string code = @"Process.Start(""notepad.exe"");";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.Exec, result[0].Kind);
|
|
Assert.Equal("Process.Start", result[0].Pattern);
|
|
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
|
|
Assert.Equal(1.0f, result[0].Confidence);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsNewProcessStartInfo()
|
|
{
|
|
const string code = @"var psi = new ProcessStartInfo(""cmd.exe"");";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.Exec, result[0].Kind);
|
|
Assert.Equal("ProcessStartInfo", result[0].Pattern);
|
|
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsProcessStartInfoObjectInitializer()
|
|
{
|
|
const string code = @"var psi = new ProcessStartInfo { FileName = ""cmd.exe"" };";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.Exec, result[0].Kind);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsUseShellExecuteTrue()
|
|
{
|
|
const string code = @"psi.UseShellExecute = true;";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.Exec, result[0].Kind);
|
|
Assert.Equal("UseShellExecute=true", result[0].Pattern);
|
|
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ScanFile - Filesystem Patterns
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsFileReadAllText()
|
|
{
|
|
const string code = @"var content = File.ReadAllText(""file.txt"");";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.Filesystem, result[0].Kind);
|
|
Assert.Equal("File.ReadAll/WriteAll", result[0].Pattern);
|
|
Assert.Equal(CapabilityRisk.Medium, result[0].Risk);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsFileWriteAllText()
|
|
{
|
|
const string code = @"File.WriteAllText(""file.txt"", content);";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.Filesystem, result[0].Kind);
|
|
Assert.Equal("File.ReadAll/WriteAll", result[0].Pattern);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsFileDelete()
|
|
{
|
|
const string code = @"File.Delete(""file.txt"");";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.Filesystem, result[0].Kind);
|
|
Assert.Equal("File/Directory.Delete", result[0].Pattern);
|
|
Assert.Equal(CapabilityRisk.High, result[0].Risk);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsDirectoryDelete()
|
|
{
|
|
const string code = @"Directory.Delete(""dir"", true);";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.Filesystem, result[0].Kind);
|
|
Assert.Equal("File/Directory.Delete", result[0].Pattern);
|
|
Assert.Equal(CapabilityRisk.High, result[0].Risk);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsFileCopy()
|
|
{
|
|
const string code = @"File.Copy(""src.txt"", ""dest.txt"");";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.Filesystem, result[0].Kind);
|
|
Assert.Equal("File/Directory operations", result[0].Pattern);
|
|
Assert.Equal(CapabilityRisk.Medium, result[0].Risk);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsNewFileStream()
|
|
{
|
|
const string code = @"using var fs = new FileStream(""file.bin"", FileMode.Open);";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.Filesystem, result[0].Kind);
|
|
Assert.Equal("FileStream", result[0].Pattern);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsSetAccessControl()
|
|
{
|
|
const string code = @"fileInfo.SetAccessControl(security);";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.Filesystem, result[0].Kind);
|
|
Assert.Equal("SetAccessControl", result[0].Pattern);
|
|
Assert.Equal(CapabilityRisk.High, result[0].Risk);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ScanFile - Network Patterns
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsNewHttpClient()
|
|
{
|
|
const string code = @"using var client = new HttpClient();";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.Network, result[0].Kind);
|
|
Assert.Equal("HttpClient", result[0].Pattern);
|
|
Assert.Equal(CapabilityRisk.Medium, result[0].Risk);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsGetAsync()
|
|
{
|
|
const string code = @"var response = await client.GetAsync(url);";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.Network, result[0].Kind);
|
|
Assert.Equal("HttpClient", result[0].Pattern);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsNewWebClient()
|
|
{
|
|
const string code = @"using var client = new WebClient();";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.Network, result[0].Kind);
|
|
Assert.Equal("WebClient", result[0].Pattern);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsNewSocket()
|
|
{
|
|
const string code = @"var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.Network, result[0].Kind);
|
|
Assert.Equal("Socket/TcpClient", result[0].Pattern);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsNewTcpClient()
|
|
{
|
|
const string code = @"var tcp = new TcpClient(""localhost"", 8080);";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.Network, result[0].Kind);
|
|
Assert.Equal("Socket/TcpClient", result[0].Pattern);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsWebRequestCreate()
|
|
{
|
|
const string code = @"var request = WebRequest.Create(url);";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.Network, result[0].Kind);
|
|
Assert.Equal("WebRequest", result[0].Pattern);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ScanFile - Environment Patterns
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsEnvironmentGetEnvironmentVariable()
|
|
{
|
|
const string code = @"var path = Environment.GetEnvironmentVariable(""PATH"");";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.Environment, result[0].Kind);
|
|
Assert.Equal("Environment.GetEnvironmentVariable", result[0].Pattern);
|
|
Assert.Equal(CapabilityRisk.Medium, result[0].Risk);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsEnvironmentSetEnvironmentVariable()
|
|
{
|
|
const string code = @"Environment.SetEnvironmentVariable(""MY_VAR"", ""value"");";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.Environment, result[0].Kind);
|
|
Assert.Equal("Environment.SetEnvironmentVariable", result[0].Pattern);
|
|
Assert.Equal(CapabilityRisk.High, result[0].Risk);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsEnvironmentGetEnvironmentVariables()
|
|
{
|
|
const string code = @"var envVars = Environment.GetEnvironmentVariables();";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.Environment, result[0].Kind);
|
|
Assert.Equal("Environment.GetEnvironmentVariables", result[0].Pattern);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ScanFile - Serialization Patterns (Critical for deserialization attacks)
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsBinaryFormatter()
|
|
{
|
|
const string code = @"var formatter = new BinaryFormatter();";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.Serialization, result[0].Kind);
|
|
Assert.Equal("BinaryFormatter", result[0].Pattern);
|
|
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsObjectStateFormatter()
|
|
{
|
|
const string code = @"var formatter = new ObjectStateFormatter();";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.Serialization, result[0].Kind);
|
|
Assert.Equal("ObjectStateFormatter", result[0].Pattern);
|
|
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsNetDataContractSerializer()
|
|
{
|
|
const string code = @"var serializer = new NetDataContractSerializer();";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.Serialization, result[0].Kind);
|
|
Assert.Equal("NetDataContractSerializer", result[0].Pattern);
|
|
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsLosFormatter()
|
|
{
|
|
const string code = @"var formatter = new LosFormatter();";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.Serialization, result[0].Kind);
|
|
Assert.Equal("LosFormatter", result[0].Pattern);
|
|
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsSoapFormatter()
|
|
{
|
|
const string code = @"var formatter = new SoapFormatter();";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.Serialization, result[0].Kind);
|
|
Assert.Equal("SoapFormatter", result[0].Pattern);
|
|
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsDataContractSerializer()
|
|
{
|
|
const string code = @"var serializer = new DataContractSerializer(typeof(MyClass));";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.Serialization, result[0].Kind);
|
|
Assert.Equal("DataContractSerializer", result[0].Pattern);
|
|
Assert.Equal(CapabilityRisk.Medium, result[0].Risk);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsJsonDeserialize()
|
|
{
|
|
const string code = @"var obj = JsonSerializer.Deserialize<MyClass>(json);";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.Serialization, result[0].Kind);
|
|
Assert.Equal("JsonSerializer.Deserialize", result[0].Pattern);
|
|
Assert.Equal(CapabilityRisk.Low, result[0].Risk);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ScanFile - Crypto Patterns
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsAesCreate()
|
|
{
|
|
const string code = @"using var aes = Aes.Create();";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.Crypto, result[0].Kind);
|
|
Assert.Equal("Cryptography", result[0].Pattern);
|
|
Assert.Equal(CapabilityRisk.Low, result[0].Risk);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsRsaCreate()
|
|
{
|
|
const string code = @"using var rsa = RSA.Create();";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.Crypto, result[0].Kind);
|
|
Assert.Equal("Asymmetric crypto", result[0].Pattern);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ScanFile - Database Patterns
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsNewSqlConnection()
|
|
{
|
|
const string code = @"using var conn = new SqlConnection(connectionString);";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.Database, result[0].Kind);
|
|
Assert.Equal("SqlConnection", result[0].Pattern);
|
|
Assert.Equal(CapabilityRisk.Medium, result[0].Risk);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsNewSqlCommand()
|
|
{
|
|
const string code = @"var cmd = new SqlCommand(""SELECT * FROM Users"", conn);";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.Database, result[0].Kind);
|
|
Assert.Equal("SqlCommand", result[0].Pattern);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsExecuteNonQuery()
|
|
{
|
|
const string code = @"cmd.ExecuteNonQuery();";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.Database, result[0].Kind);
|
|
Assert.Equal("Execute*", result[0].Pattern);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsExecuteReader()
|
|
{
|
|
const string code = @"using var reader = cmd.ExecuteReader();";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.Database, result[0].Kind);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ScanFile - Dynamic Code Patterns
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsDynamicMethod()
|
|
{
|
|
const string code = @"var dm = new DynamicMethod(""Test"", typeof(int), null);";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.DynamicCode, result[0].Kind);
|
|
Assert.Equal("DynamicMethod", result[0].Pattern);
|
|
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsILGenerator()
|
|
{
|
|
const string code = @"var il = dm.GetILGenerator();";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.DynamicCode, result[0].Kind);
|
|
Assert.Equal("ILGenerator", result[0].Pattern);
|
|
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsCSharpScript()
|
|
{
|
|
const string code = @"var result = await CSharpScript.EvaluateAsync(code);";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.DynamicCode, result[0].Kind);
|
|
Assert.Equal("CSharpScript", result[0].Pattern);
|
|
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsTypeBuilder()
|
|
{
|
|
const string code = @"var tb = mb.DefineType(""MyType"");";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
// TypeBuilder check expects "TypeBuilder" in the line
|
|
Assert.Empty(result); // DefineType doesn't match TypeBuilder pattern
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ScanFile - Reflection Patterns
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsAssemblyLoad()
|
|
{
|
|
const string code = @"var assembly = Assembly.Load(""MyAssembly"");";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.Reflection, result[0].Kind);
|
|
Assert.Equal("Assembly.Load", result[0].Pattern);
|
|
Assert.Equal(CapabilityRisk.High, result[0].Risk);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsAssemblyLoadFrom()
|
|
{
|
|
const string code = @"var assembly = Assembly.LoadFrom(""plugin.dll"");";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.Reflection, result[0].Kind);
|
|
Assert.Equal("Assembly.Load", result[0].Pattern);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsAssemblyLoadFile()
|
|
{
|
|
const string code = @"var assembly = Assembly.LoadFile(path);";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.Reflection, result[0].Kind);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsTypeInvokeMember()
|
|
{
|
|
const string code = @"type.InvokeMember(""Method"", BindingFlags.InvokeMethod, null, obj, args);";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.Reflection, result[0].Kind);
|
|
Assert.Equal("Type.InvokeMember", result[0].Pattern);
|
|
Assert.Equal(CapabilityRisk.High, result[0].Risk);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsActivatorCreateInstance()
|
|
{
|
|
const string code = @"var obj = Activator.CreateInstance(type);";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.Reflection, result[0].Kind);
|
|
Assert.Equal("Activator.CreateInstance", result[0].Pattern);
|
|
Assert.Equal(CapabilityRisk.Medium, result[0].Risk);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ScanFile - Native Code Patterns
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsDllImport()
|
|
{
|
|
const string code = @"[DllImport(""kernel32.dll"")]";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.NativeCode, result[0].Kind);
|
|
Assert.Equal("DllImport", result[0].Pattern);
|
|
Assert.Equal(CapabilityRisk.High, result[0].Risk);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsLibraryImport()
|
|
{
|
|
const string code = @"[LibraryImport(""user32.dll"")]";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.NativeCode, result[0].Kind);
|
|
Assert.Equal("LibraryImport", result[0].Pattern);
|
|
Assert.Equal(CapabilityRisk.High, result[0].Risk);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsMarshalPtrToStructure()
|
|
{
|
|
const string code = @"var obj = Marshal.PtrToStructure<MyStruct>(ptr);";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.NativeCode, result[0].Kind);
|
|
Assert.Equal("Marshal operations", result[0].Pattern);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsMarshalAllocHGlobal()
|
|
{
|
|
const string code = @"var ptr = Marshal.AllocHGlobal(size);";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.NativeCode, result[0].Kind);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsNativeLibraryLoad()
|
|
{
|
|
const string code = @"var lib = NativeLibrary.Load(""mylib.dll"");";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.NativeCode, result[0].Kind);
|
|
Assert.Equal("NativeLibrary.Load", result[0].Pattern);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsIntPtrOperations()
|
|
{
|
|
const string code = @"var ptr = new IntPtr(address);";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.NativeCode, result[0].Kind);
|
|
Assert.Equal("IntPtr operations", result[0].Pattern);
|
|
Assert.Equal(CapabilityRisk.Medium, result[0].Risk);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ScanFile - Unsafe Patterns
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsUnsafeBlock()
|
|
{
|
|
const string code = @"unsafe { var ptr = &value; }";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.NativeCode, result[0].Kind);
|
|
Assert.Equal("unsafe block", result[0].Pattern);
|
|
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsFixedStatement()
|
|
{
|
|
const string code = @"fixed (byte* ptr = array) { }";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.NativeCode, result[0].Kind);
|
|
Assert.Equal("fixed statement", result[0].Pattern);
|
|
Assert.Equal(CapabilityRisk.High, result[0].Risk);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanFile_DetectsStackalloc()
|
|
{
|
|
const string code = @"Span<byte> buffer = stackalloc byte[256];";
|
|
var result = DotNetCapabilityScanner.ScanFile(code, TestFile);
|
|
|
|
Assert.Single(result);
|
|
Assert.Equal(CapabilityKind.NativeCode, result[0].Kind);
|
|
Assert.Equal("stackalloc", result[0].Pattern);
|
|
Assert.Equal(CapabilityRisk.High, result[0].Risk);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region DotNetCapabilityEvidence Tests
|
|
|
|
[Fact]
|
|
public void Evidence_DeduplicationKey_IsCorrect()
|
|
{
|
|
var evidence = new DotNetCapabilityEvidence(
|
|
CapabilityKind.Exec,
|
|
"Test.cs",
|
|
10,
|
|
"Process.Start");
|
|
|
|
Assert.Equal("Exec|Test.cs|10|Process.Start", evidence.DeduplicationKey);
|
|
}
|
|
|
|
[Fact]
|
|
public void Evidence_ConfidenceIsClamped()
|
|
{
|
|
var evidence1 = new DotNetCapabilityEvidence(
|
|
CapabilityKind.Exec, "Test.cs", 1, "pattern",
|
|
confidence: 2.0f);
|
|
var evidence2 = new DotNetCapabilityEvidence(
|
|
CapabilityKind.Exec, "Test.cs", 1, "pattern",
|
|
confidence: -1.0f);
|
|
|
|
Assert.Equal(1.0f, evidence1.Confidence);
|
|
Assert.Equal(0.0f, evidence2.Confidence);
|
|
}
|
|
|
|
[Fact]
|
|
public void Evidence_CreateMetadata_IncludesAllFields()
|
|
{
|
|
var evidence = new DotNetCapabilityEvidence(
|
|
CapabilityKind.Exec,
|
|
"Test.cs",
|
|
10,
|
|
"Process.Start",
|
|
snippet: "Process.Start(\"cmd.exe\");",
|
|
confidence: 0.95f,
|
|
risk: CapabilityRisk.Critical);
|
|
|
|
var metadata = evidence.CreateMetadata().ToDictionary(kv => kv.Key, kv => kv.Value);
|
|
|
|
Assert.Equal("exec", metadata["capability.kind"]);
|
|
Assert.Equal("Test.cs:10", metadata["capability.source"]);
|
|
Assert.Equal("Process.Start", metadata["capability.pattern"]);
|
|
Assert.Equal("critical", metadata["capability.risk"]);
|
|
Assert.Equal("0.95", metadata["capability.confidence"]);
|
|
Assert.Contains("Process.Start", metadata["capability.snippet"]);
|
|
}
|
|
|
|
[Fact]
|
|
public void Evidence_ToLanguageEvidence_ReturnsCorrectFormat()
|
|
{
|
|
var evidence = new DotNetCapabilityEvidence(
|
|
CapabilityKind.Exec,
|
|
"Test.cs",
|
|
10,
|
|
"Process.Start");
|
|
|
|
var langEvidence = evidence.ToLanguageEvidence();
|
|
|
|
Assert.Equal(LanguageEvidenceKind.Metadata, langEvidence.Kind);
|
|
Assert.Equal("Test.cs", langEvidence.Source);
|
|
Assert.Equal("line:10", langEvidence.Locator);
|
|
Assert.Equal("Exec:Process.Start", langEvidence.Value);
|
|
}
|
|
|
|
#endregion
|
|
}
|