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>
215 lines
13 KiB
C#
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);
|
|
}
|
|
}
|