Files
git.stella-ops.org/src/BinaryIndex/__Libraries/StellaOps.BinaryIndex.GoldenSet/Services/SinkRegistry.cs
master 7f7eb8b228 Complete batch 012 (golden set diff) and 013 (advisory chat), fix build errors
Sprints completed:
- SPRINT_20260110_012_* (golden set diff layer - 10 sprints)
- SPRINT_20260110_013_* (advisory chat - 4 sprints)

Build fixes applied:
- Fix namespace conflicts with Microsoft.Extensions.Options.Options.Create
- Fix VexDecisionReachabilityIntegrationTests API drift (major rewrite)
- Fix VexSchemaValidationTests FluentAssertions method name
- Fix FixChainGateIntegrationTests ambiguous type references
- Fix AdvisoryAI test files required properties and namespace aliases
- Add stub types for CveMappingController (ICveSymbolMappingService)
- Fix VerdictBuilderService static context issue

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 10:09:07 +02:00

215 lines
13 KiB
C#

using System.Collections.Immutable;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
namespace StellaOps.BinaryIndex.GoldenSet;
/// <summary>
/// In-memory sink registry with built-in common sinks.
/// Can be extended with database or external sources.
/// </summary>
public sealed class SinkRegistry : ISinkRegistry
{
private readonly IMemoryCache _cache;
private readonly ILogger<SinkRegistry> _logger;
private readonly ImmutableDictionary<string, SinkInfo> _builtInSinks;
/// <summary>
/// Initializes a new instance of <see cref="SinkRegistry"/>.
/// </summary>
public SinkRegistry(IMemoryCache cache, ILogger<SinkRegistry> logger)
{
_cache = cache ?? throw new ArgumentNullException(nameof(cache));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_builtInSinks = BuildCommonSinks();
_logger.LogDebug("SinkRegistry initialized with {Count} built-in sinks", _builtInSinks.Count);
}
/// <inheritdoc />
public bool IsKnownSink(string sinkName)
{
if (string.IsNullOrWhiteSpace(sinkName))
{
return false;
}
return _builtInSinks.ContainsKey(sinkName);
}
/// <inheritdoc />
public Task<SinkInfo?> GetSinkInfoAsync(string sinkName, CancellationToken ct = default)
{
if (string.IsNullOrWhiteSpace(sinkName))
{
return Task.FromResult<SinkInfo?>(null);
}
_builtInSinks.TryGetValue(sinkName, out var info);
return Task.FromResult(info);
}
/// <inheritdoc />
public Task<ImmutableArray<SinkInfo>> GetSinksByCategoryAsync(string category, CancellationToken ct = default)
{
if (string.IsNullOrWhiteSpace(category))
{
return Task.FromResult(ImmutableArray<SinkInfo>.Empty);
}
var cacheKey = $"sinks_by_category_{category}";
if (!_cache.TryGetValue<ImmutableArray<SinkInfo>>(cacheKey, out var result))
{
result = _builtInSinks.Values
.Where(s => string.Equals(s.Category, category, StringComparison.OrdinalIgnoreCase))
.ToImmutableArray();
_cache.Set(cacheKey, result, TimeSpan.FromMinutes(60));
}
return Task.FromResult(result);
}
/// <inheritdoc />
public Task<ImmutableArray<SinkInfo>> GetSinksByCweAsync(string cweId, CancellationToken ct = default)
{
if (string.IsNullOrWhiteSpace(cweId))
{
return Task.FromResult(ImmutableArray<SinkInfo>.Empty);
}
var cacheKey = $"sinks_by_cwe_{cweId}";
if (!_cache.TryGetValue<ImmutableArray<SinkInfo>>(cacheKey, out var result))
{
result = _builtInSinks.Values
.Where(s => s.CweIds.Contains(cweId, StringComparer.OrdinalIgnoreCase))
.ToImmutableArray();
_cache.Set(cacheKey, result, TimeSpan.FromMinutes(60));
}
return Task.FromResult(result);
}
private static ImmutableDictionary<string, SinkInfo> BuildCommonSinks()
{
var builder = ImmutableDictionary.CreateBuilder<string, SinkInfo>(StringComparer.Ordinal);
// Memory corruption sinks
AddSink(builder, "memcpy", SinkCategory.Memory, "Buffer copy without bounds checking", ["CWE-120", "CWE-787"], "high");
AddSink(builder, "strcpy", SinkCategory.Memory, "String copy without bounds checking", ["CWE-120", "CWE-787"], "high");
AddSink(builder, "strncpy", SinkCategory.Memory, "String copy with size - may not null-terminate", ["CWE-120"], "medium");
AddSink(builder, "sprintf", SinkCategory.Memory, "Format string to buffer without bounds", ["CWE-120", "CWE-134"], "high");
AddSink(builder, "vsprintf", SinkCategory.Memory, "Variable format string without bounds", ["CWE-120", "CWE-134"], "high");
AddSink(builder, "gets", SinkCategory.Memory, "Read input without bounds - NEVER USE", ["CWE-120"], "critical");
AddSink(builder, "scanf", SinkCategory.Memory, "Format input - may overflow buffers", ["CWE-120"], "high");
AddSink(builder, "strcat", SinkCategory.Memory, "String concatenation without bounds", ["CWE-120", "CWE-787"], "high");
AddSink(builder, "strncat", SinkCategory.Memory, "String concatenation with size limit", ["CWE-120"], "medium");
AddSink(builder, "memmove", SinkCategory.Memory, "Memory move - can overlap", ["CWE-120"], "medium");
AddSink(builder, "bcopy", SinkCategory.Memory, "Legacy memory copy", ["CWE-120"], "medium");
// Memory management sinks
AddSink(builder, "free", SinkCategory.Memory, "Memory deallocation - double-free/use-after-free risk", ["CWE-415", "CWE-416"], "high");
AddSink(builder, "realloc", SinkCategory.Memory, "Memory reallocation - use-after-free risk", ["CWE-416"], "medium");
AddSink(builder, "malloc", SinkCategory.Memory, "Memory allocation - leak risk", ["CWE-401"], "low");
AddSink(builder, "calloc", SinkCategory.Memory, "Zeroed memory allocation", ["CWE-401"], "low");
AddSink(builder, "alloca", SinkCategory.Memory, "Stack allocation - stack overflow risk", ["CWE-121"], "medium");
// OpenSSL memory functions
AddSink(builder, "OPENSSL_malloc", SinkCategory.Memory, "OpenSSL memory allocation", ["CWE-401"], "low");
AddSink(builder, "OPENSSL_free", SinkCategory.Memory, "OpenSSL memory deallocation", ["CWE-415", "CWE-416"], "medium");
AddSink(builder, "OPENSSL_realloc", SinkCategory.Memory, "OpenSSL memory reallocation", ["CWE-416"], "medium");
// Command injection sinks
AddSink(builder, "system", SinkCategory.CommandInjection, "Execute shell command", ["CWE-78"], "critical");
AddSink(builder, "exec", SinkCategory.CommandInjection, "Execute command", ["CWE-78"], "critical");
AddSink(builder, "execl", SinkCategory.CommandInjection, "Execute command with args", ["CWE-78"], "critical");
AddSink(builder, "execle", SinkCategory.CommandInjection, "Execute command with environment", ["CWE-78"], "critical");
AddSink(builder, "execlp", SinkCategory.CommandInjection, "Execute command from PATH", ["CWE-78"], "critical");
AddSink(builder, "execv", SinkCategory.CommandInjection, "Execute command with arg vector", ["CWE-78"], "critical");
AddSink(builder, "execve", SinkCategory.CommandInjection, "Execute command with env vector", ["CWE-78"], "critical");
AddSink(builder, "execvp", SinkCategory.CommandInjection, "Execute command from PATH with vector", ["CWE-78"], "critical");
AddSink(builder, "popen", SinkCategory.CommandInjection, "Open pipe to command", ["CWE-78"], "high");
AddSink(builder, "ShellExecute", SinkCategory.CommandInjection, "Windows shell execution", ["CWE-78"], "critical");
AddSink(builder, "ShellExecuteEx", SinkCategory.CommandInjection, "Windows shell execution extended", ["CWE-78"], "critical");
AddSink(builder, "CreateProcess", SinkCategory.CommandInjection, "Windows process creation", ["CWE-78"], "high");
AddSink(builder, "WinExec", SinkCategory.CommandInjection, "Windows command execution", ["CWE-78"], "critical");
// Code injection sinks
AddSink(builder, "dlopen", SinkCategory.CodeInjection, "Dynamic library loading", ["CWE-427"], "high");
AddSink(builder, "dlsym", SinkCategory.CodeInjection, "Dynamic symbol lookup", ["CWE-427"], "medium");
AddSink(builder, "LoadLibrary", SinkCategory.CodeInjection, "Windows DLL loading", ["CWE-427"], "high");
AddSink(builder, "LoadLibraryEx", SinkCategory.CodeInjection, "Windows DLL loading extended", ["CWE-427"], "high");
AddSink(builder, "GetProcAddress", SinkCategory.CodeInjection, "Windows function pointer lookup", ["CWE-427"], "medium");
// Path traversal sinks
AddSink(builder, "fopen", SinkCategory.PathTraversal, "File open", ["CWE-22"], "medium");
AddSink(builder, "open", SinkCategory.PathTraversal, "POSIX file open", ["CWE-22"], "medium");
AddSink(builder, "openat", SinkCategory.PathTraversal, "POSIX file open relative", ["CWE-22"], "medium");
AddSink(builder, "freopen", SinkCategory.PathTraversal, "Reopen file stream", ["CWE-22"], "medium");
AddSink(builder, "creat", SinkCategory.PathTraversal, "Create file", ["CWE-22"], "medium");
AddSink(builder, "mkdir", SinkCategory.PathTraversal, "Create directory", ["CWE-22"], "low");
AddSink(builder, "rmdir", SinkCategory.PathTraversal, "Remove directory", ["CWE-22"], "low");
AddSink(builder, "unlink", SinkCategory.PathTraversal, "Remove file", ["CWE-22"], "medium");
AddSink(builder, "rename", SinkCategory.PathTraversal, "Rename file", ["CWE-22"], "medium");
AddSink(builder, "symlink", SinkCategory.PathTraversal, "Create symbolic link", ["CWE-59"], "medium");
AddSink(builder, "readlink", SinkCategory.PathTraversal, "Read symbolic link", ["CWE-59"], "low");
AddSink(builder, "realpath", SinkCategory.PathTraversal, "Resolve path", ["CWE-22"], "low");
AddSink(builder, "CreateFile", SinkCategory.PathTraversal, "Windows file creation", ["CWE-22"], "medium");
AddSink(builder, "DeleteFile", SinkCategory.PathTraversal, "Windows file deletion", ["CWE-22"], "medium");
// Network sinks
AddSink(builder, "connect", SinkCategory.Network, "Network connection", ["CWE-918"], "medium");
AddSink(builder, "send", SinkCategory.Network, "Send data over network", ["CWE-319"], "medium");
AddSink(builder, "sendto", SinkCategory.Network, "Send data to address", ["CWE-319"], "medium");
AddSink(builder, "recv", SinkCategory.Network, "Receive data from network", ["CWE-319"], "medium");
AddSink(builder, "recvfrom", SinkCategory.Network, "Receive data with address", ["CWE-319"], "medium");
AddSink(builder, "write", SinkCategory.Network, "Write to file descriptor", ["CWE-319"], "low");
AddSink(builder, "read", SinkCategory.Network, "Read from file descriptor", ["CWE-319"], "low");
AddSink(builder, "socket", SinkCategory.Network, "Create socket", ["CWE-918"], "low");
AddSink(builder, "bind", SinkCategory.Network, "Bind socket to address", ["CWE-918"], "low");
AddSink(builder, "listen", SinkCategory.Network, "Listen on socket", ["CWE-918"], "low");
AddSink(builder, "accept", SinkCategory.Network, "Accept connection", ["CWE-918"], "low");
// SQL injection sinks
AddSink(builder, "sqlite3_exec", SinkCategory.SqlInjection, "SQLite execute", ["CWE-89"], "high");
AddSink(builder, "mysql_query", SinkCategory.SqlInjection, "MySQL query", ["CWE-89"], "high");
AddSink(builder, "mysql_real_query", SinkCategory.SqlInjection, "MySQL real query", ["CWE-89"], "high");
AddSink(builder, "PQexec", SinkCategory.SqlInjection, "PostgreSQL execute", ["CWE-89"], "high");
AddSink(builder, "PQexecParams", SinkCategory.SqlInjection, "PostgreSQL parameterized", ["CWE-89"], "medium");
// Cryptographic sinks
AddSink(builder, "EVP_DecryptUpdate", SinkCategory.Crypto, "OpenSSL decrypt update", ["CWE-327"], "medium");
AddSink(builder, "EVP_EncryptUpdate", SinkCategory.Crypto, "OpenSSL encrypt update", ["CWE-327"], "medium");
AddSink(builder, "EVP_DigestUpdate", SinkCategory.Crypto, "OpenSSL digest update", ["CWE-327"], "low");
AddSink(builder, "EVP_SignFinal", SinkCategory.Crypto, "OpenSSL sign final", ["CWE-327"], "medium");
AddSink(builder, "EVP_VerifyFinal", SinkCategory.Crypto, "OpenSSL verify final", ["CWE-327"], "medium");
AddSink(builder, "RSA_private_decrypt", SinkCategory.Crypto, "RSA private key decrypt", ["CWE-327"], "high");
AddSink(builder, "RSA_public_encrypt", SinkCategory.Crypto, "RSA public key encrypt", ["CWE-327"], "medium");
AddSink(builder, "DES_ecb_encrypt", SinkCategory.Crypto, "DES ECB encrypt - weak", ["CWE-327", "CWE-328"], "high");
AddSink(builder, "MD5_Update", SinkCategory.Crypto, "MD5 digest - weak", ["CWE-327", "CWE-328"], "medium");
AddSink(builder, "SHA1_Update", SinkCategory.Crypto, "SHA1 digest - weak for signatures", ["CWE-327", "CWE-328"], "low");
// ASN.1/X.509 parsing sinks (common in OpenSSL vulnerabilities)
AddSink(builder, "d2i_X509", SinkCategory.Crypto, "DER to X509 certificate", ["CWE-295"], "medium");
AddSink(builder, "d2i_ASN1_OCTET_STRING", SinkCategory.Crypto, "DER to ASN1 octet string", ["CWE-295"], "medium");
AddSink(builder, "d2i_PKCS12", SinkCategory.Crypto, "DER to PKCS12", ["CWE-295"], "medium");
AddSink(builder, "PKCS12_parse", SinkCategory.Crypto, "Parse PKCS12 structure", ["CWE-295"], "medium");
AddSink(builder, "PKCS12_unpack_p7data", SinkCategory.Crypto, "Unpack PKCS7 data", ["CWE-295"], "medium");
return builder.ToImmutable();
}
private static void AddSink(
ImmutableDictionary<string, SinkInfo>.Builder builder,
string name,
string category,
string description,
string[] cweIds,
string severity)
{
builder[name] = new SinkInfo(name, category, description, [.. cweIds], severity);
}
}