Refactor code structure for improved readability and maintainability; optimize performance in key functions.

This commit is contained in:
master
2025-12-22 19:06:31 +02:00
parent dfaa2079aa
commit 4602ccc3a3
1444 changed files with 109919 additions and 8058 deletions

View File

@@ -0,0 +1,239 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Sprint: SPRINT_4300_0002_0001
// Task: Evidence Privacy Controls - Evidence model definitions
namespace StellaOps.Scanner.Evidence.Models;
/// <summary>
/// Bundle of evidence for a finding.
/// </summary>
public sealed record EvidenceBundle
{
/// <summary>
/// Reachability analysis evidence.
/// </summary>
public ReachabilityEvidence? Reachability { get; init; }
/// <summary>
/// Call stack evidence (runtime or static analysis).
/// </summary>
public CallStackEvidence? CallStack { get; init; }
/// <summary>
/// Provenance/build evidence.
/// </summary>
public ProvenanceEvidence? Provenance { get; init; }
/// <summary>
/// VEX statements.
/// </summary>
public VexEvidence? Vex { get; init; }
/// <summary>
/// EPSS evidence.
/// </summary>
public EpssEvidence? Epss { get; init; }
}
/// <summary>
/// Reachability analysis evidence.
/// </summary>
public sealed record ReachabilityEvidence
{
/// <summary>
/// Reachability result.
/// </summary>
public required string Result { get; init; }
/// <summary>
/// Confidence score [0,1].
/// </summary>
public required double Confidence { get; init; }
/// <summary>
/// Paths from entrypoints to vulnerable code.
/// </summary>
public required IReadOnlyList<ReachabilityPath> Paths { get; init; }
/// <summary>
/// Number of paths (preserved in minimal redaction).
/// </summary>
public int PathCount => Paths.Count;
/// <summary>
/// Digest of the call graph used.
/// </summary>
public required string GraphDigest { get; init; }
}
/// <summary>
/// A path from an entrypoint to vulnerable code.
/// </summary>
public sealed record ReachabilityPath
{
/// <summary>
/// Unique path identifier.
/// </summary>
public required string PathId { get; init; }
/// <summary>
/// Steps in the path.
/// </summary>
public required IReadOnlyList<ReachabilityStep> Steps { get; init; }
}
/// <summary>
/// A step in a reachability path.
/// </summary>
public sealed record ReachabilityStep
{
/// <summary>
/// Node identifier (function/method name).
/// </summary>
public required string Node { get; init; }
/// <summary>
/// Hash of the file containing this code.
/// </summary>
public required string FileHash { get; init; }
/// <summary>
/// Line range [start, end].
/// </summary>
public required int[] Lines { get; init; }
/// <summary>
/// Raw source code (null when redacted).
/// </summary>
public string? SourceCode { get; init; }
}
/// <summary>
/// Call stack evidence.
/// </summary>
public sealed record CallStackEvidence
{
/// <summary>
/// Stack frames.
/// </summary>
public required IReadOnlyList<CallFrame> Frames { get; init; }
/// <summary>
/// Stack trace digest.
/// </summary>
public string? StackDigest { get; init; }
}
/// <summary>
/// A frame in a call stack.
/// </summary>
public sealed record CallFrame
{
/// <summary>
/// Function/method name.
/// </summary>
public required string Function { get; init; }
/// <summary>
/// Hash of the file.
/// </summary>
public required string FileHash { get; init; }
/// <summary>
/// Line number.
/// </summary>
public required int Line { get; init; }
/// <summary>
/// Function arguments (null when redacted).
/// </summary>
public IReadOnlyDictionary<string, string>? Arguments { get; init; }
/// <summary>
/// Local variables (null when redacted).
/// </summary>
public IReadOnlyDictionary<string, string>? Locals { get; init; }
}
/// <summary>
/// Provenance/build evidence.
/// </summary>
public sealed record ProvenanceEvidence
{
/// <summary>
/// Build identifier.
/// </summary>
public required string BuildId { get; init; }
/// <summary>
/// Build digest.
/// </summary>
public required string BuildDigest { get; init; }
/// <summary>
/// Whether provenance was verified.
/// </summary>
public required bool Verified { get; init; }
/// <summary>
/// Additional metadata (null when redacted).
/// </summary>
public IReadOnlyDictionary<string, string>? Metadata { get; init; }
}
/// <summary>
/// VEX evidence.
/// </summary>
public sealed record VexEvidence
{
/// <summary>
/// VEX status.
/// </summary>
public required string Status { get; init; }
/// <summary>
/// Justification for not_affected status.
/// </summary>
public string? Justification { get; init; }
/// <summary>
/// Impact statement.
/// </summary>
public string? ImpactStatement { get; init; }
/// <summary>
/// Action statement.
/// </summary>
public string? ActionStatement { get; init; }
/// <summary>
/// Timestamp of the VEX statement.
/// </summary>
public DateTimeOffset? Timestamp { get; init; }
}
/// <summary>
/// EPSS evidence.
/// </summary>
public sealed record EpssEvidence
{
/// <summary>
/// EPSS probability score [0,1].
/// </summary>
public required double Score { get; init; }
/// <summary>
/// EPSS percentile rank [0,1].
/// </summary>
public required double Percentile { get; init; }
/// <summary>
/// Model date.
/// </summary>
public required DateOnly ModelDate { get; init; }
/// <summary>
/// When this evidence was captured.
/// </summary>
public required DateTimeOffset CapturedAt { get; init; }
}

View File

@@ -0,0 +1,46 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Sprint: SPRINT_4300_0002_0001
// Task: T1 - Define Redaction Levels
namespace StellaOps.Scanner.Evidence.Privacy;
/// <summary>
/// Redaction levels for evidence data.
/// </summary>
public enum EvidenceRedactionLevel
{
/// <summary>
/// Full evidence including raw source code.
/// Requires elevated permissions.
/// </summary>
Full = 0,
/// <summary>
/// Standard redaction: file hashes, symbol names, line ranges.
/// No raw source code.
/// </summary>
Standard = 1,
/// <summary>
/// Minimal: only digests and counts.
/// For external sharing.
/// </summary>
Minimal = 2
}
/// <summary>
/// Fields that can be redacted.
/// </summary>
[Flags]
public enum RedactableFields
{
None = 0,
SourceCode = 1 << 0,
FilePaths = 1 << 1,
LineNumbers = 1 << 2,
SymbolNames = 1 << 3,
CallArguments = 1 << 4,
EnvironmentVars = 1 << 5,
InternalUrls = 1 << 6,
All = SourceCode | FilePaths | LineNumbers | SymbolNames | CallArguments | EnvironmentVars | InternalUrls
}

View File

@@ -0,0 +1,227 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Sprint: SPRINT_4300_0002_0001
// Task: T2 - Implement EvidenceRedactionService
using System.Security.Claims;
using Microsoft.Extensions.Logging;
using StellaOps.Scanner.Evidence.Models;
namespace StellaOps.Scanner.Evidence.Privacy;
/// <summary>
/// Service interface for redacting evidence based on privacy rules.
/// </summary>
public interface IEvidenceRedactionService
{
/// <summary>
/// Redacts evidence based on the specified level.
/// </summary>
EvidenceBundle Redact(EvidenceBundle bundle, EvidenceRedactionLevel level);
/// <summary>
/// Redacts specific fields from evidence.
/// </summary>
EvidenceBundle RedactFields(EvidenceBundle bundle, RedactableFields fields);
/// <summary>
/// Determines the appropriate redaction level for a user.
/// </summary>
EvidenceRedactionLevel DetermineLevel(ClaimsPrincipal user);
}
/// <summary>
/// Service for redacting evidence based on privacy rules.
/// </summary>
public sealed class EvidenceRedactionService : IEvidenceRedactionService
{
private readonly ILogger<EvidenceRedactionService> _logger;
public EvidenceRedactionService(ILogger<EvidenceRedactionService> logger)
{
_logger = logger;
}
/// <summary>
/// Redacts evidence based on the specified level.
/// </summary>
public EvidenceBundle Redact(EvidenceBundle bundle, EvidenceRedactionLevel level)
{
_logger.LogDebug("Redacting evidence to level {Level}", level);
return level switch
{
EvidenceRedactionLevel.Full => bundle,
EvidenceRedactionLevel.Standard => RedactStandard(bundle),
EvidenceRedactionLevel.Minimal => RedactMinimal(bundle),
_ => RedactStandard(bundle)
};
}
/// <summary>
/// Redacts specific fields from evidence.
/// </summary>
public EvidenceBundle RedactFields(EvidenceBundle bundle, RedactableFields fields)
{
if (fields == RedactableFields.None)
{
return bundle;
}
var result = bundle;
if (fields.HasFlag(RedactableFields.SourceCode))
{
result = result with
{
Reachability = result.Reachability is not null
? RedactSourceCodeFromReachability(result.Reachability)
: null
};
}
if (fields.HasFlag(RedactableFields.CallArguments))
{
result = result with
{
CallStack = result.CallStack is not null
? RedactCallStackArguments(result.CallStack)
: null
};
}
return result;
}
/// <summary>
/// Determines the appropriate redaction level for a user.
/// </summary>
public EvidenceRedactionLevel DetermineLevel(ClaimsPrincipal user)
{
if (user.HasClaim("scope", "evidence:full") ||
user.HasClaim("role", "security_admin"))
{
_logger.LogDebug("User has full evidence access");
return EvidenceRedactionLevel.Full;
}
if (user.HasClaim("scope", "evidence:standard") ||
user.HasClaim("role", "security_analyst"))
{
_logger.LogDebug("User has standard evidence access");
return EvidenceRedactionLevel.Standard;
}
_logger.LogDebug("User has minimal evidence access (default)");
return EvidenceRedactionLevel.Minimal;
}
private EvidenceBundle RedactStandard(EvidenceBundle bundle)
{
return bundle with
{
Reachability = bundle.Reachability is not null
? RedactReachability(bundle.Reachability)
: null,
CallStack = bundle.CallStack is not null
? RedactCallStack(bundle.CallStack)
: null,
Provenance = bundle.Provenance // Keep as-is (already redacted at standard level)
};
}
private ReachabilityEvidence RedactReachability(ReachabilityEvidence evidence)
{
return evidence with
{
Paths = evidence.Paths.Select(p => new ReachabilityPath
{
PathId = p.PathId,
Steps = p.Steps.Select(s => new ReachabilityStep
{
Node = RedactSymbol(s.Node),
FileHash = s.FileHash, // Keep hash
Lines = s.Lines, // Keep line range
SourceCode = null // Redact source
}).ToList()
}).ToList()
};
}
private CallStackEvidence RedactCallStack(CallStackEvidence evidence)
{
return evidence with
{
Frames = evidence.Frames.Select(f => new CallFrame
{
Function = RedactSymbol(f.Function),
FileHash = f.FileHash,
Line = f.Line,
Arguments = null, // Redact arguments
Locals = null // Redact locals
}).ToList()
};
}
private string RedactSymbol(string symbol)
{
// Keep class and method names, redact arguments
// "MyClass.MyMethod(string arg1, int arg2)" -> "MyClass.MyMethod(...)"
var parenIndex = symbol.IndexOf('(');
if (parenIndex > 0)
{
return symbol[..parenIndex] + "(...)";
}
return symbol;
}
private EvidenceBundle RedactMinimal(EvidenceBundle bundle)
{
return bundle with
{
Reachability = bundle.Reachability is not null
? new ReachabilityEvidence
{
Result = bundle.Reachability.Result,
Confidence = bundle.Reachability.Confidence,
Paths = [], // No paths
GraphDigest = bundle.Reachability.GraphDigest
}
: null,
CallStack = null, // Remove entirely
Provenance = bundle.Provenance is not null
? new ProvenanceEvidence
{
BuildId = bundle.Provenance.BuildId,
BuildDigest = bundle.Provenance.BuildDigest,
Verified = bundle.Provenance.Verified
}
: null,
Vex = bundle.Vex, // Keep VEX (public data)
Epss = bundle.Epss // Keep EPSS (public data)
};
}
private ReachabilityEvidence RedactSourceCodeFromReachability(ReachabilityEvidence evidence)
{
return evidence with
{
Paths = evidence.Paths.Select(p => new ReachabilityPath
{
PathId = p.PathId,
Steps = p.Steps.Select(s => s with { SourceCode = null }).ToList()
}).ToList()
};
}
private CallStackEvidence RedactCallStackArguments(CallStackEvidence evidence)
{
return evidence with
{
Frames = evidence.Frames.Select(f => f with
{
Arguments = null,
Locals = null
}).ToList()
};
}
}

View File

@@ -0,0 +1,18 @@
<?xml version='1.0' encoding='utf-8'?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../../Authority/StellaOps.Authority/StellaOps.Auth.Client/StellaOps.Auth.Client.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.Auth.Security/StellaOps.Auth.Security.csproj" />
</ItemGroup>
</Project>