update
This commit is contained in:
@@ -255,6 +255,123 @@ internal sealed class TypeRegistry
|
||||
customEvidence.Properties.Add(new PropertySpec("generatedAt", PrimitiveShape.String(format: "date-time"), true, "Timestamp when the evidence was generated."));
|
||||
customEvidence.Properties.Add(new PropertySpec("properties", new ArrayShape(new ObjectShape(customProperty), 0), false, "Optional key/value properties for additional context."));
|
||||
|
||||
// Smart-Diff predicate (schema-only; consumed as in-toto predicate payload)
|
||||
var smartDiffMaterialChangeType = RegisterEnum(
|
||||
"MaterialChangeType",
|
||||
"Material change types emitted by Smart-Diff.",
|
||||
"reachability_flip",
|
||||
"vex_flip",
|
||||
"range_boundary",
|
||||
"intelligence_flip");
|
||||
|
||||
var smartDiffVexStatusType = RegisterEnum(
|
||||
"VexStatusType",
|
||||
"VEX status values captured in Smart-Diff risk state.",
|
||||
"affected",
|
||||
"not_affected",
|
||||
"fixed",
|
||||
"under_investigation",
|
||||
"unknown");
|
||||
|
||||
var findingKey = RegisterObject("FindingKey", "Unique identifier for a vulnerability finding.");
|
||||
findingKey.Properties.Add(new PropertySpec("componentPurl", PrimitiveShape.String(), true, "Component package URL (purl)."));
|
||||
findingKey.Properties.Add(new PropertySpec("componentVersion", PrimitiveShape.String(), true, "Component version string."));
|
||||
findingKey.Properties.Add(new PropertySpec("cveId", PrimitiveShape.String(), true, "Vulnerability identifier (e.g., CVE)."));
|
||||
|
||||
var riskState = RegisterObject("RiskState", "Risk state captured for a finding at a point in time.");
|
||||
riskState.Properties.Add(new PropertySpec("reachable", PrimitiveShape.Boolean(), false, "Reachability flag (null/absent indicates unknown)."));
|
||||
riskState.Properties.Add(new PropertySpec("vexStatus", new EnumShape(smartDiffVexStatusType), true, "VEX status value."));
|
||||
riskState.Properties.Add(new PropertySpec("inAffectedRange", PrimitiveShape.Boolean(), false, "True if the component version is within the affected range."));
|
||||
riskState.Properties.Add(new PropertySpec("kev", PrimitiveShape.Boolean(), true, "True if the vulnerability is in the KEV catalog."));
|
||||
riskState.Properties.Add(new PropertySpec("epssScore", PrimitiveShape.Number(minimum: 0, maximum: 1), false, "EPSS score (0..1)."));
|
||||
riskState.Properties.Add(new PropertySpec("policyFlags", new ArrayShape(PrimitiveShape.String(), 0), false, "Policy flags contributing to the decision."));
|
||||
|
||||
var materialChange = RegisterObject("MaterialChange", "Single material change detected for a finding.");
|
||||
materialChange.Properties.Add(new PropertySpec("findingKey", new ObjectShape(findingKey), true, "Finding key for the change."));
|
||||
materialChange.Properties.Add(new PropertySpec("changeType", new EnumShape(smartDiffMaterialChangeType), true, "Type of material change detected."));
|
||||
materialChange.Properties.Add(new PropertySpec("reason", PrimitiveShape.String(), true, "Human-readable reason for the change."));
|
||||
materialChange.Properties.Add(new PropertySpec("previousState", new ObjectShape(riskState), false, "Previous risk state (when available)."));
|
||||
materialChange.Properties.Add(new PropertySpec("currentState", new ObjectShape(riskState), false, "Current risk state (when available)."));
|
||||
materialChange.Properties.Add(new PropertySpec("priorityScore", PrimitiveShape.Integer(minimum: 0), false, "Priority score derived from change rules and intelligence."));
|
||||
|
||||
var imageReference = RegisterObject("ImageReference", "Reference to a container image.");
|
||||
imageReference.Properties.Add(new PropertySpec("digest", PrimitiveShape.String(pattern: "^sha256:[A-Fa-f0-9]{64}$"), true, "Image digest."));
|
||||
imageReference.Properties.Add(new PropertySpec("name", PrimitiveShape.String(), false, "Image name."));
|
||||
imageReference.Properties.Add(new PropertySpec("tag", PrimitiveShape.String(), false, "Image tag."));
|
||||
|
||||
var licenseDelta = RegisterObject("LicenseDelta", "License delta for a package or file.");
|
||||
licenseDelta.Properties.Add(new PropertySpec("added", new ArrayShape(PrimitiveShape.String(), 0), false, "Licenses added."));
|
||||
licenseDelta.Properties.Add(new PropertySpec("removed", new ArrayShape(PrimitiveShape.String(), 0), false, "Licenses removed."));
|
||||
|
||||
var packageChange = RegisterObject("PackageChange", "Package version change between the base and target scan.");
|
||||
packageChange.Properties.Add(new PropertySpec("name", PrimitiveShape.String(), true, "Package name."));
|
||||
packageChange.Properties.Add(new PropertySpec("from", PrimitiveShape.String(), true, "Previous package version."));
|
||||
packageChange.Properties.Add(new PropertySpec("to", PrimitiveShape.String(), true, "Current package version."));
|
||||
packageChange.Properties.Add(new PropertySpec("purl", PrimitiveShape.String(), false, "Package URL (purl)."));
|
||||
packageChange.Properties.Add(new PropertySpec("licenseDelta", new ObjectShape(licenseDelta), false, "License delta between versions."));
|
||||
|
||||
var packageRef = RegisterObject("PackageRef", "Package reference used in diffs.");
|
||||
packageRef.Properties.Add(new PropertySpec("name", PrimitiveShape.String(), true, "Package name."));
|
||||
packageRef.Properties.Add(new PropertySpec("version", PrimitiveShape.String(), true, "Package version."));
|
||||
packageRef.Properties.Add(new PropertySpec("purl", PrimitiveShape.String(), false, "Package URL (purl)."));
|
||||
|
||||
var diffHunk = RegisterObject("DiffHunk", "Single diff hunk for a file change.");
|
||||
diffHunk.Properties.Add(new PropertySpec("startLine", PrimitiveShape.Integer(minimum: 0), true, "Start line number."));
|
||||
diffHunk.Properties.Add(new PropertySpec("lineCount", PrimitiveShape.Integer(minimum: 0), true, "Number of lines in the hunk."));
|
||||
diffHunk.Properties.Add(new PropertySpec("content", PrimitiveShape.String(), false, "Optional hunk content."));
|
||||
|
||||
var fileChange = RegisterObject("FileChange", "File-level delta captured by Smart-Diff.");
|
||||
fileChange.Properties.Add(new PropertySpec("path", PrimitiveShape.String(), true, "File path."));
|
||||
fileChange.Properties.Add(new PropertySpec("hunks", new ArrayShape(new ObjectShape(diffHunk), 0), false, "Optional hunks describing the file change."));
|
||||
fileChange.Properties.Add(new PropertySpec("fromHash", PrimitiveShape.String(), false, "Previous file hash (when available)."));
|
||||
fileChange.Properties.Add(new PropertySpec("toHash", PrimitiveShape.String(), false, "Current file hash (when available)."));
|
||||
|
||||
var diffPayload = RegisterObject("DiffPayload", "Diff payload describing file and package deltas.");
|
||||
diffPayload.Properties.Add(new PropertySpec("filesAdded", new ArrayShape(PrimitiveShape.String(), 0), false, "Paths of files added."));
|
||||
diffPayload.Properties.Add(new PropertySpec("filesRemoved", new ArrayShape(PrimitiveShape.String(), 0), false, "Paths of files removed."));
|
||||
diffPayload.Properties.Add(new PropertySpec("filesChanged", new ArrayShape(new ObjectShape(fileChange), 0), false, "Collection of file changes."));
|
||||
diffPayload.Properties.Add(new PropertySpec("packagesChanged", new ArrayShape(new ObjectShape(packageChange), 0), false, "Collection of package changes."));
|
||||
diffPayload.Properties.Add(new PropertySpec("packagesAdded", new ArrayShape(new ObjectShape(packageRef), 0), false, "Packages added."));
|
||||
diffPayload.Properties.Add(new PropertySpec("packagesRemoved", new ArrayShape(new ObjectShape(packageRef), 0), false, "Packages removed."));
|
||||
|
||||
var userContext = RegisterObject("UserContext", "Runtime user context for the image.");
|
||||
userContext.Properties.Add(new PropertySpec("uid", PrimitiveShape.Integer(minimum: 0), false, "User ID."));
|
||||
userContext.Properties.Add(new PropertySpec("gid", PrimitiveShape.Integer(minimum: 0), false, "Group ID."));
|
||||
userContext.Properties.Add(new PropertySpec("caps", new ArrayShape(PrimitiveShape.String(), 0), false, "Linux capabilities (string names)."));
|
||||
|
||||
var runtimeContext = RegisterObject("RuntimeContext", "Runtime context used for reachability gating and policy decisions.");
|
||||
runtimeContext.Properties.Add(new PropertySpec("entrypoint", new ArrayShape(PrimitiveShape.String(), 0), false, "Entrypoint command array."));
|
||||
runtimeContext.Properties.Add(new PropertySpec("env", new MapShape(PrimitiveShape.String()), false, "Environment variables map."));
|
||||
runtimeContext.Properties.Add(new PropertySpec("user", new ObjectShape(userContext), false, "Runtime user context."));
|
||||
|
||||
var reachabilityGate = RegisterObject("ReachabilityGate", "3-bit reachability gate derived from the 7-state lattice.");
|
||||
reachabilityGate.Properties.Add(new PropertySpec("reachable", PrimitiveShape.Boolean(), false, "True/false if reachability is known; absent indicates unknown."));
|
||||
reachabilityGate.Properties.Add(new PropertySpec("configActivated", PrimitiveShape.Boolean(), false, "True if configuration activates the finding."));
|
||||
reachabilityGate.Properties.Add(new PropertySpec("runningUser", PrimitiveShape.Boolean(), false, "True if running user enables the finding."));
|
||||
reachabilityGate.Properties.Add(new PropertySpec("class", PrimitiveShape.Integer(minimum: -1, maximum: 7), true, "Derived 3-bit class (0..7), or -1 if any bit is unknown."));
|
||||
reachabilityGate.Properties.Add(new PropertySpec("rationale", PrimitiveShape.String(), false, "Optional human-readable rationale for the gate."));
|
||||
|
||||
var scannerInfo = RegisterObject("ScannerInfo", "Scanner identity and ruleset information.");
|
||||
scannerInfo.Properties.Add(new PropertySpec("name", PrimitiveShape.String(), true, "Scanner name."));
|
||||
scannerInfo.Properties.Add(new PropertySpec("version", PrimitiveShape.String(), true, "Scanner version string."));
|
||||
scannerInfo.Properties.Add(new PropertySpec("ruleset", PrimitiveShape.String(), false, "Optional ruleset identifier."));
|
||||
|
||||
var smartDiffPredicate = RegisterObject(
|
||||
"SmartDiffPredicate",
|
||||
"Smart-Diff predicate describing differential analysis between two scans.",
|
||||
isRoot: true,
|
||||
schemaFileStem: "stellaops-smart-diff.v1",
|
||||
qualifiedVersion: "1.0.0");
|
||||
smartDiffPredicate.Properties.Add(new PropertySpec("schemaVersion", PrimitiveShape.String(constValue: "1.0.0"), true, "Schema version (semver)."));
|
||||
smartDiffPredicate.Properties.Add(new PropertySpec("baseImage", new ObjectShape(imageReference), true, "Base scan image reference."));
|
||||
smartDiffPredicate.Properties.Add(new PropertySpec("targetImage", new ObjectShape(imageReference), true, "Target scan image reference."));
|
||||
smartDiffPredicate.Properties.Add(new PropertySpec("diff", new ObjectShape(diffPayload), true, "Diff payload between base and target."));
|
||||
smartDiffPredicate.Properties.Add(new PropertySpec("context", new ObjectShape(runtimeContext), false, "Optional runtime context."));
|
||||
smartDiffPredicate.Properties.Add(new PropertySpec("reachabilityGate", new ObjectShape(reachabilityGate), true, "Derived reachability gate."));
|
||||
smartDiffPredicate.Properties.Add(new PropertySpec("scanner", new ObjectShape(scannerInfo), true, "Scanner identity."));
|
||||
smartDiffPredicate.Properties.Add(new PropertySpec("suppressedCount", PrimitiveShape.Integer(minimum: 0), false, "Number of findings suppressed by pre-filters."));
|
||||
smartDiffPredicate.Properties.Add(new PropertySpec("materialChanges", new ArrayShape(new ObjectShape(materialChange), 0), false, "Optional list of material changes."));
|
||||
|
||||
return new TypeRegistry(roots, objects, enums);
|
||||
}
|
||||
}
|
||||
@@ -336,6 +453,8 @@ internal sealed record ObjectShape(ObjectSpec Object) : TypeShape;
|
||||
|
||||
internal sealed record ArrayShape(TypeShape Item, int? MinItems = null) : TypeShape;
|
||||
|
||||
internal sealed record MapShape(TypeShape Value) : TypeShape;
|
||||
|
||||
internal static class SchemaBuilder
|
||||
{
|
||||
public static string Build(ObjectSpec root)
|
||||
@@ -512,6 +631,13 @@ internal static class SchemaBuilder
|
||||
|
||||
return arraySchema;
|
||||
|
||||
case MapShape mapShape:
|
||||
return new JsonObject
|
||||
{
|
||||
["type"] = "object",
|
||||
["additionalProperties"] = BuildPropertySchema(mapShape.Value, defs, visitedObjects, visitedEnums)
|
||||
};
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException("Unsupported property type.");
|
||||
}
|
||||
@@ -692,6 +818,9 @@ internal static class TypeScriptEmitter
|
||||
case ArrayShape arrayShape:
|
||||
lines.AddRange(EmitArrayAssertion(arrayShape, accessor, pathExpression));
|
||||
break;
|
||||
case MapShape mapShape:
|
||||
lines.AddRange(EmitMapAssertion(mapShape, accessor, pathExpression));
|
||||
break;
|
||||
default:
|
||||
lines.Add("// Unsupported type encountered during validation.");
|
||||
break;
|
||||
@@ -754,6 +883,29 @@ internal static class TypeScriptEmitter
|
||||
return lines;
|
||||
}
|
||||
|
||||
private static IReadOnlyList<string> EmitMapAssertion(MapShape mapShape, string accessor, string pathExpression)
|
||||
{
|
||||
var lines = new List<string>
|
||||
{
|
||||
"if (!isRecord(" + accessor + ")) {",
|
||||
" throw new Error(`${pathString(" + pathExpression + ")} must be an object.`);",
|
||||
"}"
|
||||
};
|
||||
|
||||
lines.Add("for (const key of Object.keys(" + accessor + ")) {");
|
||||
lines.Add(" const entry = (" + accessor + " as Record<string, unknown>)[key];");
|
||||
lines.Add(" const entryPath = [..." + pathExpression + ", key];");
|
||||
|
||||
var childLines = EmitTsAssertion(mapShape.Value, "entry", "entryPath");
|
||||
foreach (var line in childLines)
|
||||
{
|
||||
lines.Add(" " + line);
|
||||
}
|
||||
|
||||
lines.Add("}");
|
||||
return lines;
|
||||
}
|
||||
|
||||
private static string ToTsType(TypeShape type)
|
||||
=> type switch
|
||||
{
|
||||
@@ -766,6 +918,7 @@ internal static class TypeScriptEmitter
|
||||
EnumShape enumShape => enumShape.Enum.Name,
|
||||
ObjectShape objectShape => objectShape.Object.Name,
|
||||
ArrayShape array => $"Array<{ToTsType(array.Item)}>",
|
||||
MapShape map => $"Record<string, {ToTsType(map.Value)}>",
|
||||
_ => "unknown"
|
||||
};
|
||||
}
|
||||
@@ -898,6 +1051,9 @@ internal static class GoEmitter
|
||||
case ArrayShape arrayShape:
|
||||
EmitArrayValidation(builder, arrayShape, accessor, path, indent);
|
||||
break;
|
||||
case MapShape mapShape:
|
||||
EmitMapValidation(builder, mapShape, accessor, path, indent);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1014,6 +1170,62 @@ internal static class GoEmitter
|
||||
}
|
||||
}
|
||||
|
||||
private static void EmitMapValidation(StringBuilder builder, MapShape mapShape, string accessor, string path, int indent)
|
||||
{
|
||||
if (!NeedsArrayItemValidation(mapShape.Value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AppendLine(builder, indent, $"for key, item := range {accessor} {{");
|
||||
EmitMapValueValidation(builder, mapShape.Value, "item", path, "key", indent + 1);
|
||||
AppendLine(builder, indent, "}");
|
||||
}
|
||||
|
||||
private static void EmitMapValueValidation(StringBuilder builder, TypeShape valueType, string accessor, string path, string keyVar, int indent)
|
||||
{
|
||||
switch (valueType)
|
||||
{
|
||||
case PrimitiveShape primitive:
|
||||
EmitMapPrimitiveValidation(builder, primitive, accessor, path, keyVar, indent);
|
||||
break;
|
||||
case EnumShape:
|
||||
AppendLine(builder, indent, $"if err := {accessor}.Validate(); err != nil {{");
|
||||
AppendLine(builder, indent + 1, $"return fmt.Errorf(\"invalid {path}[%s]: %w\", {keyVar}, err)");
|
||||
AppendLine(builder, indent, "}");
|
||||
break;
|
||||
case ObjectShape:
|
||||
AppendLine(builder, indent, $"if err := {accessor}.Validate(); err != nil {{");
|
||||
AppendLine(builder, indent + 1, $"return fmt.Errorf(\"invalid {path}[%s]: %w\", {keyVar}, err)");
|
||||
AppendLine(builder, indent, "}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void EmitMapPrimitiveValidation(StringBuilder builder, PrimitiveShape primitive, string accessor, string path, string keyVar, int indent)
|
||||
{
|
||||
if (primitive.Kind == PrimitiveKind.String && !string.IsNullOrEmpty(primitive.ConstValue))
|
||||
{
|
||||
AppendLine(builder, indent, $"if {accessor} != \"{primitive.ConstValue}\" {{");
|
||||
AppendLine(builder, indent + 1, $"return fmt.Errorf(\"{path}[%s] must equal {primitive.ConstValue}\", {keyVar})");
|
||||
AppendLine(builder, indent, "}");
|
||||
}
|
||||
|
||||
if ((primitive.Kind == PrimitiveKind.Number || primitive.Kind == PrimitiveKind.Integer) && primitive.Minimum.HasValue)
|
||||
{
|
||||
AppendLine(builder, indent, $"if {accessor} < {primitive.Minimum.Value} {{");
|
||||
AppendLine(builder, indent + 1, $"return fmt.Errorf(\"{path}[%s] must be >= {primitive.Minimum.Value}\", {keyVar})");
|
||||
AppendLine(builder, indent, "}");
|
||||
}
|
||||
|
||||
if ((primitive.Kind == PrimitiveKind.Number || primitive.Kind == PrimitiveKind.Integer) && primitive.Maximum.HasValue)
|
||||
{
|
||||
AppendLine(builder, indent, $"if {accessor} > {primitive.Maximum.Value} {{");
|
||||
AppendLine(builder, indent + 1, $"return fmt.Errorf(\"{path}[%s] must be <= {primitive.Maximum.Value}\", {keyVar})");
|
||||
AppendLine(builder, indent, "}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void EmitArrayItemValidation(StringBuilder builder, TypeShape itemType, string accessor, string path, string indexVar, int indent)
|
||||
{
|
||||
switch (itemType)
|
||||
@@ -1091,6 +1303,7 @@ internal static class GoEmitter
|
||||
ObjectShape objectShape when property.Required => objectShape.Object.Name,
|
||||
ObjectShape objectShape => "*" + objectShape.Object.Name,
|
||||
ArrayShape arrayShape => $"[]{GoElementType(arrayShape.Item)}",
|
||||
MapShape mapShape => $"map[string]{GoElementType(mapShape.Value)}",
|
||||
_ => "interface{}"
|
||||
};
|
||||
}
|
||||
@@ -1104,6 +1317,7 @@ internal static class GoEmitter
|
||||
EnumShape enumShape => enumShape.Enum.Name,
|
||||
ObjectShape objectShape => objectShape.Object.Name,
|
||||
ArrayShape nested => $"[]{GoElementType(nested.Item)}",
|
||||
MapShape mapShape => $"map[string]{GoElementType(mapShape.Value)}",
|
||||
_ => "interface{}"
|
||||
};
|
||||
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<UseConcelierTestInfra>false</UseConcelierTestInfra>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Include="JsonSchema.Net" Version="7.3.2" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\..\StellaOps.Attestor.Types\samples\**\*.json" LinkBase="samples" CopyToOutputDirectory="PreserveNewest" />
|
||||
<None Include="..\..\StellaOps.Attestor.Types\schemas\**\*.schema.json" LinkBase="schemas" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user