feat: Add native binary analyzer test utilities and implement SM2 signing tests
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
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Discover 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
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled

- Introduced `NativeTestBase` class for ELF, PE, and Mach-O binary parsing helpers and assertions.
- Created `TestCryptoFactory` for SM2 cryptographic provider setup and key generation.
- Implemented `Sm2SigningTests` to validate signing functionality with environment gate checks.
- Developed console export service and store with comprehensive unit tests for export status management.
This commit is contained in:
StellaOps Bot
2025-12-07 13:12:41 +02:00
parent d907729778
commit e53a282fbe
387 changed files with 21941 additions and 1518 deletions

View File

@@ -1,6 +1,8 @@
global using System;
global using System.Collections.Generic;
global using System.Globalization;
global using System.IO;
global using System.Linq;
global using System.Threading;
global using System.Threading.Tasks;

View File

@@ -128,15 +128,15 @@ internal static class SingleFileAppDetector
return results.ToImmutableArray();
}
private static int IndexOf(byte[] buffer, byte[] pattern, int bufferLength)
private static int IndexOf(byte[] buffer, byte[] pattern, int bufferLength, int startIndex = 0)
{
if (pattern.Length == 0 || bufferLength < pattern.Length)
if (pattern.Length == 0 || bufferLength < pattern.Length || startIndex < 0)
{
return -1;
}
var maxIndex = bufferLength - pattern.Length;
for (var i = 0; i <= maxIndex; i++)
for (var i = startIndex; i <= maxIndex; i++)
{
var found = true;
for (var j = 0; j < pattern.Length; j++)
@@ -164,26 +164,30 @@ internal static class SingleFileAppDetector
var dllPattern = ".dll"u8.ToArray();
var systemPattern = "System."u8.ToArray();
var index = 0;
while ((index = IndexOf(buffer[index..bufferLength], dllPattern, bufferLength - index)) >= 0)
// Count .dll patterns
var searchStart = 0;
while (searchStart <= bufferLength - dllPattern.Length)
{
count++;
index++;
if (index >= bufferLength - dllPattern.Length)
var foundAt = IndexOf(buffer, dllPattern, bufferLength, searchStart);
if (foundAt < 0)
{
break;
}
count++;
searchStart = foundAt + 1;
}
index = 0;
while ((index = IndexOf(buffer[index..bufferLength], systemPattern, bufferLength - index)) >= 0)
// Count System. patterns
searchStart = 0;
while (searchStart <= bufferLength - systemPattern.Length)
{
count++;
index++;
if (index >= bufferLength - systemPattern.Length)
var foundAt = IndexOf(buffer, systemPattern, bufferLength, searchStart);
if (foundAt < 0)
{
break;
}
count++;
searchStart = foundAt + 1;
}
return count;

View File

@@ -0,0 +1,102 @@
namespace StellaOps.Scanner.Analyzers.Lang.DotNet.Internal.Capabilities;
/// <summary>
/// Represents evidence of a capability usage detected in .NET/C# source code.
/// </summary>
internal sealed record DotNetCapabilityEvidence
{
public DotNetCapabilityEvidence(
CapabilityKind kind,
string sourceFile,
int sourceLine,
string pattern,
string? snippet = null,
float confidence = 1.0f,
CapabilityRisk risk = CapabilityRisk.Low)
{
ArgumentException.ThrowIfNullOrWhiteSpace(sourceFile, nameof(sourceFile));
ArgumentException.ThrowIfNullOrWhiteSpace(pattern, nameof(pattern));
Kind = kind;
SourceFile = NormalizePath(sourceFile);
SourceLine = sourceLine;
Pattern = pattern;
Snippet = snippet;
Confidence = Math.Clamp(confidence, 0f, 1f);
Risk = risk;
}
/// <summary>
/// The capability category.
/// </summary>
public CapabilityKind Kind { get; }
/// <summary>
/// The source file where the capability is used.
/// </summary>
public string SourceFile { get; }
/// <summary>
/// The line number of the capability usage.
/// </summary>
public int SourceLine { get; }
/// <summary>
/// The API, method, or pattern matched.
/// </summary>
public string Pattern { get; }
/// <summary>
/// A snippet of the code (for context).
/// </summary>
public string? Snippet { get; }
/// <summary>
/// Confidence level (0.0 to 1.0).
/// </summary>
public float Confidence { get; }
/// <summary>
/// Risk level associated with this capability usage.
/// </summary>
public CapabilityRisk Risk { get; }
/// <summary>
/// Unique key for deduplication.
/// </summary>
public string DeduplicationKey => $"{Kind}|{SourceFile}|{SourceLine}|{Pattern}";
/// <summary>
/// Creates metadata entries for this evidence.
/// </summary>
public IEnumerable<KeyValuePair<string, string?>> CreateMetadata()
{
yield return new KeyValuePair<string, string?>("capability.kind", Kind.ToString().ToLowerInvariant());
yield return new KeyValuePair<string, string?>("capability.source", $"{SourceFile}:{SourceLine}");
yield return new KeyValuePair<string, string?>("capability.pattern", Pattern);
yield return new KeyValuePair<string, string?>("capability.risk", Risk.ToString().ToLowerInvariant());
yield return new KeyValuePair<string, string?>("capability.confidence", Confidence.ToString("F2", CultureInfo.InvariantCulture));
if (!string.IsNullOrWhiteSpace(Snippet))
{
var truncated = Snippet.Length > 200 ? Snippet[..197] + "..." : Snippet;
yield return new KeyValuePair<string, string?>("capability.snippet", truncated);
}
}
/// <summary>
/// Converts to base LanguageComponentEvidence.
/// </summary>
public LanguageComponentEvidence ToLanguageEvidence()
{
return new LanguageComponentEvidence(
Kind: LanguageEvidenceKind.Metadata,
Source: SourceFile,
Locator: $"line:{SourceLine}",
Value: $"{Kind}:{Pattern}",
Sha256: null);
}
private static string NormalizePath(string path)
=> path.Replace('\\', '/');
}

View File

@@ -0,0 +1,136 @@
namespace StellaOps.Scanner.Analyzers.Lang.DotNet.Internal.Capabilities;
/// <summary>
/// Orchestrates capability scanning across .NET source files.
/// </summary>
internal static class DotNetCapabilityScanBuilder
{
private static readonly string[] SourceExtensions = [".cs", ".vb", ".fs"];
/// <summary>
/// Scans a .NET project directory for capabilities.
/// </summary>
public static DotNetCapabilityScanResult ScanProject(string projectPath, CancellationToken cancellationToken = default)
{
ArgumentException.ThrowIfNullOrWhiteSpace(projectPath);
if (!Directory.Exists(projectPath))
{
return DotNetCapabilityScanResult.Empty;
}
var allEvidences = new List<DotNetCapabilityEvidence>();
foreach (var sourceFile in EnumerateSourceFiles(projectPath))
{
cancellationToken.ThrowIfCancellationRequested();
try
{
var content = File.ReadAllText(sourceFile);
var relativePath = Path.GetRelativePath(projectPath, sourceFile);
var evidences = DotNetCapabilityScanner.ScanFile(content, relativePath);
allEvidences.AddRange(evidences);
}
catch (IOException)
{
// Skip inaccessible files
}
catch (UnauthorizedAccessException)
{
// Skip inaccessible files
}
}
// Deduplicate and sort for determinism
var finalEvidences = allEvidences
.DistinctBy(e => e.DeduplicationKey)
.OrderBy(e => e.SourceFile, StringComparer.Ordinal)
.ThenBy(e => e.SourceLine)
.ThenBy(e => e.Kind)
.ToList();
return new DotNetCapabilityScanResult(finalEvidences);
}
/// <summary>
/// Scans a solution directory for capabilities (multiple projects).
/// </summary>
public static DotNetCapabilityScanResult ScanSolution(string solutionPath, CancellationToken cancellationToken = default)
{
ArgumentException.ThrowIfNullOrWhiteSpace(solutionPath);
var solutionDir = File.Exists(solutionPath)
? Path.GetDirectoryName(solutionPath) ?? solutionPath
: solutionPath;
if (!Directory.Exists(solutionDir))
{
return DotNetCapabilityScanResult.Empty;
}
return ScanProject(solutionDir, cancellationToken);
}
/// <summary>
/// Scans specific .NET source content.
/// </summary>
public static DotNetCapabilityScanResult ScanContent(string content, string filePath)
{
if (string.IsNullOrWhiteSpace(content))
{
return DotNetCapabilityScanResult.Empty;
}
var evidences = DotNetCapabilityScanner.ScanFile(content, filePath);
return new DotNetCapabilityScanResult(evidences.ToList());
}
private static IEnumerable<string> EnumerateSourceFiles(string rootPath)
{
var options = new EnumerationOptions
{
RecurseSubdirectories = true,
IgnoreInaccessible = true,
MaxRecursionDepth = 20
};
foreach (var ext in SourceExtensions)
{
foreach (var file in Directory.EnumerateFiles(rootPath, $"*{ext}", options))
{
// Skip obj/bin directories
if (file.Contains($"{Path.DirectorySeparatorChar}obj{Path.DirectorySeparatorChar}") ||
file.Contains($"{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}") ||
file.Contains($"{Path.AltDirectorySeparatorChar}obj{Path.AltDirectorySeparatorChar}") ||
file.Contains($"{Path.AltDirectorySeparatorChar}bin{Path.AltDirectorySeparatorChar}"))
{
continue;
}
// Skip designer.cs files
if (file.EndsWith(".Designer.cs", StringComparison.OrdinalIgnoreCase) ||
file.EndsWith(".designer.cs", StringComparison.OrdinalIgnoreCase))
{
continue;
}
// Skip generated files
if (file.EndsWith(".g.cs", StringComparison.OrdinalIgnoreCase) ||
file.EndsWith(".generated.cs", StringComparison.OrdinalIgnoreCase))
{
continue;
}
// Skip test directories
if (file.Contains($"{Path.DirectorySeparatorChar}TestResults{Path.DirectorySeparatorChar}") ||
file.Contains($"{Path.AltDirectorySeparatorChar}TestResults{Path.AltDirectorySeparatorChar}"))
{
continue;
}
yield return file;
}
}
}
}

View File

@@ -0,0 +1,215 @@
namespace StellaOps.Scanner.Analyzers.Lang.DotNet.Internal.Capabilities;
/// <summary>
/// Aggregates capability scan results from .NET source code analysis.
/// </summary>
internal sealed class DotNetCapabilityScanResult
{
private readonly IReadOnlyList<DotNetCapabilityEvidence> _evidences;
private ILookup<CapabilityKind, DotNetCapabilityEvidence>? _byKind;
private ILookup<CapabilityRisk, DotNetCapabilityEvidence>? _byRisk;
private ILookup<string, DotNetCapabilityEvidence>? _byFile;
public DotNetCapabilityScanResult(IReadOnlyList<DotNetCapabilityEvidence> evidences)
{
_evidences = evidences ?? Array.Empty<DotNetCapabilityEvidence>();
}
/// <summary>
/// All capability evidences found.
/// </summary>
public IReadOnlyList<DotNetCapabilityEvidence> Evidences => _evidences;
/// <summary>
/// Gets whether any capabilities were detected.
/// </summary>
public bool HasCapabilities => _evidences.Count > 0;
/// <summary>
/// Gets evidences grouped by capability kind.
/// </summary>
public ILookup<CapabilityKind, DotNetCapabilityEvidence> EvidencesByKind
=> _byKind ??= _evidences.ToLookup(e => e.Kind);
/// <summary>
/// Gets evidences grouped by risk level.
/// </summary>
public ILookup<CapabilityRisk, DotNetCapabilityEvidence> EvidencesByRisk
=> _byRisk ??= _evidences.ToLookup(e => e.Risk);
/// <summary>
/// Gets evidences grouped by source file.
/// </summary>
public ILookup<string, DotNetCapabilityEvidence> EvidencesByFile
=> _byFile ??= _evidences.ToLookup(e => e.SourceFile, StringComparer.OrdinalIgnoreCase);
/// <summary>
/// Gets all critical risk evidences.
/// </summary>
public IEnumerable<DotNetCapabilityEvidence> CriticalRiskEvidences
=> _evidences.Where(e => e.Risk == CapabilityRisk.Critical);
/// <summary>
/// Gets all high risk evidences.
/// </summary>
public IEnumerable<DotNetCapabilityEvidence> HighRiskEvidences
=> _evidences.Where(e => e.Risk == CapabilityRisk.High);
/// <summary>
/// Gets the set of detected capability kinds.
/// </summary>
public IReadOnlySet<CapabilityKind> DetectedKinds
=> _evidences.Select(e => e.Kind).ToHashSet();
/// <summary>
/// Gets the highest risk level found.
/// </summary>
public CapabilityRisk HighestRisk
=> _evidences.Count > 0
? _evidences.Max(e => e.Risk)
: CapabilityRisk.Low;
/// <summary>
/// Gets evidences for a specific capability kind.
/// </summary>
public IEnumerable<DotNetCapabilityEvidence> GetByKind(CapabilityKind kind)
=> EvidencesByKind[kind];
/// <summary>
/// Gets evidences at or above a specific risk level.
/// </summary>
public IEnumerable<DotNetCapabilityEvidence> GetByMinimumRisk(CapabilityRisk minRisk)
=> _evidences.Where(e => e.Risk >= minRisk);
/// <summary>
/// Creates metadata entries for the scan result.
/// </summary>
public IEnumerable<KeyValuePair<string, string?>> CreateMetadata()
{
yield return new KeyValuePair<string, string?>(
"capability.total_count",
_evidences.Count.ToString(CultureInfo.InvariantCulture));
foreach (var kindGroup in EvidencesByKind.OrderBy(g => g.Key.ToString(), StringComparer.Ordinal))
{
yield return new KeyValuePair<string, string?>(
$"capability.{kindGroup.Key.ToString().ToLowerInvariant()}_count",
kindGroup.Count().ToString(CultureInfo.InvariantCulture));
}
var criticalCount = CriticalRiskEvidences.Count();
var highCount = HighRiskEvidences.Count();
var mediumCount = _evidences.Count(e => e.Risk == CapabilityRisk.Medium);
var lowCount = _evidences.Count(e => e.Risk == CapabilityRisk.Low);
yield return new KeyValuePair<string, string?>("capability.critical_risk_count", criticalCount.ToString(CultureInfo.InvariantCulture));
yield return new KeyValuePair<string, string?>("capability.high_risk_count", highCount.ToString(CultureInfo.InvariantCulture));
yield return new KeyValuePair<string, string?>("capability.medium_risk_count", mediumCount.ToString(CultureInfo.InvariantCulture));
yield return new KeyValuePair<string, string?>("capability.low_risk_count", lowCount.ToString(CultureInfo.InvariantCulture));
if (_evidences.Count > 0)
{
yield return new KeyValuePair<string, string?>(
"capability.highest_risk",
HighestRisk.ToString().ToLowerInvariant());
}
if (DetectedKinds.Count > 0)
{
yield return new KeyValuePair<string, string?>(
"capability.detected_kinds",
string.Join(';', DetectedKinds.OrderBy(k => k.ToString(), StringComparer.Ordinal).Select(k => k.ToString().ToLowerInvariant())));
}
var criticalFiles = CriticalRiskEvidences
.Select(e => e.SourceFile)
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(f => f, StringComparer.Ordinal)
.ToList();
if (criticalFiles.Count > 0)
{
yield return new KeyValuePair<string, string?>(
"capability.critical_files",
string.Join(';', criticalFiles.Take(10)));
if (criticalFiles.Count > 10)
{
yield return new KeyValuePair<string, string?>(
"capability.critical_files_truncated",
"true");
}
}
var uniquePatterns = _evidences
.Select(e => e.Pattern)
.Distinct(StringComparer.OrdinalIgnoreCase)
.Count();
yield return new KeyValuePair<string, string?>(
"capability.unique_pattern_count",
uniquePatterns.ToString(CultureInfo.InvariantCulture));
}
/// <summary>
/// Creates a summary of detected capabilities.
/// </summary>
public DotNetCapabilitySummary CreateSummary()
{
return new DotNetCapabilitySummary(
HasExec: EvidencesByKind[CapabilityKind.Exec].Any(),
HasFilesystem: EvidencesByKind[CapabilityKind.Filesystem].Any(),
HasNetwork: EvidencesByKind[CapabilityKind.Network].Any(),
HasEnvironment: EvidencesByKind[CapabilityKind.Environment].Any(),
HasSerialization: EvidencesByKind[CapabilityKind.Serialization].Any(),
HasCrypto: EvidencesByKind[CapabilityKind.Crypto].Any(),
HasDatabase: EvidencesByKind[CapabilityKind.Database].Any(),
HasDynamicCode: EvidencesByKind[CapabilityKind.DynamicCode].Any(),
HasReflection: EvidencesByKind[CapabilityKind.Reflection].Any(),
HasNativeCode: EvidencesByKind[CapabilityKind.NativeCode].Any(),
CriticalCount: CriticalRiskEvidences.Count(),
HighRiskCount: HighRiskEvidences.Count(),
TotalCount: _evidences.Count);
}
/// <summary>
/// Empty scan result with no capabilities detected.
/// </summary>
public static DotNetCapabilityScanResult Empty { get; } = new(Array.Empty<DotNetCapabilityEvidence>());
}
/// <summary>
/// Summary of detected .NET capabilities.
/// </summary>
internal sealed record DotNetCapabilitySummary(
bool HasExec,
bool HasFilesystem,
bool HasNetwork,
bool HasEnvironment,
bool HasSerialization,
bool HasCrypto,
bool HasDatabase,
bool HasDynamicCode,
bool HasReflection,
bool HasNativeCode,
int CriticalCount,
int HighRiskCount,
int TotalCount)
{
/// <summary>
/// Creates metadata entries for the summary.
/// </summary>
public IEnumerable<KeyValuePair<string, string?>> CreateMetadata()
{
yield return new KeyValuePair<string, string?>("capability.has_exec", HasExec.ToString().ToLowerInvariant());
yield return new KeyValuePair<string, string?>("capability.has_filesystem", HasFilesystem.ToString().ToLowerInvariant());
yield return new KeyValuePair<string, string?>("capability.has_network", HasNetwork.ToString().ToLowerInvariant());
yield return new KeyValuePair<string, string?>("capability.has_environment", HasEnvironment.ToString().ToLowerInvariant());
yield return new KeyValuePair<string, string?>("capability.has_serialization", HasSerialization.ToString().ToLowerInvariant());
yield return new KeyValuePair<string, string?>("capability.has_crypto", HasCrypto.ToString().ToLowerInvariant());
yield return new KeyValuePair<string, string?>("capability.has_database", HasDatabase.ToString().ToLowerInvariant());
yield return new KeyValuePair<string, string?>("capability.has_dynamic_code", HasDynamicCode.ToString().ToLowerInvariant());
yield return new KeyValuePair<string, string?>("capability.has_reflection", HasReflection.ToString().ToLowerInvariant());
yield return new KeyValuePair<string, string?>("capability.has_native_code", HasNativeCode.ToString().ToLowerInvariant());
}
}

View File

@@ -0,0 +1,876 @@
using System.Text.RegularExpressions;
namespace StellaOps.Scanner.Analyzers.Lang.DotNet.Internal.Capabilities;
/// <summary>
/// Scans .NET/C# source files for security-relevant capabilities.
/// Detects exec, P/Invoke, reflection, serialization, and other dangerous patterns.
/// </summary>
internal static partial class DotNetCapabilityScanner
{
/// <summary>
/// Scans a C# source file for capabilities.
/// </summary>
public static IReadOnlyList<DotNetCapabilityEvidence> ScanFile(string content, string filePath)
{
if (string.IsNullOrWhiteSpace(content))
{
return [];
}
var evidences = new List<DotNetCapabilityEvidence>();
var strippedContent = StripComments(content);
var lines = content.Split('\n');
var strippedLines = strippedContent.Split('\n');
// Track usings for context
var usings = ParseUsings(content);
for (var lineIndex = 0; lineIndex < strippedLines.Length; lineIndex++)
{
var strippedLine = strippedLines[lineIndex];
var originalLine = lineIndex < lines.Length ? lines[lineIndex] : strippedLine;
var lineNumber = lineIndex + 1;
if (string.IsNullOrWhiteSpace(strippedLine))
{
continue;
}
CheckExecPatterns(strippedLine, originalLine, filePath, lineNumber, usings, evidences);
CheckFilesystemPatterns(strippedLine, originalLine, filePath, lineNumber, usings, evidences);
CheckNetworkPatterns(strippedLine, originalLine, filePath, lineNumber, usings, evidences);
CheckEnvironmentPatterns(strippedLine, originalLine, filePath, lineNumber, usings, evidences);
CheckSerializationPatterns(strippedLine, originalLine, filePath, lineNumber, usings, evidences);
CheckCryptoPatterns(strippedLine, originalLine, filePath, lineNumber, usings, evidences);
CheckDatabasePatterns(strippedLine, originalLine, filePath, lineNumber, usings, evidences);
CheckDynamicCodePatterns(strippedLine, originalLine, filePath, lineNumber, usings, evidences);
CheckReflectionPatterns(strippedLine, originalLine, filePath, lineNumber, usings, evidences);
CheckNativeCodePatterns(strippedLine, originalLine, filePath, lineNumber, usings, evidences);
CheckUnsafePatterns(strippedLine, originalLine, filePath, lineNumber, usings, evidences);
}
return evidences
.DistinctBy(e => e.DeduplicationKey)
.OrderBy(e => e.SourceFile, StringComparer.Ordinal)
.ThenBy(e => e.SourceLine)
.ThenBy(e => e.Kind)
.ToList();
}
private static HashSet<string> ParseUsings(string content)
{
var usings = new HashSet<string>(StringComparer.Ordinal);
foreach (Match match in UsingPattern().Matches(content))
{
usings.Add(match.Groups[1].Value);
}
return usings;
}
private static void CheckExecPatterns(
string strippedLine, string originalLine, string filePath, int lineNumber,
HashSet<string> usings, List<DotNetCapabilityEvidence> evidences)
{
// Process.Start - Critical
if (strippedLine.Contains("Process.Start("))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.Exec,
filePath,
lineNumber,
"Process.Start",
GetSnippet(originalLine),
1.0f,
CapabilityRisk.Critical));
}
// new ProcessStartInfo - Critical
if (strippedLine.Contains("new ProcessStartInfo(") ||
strippedLine.Contains("ProcessStartInfo {"))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.Exec,
filePath,
lineNumber,
"ProcessStartInfo",
GetSnippet(originalLine),
1.0f,
CapabilityRisk.Critical));
}
// ShellExecute patterns
if (strippedLine.Contains("UseShellExecute") &&
(strippedLine.Contains("= true") || strippedLine.Contains("=true")))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.Exec,
filePath,
lineNumber,
"UseShellExecute=true",
GetSnippet(originalLine),
0.95f,
CapabilityRisk.Critical));
}
}
private static void CheckFilesystemPatterns(
string strippedLine, string originalLine, string filePath, int lineNumber,
HashSet<string> usings, List<DotNetCapabilityEvidence> evidences)
{
// File.ReadAllText/WriteAllText - Medium
if (FileReadWritePattern().IsMatch(strippedLine))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.Filesystem,
filePath,
lineNumber,
"File.ReadAll/WriteAll",
GetSnippet(originalLine),
0.9f,
CapabilityRisk.Medium));
}
// File.Delete - High
if (strippedLine.Contains("File.Delete(") ||
strippedLine.Contains("Directory.Delete("))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.Filesystem,
filePath,
lineNumber,
"File/Directory.Delete",
GetSnippet(originalLine),
1.0f,
CapabilityRisk.High));
}
// File/Directory operations - Medium
if (strippedLine.Contains("File.Move(") ||
strippedLine.Contains("File.Copy(") ||
strippedLine.Contains("Directory.Move(") ||
strippedLine.Contains("Directory.CreateDirectory("))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.Filesystem,
filePath,
lineNumber,
"File/Directory operations",
GetSnippet(originalLine),
0.9f,
CapabilityRisk.Medium));
}
// FileStream - Medium
if (strippedLine.Contains("new FileStream(") ||
strippedLine.Contains("File.Open(") ||
strippedLine.Contains("File.OpenRead(") ||
strippedLine.Contains("File.OpenWrite("))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.Filesystem,
filePath,
lineNumber,
"FileStream",
GetSnippet(originalLine),
0.85f,
CapabilityRisk.Medium));
}
// SetAccessControl - High
if (strippedLine.Contains("SetAccessControl(") ||
strippedLine.Contains("FileSecurity"))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.Filesystem,
filePath,
lineNumber,
"SetAccessControl",
GetSnippet(originalLine),
1.0f,
CapabilityRisk.High));
}
}
private static void CheckNetworkPatterns(
string strippedLine, string originalLine, string filePath, int lineNumber,
HashSet<string> usings, List<DotNetCapabilityEvidence> evidences)
{
// HttpClient - Medium
if (strippedLine.Contains("new HttpClient(") ||
strippedLine.Contains("HttpClient.") ||
strippedLine.Contains(".GetAsync(") ||
strippedLine.Contains(".PostAsync(") ||
strippedLine.Contains(".SendAsync("))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.Network,
filePath,
lineNumber,
"HttpClient",
GetSnippet(originalLine),
0.9f,
CapabilityRisk.Medium));
}
// WebClient (obsolete but still used) - Medium
if (strippedLine.Contains("new WebClient(") ||
strippedLine.Contains("WebClient."))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.Network,
filePath,
lineNumber,
"WebClient",
GetSnippet(originalLine),
0.85f,
CapabilityRisk.Medium));
}
// Socket - Medium
if (strippedLine.Contains("new Socket(") ||
strippedLine.Contains("Socket.") ||
strippedLine.Contains("new TcpClient(") ||
strippedLine.Contains("new TcpListener(") ||
strippedLine.Contains("new UdpClient("))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.Network,
filePath,
lineNumber,
"Socket/TcpClient",
GetSnippet(originalLine),
0.9f,
CapabilityRisk.Medium));
}
// WebRequest - Medium
if (strippedLine.Contains("WebRequest.Create(") ||
strippedLine.Contains("HttpWebRequest"))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.Network,
filePath,
lineNumber,
"WebRequest",
GetSnippet(originalLine),
0.85f,
CapabilityRisk.Medium));
}
}
private static void CheckEnvironmentPatterns(
string strippedLine, string originalLine, string filePath, int lineNumber,
HashSet<string> usings, List<DotNetCapabilityEvidence> evidences)
{
// Environment.GetEnvironmentVariable - Medium
if (strippedLine.Contains("Environment.GetEnvironmentVariable("))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.Environment,
filePath,
lineNumber,
"Environment.GetEnvironmentVariable",
GetSnippet(originalLine),
0.9f,
CapabilityRisk.Medium));
}
// Environment.SetEnvironmentVariable - High
if (strippedLine.Contains("Environment.SetEnvironmentVariable("))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.Environment,
filePath,
lineNumber,
"Environment.SetEnvironmentVariable",
GetSnippet(originalLine),
1.0f,
CapabilityRisk.High));
}
// Environment.GetEnvironmentVariables - Medium
if (strippedLine.Contains("Environment.GetEnvironmentVariables("))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.Environment,
filePath,
lineNumber,
"Environment.GetEnvironmentVariables",
GetSnippet(originalLine),
0.9f,
CapabilityRisk.Medium));
}
// Environment.ExpandEnvironmentVariables - Medium
if (strippedLine.Contains("Environment.ExpandEnvironmentVariables("))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.Environment,
filePath,
lineNumber,
"Environment.ExpandEnvironmentVariables",
GetSnippet(originalLine),
0.85f,
CapabilityRisk.Medium));
}
}
private static void CheckSerializationPatterns(
string strippedLine, string originalLine, string filePath, int lineNumber,
HashSet<string> usings, List<DotNetCapabilityEvidence> evidences)
{
// BinaryFormatter - Critical (dangerous deserialization)
if (strippedLine.Contains("BinaryFormatter") ||
strippedLine.Contains("new BinaryFormatter("))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.Serialization,
filePath,
lineNumber,
"BinaryFormatter",
GetSnippet(originalLine),
1.0f,
CapabilityRisk.Critical));
}
// ObjectStateFormatter - Critical
if (strippedLine.Contains("ObjectStateFormatter"))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.Serialization,
filePath,
lineNumber,
"ObjectStateFormatter",
GetSnippet(originalLine),
1.0f,
CapabilityRisk.Critical));
}
// NetDataContractSerializer - Critical
if (strippedLine.Contains("NetDataContractSerializer"))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.Serialization,
filePath,
lineNumber,
"NetDataContractSerializer",
GetSnippet(originalLine),
1.0f,
CapabilityRisk.Critical));
}
// LosFormatter - Critical
if (strippedLine.Contains("LosFormatter"))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.Serialization,
filePath,
lineNumber,
"LosFormatter",
GetSnippet(originalLine),
1.0f,
CapabilityRisk.Critical));
}
// SoapFormatter - Critical
if (strippedLine.Contains("SoapFormatter"))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.Serialization,
filePath,
lineNumber,
"SoapFormatter",
GetSnippet(originalLine),
1.0f,
CapabilityRisk.Critical));
}
// XmlSerializer with TypeResolver - High
if (strippedLine.Contains("XmlSerializer") &&
strippedLine.Contains("Type"))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.Serialization,
filePath,
lineNumber,
"XmlSerializer with Type",
GetSnippet(originalLine),
0.8f,
CapabilityRisk.High));
}
// DataContractSerializer - Medium
if (strippedLine.Contains("DataContractSerializer"))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.Serialization,
filePath,
lineNumber,
"DataContractSerializer",
GetSnippet(originalLine),
0.9f,
CapabilityRisk.Medium));
}
// JsonSerializer.Deserialize - Low
if (strippedLine.Contains("JsonSerializer.Deserialize") ||
strippedLine.Contains("JsonConvert.DeserializeObject"))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.Serialization,
filePath,
lineNumber,
"JsonSerializer.Deserialize",
GetSnippet(originalLine),
0.8f,
CapabilityRisk.Low));
}
}
private static void CheckCryptoPatterns(
string strippedLine, string originalLine, string filePath, int lineNumber,
HashSet<string> usings, List<DotNetCapabilityEvidence> evidences)
{
// Crypto algorithms - Low
if (CryptoPattern().IsMatch(strippedLine))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.Crypto,
filePath,
lineNumber,
"Cryptography",
GetSnippet(originalLine),
0.85f,
CapabilityRisk.Low));
}
// RSA/DSA/ECDsa - Low
if (strippedLine.Contains("RSA.Create(") ||
strippedLine.Contains("DSA.Create(") ||
strippedLine.Contains("ECDsa.Create(") ||
strippedLine.Contains("new RSACryptoServiceProvider("))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.Crypto,
filePath,
lineNumber,
"Asymmetric crypto",
GetSnippet(originalLine),
0.9f,
CapabilityRisk.Low));
}
}
private static void CheckDatabasePatterns(
string strippedLine, string originalLine, string filePath, int lineNumber,
HashSet<string> usings, List<DotNetCapabilityEvidence> evidences)
{
// SqlConnection - Medium
if (strippedLine.Contains("new SqlConnection(") ||
strippedLine.Contains("SqlConnection."))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.Database,
filePath,
lineNumber,
"SqlConnection",
GetSnippet(originalLine),
0.95f,
CapabilityRisk.Medium));
}
// SqlCommand - Medium
if (strippedLine.Contains("new SqlCommand(") ||
strippedLine.Contains("SqlCommand."))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.Database,
filePath,
lineNumber,
"SqlCommand",
GetSnippet(originalLine),
0.9f,
CapabilityRisk.Medium));
}
// ExecuteNonQuery/ExecuteReader/ExecuteScalar - Medium
if (strippedLine.Contains(".ExecuteNonQuery(") ||
strippedLine.Contains(".ExecuteReader(") ||
strippedLine.Contains(".ExecuteScalar("))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.Database,
filePath,
lineNumber,
"Execute*",
GetSnippet(originalLine),
0.85f,
CapabilityRisk.Medium));
}
// String concatenation with SQL keywords - High
if (SqlInjectionPattern().IsMatch(strippedLine))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.Database,
filePath,
lineNumber,
"SQL string concat",
GetSnippet(originalLine),
0.7f,
CapabilityRisk.High));
}
// DbConnection - Medium
if (strippedLine.Contains("DbConnection") ||
strippedLine.Contains("IDbConnection") ||
strippedLine.Contains("IDbCommand"))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.Database,
filePath,
lineNumber,
"DbConnection/IDbCommand",
GetSnippet(originalLine),
0.8f,
CapabilityRisk.Medium));
}
}
private static void CheckDynamicCodePatterns(
string strippedLine, string originalLine, string filePath, int lineNumber,
HashSet<string> usings, List<DotNetCapabilityEvidence> evidences)
{
// DynamicMethod - Critical
if (strippedLine.Contains("new DynamicMethod(") ||
strippedLine.Contains("DynamicMethod."))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.DynamicCode,
filePath,
lineNumber,
"DynamicMethod",
GetSnippet(originalLine),
1.0f,
CapabilityRisk.Critical));
}
// Expression.Compile - High
if (strippedLine.Contains(".Compile(") &&
(usings.Contains("System.Linq.Expressions") ||
strippedLine.Contains("Expression")))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.DynamicCode,
filePath,
lineNumber,
"Expression.Compile",
GetSnippet(originalLine),
0.9f,
CapabilityRisk.High));
}
// ILGenerator - Critical
if (strippedLine.Contains("ILGenerator") ||
strippedLine.Contains(".GetILGenerator(") ||
strippedLine.Contains(".Emit("))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.DynamicCode,
filePath,
lineNumber,
"ILGenerator",
GetSnippet(originalLine),
1.0f,
CapabilityRisk.Critical));
}
// CSharpScript/Roslyn scripting - Critical
if (strippedLine.Contains("CSharpScript.") ||
strippedLine.Contains("ScriptEngine.") ||
strippedLine.Contains(".EvaluateAsync("))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.DynamicCode,
filePath,
lineNumber,
"CSharpScript",
GetSnippet(originalLine),
1.0f,
CapabilityRisk.Critical));
}
// TypeBuilder - High
if (strippedLine.Contains("TypeBuilder") ||
strippedLine.Contains("ModuleBuilder") ||
strippedLine.Contains("AssemblyBuilder"))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.DynamicCode,
filePath,
lineNumber,
"TypeBuilder",
GetSnippet(originalLine),
1.0f,
CapabilityRisk.High));
}
}
private static void CheckReflectionPatterns(
string strippedLine, string originalLine, string filePath, int lineNumber,
HashSet<string> usings, List<DotNetCapabilityEvidence> evidences)
{
// Assembly.Load* - High
if (strippedLine.Contains("Assembly.Load(") ||
strippedLine.Contains("Assembly.LoadFrom(") ||
strippedLine.Contains("Assembly.LoadFile(") ||
strippedLine.Contains("Assembly.LoadWithPartialName("))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.Reflection,
filePath,
lineNumber,
"Assembly.Load",
GetSnippet(originalLine),
1.0f,
CapabilityRisk.High));
}
// MethodInfo.Invoke - Medium
if (strippedLine.Contains(".Invoke(") &&
(strippedLine.Contains("MethodInfo") ||
strippedLine.Contains("GetMethod(") ||
strippedLine.Contains("GetMethods(")))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.Reflection,
filePath,
lineNumber,
"MethodInfo.Invoke",
GetSnippet(originalLine),
0.9f,
CapabilityRisk.Medium));
}
// Type.InvokeMember - High
if (strippedLine.Contains(".InvokeMember("))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.Reflection,
filePath,
lineNumber,
"Type.InvokeMember",
GetSnippet(originalLine),
0.95f,
CapabilityRisk.High));
}
// Activator.CreateInstance - Medium
if (strippedLine.Contains("Activator.CreateInstance("))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.Reflection,
filePath,
lineNumber,
"Activator.CreateInstance",
GetSnippet(originalLine),
0.9f,
CapabilityRisk.Medium));
}
// Type.GetType with string - Medium
if (TypeGetTypePattern().IsMatch(strippedLine))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.Reflection,
filePath,
lineNumber,
"Type.GetType",
GetSnippet(originalLine),
0.85f,
CapabilityRisk.Medium));
}
}
private static void CheckNativeCodePatterns(
string strippedLine, string originalLine, string filePath, int lineNumber,
HashSet<string> usings, List<DotNetCapabilityEvidence> evidences)
{
// DllImport - High
if (strippedLine.Contains("[DllImport(") ||
strippedLine.Contains("[DllImportAttribute("))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.NativeCode,
filePath,
lineNumber,
"DllImport",
GetSnippet(originalLine),
1.0f,
CapabilityRisk.High));
}
// LibraryImport (.NET 7+) - High
if (strippedLine.Contains("[LibraryImport(") ||
strippedLine.Contains("[LibraryImportAttribute("))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.NativeCode,
filePath,
lineNumber,
"LibraryImport",
GetSnippet(originalLine),
1.0f,
CapabilityRisk.High));
}
// Marshal operations - High
if (strippedLine.Contains("Marshal.") &&
(strippedLine.Contains("PtrToStructure") ||
strippedLine.Contains("StructureToPtr") ||
strippedLine.Contains("GetDelegateForFunctionPointer") ||
strippedLine.Contains("GetFunctionPointerForDelegate") ||
strippedLine.Contains("AllocHGlobal") ||
strippedLine.Contains("FreeHGlobal")))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.NativeCode,
filePath,
lineNumber,
"Marshal operations",
GetSnippet(originalLine),
1.0f,
CapabilityRisk.High));
}
// NativeLibrary - High
if (strippedLine.Contains("NativeLibrary.Load(") ||
strippedLine.Contains("NativeLibrary.TryLoad("))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.NativeCode,
filePath,
lineNumber,
"NativeLibrary.Load",
GetSnippet(originalLine),
1.0f,
CapabilityRisk.High));
}
// IntPtr/nint operations - Medium
if (strippedLine.Contains("IntPtr.") ||
strippedLine.Contains("new IntPtr(") ||
strippedLine.Contains("(IntPtr)") ||
strippedLine.Contains("nint."))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.NativeCode,
filePath,
lineNumber,
"IntPtr operations",
GetSnippet(originalLine),
0.8f,
CapabilityRisk.Medium));
}
}
private static void CheckUnsafePatterns(
string strippedLine, string originalLine, string filePath, int lineNumber,
HashSet<string> usings, List<DotNetCapabilityEvidence> evidences)
{
// unsafe keyword - Critical
if (UnsafeBlockPattern().IsMatch(strippedLine))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.NativeCode,
filePath,
lineNumber,
"unsafe block",
GetSnippet(originalLine),
1.0f,
CapabilityRisk.Critical));
}
// fixed statement - High
if (FixedStatementPattern().IsMatch(strippedLine))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.NativeCode,
filePath,
lineNumber,
"fixed statement",
GetSnippet(originalLine),
0.95f,
CapabilityRisk.High));
}
// stackalloc - High
if (strippedLine.Contains("stackalloc"))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.NativeCode,
filePath,
lineNumber,
"stackalloc",
GetSnippet(originalLine),
0.95f,
CapabilityRisk.High));
}
// Span<T> with pointers - Medium
if (strippedLine.Contains("Span<") && strippedLine.Contains("*"))
{
evidences.Add(new DotNetCapabilityEvidence(
CapabilityKind.NativeCode,
filePath,
lineNumber,
"Span with pointers",
GetSnippet(originalLine),
0.85f,
CapabilityRisk.Medium));
}
}
private static string StripComments(string content)
{
var result = SingleLineCommentPattern().Replace(content, "");
result = MultiLineCommentPattern().Replace(result, "");
return result;
}
private static string GetSnippet(string line)
{
var trimmed = line.Trim();
return trimmed.Length > 150 ? trimmed[..147] + "..." : trimmed;
}
// Regex patterns
[GeneratedRegex(@"using\s+([A-Za-z0-9_.]+)\s*;")]
private static partial Regex UsingPattern();
[GeneratedRegex(@"File\.(ReadAll|WriteAll)(Text|Bytes|Lines)(Async)?\s*\(")]
private static partial Regex FileReadWritePattern();
[GeneratedRegex(@"(Aes|SHA256|SHA512|MD5|SHA1|TripleDES|Rijndael|HMAC)\.(Create|New)")]
private static partial Regex CryptoPattern();
[GeneratedRegex(@"(?i)(SELECT|INSERT|UPDATE|DELETE|DROP)\s+.*(\+|String\.Format|\$"")")]
private static partial Regex SqlInjectionPattern();
[GeneratedRegex(@"Type\.GetType\s*\(\s*[^)]+\)")]
private static partial Regex TypeGetTypePattern();
[GeneratedRegex(@"\bunsafe\s*\{")]
private static partial Regex UnsafeBlockPattern();
[GeneratedRegex(@"\bfixed\s*\(")]
private static partial Regex FixedStatementPattern();
[GeneratedRegex(@"//.*$", RegexOptions.Multiline)]
private static partial Regex SingleLineCommentPattern();
[GeneratedRegex(@"/\*[\s\S]*?\*/")]
private static partial Regex MultiLineCommentPattern();
}

View File

@@ -4,7 +4,7 @@
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<EnableDefaultItems>false</EnableDefaultItems>
</PropertyGroup>