using System.Collections.Immutable;
using System.Globalization;
namespace StellaOps.Scanner.Analyzers.Lang.Go.Internal;
///
/// Aggregates capability scan results from Go source code analysis.
///
internal sealed class GoCapabilityScanResult
{
private readonly IReadOnlyList _evidences;
private ILookup? _byKind;
private ILookup? _byRisk;
private ILookup? _byFile;
public GoCapabilityScanResult(IReadOnlyList evidences)
{
_evidences = evidences ?? Array.Empty();
}
///
/// All capability evidences found.
///
public IReadOnlyList Evidences => _evidences;
///
/// Gets whether any capabilities were detected.
///
public bool HasCapabilities => _evidences.Count > 0;
///
/// Gets evidences grouped by capability kind.
///
public ILookup EvidencesByKind
=> _byKind ??= _evidences.ToLookup(e => e.Kind);
///
/// Gets evidences grouped by risk level.
///
public ILookup EvidencesByRisk
=> _byRisk ??= _evidences.ToLookup(e => e.Risk);
///
/// Gets evidences grouped by source file.
///
public ILookup EvidencesByFile
=> _byFile ??= _evidences.ToLookup(e => e.SourceFile, StringComparer.OrdinalIgnoreCase);
///
/// Gets all critical risk evidences.
///
public IEnumerable CriticalRiskEvidences
=> _evidences.Where(e => e.Risk == CapabilityRisk.Critical);
///
/// Gets all high risk evidences.
///
public IEnumerable HighRiskEvidences
=> _evidences.Where(e => e.Risk == CapabilityRisk.High);
///
/// Gets the set of detected capability kinds.
///
public IReadOnlySet DetectedKinds
=> _evidences.Select(e => e.Kind).ToHashSet();
///
/// Gets the highest risk level found.
///
public CapabilityRisk HighestRisk
=> _evidences.Count > 0
? _evidences.Max(e => e.Risk)
: CapabilityRisk.Low;
///
/// Gets evidences for a specific capability kind.
///
public IEnumerable GetByKind(CapabilityKind kind)
=> EvidencesByKind[kind];
///
/// Gets evidences at or above a specific risk level.
///
public IEnumerable GetByMinimumRisk(CapabilityRisk minRisk)
=> _evidences.Where(e => e.Risk >= minRisk);
///
/// Creates metadata entries for the scan result.
///
public IEnumerable> CreateMetadata()
{
yield return new KeyValuePair(
"capability.total_count",
_evidences.Count.ToString(CultureInfo.InvariantCulture));
// Count by kind (only emit non-zero)
foreach (var kindGroup in EvidencesByKind.OrderBy(g => g.Key.ToString(), StringComparer.Ordinal))
{
yield return new KeyValuePair(
$"capability.{kindGroup.Key.ToString().ToLowerInvariant()}_count",
kindGroup.Count().ToString(CultureInfo.InvariantCulture));
}
// Count by risk
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("capability.critical_risk_count", criticalCount.ToString(CultureInfo.InvariantCulture));
yield return new KeyValuePair("capability.high_risk_count", highCount.ToString(CultureInfo.InvariantCulture));
yield return new KeyValuePair("capability.medium_risk_count", mediumCount.ToString(CultureInfo.InvariantCulture));
yield return new KeyValuePair("capability.low_risk_count", lowCount.ToString(CultureInfo.InvariantCulture));
// Highest risk
if (_evidences.Count > 0)
{
yield return new KeyValuePair(
"capability.highest_risk",
HighestRisk.ToString().ToLowerInvariant());
}
// Detected capabilities as semicolon-separated list
if (DetectedKinds.Count > 0)
{
yield return new KeyValuePair(
"capability.detected_kinds",
string.Join(';', DetectedKinds.OrderBy(k => k.ToString(), StringComparer.Ordinal).Select(k => k.ToString().ToLowerInvariant())));
}
// Files with critical issues (first 10)
var criticalFiles = CriticalRiskEvidences
.Select(e => e.SourceFile)
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(f => f, StringComparer.Ordinal)
.ToList();
if (criticalFiles.Count > 0)
{
yield return new KeyValuePair(
"capability.critical_files",
string.Join(';', criticalFiles.Take(10)));
if (criticalFiles.Count > 10)
{
yield return new KeyValuePair(
"capability.critical_files_truncated",
"true");
}
}
// Unique patterns detected
var uniquePatterns = _evidences
.Select(e => e.Pattern)
.Distinct(StringComparer.OrdinalIgnoreCase)
.Count();
yield return new KeyValuePair(
"capability.unique_pattern_count",
uniquePatterns.ToString(CultureInfo.InvariantCulture));
}
///
/// Creates a summary of detected capabilities.
///
public GoCapabilitySummary CreateSummary()
{
return new GoCapabilitySummary(
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(),
HasPluginLoading: EvidencesByKind[CapabilityKind.PluginLoading].Any(),
CriticalCount: CriticalRiskEvidences.Count(),
HighRiskCount: HighRiskEvidences.Count(),
TotalCount: _evidences.Count);
}
///
/// Empty scan result with no capabilities detected.
///
public static GoCapabilityScanResult Empty { get; } = new(Array.Empty());
}
///
/// Summary of detected Go capabilities.
///
internal sealed record GoCapabilitySummary(
bool HasExec,
bool HasFilesystem,
bool HasNetwork,
bool HasEnvironment,
bool HasSerialization,
bool HasCrypto,
bool HasDatabase,
bool HasDynamicCode,
bool HasReflection,
bool HasNativeCode,
bool HasPluginLoading,
int CriticalCount,
int HighRiskCount,
int TotalCount)
{
///
/// Creates metadata entries for the summary.
///
public IEnumerable> CreateMetadata()
{
yield return new KeyValuePair("capability.has_exec", HasExec.ToString().ToLowerInvariant());
yield return new KeyValuePair("capability.has_filesystem", HasFilesystem.ToString().ToLowerInvariant());
yield return new KeyValuePair("capability.has_network", HasNetwork.ToString().ToLowerInvariant());
yield return new KeyValuePair("capability.has_environment", HasEnvironment.ToString().ToLowerInvariant());
yield return new KeyValuePair("capability.has_serialization", HasSerialization.ToString().ToLowerInvariant());
yield return new KeyValuePair("capability.has_crypto", HasCrypto.ToString().ToLowerInvariant());
yield return new KeyValuePair("capability.has_database", HasDatabase.ToString().ToLowerInvariant());
yield return new KeyValuePair("capability.has_dynamic_code", HasDynamicCode.ToString().ToLowerInvariant());
yield return new KeyValuePair("capability.has_reflection", HasReflection.ToString().ToLowerInvariant());
yield return new KeyValuePair("capability.has_native_code", HasNativeCode.ToString().ToLowerInvariant());
yield return new KeyValuePair("capability.has_plugin_loading", HasPluginLoading.ToString().ToLowerInvariant());
}
}