Files
git.stella-ops.org/src/Scanner/__Libraries/StellaOps.Scanner.SmartDiff/SmartDiffPredicate.cs
2026-02-01 21:37:40 +02:00

178 lines
7.2 KiB
C#

using System.Collections.Immutable;
using System.Text.Json.Serialization;
namespace StellaOps.Scanner.SmartDiff;
/// <summary>
/// Smart-Diff predicate for DSSE attestation.
/// </summary>
public sealed record SmartDiffPredicate(
[property: JsonPropertyName("schemaVersion")] string SchemaVersion,
[property: JsonPropertyName("baseImage")] ImageReference BaseImage,
[property: JsonPropertyName("targetImage")] ImageReference TargetImage,
[property: JsonPropertyName("diff")] DiffPayload Diff,
[property: JsonPropertyName("reachabilityGate")] ReachabilityGate ReachabilityGate,
[property: JsonPropertyName("scanner")] ScannerInfo Scanner,
[property: JsonPropertyName("context")] RuntimeContext? Context = null,
[property: JsonPropertyName("suppressedCount")] int SuppressedCount = 0,
[property: JsonPropertyName("materialChanges")] ImmutableArray<MaterialChange>? MaterialChanges = null)
{
public const string PredicateType = "stellaops.dev/predicates/smart-diff@v1";
public const string CurrentSchemaVersion = "1.0.0";
}
public sealed record ImageReference(
[property: JsonPropertyName("digest")] string Digest,
[property: JsonPropertyName("name")] string? Name = null,
[property: JsonPropertyName("tag")] string? Tag = null);
public sealed record DiffPayload(
[property: JsonPropertyName("filesAdded")] ImmutableArray<string>? FilesAdded = null,
[property: JsonPropertyName("filesRemoved")] ImmutableArray<string>? FilesRemoved = null,
[property: JsonPropertyName("filesChanged")] ImmutableArray<FileChange>? FilesChanged = null,
[property: JsonPropertyName("packagesChanged")] ImmutableArray<PackageChange>? PackagesChanged = null,
[property: JsonPropertyName("packagesAdded")] ImmutableArray<PackageRef>? PackagesAdded = null,
[property: JsonPropertyName("packagesRemoved")] ImmutableArray<PackageRef>? PackagesRemoved = null);
public sealed record FileChange(
[property: JsonPropertyName("path")] string Path,
[property: JsonPropertyName("hunks")] ImmutableArray<DiffHunk>? Hunks = null,
[property: JsonPropertyName("fromHash")] string? FromHash = null,
[property: JsonPropertyName("toHash")] string? ToHash = null);
public sealed record DiffHunk(
[property: JsonPropertyName("startLine")] int StartLine,
[property: JsonPropertyName("lineCount")] int LineCount,
[property: JsonPropertyName("content")] string? Content = null);
public sealed record PackageChange(
[property: JsonPropertyName("name")] string Name,
[property: JsonPropertyName("from")] string From,
[property: JsonPropertyName("to")] string To,
[property: JsonPropertyName("purl")] string? Purl = null,
[property: JsonPropertyName("licenseDelta")] LicenseDelta? LicenseDelta = null);
public sealed record LicenseDelta(
[property: JsonPropertyName("added")] ImmutableArray<string>? Added = null,
[property: JsonPropertyName("removed")] ImmutableArray<string>? Removed = null);
public sealed record PackageRef(
[property: JsonPropertyName("name")] string Name,
[property: JsonPropertyName("version")] string Version,
[property: JsonPropertyName("purl")] string? Purl = null);
public sealed record RuntimeContext(
[property: JsonPropertyName("entrypoint")] ImmutableArray<string>? Entrypoint = null,
[property: JsonPropertyName("env")] ImmutableDictionary<string, string>? Env = null,
[property: JsonPropertyName("user")] UserContext? User = null);
public sealed record UserContext(
[property: JsonPropertyName("uid")] int? Uid = null,
[property: JsonPropertyName("gid")] int? Gid = null,
[property: JsonPropertyName("caps")] ImmutableArray<string>? Caps = null);
/// <summary>
/// 3-bit reachability gate derived from the 7-state lattice.
/// </summary>
public sealed record ReachabilityGate(
[property: JsonPropertyName("reachable")] bool? Reachable,
[property: JsonPropertyName("configActivated")] bool? ConfigActivated,
[property: JsonPropertyName("runningUser")] bool? RunningUser,
[property: JsonPropertyName("class")] int Class,
[property: JsonPropertyName("rationale")] string? Rationale = null)
{
/// <summary>
/// Computes the 3-bit class from the gate values.
/// Returns -1 if any gate value is unknown (null).
/// </summary>
public static int ComputeClass(bool? reachable, bool? configActivated, bool? runningUser)
{
if (reachable is null || configActivated is null || runningUser is null)
{
return -1;
}
return (reachable.Value ? 4 : 0)
+ (configActivated.Value ? 2 : 0)
+ (runningUser.Value ? 1 : 0);
}
/// <summary>
/// Creates a ReachabilityGate with auto-computed class.
/// </summary>
public static ReachabilityGate Create(
bool? reachable,
bool? configActivated,
bool? runningUser,
string? rationale = null)
=> new(
reachable,
configActivated,
runningUser,
ComputeClass(reachable, configActivated, runningUser),
rationale);
}
public sealed record ScannerInfo(
[property: JsonPropertyName("name")] string Name,
[property: JsonPropertyName("version")] string Version,
[property: JsonPropertyName("ruleset")] string? Ruleset = null);
public sealed record MaterialChange(
[property: JsonPropertyName("findingKey")] FindingKey FindingKey,
[property: JsonPropertyName("changeType")] MaterialChangeType ChangeType,
[property: JsonPropertyName("reason")] string Reason,
[property: JsonPropertyName("previousState")] RiskState? PreviousState = null,
[property: JsonPropertyName("currentState")] RiskState? CurrentState = null,
[property: JsonPropertyName("priorityScore")] int? PriorityScore = null);
public sealed record FindingKey(
[property: JsonPropertyName("componentPurl")] string ComponentPurl,
[property: JsonPropertyName("componentVersion")] string ComponentVersion,
[property: JsonPropertyName("cveId")] string CveId);
[JsonConverter(typeof(JsonStringEnumConverter<MaterialChangeType>))]
public enum MaterialChangeType
{
[JsonStringEnumMemberName("reachability_flip")]
ReachabilityFlip,
[JsonStringEnumMemberName("vex_flip")]
VexFlip,
[JsonStringEnumMemberName("range_boundary")]
RangeBoundary,
[JsonStringEnumMemberName("intelligence_flip")]
IntelligenceFlip
}
public sealed record RiskState(
[property: JsonPropertyName("reachable")] bool? Reachable = null,
[property: JsonPropertyName("vexStatus")] VexStatusType VexStatus = VexStatusType.Unknown,
[property: JsonPropertyName("inAffectedRange")] bool? InAffectedRange = null,
[property: JsonPropertyName("kev")] bool Kev = false,
[property: JsonPropertyName("epssScore")] double? EpssScore = null,
[property: JsonPropertyName("policyFlags")] ImmutableArray<string>? PolicyFlags = null);
[JsonConverter(typeof(JsonStringEnumConverter<VexStatusType>))]
public enum VexStatusType
{
[JsonStringEnumMemberName("affected")]
Affected,
[JsonStringEnumMemberName("not_affected")]
NotAffected,
[JsonStringEnumMemberName("fixed")]
Fixed,
[JsonStringEnumMemberName("under_investigation")]
UnderInvestigation,
[JsonStringEnumMemberName("unknown")]
Unknown
}