178 lines
7.2 KiB
C#
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
|
|
}
|
|
|