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,9 +1,12 @@
global using System;
global using System.Collections.Generic;
global using System.Globalization;
global using System.IO;
global using System.IO.Compression;
global using System.Linq;
global using System.Security.Cryptography;
global using System.Text;
global using System.Text.RegularExpressions;
global using System.Threading;
global using System.Threading.Tasks;

View File

@@ -0,0 +1,102 @@
namespace StellaOps.Scanner.Analyzers.Lang.Java.Internal.Capabilities;
/// <summary>
/// Represents evidence of a capability usage detected in Java source code.
/// </summary>
internal sealed record JavaCapabilityEvidence
{
public JavaCapabilityEvidence(
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,170 @@
namespace StellaOps.Scanner.Analyzers.Lang.Java.Internal.Capabilities;
/// <summary>
/// Orchestrates capability scanning across Java source files.
/// </summary>
internal static class JavaCapabilityScanBuilder
{
private static readonly string[] SourceExtensions = [".java"];
/// <summary>
/// Scans a Java project directory for capabilities.
/// </summary>
public static JavaCapabilityScanResult ScanProject(string projectPath, CancellationToken cancellationToken = default)
{
ArgumentException.ThrowIfNullOrWhiteSpace(projectPath);
if (!Directory.Exists(projectPath))
{
return JavaCapabilityScanResult.Empty;
}
var allEvidences = new List<JavaCapabilityEvidence>();
foreach (var sourceFile in EnumerateSourceFiles(projectPath))
{
cancellationToken.ThrowIfCancellationRequested();
try
{
var content = File.ReadAllText(sourceFile);
var relativePath = Path.GetRelativePath(projectPath, sourceFile);
var evidences = JavaCapabilityScanner.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 JavaCapabilityScanResult(finalEvidences);
}
/// <summary>
/// Scans a Maven/Gradle project for capabilities.
/// </summary>
public static JavaCapabilityScanResult ScanMavenProject(string pomPath, CancellationToken cancellationToken = default)
{
ArgumentException.ThrowIfNullOrWhiteSpace(pomPath);
var projectDir = File.Exists(pomPath)
? Path.GetDirectoryName(pomPath) ?? pomPath
: pomPath;
if (!Directory.Exists(projectDir))
{
return JavaCapabilityScanResult.Empty;
}
// Scan src/main/java and src/test/java
var allEvidences = new List<JavaCapabilityEvidence>();
var srcMainJava = Path.Combine(projectDir, "src", "main", "java");
if (Directory.Exists(srcMainJava))
{
var result = ScanProject(srcMainJava, cancellationToken);
allEvidences.AddRange(result.Evidences);
}
var srcTestJava = Path.Combine(projectDir, "src", "test", "java");
if (Directory.Exists(srcTestJava))
{
var result = ScanProject(srcTestJava, cancellationToken);
allEvidences.AddRange(result.Evidences);
}
// Also scan root if no Maven structure
if (allEvidences.Count == 0)
{
return ScanProject(projectDir, cancellationToken);
}
var finalEvidences = allEvidences
.DistinctBy(e => e.DeduplicationKey)
.OrderBy(e => e.SourceFile, StringComparer.Ordinal)
.ThenBy(e => e.SourceLine)
.ThenBy(e => e.Kind)
.ToList();
return new JavaCapabilityScanResult(finalEvidences);
}
/// <summary>
/// Scans specific Java source content.
/// </summary>
public static JavaCapabilityScanResult ScanContent(string content, string filePath)
{
if (string.IsNullOrWhiteSpace(content))
{
return JavaCapabilityScanResult.Empty;
}
var evidences = JavaCapabilityScanner.ScanFile(content, filePath);
return new JavaCapabilityScanResult(evidences.ToList());
}
private static IEnumerable<string> EnumerateSourceFiles(string rootPath)
{
var options = new EnumerationOptions
{
RecurseSubdirectories = true,
IgnoreInaccessible = true,
MaxRecursionDepth = 30
};
foreach (var ext in SourceExtensions)
{
foreach (var file in Directory.EnumerateFiles(rootPath, $"*{ext}", options))
{
// Skip build output directories
if (file.Contains($"{Path.DirectorySeparatorChar}target{Path.DirectorySeparatorChar}") ||
file.Contains($"{Path.DirectorySeparatorChar}build{Path.DirectorySeparatorChar}") ||
file.Contains($"{Path.AltDirectorySeparatorChar}target{Path.AltDirectorySeparatorChar}") ||
file.Contains($"{Path.AltDirectorySeparatorChar}build{Path.AltDirectorySeparatorChar}"))
{
continue;
}
// Skip generated sources
if (file.Contains($"{Path.DirectorySeparatorChar}generated-sources{Path.DirectorySeparatorChar}") ||
file.Contains($"{Path.DirectorySeparatorChar}generated-test-sources{Path.DirectorySeparatorChar}") ||
file.Contains($"{Path.AltDirectorySeparatorChar}generated-sources{Path.AltDirectorySeparatorChar}") ||
file.Contains($"{Path.AltDirectorySeparatorChar}generated-test-sources{Path.AltDirectorySeparatorChar}"))
{
continue;
}
// Skip annotation processor output
if (file.Contains($"{Path.DirectorySeparatorChar}apt{Path.DirectorySeparatorChar}") ||
file.Contains($"{Path.AltDirectorySeparatorChar}apt{Path.AltDirectorySeparatorChar}"))
{
continue;
}
// Skip IDE output
if (file.Contains($"{Path.DirectorySeparatorChar}.idea{Path.DirectorySeparatorChar}") ||
file.Contains($"{Path.DirectorySeparatorChar}.gradle{Path.DirectorySeparatorChar}") ||
file.Contains($"{Path.AltDirectorySeparatorChar}.idea{Path.AltDirectorySeparatorChar}") ||
file.Contains($"{Path.AltDirectorySeparatorChar}.gradle{Path.AltDirectorySeparatorChar}"))
{
continue;
}
yield return file;
}
}
}
}

View File

@@ -0,0 +1,218 @@
namespace StellaOps.Scanner.Analyzers.Lang.Java.Internal.Capabilities;
/// <summary>
/// Aggregates capability scan results from Java source code analysis.
/// </summary>
internal sealed class JavaCapabilityScanResult
{
private readonly IReadOnlyList<JavaCapabilityEvidence> _evidences;
private ILookup<CapabilityKind, JavaCapabilityEvidence>? _byKind;
private ILookup<CapabilityRisk, JavaCapabilityEvidence>? _byRisk;
private ILookup<string, JavaCapabilityEvidence>? _byFile;
public JavaCapabilityScanResult(IReadOnlyList<JavaCapabilityEvidence> evidences)
{
_evidences = evidences ?? Array.Empty<JavaCapabilityEvidence>();
}
/// <summary>
/// All capability evidences found.
/// </summary>
public IReadOnlyList<JavaCapabilityEvidence> 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, JavaCapabilityEvidence> EvidencesByKind
=> _byKind ??= _evidences.ToLookup(e => e.Kind);
/// <summary>
/// Gets evidences grouped by risk level.
/// </summary>
public ILookup<CapabilityRisk, JavaCapabilityEvidence> EvidencesByRisk
=> _byRisk ??= _evidences.ToLookup(e => e.Risk);
/// <summary>
/// Gets evidences grouped by source file.
/// </summary>
public ILookup<string, JavaCapabilityEvidence> EvidencesByFile
=> _byFile ??= _evidences.ToLookup(e => e.SourceFile, StringComparer.OrdinalIgnoreCase);
/// <summary>
/// Gets all critical risk evidences.
/// </summary>
public IEnumerable<JavaCapabilityEvidence> CriticalRiskEvidences
=> _evidences.Where(e => e.Risk == CapabilityRisk.Critical);
/// <summary>
/// Gets all high risk evidences.
/// </summary>
public IEnumerable<JavaCapabilityEvidence> 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<JavaCapabilityEvidence> GetByKind(CapabilityKind kind)
=> EvidencesByKind[kind];
/// <summary>
/// Gets evidences at or above a specific risk level.
/// </summary>
public IEnumerable<JavaCapabilityEvidence> 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 JavaCapabilitySummary CreateSummary()
{
return new JavaCapabilitySummary(
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(),
HasJndi: EvidencesByKind[CapabilityKind.Other].Any(e => e.Pattern.Contains("JNDI", StringComparison.OrdinalIgnoreCase)),
CriticalCount: CriticalRiskEvidences.Count(),
HighRiskCount: HighRiskEvidences.Count(),
TotalCount: _evidences.Count);
}
/// <summary>
/// Empty scan result with no capabilities detected.
/// </summary>
public static JavaCapabilityScanResult Empty { get; } = new(Array.Empty<JavaCapabilityEvidence>());
}
/// <summary>
/// Summary of detected Java capabilities.
/// </summary>
internal sealed record JavaCapabilitySummary(
bool HasExec,
bool HasFilesystem,
bool HasNetwork,
bool HasEnvironment,
bool HasSerialization,
bool HasCrypto,
bool HasDatabase,
bool HasDynamicCode,
bool HasReflection,
bool HasNativeCode,
bool HasJndi,
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());
yield return new KeyValuePair<string, string?>("capability.has_jndi", HasJndi.ToString().ToLowerInvariant());
}
}

View File

@@ -0,0 +1,510 @@
namespace StellaOps.Scanner.Analyzers.Lang.Java.Internal.Capabilities;
/// <summary>
/// Scans Java source files for security-relevant capabilities.
/// Detects patterns for command execution, file I/O, network access,
/// serialization, reflection, JNI, JNDI, and more.
/// </summary>
internal static class JavaCapabilityScanner
{
// ========================================
// EXEC - Command/Process Execution (Critical)
// ========================================
private static readonly (Regex Pattern, string Name, CapabilityRisk Risk, float Confidence)[] ExecPatterns =
[
// Runtime.exec - most common command execution
(new Regex(@"Runtime\s*\.\s*getRuntime\s*\(\s*\)\s*\.\s*exec\s*\(", RegexOptions.Compiled), "Runtime.exec", CapabilityRisk.Critical, 1.0f),
(new Regex(@"\.exec\s*\(\s*(?:new\s+String\s*\[\]|"")", RegexOptions.Compiled), "Runtime.exec(String[])", CapabilityRisk.Critical, 0.95f),
// ProcessBuilder
(new Regex(@"new\s+ProcessBuilder\s*\(", RegexOptions.Compiled), "ProcessBuilder", CapabilityRisk.Critical, 1.0f),
(new Regex(@"ProcessBuilder\s*\.\s*command\s*\(", RegexOptions.Compiled), "ProcessBuilder.command", CapabilityRisk.Critical, 0.95f),
(new Regex(@"ProcessBuilder\s*\.\s*start\s*\(", RegexOptions.Compiled), "ProcessBuilder.start", CapabilityRisk.Critical, 0.95f),
// Direct Process
(new Regex(@"Process\s+\w+\s*=", RegexOptions.Compiled), "Process variable", CapabilityRisk.High, 0.7f),
];
// ========================================
// FILESYSTEM - File/Directory Operations
// ========================================
private static readonly (Regex Pattern, string Name, CapabilityRisk Risk, float Confidence)[] FilesystemPatterns =
[
// File streams
(new Regex(@"new\s+FileInputStream\s*\(", RegexOptions.Compiled), "FileInputStream", CapabilityRisk.Medium, 0.95f),
(new Regex(@"new\s+FileOutputStream\s*\(", RegexOptions.Compiled), "FileOutputStream", CapabilityRisk.High, 0.95f),
(new Regex(@"new\s+FileReader\s*\(", RegexOptions.Compiled), "FileReader", CapabilityRisk.Medium, 0.95f),
(new Regex(@"new\s+FileWriter\s*\(", RegexOptions.Compiled), "FileWriter", CapabilityRisk.High, 0.95f),
(new Regex(@"new\s+RandomAccessFile\s*\(", RegexOptions.Compiled), "RandomAccessFile", CapabilityRisk.High, 0.95f),
// NIO Files API
(new Regex(@"Files\s*\.\s*(?:read|write|copy|move|delete|createFile|createDirectory|createTempFile|createTempDirectory)\w*\s*\(", RegexOptions.Compiled), "Files.*", CapabilityRisk.Medium, 0.9f),
(new Regex(@"Files\s*\.\s*(?:newInputStream|newOutputStream|newBufferedReader|newBufferedWriter)\s*\(", RegexOptions.Compiled), "Files.new*Stream", CapabilityRisk.Medium, 0.9f),
(new Regex(@"Files\s*\.\s*walkFileTree\s*\(", RegexOptions.Compiled), "Files.walkFileTree", CapabilityRisk.Medium, 0.85f),
// File object operations
(new Regex(@"\.delete\s*\(\s*\)", RegexOptions.Compiled), "File.delete", CapabilityRisk.High, 0.8f),
(new Regex(@"\.deleteOnExit\s*\(\s*\)", RegexOptions.Compiled), "File.deleteOnExit", CapabilityRisk.Medium, 0.85f),
(new Regex(@"\.setReadable\s*\(", RegexOptions.Compiled), "File.setReadable", CapabilityRisk.Medium, 0.8f),
(new Regex(@"\.setWritable\s*\(", RegexOptions.Compiled), "File.setWritable", CapabilityRisk.Medium, 0.8f),
(new Regex(@"\.setExecutable\s*\(", RegexOptions.Compiled), "File.setExecutable", CapabilityRisk.High, 0.85f),
(new Regex(@"\.createNewFile\s*\(\s*\)", RegexOptions.Compiled), "File.createNewFile", CapabilityRisk.Medium, 0.8f),
(new Regex(@"\.mkdirs?\s*\(\s*\)", RegexOptions.Compiled), "File.mkdir(s)", CapabilityRisk.Medium, 0.8f),
(new Regex(@"\.renameTo\s*\(", RegexOptions.Compiled), "File.renameTo", CapabilityRisk.Medium, 0.8f),
];
// ========================================
// NETWORK - Network I/O
// ========================================
private static readonly (Regex Pattern, string Name, CapabilityRisk Risk, float Confidence)[] NetworkPatterns =
[
// Sockets
(new Regex(@"new\s+Socket\s*\(", RegexOptions.Compiled), "Socket", CapabilityRisk.Medium, 0.95f),
(new Regex(@"new\s+ServerSocket\s*\(", RegexOptions.Compiled), "ServerSocket", CapabilityRisk.Medium, 0.95f),
(new Regex(@"new\s+DatagramSocket\s*\(", RegexOptions.Compiled), "DatagramSocket", CapabilityRisk.Medium, 0.95f),
(new Regex(@"SocketChannel\s*\.\s*open\s*\(", RegexOptions.Compiled), "SocketChannel.open", CapabilityRisk.Medium, 0.9f),
(new Regex(@"ServerSocketChannel\s*\.\s*open\s*\(", RegexOptions.Compiled), "ServerSocketChannel.open", CapabilityRisk.Medium, 0.9f),
// URL connections
(new Regex(@"\.openConnection\s*\(\s*\)", RegexOptions.Compiled), "URL.openConnection", CapabilityRisk.Medium, 0.9f),
(new Regex(@"\.openStream\s*\(\s*\)", RegexOptions.Compiled), "URL.openStream", CapabilityRisk.Medium, 0.85f),
(new Regex(@"new\s+URL\s*\(", RegexOptions.Compiled), "URL constructor", CapabilityRisk.Low, 0.7f),
// HTTP clients
(new Regex(@"HttpURLConnection", RegexOptions.Compiled), "HttpURLConnection", CapabilityRisk.Medium, 0.85f),
(new Regex(@"HttpsURLConnection", RegexOptions.Compiled), "HttpsURLConnection", CapabilityRisk.Medium, 0.85f),
(new Regex(@"HttpClient\s*\.\s*newBuilder\s*\(", RegexOptions.Compiled), "HttpClient.newBuilder", CapabilityRisk.Medium, 0.9f),
(new Regex(@"HttpClient\s*\.\s*newHttpClient\s*\(", RegexOptions.Compiled), "HttpClient.newHttpClient", CapabilityRisk.Medium, 0.9f),
(new Regex(@"HttpRequest\s*\.\s*newBuilder\s*\(", RegexOptions.Compiled), "HttpRequest.newBuilder", CapabilityRisk.Medium, 0.85f),
// Apache/OkHttp clients
(new Regex(@"new\s+CloseableHttpClient", RegexOptions.Compiled), "CloseableHttpClient", CapabilityRisk.Medium, 0.85f),
(new Regex(@"HttpClients\s*\.\s*create", RegexOptions.Compiled), "HttpClients.create", CapabilityRisk.Medium, 0.85f),
(new Regex(@"new\s+OkHttpClient", RegexOptions.Compiled), "OkHttpClient", CapabilityRisk.Medium, 0.85f),
];
// ========================================
// ENVIRONMENT - Environment Variables
// ========================================
private static readonly (Regex Pattern, string Name, CapabilityRisk Risk, float Confidence)[] EnvironmentPatterns =
[
(new Regex(@"System\s*\.\s*getenv\s*\(", RegexOptions.Compiled), "System.getenv", CapabilityRisk.Medium, 0.95f),
(new Regex(@"System\s*\.\s*getProperty\s*\(", RegexOptions.Compiled), "System.getProperty", CapabilityRisk.Medium, 0.9f),
(new Regex(@"System\s*\.\s*setProperty\s*\(", RegexOptions.Compiled), "System.setProperty", CapabilityRisk.High, 0.95f),
(new Regex(@"System\s*\.\s*clearProperty\s*\(", RegexOptions.Compiled), "System.clearProperty", CapabilityRisk.High, 0.9f),
(new Regex(@"System\s*\.\s*getProperties\s*\(\s*\)", RegexOptions.Compiled), "System.getProperties", CapabilityRisk.Medium, 0.85f),
(new Regex(@"System\s*\.\s*setProperties\s*\(", RegexOptions.Compiled), "System.setProperties", CapabilityRisk.High, 0.9f),
(new Regex(@"ProcessBuilder\s*\.\s*environment\s*\(", RegexOptions.Compiled), "ProcessBuilder.environment", CapabilityRisk.High, 0.9f),
];
// ========================================
// SERIALIZATION - Object Serialization (Critical for deserialization attacks)
// ========================================
private static readonly (Regex Pattern, string Name, CapabilityRisk Risk, float Confidence)[] SerializationPatterns =
[
// Java native serialization - HIGH RISK for deserialization attacks
(new Regex(@"new\s+ObjectInputStream\s*\(", RegexOptions.Compiled), "ObjectInputStream", CapabilityRisk.Critical, 1.0f),
(new Regex(@"\.readObject\s*\(\s*\)", RegexOptions.Compiled), "readObject", CapabilityRisk.Critical, 0.95f),
(new Regex(@"\.readUnshared\s*\(\s*\)", RegexOptions.Compiled), "readUnshared", CapabilityRisk.Critical, 0.95f),
(new Regex(@"new\s+ObjectOutputStream\s*\(", RegexOptions.Compiled), "ObjectOutputStream", CapabilityRisk.Medium, 0.85f),
(new Regex(@"\.writeObject\s*\(", RegexOptions.Compiled), "writeObject", CapabilityRisk.Medium, 0.75f),
// XMLDecoder - known vulnerability vector
(new Regex(@"new\s+XMLDecoder\s*\(", RegexOptions.Compiled), "XMLDecoder", CapabilityRisk.Critical, 1.0f),
(new Regex(@"XMLDecoder\s*\.\s*readObject\s*\(", RegexOptions.Compiled), "XMLDecoder.readObject", CapabilityRisk.Critical, 1.0f),
// XStream - historically vulnerable
(new Regex(@"new\s+XStream\s*\(", RegexOptions.Compiled), "XStream", CapabilityRisk.High, 0.9f),
(new Regex(@"xstream\s*\.\s*fromXML\s*\(", RegexOptions.Compiled | RegexOptions.IgnoreCase), "XStream.fromXML", CapabilityRisk.Critical, 0.95f),
// Jackson/JSON - generally safer but check for polymorphic deserialization
(new Regex(@"new\s+ObjectMapper\s*\(", RegexOptions.Compiled), "ObjectMapper", CapabilityRisk.Low, 0.7f),
(new Regex(@"\.readValue\s*\(", RegexOptions.Compiled), "ObjectMapper.readValue", CapabilityRisk.Medium, 0.75f),
(new Regex(@"@JsonTypeInfo", RegexOptions.Compiled), "Jackson polymorphic", CapabilityRisk.High, 0.85f),
(new Regex(@"enableDefaultTyping\s*\(", RegexOptions.Compiled), "Jackson defaultTyping", CapabilityRisk.Critical, 0.95f),
// Kryo
(new Regex(@"new\s+Kryo\s*\(", RegexOptions.Compiled), "Kryo", CapabilityRisk.High, 0.85f),
(new Regex(@"\.readObject\s*\(.*Kryo", RegexOptions.Compiled | RegexOptions.IgnoreCase), "Kryo.readObject", CapabilityRisk.High, 0.85f),
// SnakeYAML - known for unsafe defaults
(new Regex(@"new\s+Yaml\s*\(", RegexOptions.Compiled), "SnakeYAML", CapabilityRisk.High, 0.9f),
(new Regex(@"yaml\s*\.\s*load\s*\(", RegexOptions.Compiled | RegexOptions.IgnoreCase), "Yaml.load", CapabilityRisk.Critical, 0.95f),
(new Regex(@"yaml\s*\.\s*loadAs\s*\(", RegexOptions.Compiled | RegexOptions.IgnoreCase), "Yaml.loadAs", CapabilityRisk.High, 0.9f),
];
// ========================================
// CRYPTO - Cryptographic Operations
// ========================================
private static readonly (Regex Pattern, string Name, CapabilityRisk Risk, float Confidence)[] CryptoPatterns =
[
(new Regex(@"MessageDigest\s*\.\s*getInstance\s*\(", RegexOptions.Compiled), "MessageDigest", CapabilityRisk.Low, 0.9f),
(new Regex(@"Cipher\s*\.\s*getInstance\s*\(", RegexOptions.Compiled), "Cipher", CapabilityRisk.Low, 0.95f),
(new Regex(@"Mac\s*\.\s*getInstance\s*\(", RegexOptions.Compiled), "Mac", CapabilityRisk.Low, 0.9f),
(new Regex(@"Signature\s*\.\s*getInstance\s*\(", RegexOptions.Compiled), "Signature", CapabilityRisk.Low, 0.9f),
(new Regex(@"KeyGenerator\s*\.\s*getInstance\s*\(", RegexOptions.Compiled), "KeyGenerator", CapabilityRisk.Low, 0.9f),
(new Regex(@"KeyPairGenerator\s*\.\s*getInstance\s*\(", RegexOptions.Compiled), "KeyPairGenerator", CapabilityRisk.Low, 0.9f),
(new Regex(@"SecretKeyFactory\s*\.\s*getInstance\s*\(", RegexOptions.Compiled), "SecretKeyFactory", CapabilityRisk.Low, 0.9f),
(new Regex(@"KeyFactory\s*\.\s*getInstance\s*\(", RegexOptions.Compiled), "KeyFactory", CapabilityRisk.Low, 0.9f),
(new Regex(@"SecureRandom", RegexOptions.Compiled), "SecureRandom", CapabilityRisk.Low, 0.85f),
(new Regex(@"KeyStore\s*\.\s*getInstance\s*\(", RegexOptions.Compiled), "KeyStore", CapabilityRisk.Low, 0.9f),
// Weak crypto patterns
(new Regex(@"""(?:MD5|SHA-?1|DES|RC4|RC2)""", RegexOptions.Compiled | RegexOptions.IgnoreCase), "Weak crypto algorithm", CapabilityRisk.High, 0.85f),
(new Regex(@"DESede|TripleDES", RegexOptions.Compiled | RegexOptions.IgnoreCase), "3DES (deprecated)", CapabilityRisk.Medium, 0.8f),
];
// ========================================
// DATABASE - Database Access
// ========================================
private static readonly (Regex Pattern, string Name, CapabilityRisk Risk, float Confidence)[] DatabasePatterns =
[
(new Regex(@"DriverManager\s*\.\s*getConnection\s*\(", RegexOptions.Compiled), "DriverManager.getConnection", CapabilityRisk.Medium, 0.95f),
(new Regex(@"DataSource\s*\.\s*getConnection\s*\(", RegexOptions.Compiled), "DataSource.getConnection", CapabilityRisk.Medium, 0.9f),
// Statement execution
(new Regex(@"\.executeQuery\s*\(", RegexOptions.Compiled), "Statement.executeQuery", CapabilityRisk.Medium, 0.85f),
(new Regex(@"\.executeUpdate\s*\(", RegexOptions.Compiled), "Statement.executeUpdate", CapabilityRisk.Medium, 0.85f),
(new Regex(@"\.execute\s*\([^)]*\)", RegexOptions.Compiled), "Statement.execute", CapabilityRisk.Medium, 0.8f),
(new Regex(@"\.executeBatch\s*\(\s*\)", RegexOptions.Compiled), "Statement.executeBatch", CapabilityRisk.Medium, 0.85f),
// Prepared statements (safer)
(new Regex(@"\.prepareStatement\s*\(", RegexOptions.Compiled), "PreparedStatement", CapabilityRisk.Low, 0.85f),
(new Regex(@"\.prepareCall\s*\(", RegexOptions.Compiled), "CallableStatement", CapabilityRisk.Medium, 0.85f),
// SQL injection patterns - string concatenation with SQL
(new Regex(@"""(?:SELECT|INSERT|UPDATE|DELETE|DROP|CREATE|ALTER|TRUNCATE)\s+.*""\s*\+", RegexOptions.Compiled | RegexOptions.IgnoreCase), "SQL concatenation", CapabilityRisk.Critical, 0.9f),
(new Regex(@"String\s+.*=\s*"".*(?:SELECT|INSERT|UPDATE|DELETE).*""\s*\+", RegexOptions.Compiled | RegexOptions.IgnoreCase), "SQL string concat", CapabilityRisk.Critical, 0.85f),
// JPA/Hibernate
(new Regex(@"\.createQuery\s*\(", RegexOptions.Compiled), "EntityManager.createQuery", CapabilityRisk.Medium, 0.8f),
(new Regex(@"\.createNativeQuery\s*\(", RegexOptions.Compiled), "Native SQL query", CapabilityRisk.High, 0.85f),
];
// ========================================
// DYNAMIC CODE - Dynamic Code Execution
// ========================================
private static readonly (Regex Pattern, string Name, CapabilityRisk Risk, float Confidence)[] DynamicCodePatterns =
[
// ScriptEngine (JavaScript, Groovy, etc.)
(new Regex(@"ScriptEngineManager\s*\.\s*getEngineByName\s*\(", RegexOptions.Compiled), "ScriptEngineManager", CapabilityRisk.High, 0.95f),
(new Regex(@"new\s+ScriptEngineManager\s*\(", RegexOptions.Compiled), "ScriptEngineManager", CapabilityRisk.High, 0.9f),
(new Regex(@"\.eval\s*\([^)]*\)", RegexOptions.Compiled), "ScriptEngine.eval", CapabilityRisk.Critical, 0.9f),
// MethodHandles
(new Regex(@"MethodHandles\s*\.\s*lookup\s*\(", RegexOptions.Compiled), "MethodHandles.lookup", CapabilityRisk.High, 0.85f),
(new Regex(@"MethodHandle\s*\.\s*invoke\w*\s*\(", RegexOptions.Compiled), "MethodHandle.invoke", CapabilityRisk.High, 0.9f),
// Java Compiler API
(new Regex(@"ToolProvider\s*\.\s*getSystemJavaCompiler\s*\(", RegexOptions.Compiled), "JavaCompiler", CapabilityRisk.Critical, 0.95f),
(new Regex(@"JavaCompiler\s*\.\s*getTask\s*\(", RegexOptions.Compiled), "JavaCompiler.getTask", CapabilityRisk.Critical, 0.95f),
// Expression Language (EL) injection
(new Regex(@"ValueExpression\s*\.\s*getValue\s*\(", RegexOptions.Compiled), "EL ValueExpression", CapabilityRisk.High, 0.85f),
(new Regex(@"MethodExpression\s*\.\s*invoke\s*\(", RegexOptions.Compiled), "EL MethodExpression", CapabilityRisk.High, 0.85f),
(new Regex(@"ExpressionFactory\s*\.\s*createValueExpression\s*\(", RegexOptions.Compiled), "EL ExpressionFactory", CapabilityRisk.High, 0.8f),
// SpEL (Spring Expression Language)
(new Regex(@"SpelExpressionParser", RegexOptions.Compiled), "SpEL Parser", CapabilityRisk.High, 0.9f),
(new Regex(@"new\s+SpelExpressionParser\s*\(", RegexOptions.Compiled), "SpEL Parser", CapabilityRisk.High, 0.95f),
(new Regex(@"\.parseExpression\s*\(", RegexOptions.Compiled), "SpEL parseExpression", CapabilityRisk.High, 0.85f),
// OGNL (Object-Graph Navigation Language)
(new Regex(@"Ognl\s*\.\s*getValue\s*\(", RegexOptions.Compiled), "OGNL.getValue", CapabilityRisk.Critical, 0.95f),
(new Regex(@"Ognl\s*\.\s*setValue\s*\(", RegexOptions.Compiled), "OGNL.setValue", CapabilityRisk.Critical, 0.95f),
(new Regex(@"OgnlUtil", RegexOptions.Compiled), "OgnlUtil", CapabilityRisk.High, 0.85f),
// Velocity/Freemarker templates
(new Regex(@"VelocityEngine", RegexOptions.Compiled), "Velocity", CapabilityRisk.High, 0.8f),
(new Regex(@"Velocity\s*\.\s*evaluate\s*\(", RegexOptions.Compiled), "Velocity.evaluate", CapabilityRisk.High, 0.9f),
(new Regex(@"Configuration\s*\.\s*setTemplateLoader", RegexOptions.Compiled), "Freemarker", CapabilityRisk.Medium, 0.75f),
];
// ========================================
// REFLECTION - Code Introspection
// ========================================
private static readonly (Regex Pattern, string Name, CapabilityRisk Risk, float Confidence)[] ReflectionPatterns =
[
// Class loading
(new Regex(@"Class\s*\.\s*forName\s*\(", RegexOptions.Compiled), "Class.forName", CapabilityRisk.High, 0.95f),
(new Regex(@"ClassLoader\s*\.\s*loadClass\s*\(", RegexOptions.Compiled), "ClassLoader.loadClass", CapabilityRisk.High, 0.9f),
(new Regex(@"\.loadClass\s*\(", RegexOptions.Compiled), "loadClass", CapabilityRisk.High, 0.8f),
(new Regex(@"\.defineClass\s*\(", RegexOptions.Compiled), "defineClass", CapabilityRisk.Critical, 0.95f),
(new Regex(@"new\s+URLClassLoader\s*\(", RegexOptions.Compiled), "URLClassLoader", CapabilityRisk.High, 0.9f),
// Method/Field invocation
(new Regex(@"Method\s*\.\s*invoke\s*\(", RegexOptions.Compiled), "Method.invoke", CapabilityRisk.High, 0.95f),
(new Regex(@"\.invoke\s*\([^)]*\)", RegexOptions.Compiled), "invoke", CapabilityRisk.Medium, 0.7f),
(new Regex(@"\.getMethod\s*\(", RegexOptions.Compiled), "getMethod", CapabilityRisk.Medium, 0.8f),
(new Regex(@"\.getDeclaredMethod\s*\(", RegexOptions.Compiled), "getDeclaredMethod", CapabilityRisk.Medium, 0.85f),
(new Regex(@"\.getDeclaredField\s*\(", RegexOptions.Compiled), "getDeclaredField", CapabilityRisk.Medium, 0.8f),
(new Regex(@"Field\s*\.\s*set\s*\(", RegexOptions.Compiled), "Field.set", CapabilityRisk.High, 0.9f),
(new Regex(@"\.setAccessible\s*\(\s*true\s*\)", RegexOptions.Compiled), "setAccessible(true)", CapabilityRisk.High, 0.95f),
// Constructor invocation
(new Regex(@"Constructor\s*\.\s*newInstance\s*\(", RegexOptions.Compiled), "Constructor.newInstance", CapabilityRisk.High, 0.9f),
(new Regex(@"\.getDeclaredConstructor\s*\(", RegexOptions.Compiled), "getDeclaredConstructor", CapabilityRisk.Medium, 0.8f),
(new Regex(@"\.newInstance\s*\(", RegexOptions.Compiled), "newInstance", CapabilityRisk.High, 0.75f),
// Proxy creation
(new Regex(@"Proxy\s*\.\s*newProxyInstance\s*\(", RegexOptions.Compiled), "Proxy.newProxyInstance", CapabilityRisk.High, 0.9f),
];
// ========================================
// NATIVE CODE - JNI/JNA
// ========================================
private static readonly (Regex Pattern, string Name, CapabilityRisk Risk, float Confidence)[] NativeCodePatterns =
[
// JNI library loading
(new Regex(@"System\s*\.\s*loadLibrary\s*\(", RegexOptions.Compiled), "System.loadLibrary", CapabilityRisk.Critical, 1.0f),
(new Regex(@"System\s*\.\s*load\s*\(", RegexOptions.Compiled), "System.load", CapabilityRisk.Critical, 1.0f),
(new Regex(@"Runtime\s*\.\s*load\w*\s*\(", RegexOptions.Compiled), "Runtime.load", CapabilityRisk.Critical, 0.95f),
// JNA (Java Native Access)
(new Regex(@"Native\s*\.\s*load\w*\s*\(", RegexOptions.Compiled), "JNA Native.load", CapabilityRisk.Critical, 0.95f),
(new Regex(@"Native\s*\.\s*getLibrary\s*\(", RegexOptions.Compiled), "JNA Native.getLibrary", CapabilityRisk.Critical, 0.9f),
(new Regex(@"extends\s+(?:Structure|StdCallLibrary|Library)", RegexOptions.Compiled), "JNA Structure/Library", CapabilityRisk.High, 0.85f),
// JNR (Java Native Runtime)
(new Regex(@"LibraryLoader\s*\.\s*create\s*\(", RegexOptions.Compiled), "JNR LibraryLoader", CapabilityRisk.High, 0.85f),
// native method declaration
(new Regex(@"\bnative\s+\w+\s+\w+\s*\(", RegexOptions.Compiled), "native method", CapabilityRisk.High, 0.9f),
// Unsafe
(new Regex(@"Unsafe\s*\.\s*getUnsafe\s*\(", RegexOptions.Compiled), "Unsafe.getUnsafe", CapabilityRisk.Critical, 1.0f),
(new Regex(@"theUnsafe", RegexOptions.Compiled), "Unsafe field access", CapabilityRisk.Critical, 0.9f),
(new Regex(@"\.allocateInstance\s*\(", RegexOptions.Compiled), "Unsafe.allocateInstance", CapabilityRisk.Critical, 0.95f),
(new Regex(@"\.putObject\s*\(", RegexOptions.Compiled), "Unsafe.putObject", CapabilityRisk.Critical, 0.9f),
(new Regex(@"\.getObject\s*\(", RegexOptions.Compiled), "Unsafe.getObject", CapabilityRisk.High, 0.85f),
];
// ========================================
// JNDI - Java Naming and Directory Interface
// ========================================
private static readonly (Regex Pattern, string Name, CapabilityRisk Risk, float Confidence)[] JndiPatterns =
[
// JNDI lookups - Log4Shell attack vector
(new Regex(@"new\s+InitialContext\s*\(", RegexOptions.Compiled), "InitialContext", CapabilityRisk.High, 0.9f),
(new Regex(@"InitialContext\s*\.\s*lookup\s*\(", RegexOptions.Compiled), "InitialContext.lookup", CapabilityRisk.Critical, 0.95f),
(new Regex(@"\.lookup\s*\(\s*[""'][^""']*(?:ldap|rmi|dns|corba):", RegexOptions.Compiled | RegexOptions.IgnoreCase), "JNDI remote lookup", CapabilityRisk.Critical, 1.0f),
(new Regex(@"Context\s*\.\s*lookup\s*\(", RegexOptions.Compiled), "Context.lookup", CapabilityRisk.High, 0.85f),
// LDAP
(new Regex(@"new\s+InitialLdapContext\s*\(", RegexOptions.Compiled), "InitialLdapContext", CapabilityRisk.High, 0.9f),
(new Regex(@"new\s+InitialDirContext\s*\(", RegexOptions.Compiled), "InitialDirContext", CapabilityRisk.High, 0.85f),
(new Regex(@"LdapContext\s*\.\s*search\s*\(", RegexOptions.Compiled), "LdapContext.search", CapabilityRisk.Medium, 0.8f),
];
/// <summary>
/// Scans a Java source file for capability usages.
/// </summary>
public static IEnumerable<JavaCapabilityEvidence> ScanFile(string content, string filePath)
{
if (string.IsNullOrWhiteSpace(content))
{
yield break;
}
// Strip comments for more accurate detection
var cleanedContent = StripComments(content);
var lines = cleanedContent.Split('\n');
for (var lineNumber = 0; lineNumber < lines.Length; lineNumber++)
{
var line = lines[lineNumber];
var lineNum = lineNumber + 1;
// Exec patterns
foreach (var evidence in ScanPatterns(line, lineNum, filePath, ExecPatterns, CapabilityKind.Exec))
{
yield return evidence;
}
// Filesystem patterns
foreach (var evidence in ScanPatterns(line, lineNum, filePath, FilesystemPatterns, CapabilityKind.Filesystem))
{
yield return evidence;
}
// Network patterns
foreach (var evidence in ScanPatterns(line, lineNum, filePath, NetworkPatterns, CapabilityKind.Network))
{
yield return evidence;
}
// Environment patterns
foreach (var evidence in ScanPatterns(line, lineNum, filePath, EnvironmentPatterns, CapabilityKind.Environment))
{
yield return evidence;
}
// Serialization patterns
foreach (var evidence in ScanPatterns(line, lineNum, filePath, SerializationPatterns, CapabilityKind.Serialization))
{
yield return evidence;
}
// Crypto patterns
foreach (var evidence in ScanPatterns(line, lineNum, filePath, CryptoPatterns, CapabilityKind.Crypto))
{
yield return evidence;
}
// Database patterns
foreach (var evidence in ScanPatterns(line, lineNum, filePath, DatabasePatterns, CapabilityKind.Database))
{
yield return evidence;
}
// Dynamic code patterns
foreach (var evidence in ScanPatterns(line, lineNum, filePath, DynamicCodePatterns, CapabilityKind.DynamicCode))
{
yield return evidence;
}
// Reflection patterns
foreach (var evidence in ScanPatterns(line, lineNum, filePath, ReflectionPatterns, CapabilityKind.Reflection))
{
yield return evidence;
}
// Native code patterns
foreach (var evidence in ScanPatterns(line, lineNum, filePath, NativeCodePatterns, CapabilityKind.NativeCode))
{
yield return evidence;
}
// JNDI patterns (categorized as Other since it's Java-specific)
foreach (var evidence in ScanPatterns(line, lineNum, filePath, JndiPatterns, CapabilityKind.Other))
{
yield return evidence;
}
}
}
private static IEnumerable<JavaCapabilityEvidence> ScanPatterns(
string line,
int lineNumber,
string filePath,
(Regex Pattern, string Name, CapabilityRisk Risk, float Confidence)[] patterns,
CapabilityKind kind)
{
foreach (var (pattern, name, risk, confidence) in patterns)
{
if (pattern.IsMatch(line))
{
yield return new JavaCapabilityEvidence(
kind: kind,
sourceFile: filePath,
sourceLine: lineNumber,
pattern: name,
snippet: line.Trim(),
confidence: confidence,
risk: risk);
}
}
}
/// <summary>
/// Strips single-line (//) and multi-line (/* */) comments from Java source.
/// </summary>
private static string StripComments(string content)
{
var sb = new StringBuilder(content.Length);
var i = 0;
var inString = false;
var inChar = false;
var stringChar = '"';
while (i < content.Length)
{
// Handle escape sequences in strings
if ((inString || inChar) && content[i] == '\\' && i + 1 < content.Length)
{
sb.Append(content[i]);
sb.Append(content[i + 1]);
i += 2;
continue;
}
// Handle string literals
if (!inChar && content[i] == '"')
{
if (!inString)
{
inString = true;
stringChar = '"';
}
else if (stringChar == '"')
{
inString = false;
}
sb.Append(content[i]);
i++;
continue;
}
// Handle char literals
if (!inString && content[i] == '\'')
{
if (!inChar)
{
inChar = true;
}
else
{
inChar = false;
}
sb.Append(content[i]);
i++;
continue;
}
// Skip comments only when not in string/char
if (!inString && !inChar)
{
// Single-line comment
if (i + 1 < content.Length && content[i] == '/' && content[i + 1] == '/')
{
// Skip until end of line
while (i < content.Length && content[i] != '\n')
{
i++;
}
if (i < content.Length)
{
sb.Append('\n');
i++;
}
continue;
}
// Multi-line comment
if (i + 1 < content.Length && content[i] == '/' && content[i + 1] == '*')
{
i += 2;
while (i + 1 < content.Length && !(content[i] == '*' && content[i + 1] == '/'))
{
// Preserve newlines for line number accuracy
if (content[i] == '\n')
{
sb.Append('\n');
}
i++;
}
if (i + 1 < content.Length)
{
i += 2; // Skip */
}
continue;
}
}
sb.Append(content[i]);
i++;
}
return sb.ToString();
}
}

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>