update evidence bundle to include new evidence types and implement ProofSpine integration
Some checks failed
Lighthouse CI / Lighthouse Audit (push) Has been cancelled
Lighthouse CI / Axe Accessibility Audit (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
sm-remote-ci / build-and-test (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
oas-ci / oas-validate (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-12-15 09:15:30 +02:00
parent 8c8f0c632d
commit 505fe7a885
49 changed files with 4756 additions and 551 deletions

View File

@@ -25,6 +25,24 @@ func (v FindingStatus) Validate() error {
}
}
type MaterialChangeType string
const (
MaterialChangeTypeReachabilityFlip MaterialChangeType = "reachability_flip"
MaterialChangeTypeVexFlip MaterialChangeType = "vex_flip"
MaterialChangeTypeRangeBoundary MaterialChangeType = "range_boundary"
MaterialChangeTypeIntelligenceFlip MaterialChangeType = "intelligence_flip"
)
func (v MaterialChangeType) Validate() error {
switch v {
case MaterialChangeTypeReachabilityFlip, MaterialChangeTypeVexFlip, MaterialChangeTypeRangeBoundary, MaterialChangeTypeIntelligenceFlip:
return nil
default:
return fmt.Errorf("invalid value for MaterialChangeType: %s", string(v))
}
}
type PolicyEffect string
const (
@@ -131,6 +149,25 @@ func (v VexStatus) Validate() error {
}
}
type VexStatusType string
const (
VexStatusTypeAffected VexStatusType = "affected"
VexStatusTypeNotAffected VexStatusType = "not_affected"
VexStatusTypeFixed VexStatusType = "fixed"
VexStatusTypeUnderInvestigation VexStatusType = "under_investigation"
VexStatusTypeUnknown VexStatusType = "unknown"
)
func (v VexStatusType) Validate() error {
switch v {
case VexStatusTypeAffected, VexStatusTypeNotAffected, VexStatusTypeFixed, VexStatusTypeUnderInvestigation, VexStatusTypeUnknown:
return nil
default:
return fmt.Errorf("invalid value for VexStatusType: %s", string(v))
}
}
const BuildProvenanceSchemaVersion = "StellaOps.BuildProvenance@1"
const CustomEvidenceSchemaVersion = "StellaOps.CustomEvidence@1"
@@ -143,6 +180,8 @@ const SbomAttestationSchemaVersion = "StellaOps.SBOMAttestation@1"
const ScanResultsSchemaVersion = "StellaOps.ScanResults@1"
const SmartDiffPredicateSchemaVersion = "1.0.0"
const VexAttestationSchemaVersion = "StellaOps.VEXAttestation@1"
type BuildMetadata struct {
@@ -245,6 +284,61 @@ func (value *CustomProperty) Validate() error {
return nil
}
type DiffHunk struct {
StartLine float64 `json:"startLine"`
LineCount float64 `json:"lineCount"`
Content *string `json:"content,omitempty"`
}
func (value *DiffHunk) Validate() error {
if value == nil {
return errors.New("DiffHunk is nil")
}
if value.StartLine < 0 {
return fmt.Errorf("DiffHunk.StartLine must be >= 0")
}
if value.LineCount < 0 {
return fmt.Errorf("DiffHunk.LineCount must be >= 0")
}
return nil
}
type DiffPayload struct {
FilesAdded []string `json:"filesAdded,omitempty"`
FilesRemoved []string `json:"filesRemoved,omitempty"`
FilesChanged []FileChange `json:"filesChanged,omitempty"`
PackagesChanged []PackageChange `json:"packagesChanged,omitempty"`
PackagesAdded []PackageRef `json:"packagesAdded,omitempty"`
PackagesRemoved []PackageRef `json:"packagesRemoved,omitempty"`
}
func (value *DiffPayload) Validate() error {
if value == nil {
return errors.New("DiffPayload is nil")
}
for i := range value.FilesChanged {
if err := value.FilesChanged[i].Validate(); err != nil {
return fmt.Errorf("invalid DiffPayload.FilesChanged[%d]: %w", i, err)
}
}
for i := range value.PackagesChanged {
if err := value.PackagesChanged[i].Validate(); err != nil {
return fmt.Errorf("invalid DiffPayload.PackagesChanged[%d]: %w", i, err)
}
}
for i := range value.PackagesAdded {
if err := value.PackagesAdded[i].Validate(); err != nil {
return fmt.Errorf("invalid DiffPayload.PackagesAdded[%d]: %w", i, err)
}
}
for i := range value.PackagesRemoved {
if err := value.PackagesRemoved[i].Validate(); err != nil {
return fmt.Errorf("invalid DiffPayload.PackagesRemoved[%d]: %w", i, err)
}
}
return nil
}
type DigestReference struct {
Algorithm string `json:"algorithm"`
Value string `json:"value"`
@@ -274,6 +368,100 @@ func (value *EnvironmentMetadata) Validate() error {
return nil
}
type FileChange struct {
Path string `json:"path"`
Hunks []DiffHunk `json:"hunks,omitempty"`
FromHash *string `json:"fromHash,omitempty"`
ToHash *string `json:"toHash,omitempty"`
}
func (value *FileChange) Validate() error {
if value == nil {
return errors.New("FileChange is nil")
}
for i := range value.Hunks {
if err := value.Hunks[i].Validate(); err != nil {
return fmt.Errorf("invalid FileChange.Hunks[%d]: %w", i, err)
}
}
return nil
}
type FindingKey struct {
ComponentPurl string `json:"componentPurl"`
ComponentVersion string `json:"componentVersion"`
CveId string `json:"cveId"`
}
func (value *FindingKey) Validate() error {
if value == nil {
return errors.New("FindingKey is nil")
}
return nil
}
type ImageReference struct {
Digest string `json:"digest"`
Name *string `json:"name,omitempty"`
Tag *string `json:"tag,omitempty"`
}
func (value *ImageReference) Validate() error {
if value == nil {
return errors.New("ImageReference is nil")
}
return nil
}
type LicenseDelta struct {
Added []string `json:"added,omitempty"`
Removed []string `json:"removed,omitempty"`
}
func (value *LicenseDelta) Validate() error {
if value == nil {
return errors.New("LicenseDelta is nil")
}
return nil
}
type MaterialChange struct {
FindingKey FindingKey `json:"findingKey"`
ChangeType MaterialChangeType `json:"changeType"`
Reason string `json:"reason"`
PreviousState *RiskState `json:"previousState,omitempty"`
CurrentState *RiskState `json:"currentState,omitempty"`
PriorityScore *float64 `json:"priorityScore,omitempty"`
}
func (value *MaterialChange) Validate() error {
if value == nil {
return errors.New("MaterialChange is nil")
}
if err := value.FindingKey.Validate(); err != nil {
return fmt.Errorf("invalid MaterialChange.FindingKey: %w", err)
}
if err := value.ChangeType.Validate(); err != nil {
return fmt.Errorf("invalid MaterialChange.ChangeType: %w", err)
}
if value.PreviousState != nil {
if err := value.PreviousState.Validate(); err != nil {
return fmt.Errorf("invalid MaterialChange.PreviousState: %w", err)
}
}
if value.CurrentState != nil {
if err := value.CurrentState.Validate(); err != nil {
return fmt.Errorf("invalid MaterialChange.CurrentState: %w", err)
}
}
if value.PriorityScore != nil {
if *value.PriorityScore < 0 {
return fmt.Errorf("MaterialChange.PriorityScore must be >= 0")
}
}
return nil
}
type MaterialReference struct {
Uri string `json:"uri"`
Digests []DigestReference `json:"digests"`
@@ -295,6 +483,39 @@ func (value *MaterialReference) Validate() error {
return nil
}
type PackageChange struct {
Name string `json:"name"`
From string `json:"from"`
To string `json:"to"`
Purl *string `json:"purl,omitempty"`
LicenseDelta *LicenseDelta `json:"licenseDelta,omitempty"`
}
func (value *PackageChange) Validate() error {
if value == nil {
return errors.New("PackageChange is nil")
}
if value.LicenseDelta != nil {
if err := value.LicenseDelta.Validate(); err != nil {
return fmt.Errorf("invalid PackageChange.LicenseDelta: %w", err)
}
}
return nil
}
type PackageRef struct {
Name string `json:"name"`
Version string `json:"version"`
Purl *string `json:"purl,omitempty"`
}
func (value *PackageRef) Validate() error {
if value == nil {
return errors.New("PackageRef is nil")
}
return nil
}
type PolicyDecision struct {
PolicyId string `json:"policyId"`
RuleId string `json:"ruleId"`
@@ -340,6 +561,27 @@ func (value *PolicyEvaluation) Validate() error {
return nil
}
type ReachabilityGate struct {
Reachable *bool `json:"reachable,omitempty"`
ConfigActivated *bool `json:"configActivated,omitempty"`
RunningUser *bool `json:"runningUser,omitempty"`
Class float64 `json:"class"`
Rationale *string `json:"rationale,omitempty"`
}
func (value *ReachabilityGate) Validate() error {
if value == nil {
return errors.New("ReachabilityGate is nil")
}
if value.Class < -1 {
return fmt.Errorf("ReachabilityGate.Class must be >= -1")
}
if value.Class > 7 {
return fmt.Errorf("ReachabilityGate.Class must be <= 7")
}
return nil
}
type RiskFactor struct {
Name string `json:"name"`
Weight float64 `json:"weight"`
@@ -392,6 +634,51 @@ func (value *RiskProfileEvidence) Validate() error {
return nil
}
type RiskState struct {
Reachable *bool `json:"reachable,omitempty"`
VexStatus VexStatusType `json:"vexStatus"`
InAffectedRange *bool `json:"inAffectedRange,omitempty"`
Kev bool `json:"kev"`
EpssScore *float64 `json:"epssScore,omitempty"`
PolicyFlags []string `json:"policyFlags,omitempty"`
}
func (value *RiskState) Validate() error {
if value == nil {
return errors.New("RiskState is nil")
}
if err := value.VexStatus.Validate(); err != nil {
return fmt.Errorf("invalid RiskState.VexStatus: %w", err)
}
if value.EpssScore != nil {
if *value.EpssScore < 0 {
return fmt.Errorf("RiskState.EpssScore must be >= 0")
}
if *value.EpssScore > 1 {
return fmt.Errorf("RiskState.EpssScore must be <= 1")
}
}
return nil
}
type RuntimeContext struct {
Entrypoint []string `json:"entrypoint,omitempty"`
Env map[string]string `json:"env,omitempty"`
User *UserContext `json:"user,omitempty"`
}
func (value *RuntimeContext) Validate() error {
if value == nil {
return errors.New("RuntimeContext is nil")
}
if value.User != nil {
if err := value.User.Validate(); err != nil {
return fmt.Errorf("invalid RuntimeContext.User: %w", err)
}
}
return nil
}
type SbomAttestation struct {
SchemaVersion string `json:"schemaVersion"`
SubjectDigest string `json:"subjectDigest"`
@@ -501,6 +788,94 @@ func (value *ScanResults) Validate() error {
return nil
}
type ScannerInfo struct {
Name string `json:"name"`
Version string `json:"version"`
Ruleset *string `json:"ruleset,omitempty"`
}
func (value *ScannerInfo) Validate() error {
if value == nil {
return errors.New("ScannerInfo is nil")
}
return nil
}
type SmartDiffPredicate struct {
SchemaVersion string `json:"schemaVersion"`
BaseImage ImageReference `json:"baseImage"`
TargetImage ImageReference `json:"targetImage"`
Diff DiffPayload `json:"diff"`
Context *RuntimeContext `json:"context,omitempty"`
ReachabilityGate ReachabilityGate `json:"reachabilityGate"`
Scanner ScannerInfo `json:"scanner"`
SuppressedCount *float64 `json:"suppressedCount,omitempty"`
MaterialChanges []MaterialChange `json:"materialChanges,omitempty"`
}
func (value *SmartDiffPredicate) Validate() error {
if value == nil {
return errors.New("SmartDiffPredicate is nil")
}
if value.SchemaVersion != "1.0.0" {
return fmt.Errorf("SmartDiffPredicate.SchemaVersion must equal 1.0.0")
}
if err := value.BaseImage.Validate(); err != nil {
return fmt.Errorf("invalid SmartDiffPredicate.BaseImage: %w", err)
}
if err := value.TargetImage.Validate(); err != nil {
return fmt.Errorf("invalid SmartDiffPredicate.TargetImage: %w", err)
}
if err := value.Diff.Validate(); err != nil {
return fmt.Errorf("invalid SmartDiffPredicate.Diff: %w", err)
}
if value.Context != nil {
if err := value.Context.Validate(); err != nil {
return fmt.Errorf("invalid SmartDiffPredicate.Context: %w", err)
}
}
if err := value.ReachabilityGate.Validate(); err != nil {
return fmt.Errorf("invalid SmartDiffPredicate.ReachabilityGate: %w", err)
}
if err := value.Scanner.Validate(); err != nil {
return fmt.Errorf("invalid SmartDiffPredicate.Scanner: %w", err)
}
if value.SuppressedCount != nil {
if *value.SuppressedCount < 0 {
return fmt.Errorf("SmartDiffPredicate.SuppressedCount must be >= 0")
}
}
for i := range value.MaterialChanges {
if err := value.MaterialChanges[i].Validate(); err != nil {
return fmt.Errorf("invalid SmartDiffPredicate.MaterialChanges[%d]: %w", i, err)
}
}
return nil
}
type UserContext struct {
Uid *float64 `json:"uid,omitempty"`
Gid *float64 `json:"gid,omitempty"`
Caps []string `json:"caps,omitempty"`
}
func (value *UserContext) Validate() error {
if value == nil {
return errors.New("UserContext is nil")
}
if value.Uid != nil {
if *value.Uid < 0 {
return fmt.Errorf("UserContext.Uid must be >= 0")
}
}
if value.Gid != nil {
if *value.Gid < 0 {
return fmt.Errorf("UserContext.Gid must be >= 0")
}
}
return nil
}
type VexAttestation struct {
SchemaVersion string `json:"schemaVersion"`
SubjectDigest string `json:"subjectDigest"`
@@ -615,6 +990,17 @@ func (value *ScanResults) CanonicalJSON() ([]byte, error) {
return buf, nil
}
func (value *SmartDiffPredicate) CanonicalJSON() ([]byte, error) {
if err := value.Validate(); err != nil {
return nil, err
}
buf, err := json.Marshal(value)
if err != nil {
return nil, fmt.Errorf("failed to marshal SmartDiffPredicate: %w", err)
}
return buf, nil
}
func (value *VexAttestation) CanonicalJSON() ([]byte, error) {
if err := value.Validate(); err != nil {
return nil, err

View File

@@ -6,6 +6,9 @@
export const FindingStatusValues = Object.freeze(['detected', 'confirmed', 'fixed', 'not_affected'] as const);
export type FindingStatus = typeof FindingStatusValues[number];
export const MaterialChangeTypeValues = Object.freeze(['reachability_flip', 'vex_flip', 'range_boundary', 'intelligence_flip'] as const);
export type MaterialChangeType = typeof MaterialChangeTypeValues[number];
export const PolicyEffectValues = Object.freeze(['allow', 'deny', 'warn'] as const);
export type PolicyEffect = typeof PolicyEffectValues[number];
@@ -24,6 +27,9 @@ export type Severity = typeof SeverityValues[number];
export const VexStatusValues = Object.freeze(['not_affected', 'affected', 'under_investigation', 'fixed'] as const);
export type VexStatus = typeof VexStatusValues[number];
export const VexStatusTypeValues = Object.freeze(['affected', 'not_affected', 'fixed', 'under_investigation', 'unknown'] as const);
export type VexStatusType = typeof VexStatusTypeValues[number];
export interface BuildMetadata {
buildStartedOn: string;
buildFinishedOn: string;
@@ -59,6 +65,21 @@ export interface CustomProperty {
value: string;
}
export interface DiffHunk {
startLine: number;
lineCount: number;
content?: string;
}
export interface DiffPayload {
filesAdded?: Array<string>;
filesRemoved?: Array<string>;
filesChanged?: Array<FileChange>;
packagesChanged?: Array<PackageChange>;
packagesAdded?: Array<PackageRef>;
packagesRemoved?: Array<PackageRef>;
}
export interface DigestReference {
algorithm: string;
value: string;
@@ -69,12 +90,59 @@ export interface EnvironmentMetadata {
imageDigest?: DigestReference;
}
export interface FileChange {
path: string;
hunks?: Array<DiffHunk>;
fromHash?: string;
toHash?: string;
}
export interface FindingKey {
componentPurl: string;
componentVersion: string;
cveId: string;
}
export interface ImageReference {
digest: string;
name?: string;
tag?: string;
}
export interface LicenseDelta {
added?: Array<string>;
removed?: Array<string>;
}
export interface MaterialChange {
findingKey: FindingKey;
changeType: MaterialChangeType;
reason: string;
previousState?: RiskState;
currentState?: RiskState;
priorityScore?: number;
}
export interface MaterialReference {
uri: string;
digests: Array<DigestReference>;
note?: string;
}
export interface PackageChange {
name: string;
from: string;
to: string;
purl?: string;
licenseDelta?: LicenseDelta;
}
export interface PackageRef {
name: string;
version: string;
purl?: string;
}
export interface PolicyDecision {
policyId: string;
ruleId: string;
@@ -92,6 +160,14 @@ export interface PolicyEvaluation {
decisions: Array<PolicyDecision>;
}
export interface ReachabilityGate {
reachable?: boolean;
configActivated?: boolean;
runningUser?: boolean;
class: number;
rationale?: string;
}
export interface RiskFactor {
name: string;
weight: number;
@@ -107,6 +183,21 @@ export interface RiskProfileEvidence {
factors: Array<RiskFactor>;
}
export interface RiskState {
reachable?: boolean;
vexStatus: VexStatusType;
inAffectedRange?: boolean;
kev: boolean;
epssScore?: number;
policyFlags?: Array<string>;
}
export interface RuntimeContext {
entrypoint?: Array<string>;
env?: Record<string, string>;
user?: UserContext;
}
export interface SbomAttestation {
schemaVersion: 'StellaOps.SBOMAttestation@1';
subjectDigest: string;
@@ -143,6 +234,30 @@ export interface ScanResults {
findings: Array<ScanFinding>;
}
export interface ScannerInfo {
name: string;
version: string;
ruleset?: string;
}
export interface SmartDiffPredicate {
schemaVersion: '1.0.0';
baseImage: ImageReference;
targetImage: ImageReference;
diff: DiffPayload;
context?: RuntimeContext;
reachabilityGate: ReachabilityGate;
scanner: ScannerInfo;
suppressedCount?: number;
materialChanges?: Array<MaterialChange>;
}
export interface UserContext {
uid?: number;
gid?: number;
caps?: Array<string>;
}
export interface VexAttestation {
schemaVersion: 'StellaOps.VEXAttestation@1';
subjectDigest: string;
@@ -324,6 +439,93 @@ function assertCustomProperty(value: unknown, path: string[]): asserts value is
}
}
function assertDiffHunk(value: unknown, path: string[]): asserts value is DiffHunk {
if (!isRecord(value)) {
throw new Error(`${pathString(path)} must be an object.`);
}
if (value.startLine === undefined) {
throw new Error(`${pathString([...path, 'startLine'])} is required.`);
}
if (typeof value.startLine !== 'number') {
throw new Error(`${pathString([...path, 'startLine'])} must be a number.`);
}
if (value.startLine < 0) {
throw new Error(`${pathString([...path, 'startLine'])} must be >= 0`);
}
if (value.lineCount === undefined) {
throw new Error(`${pathString([...path, 'lineCount'])} is required.`);
}
if (typeof value.lineCount !== 'number') {
throw new Error(`${pathString([...path, 'lineCount'])} must be a number.`);
}
if (value.lineCount < 0) {
throw new Error(`${pathString([...path, 'lineCount'])} must be >= 0`);
}
if (value.content !== undefined) {
if (typeof value.content !== 'string') {
throw new Error(`${pathString([...path, 'content'])} must be a string.`);
}
}
}
function assertDiffPayload(value: unknown, path: string[]): asserts value is DiffPayload {
if (!isRecord(value)) {
throw new Error(`${pathString(path)} must be an object.`);
}
if (value.filesAdded !== undefined) {
if (!Array.isArray(value.filesAdded)) {
throw new Error(`${pathString([...path, 'filesAdded'])} must be an array.`);
}
for (let i = 0; i < value.filesAdded.length; i += 1) {
if (typeof value.filesAdded[i] !== 'string') {
throw new Error(`${pathString([...[...path, 'filesAdded'], String(i)])} must be a string.`);
}
}
}
if (value.filesRemoved !== undefined) {
if (!Array.isArray(value.filesRemoved)) {
throw new Error(`${pathString([...path, 'filesRemoved'])} must be an array.`);
}
for (let i = 0; i < value.filesRemoved.length; i += 1) {
if (typeof value.filesRemoved[i] !== 'string') {
throw new Error(`${pathString([...[...path, 'filesRemoved'], String(i)])} must be a string.`);
}
}
}
if (value.filesChanged !== undefined) {
if (!Array.isArray(value.filesChanged)) {
throw new Error(`${pathString([...path, 'filesChanged'])} must be an array.`);
}
for (let i = 0; i < value.filesChanged.length; i += 1) {
assertFileChange(value.filesChanged[i], [...[...path, 'filesChanged'], String(i)]);
}
}
if (value.packagesChanged !== undefined) {
if (!Array.isArray(value.packagesChanged)) {
throw new Error(`${pathString([...path, 'packagesChanged'])} must be an array.`);
}
for (let i = 0; i < value.packagesChanged.length; i += 1) {
assertPackageChange(value.packagesChanged[i], [...[...path, 'packagesChanged'], String(i)]);
}
}
if (value.packagesAdded !== undefined) {
if (!Array.isArray(value.packagesAdded)) {
throw new Error(`${pathString([...path, 'packagesAdded'])} must be an array.`);
}
for (let i = 0; i < value.packagesAdded.length; i += 1) {
assertPackageRef(value.packagesAdded[i], [...[...path, 'packagesAdded'], String(i)]);
}
}
if (value.packagesRemoved !== undefined) {
if (!Array.isArray(value.packagesRemoved)) {
throw new Error(`${pathString([...path, 'packagesRemoved'])} must be an array.`);
}
for (let i = 0; i < value.packagesRemoved.length; i += 1) {
assertPackageRef(value.packagesRemoved[i], [...[...path, 'packagesRemoved'], String(i)]);
}
}
}
function assertDigestReference(value: unknown, path: string[]): asserts value is DigestReference {
if (!isRecord(value)) {
throw new Error(`${pathString(path)} must be an object.`);
@@ -359,6 +561,147 @@ function assertEnvironmentMetadata(value: unknown, path: string[]): asserts valu
}
}
function assertFileChange(value: unknown, path: string[]): asserts value is FileChange {
if (!isRecord(value)) {
throw new Error(`${pathString(path)} must be an object.`);
}
if (value.path === undefined) {
throw new Error(`${pathString([...path, 'path'])} is required.`);
}
if (typeof value.path !== 'string') {
throw new Error(`${pathString([...path, 'path'])} must be a string.`);
}
if (value.hunks !== undefined) {
if (!Array.isArray(value.hunks)) {
throw new Error(`${pathString([...path, 'hunks'])} must be an array.`);
}
for (let i = 0; i < value.hunks.length; i += 1) {
assertDiffHunk(value.hunks[i], [...[...path, 'hunks'], String(i)]);
}
}
if (value.fromHash !== undefined) {
if (typeof value.fromHash !== 'string') {
throw new Error(`${pathString([...path, 'fromHash'])} must be a string.`);
}
}
if (value.toHash !== undefined) {
if (typeof value.toHash !== 'string') {
throw new Error(`${pathString([...path, 'toHash'])} must be a string.`);
}
}
}
function assertFindingKey(value: unknown, path: string[]): asserts value is FindingKey {
if (!isRecord(value)) {
throw new Error(`${pathString(path)} must be an object.`);
}
if (value.componentPurl === undefined) {
throw new Error(`${pathString([...path, 'componentPurl'])} is required.`);
}
if (typeof value.componentPurl !== 'string') {
throw new Error(`${pathString([...path, 'componentPurl'])} must be a string.`);
}
if (value.componentVersion === undefined) {
throw new Error(`${pathString([...path, 'componentVersion'])} is required.`);
}
if (typeof value.componentVersion !== 'string') {
throw new Error(`${pathString([...path, 'componentVersion'])} must be a string.`);
}
if (value.cveId === undefined) {
throw new Error(`${pathString([...path, 'cveId'])} is required.`);
}
if (typeof value.cveId !== 'string') {
throw new Error(`${pathString([...path, 'cveId'])} must be a string.`);
}
}
function assertImageReference(value: unknown, path: string[]): asserts value is ImageReference {
if (!isRecord(value)) {
throw new Error(`${pathString(path)} must be an object.`);
}
if (value.digest === undefined) {
throw new Error(`${pathString([...path, 'digest'])} is required.`);
}
if (typeof value.digest !== 'string') {
throw new Error(`${pathString([...path, 'digest'])} must be a string.`);
}
if (!/^sha256:[A-Fa-f0-9]{64}$/.test(value.digest)) {
throw new Error(`${pathString([...path, 'digest'])} does not match expected format.`);
}
if (value.name !== undefined) {
if (typeof value.name !== 'string') {
throw new Error(`${pathString([...path, 'name'])} must be a string.`);
}
}
if (value.tag !== undefined) {
if (typeof value.tag !== 'string') {
throw new Error(`${pathString([...path, 'tag'])} must be a string.`);
}
}
}
function assertLicenseDelta(value: unknown, path: string[]): asserts value is LicenseDelta {
if (!isRecord(value)) {
throw new Error(`${pathString(path)} must be an object.`);
}
if (value.added !== undefined) {
if (!Array.isArray(value.added)) {
throw new Error(`${pathString([...path, 'added'])} must be an array.`);
}
for (let i = 0; i < value.added.length; i += 1) {
if (typeof value.added[i] !== 'string') {
throw new Error(`${pathString([...[...path, 'added'], String(i)])} must be a string.`);
}
}
}
if (value.removed !== undefined) {
if (!Array.isArray(value.removed)) {
throw new Error(`${pathString([...path, 'removed'])} must be an array.`);
}
for (let i = 0; i < value.removed.length; i += 1) {
if (typeof value.removed[i] !== 'string') {
throw new Error(`${pathString([...[...path, 'removed'], String(i)])} must be a string.`);
}
}
}
}
function assertMaterialChange(value: unknown, path: string[]): asserts value is MaterialChange {
if (!isRecord(value)) {
throw new Error(`${pathString(path)} must be an object.`);
}
if (value.findingKey === undefined) {
throw new Error(`${pathString([...path, 'findingKey'])} is required.`);
}
assertFindingKey(value.findingKey, [...path, 'findingKey']);
if (value.changeType === undefined) {
throw new Error(`${pathString([...path, 'changeType'])} is required.`);
}
if (!MaterialChangeTypeValues.includes(value.changeType as MaterialChangeType)) {
throw new Error(`${pathString([...path, 'changeType'])} must be one of ${MaterialChangeTypeValues.join(', ')}`);
}
if (value.reason === undefined) {
throw new Error(`${pathString([...path, 'reason'])} is required.`);
}
if (typeof value.reason !== 'string') {
throw new Error(`${pathString([...path, 'reason'])} must be a string.`);
}
if (value.previousState !== undefined) {
assertRiskState(value.previousState, [...path, 'previousState']);
}
if (value.currentState !== undefined) {
assertRiskState(value.currentState, [...path, 'currentState']);
}
if (value.priorityScore !== undefined) {
if (typeof value.priorityScore !== 'number') {
throw new Error(`${pathString([...path, 'priorityScore'])} must be a number.`);
}
if (value.priorityScore < 0) {
throw new Error(`${pathString([...path, 'priorityScore'])} must be >= 0`);
}
}
}
function assertMaterialReference(value: unknown, path: string[]): asserts value is MaterialReference {
if (!isRecord(value)) {
throw new Error(`${pathString(path)} must be an object.`);
@@ -388,6 +731,61 @@ function assertMaterialReference(value: unknown, path: string[]): asserts value
}
}
function assertPackageChange(value: unknown, path: string[]): asserts value is PackageChange {
if (!isRecord(value)) {
throw new Error(`${pathString(path)} must be an object.`);
}
if (value.name === undefined) {
throw new Error(`${pathString([...path, 'name'])} is required.`);
}
if (typeof value.name !== 'string') {
throw new Error(`${pathString([...path, 'name'])} must be a string.`);
}
if (value.from === undefined) {
throw new Error(`${pathString([...path, 'from'])} is required.`);
}
if (typeof value.from !== 'string') {
throw new Error(`${pathString([...path, 'from'])} must be a string.`);
}
if (value.to === undefined) {
throw new Error(`${pathString([...path, 'to'])} is required.`);
}
if (typeof value.to !== 'string') {
throw new Error(`${pathString([...path, 'to'])} must be a string.`);
}
if (value.purl !== undefined) {
if (typeof value.purl !== 'string') {
throw new Error(`${pathString([...path, 'purl'])} must be a string.`);
}
}
if (value.licenseDelta !== undefined) {
assertLicenseDelta(value.licenseDelta, [...path, 'licenseDelta']);
}
}
function assertPackageRef(value: unknown, path: string[]): asserts value is PackageRef {
if (!isRecord(value)) {
throw new Error(`${pathString(path)} must be an object.`);
}
if (value.name === undefined) {
throw new Error(`${pathString([...path, 'name'])} is required.`);
}
if (typeof value.name !== 'string') {
throw new Error(`${pathString([...path, 'name'])} must be a string.`);
}
if (value.version === undefined) {
throw new Error(`${pathString([...path, 'version'])} is required.`);
}
if (typeof value.version !== 'string') {
throw new Error(`${pathString([...path, 'version'])} must be a string.`);
}
if (value.purl !== undefined) {
if (typeof value.purl !== 'string') {
throw new Error(`${pathString([...path, 'purl'])} must be a string.`);
}
}
}
function assertPolicyDecision(value: unknown, path: string[]): asserts value is PolicyDecision {
if (!isRecord(value)) {
throw new Error(`${pathString(path)} must be an object.`);
@@ -473,6 +871,44 @@ function assertPolicyEvaluation(value: unknown, path: string[]): asserts value i
}
}
function assertReachabilityGate(value: unknown, path: string[]): asserts value is ReachabilityGate {
if (!isRecord(value)) {
throw new Error(`${pathString(path)} must be an object.`);
}
if (value.reachable !== undefined) {
if (typeof value.reachable !== 'boolean') {
throw new Error(`${pathString([...path, 'reachable'])} must be a boolean.`);
}
}
if (value.configActivated !== undefined) {
if (typeof value.configActivated !== 'boolean') {
throw new Error(`${pathString([...path, 'configActivated'])} must be a boolean.`);
}
}
if (value.runningUser !== undefined) {
if (typeof value.runningUser !== 'boolean') {
throw new Error(`${pathString([...path, 'runningUser'])} must be a boolean.`);
}
}
if (value.class === undefined) {
throw new Error(`${pathString([...path, 'class'])} is required.`);
}
if (typeof value.class !== 'number') {
throw new Error(`${pathString([...path, 'class'])} must be a number.`);
}
if (value.class < -1) {
throw new Error(`${pathString([...path, 'class'])} must be >= -1`);
}
if (value.class > 7) {
throw new Error(`${pathString([...path, 'class'])} must be <= 7`);
}
if (value.rationale !== undefined) {
if (typeof value.rationale !== 'string') {
throw new Error(`${pathString([...path, 'rationale'])} must be a string.`);
}
}
}
function assertRiskFactor(value: unknown, path: string[]): asserts value is RiskFactor {
if (!isRecord(value)) {
throw new Error(`${pathString(path)} must be an object.`);
@@ -559,6 +995,86 @@ function assertRiskProfileEvidence(value: unknown, path: string[]): asserts valu
}
}
function assertRiskState(value: unknown, path: string[]): asserts value is RiskState {
if (!isRecord(value)) {
throw new Error(`${pathString(path)} must be an object.`);
}
if (value.reachable !== undefined) {
if (typeof value.reachable !== 'boolean') {
throw new Error(`${pathString([...path, 'reachable'])} must be a boolean.`);
}
}
if (value.vexStatus === undefined) {
throw new Error(`${pathString([...path, 'vexStatus'])} is required.`);
}
if (!VexStatusTypeValues.includes(value.vexStatus as VexStatusType)) {
throw new Error(`${pathString([...path, 'vexStatus'])} must be one of ${VexStatusTypeValues.join(', ')}`);
}
if (value.inAffectedRange !== undefined) {
if (typeof value.inAffectedRange !== 'boolean') {
throw new Error(`${pathString([...path, 'inAffectedRange'])} must be a boolean.`);
}
}
if (value.kev === undefined) {
throw new Error(`${pathString([...path, 'kev'])} is required.`);
}
if (typeof value.kev !== 'boolean') {
throw new Error(`${pathString([...path, 'kev'])} must be a boolean.`);
}
if (value.epssScore !== undefined) {
if (typeof value.epssScore !== 'number') {
throw new Error(`${pathString([...path, 'epssScore'])} must be a number.`);
}
if (value.epssScore < 0) {
throw new Error(`${pathString([...path, 'epssScore'])} must be >= 0`);
}
if (value.epssScore > 1) {
throw new Error(`${pathString([...path, 'epssScore'])} must be <= 1`);
}
}
if (value.policyFlags !== undefined) {
if (!Array.isArray(value.policyFlags)) {
throw new Error(`${pathString([...path, 'policyFlags'])} must be an array.`);
}
for (let i = 0; i < value.policyFlags.length; i += 1) {
if (typeof value.policyFlags[i] !== 'string') {
throw new Error(`${pathString([...[...path, 'policyFlags'], String(i)])} must be a string.`);
}
}
}
}
function assertRuntimeContext(value: unknown, path: string[]): asserts value is RuntimeContext {
if (!isRecord(value)) {
throw new Error(`${pathString(path)} must be an object.`);
}
if (value.entrypoint !== undefined) {
if (!Array.isArray(value.entrypoint)) {
throw new Error(`${pathString([...path, 'entrypoint'])} must be an array.`);
}
for (let i = 0; i < value.entrypoint.length; i += 1) {
if (typeof value.entrypoint[i] !== 'string') {
throw new Error(`${pathString([...[...path, 'entrypoint'], String(i)])} must be a string.`);
}
}
}
if (value.env !== undefined) {
if (!isRecord(value.env)) {
throw new Error(`${pathString([...path, 'env'])} must be an object.`);
}
for (const key of Object.keys(value.env)) {
const entry = (value.env as Record<string, unknown>)[key];
const entryPath = [...[...path, 'env'], key];
if (typeof entry !== 'string') {
throw new Error(`${pathString(entryPath)} must be a string.`);
}
}
}
if (value.user !== undefined) {
assertUserContext(value.user, [...path, 'user']);
}
}
function assertSbomAttestation(value: unknown, path: string[]): asserts value is SbomAttestation {
if (!isRecord(value)) {
throw new Error(`${pathString(path)} must be an object.`);
@@ -760,6 +1276,115 @@ function assertScanResults(value: unknown, path: string[]): asserts value is Sca
}
}
function assertScannerInfo(value: unknown, path: string[]): asserts value is ScannerInfo {
if (!isRecord(value)) {
throw new Error(`${pathString(path)} must be an object.`);
}
if (value.name === undefined) {
throw new Error(`${pathString([...path, 'name'])} is required.`);
}
if (typeof value.name !== 'string') {
throw new Error(`${pathString([...path, 'name'])} must be a string.`);
}
if (value.version === undefined) {
throw new Error(`${pathString([...path, 'version'])} is required.`);
}
if (typeof value.version !== 'string') {
throw new Error(`${pathString([...path, 'version'])} must be a string.`);
}
if (value.ruleset !== undefined) {
if (typeof value.ruleset !== 'string') {
throw new Error(`${pathString([...path, 'ruleset'])} must be a string.`);
}
}
}
function assertSmartDiffPredicate(value: unknown, path: string[]): asserts value is SmartDiffPredicate {
if (!isRecord(value)) {
throw new Error(`${pathString(path)} must be an object.`);
}
if (value.schemaVersion === undefined) {
throw new Error(`${pathString([...path, 'schemaVersion'])} is required.`);
}
if (typeof value.schemaVersion !== 'string') {
throw new Error(`${pathString([...path, 'schemaVersion'])} must be a string.`);
}
if (value.schemaVersion !== '1.0.0') {
throw new Error(`${pathString([...path, 'schemaVersion'])} must equal '1.0.0'.`);
}
if (value.baseImage === undefined) {
throw new Error(`${pathString([...path, 'baseImage'])} is required.`);
}
assertImageReference(value.baseImage, [...path, 'baseImage']);
if (value.targetImage === undefined) {
throw new Error(`${pathString([...path, 'targetImage'])} is required.`);
}
assertImageReference(value.targetImage, [...path, 'targetImage']);
if (value.diff === undefined) {
throw new Error(`${pathString([...path, 'diff'])} is required.`);
}
assertDiffPayload(value.diff, [...path, 'diff']);
if (value.context !== undefined) {
assertRuntimeContext(value.context, [...path, 'context']);
}
if (value.reachabilityGate === undefined) {
throw new Error(`${pathString([...path, 'reachabilityGate'])} is required.`);
}
assertReachabilityGate(value.reachabilityGate, [...path, 'reachabilityGate']);
if (value.scanner === undefined) {
throw new Error(`${pathString([...path, 'scanner'])} is required.`);
}
assertScannerInfo(value.scanner, [...path, 'scanner']);
if (value.suppressedCount !== undefined) {
if (typeof value.suppressedCount !== 'number') {
throw new Error(`${pathString([...path, 'suppressedCount'])} must be a number.`);
}
if (value.suppressedCount < 0) {
throw new Error(`${pathString([...path, 'suppressedCount'])} must be >= 0`);
}
}
if (value.materialChanges !== undefined) {
if (!Array.isArray(value.materialChanges)) {
throw new Error(`${pathString([...path, 'materialChanges'])} must be an array.`);
}
for (let i = 0; i < value.materialChanges.length; i += 1) {
assertMaterialChange(value.materialChanges[i], [...[...path, 'materialChanges'], String(i)]);
}
}
}
function assertUserContext(value: unknown, path: string[]): asserts value is UserContext {
if (!isRecord(value)) {
throw new Error(`${pathString(path)} must be an object.`);
}
if (value.uid !== undefined) {
if (typeof value.uid !== 'number') {
throw new Error(`${pathString([...path, 'uid'])} must be a number.`);
}
if (value.uid < 0) {
throw new Error(`${pathString([...path, 'uid'])} must be >= 0`);
}
}
if (value.gid !== undefined) {
if (typeof value.gid !== 'number') {
throw new Error(`${pathString([...path, 'gid'])} must be a number.`);
}
if (value.gid < 0) {
throw new Error(`${pathString([...path, 'gid'])} must be >= 0`);
}
}
if (value.caps !== undefined) {
if (!Array.isArray(value.caps)) {
throw new Error(`${pathString([...path, 'caps'])} must be an array.`);
}
for (let i = 0; i < value.caps.length; i += 1) {
if (typeof value.caps[i] !== 'string') {
throw new Error(`${pathString([...[...path, 'caps'], String(i)])} must be a string.`);
}
}
}
}
function assertVexAttestation(value: unknown, path: string[]): asserts value is VexAttestation {
if (!isRecord(value)) {
throw new Error(`${pathString(path)} must be an object.`);
@@ -914,6 +1539,16 @@ export function canonicalizeScanResults(value: ScanResults): string {
return canonicalStringify(value);
}
export function validateSmartDiffPredicate(value: unknown): SmartDiffPredicate {
assertSmartDiffPredicate(value, []);
return value as SmartDiffPredicate;
}
export function canonicalizeSmartDiffPredicate(value: SmartDiffPredicate): string {
assertSmartDiffPredicate(value, []);
return canonicalStringify(value);
}
export function validateVexAttestation(value: unknown): VexAttestation {
assertVexAttestation(value, []);
return value as VexAttestation;

View File

@@ -0,0 +1,501 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://stella-ops.org/schemas/attestor/stellaops-smart-diff.v1.json",
"title": "Smart-Diff predicate describing differential analysis between two scans.",
"type": "object",
"additionalProperties": false,
"required": [
"schemaVersion",
"baseImage",
"targetImage",
"diff",
"reachabilityGate",
"scanner"
],
"properties": {
"schemaVersion": {
"type": "string",
"const": "1.0.0",
"description": "Schema version (semver)."
},
"baseImage": {
"$ref": "#/$defs/ImageReference",
"description": "Base scan image reference."
},
"targetImage": {
"$ref": "#/$defs/ImageReference",
"description": "Target scan image reference."
},
"diff": {
"$ref": "#/$defs/DiffPayload",
"description": "Diff payload between base and target."
},
"context": {
"$ref": "#/$defs/RuntimeContext",
"description": "Optional runtime context."
},
"reachabilityGate": {
"$ref": "#/$defs/ReachabilityGate",
"description": "Derived reachability gate."
},
"scanner": {
"$ref": "#/$defs/ScannerInfo",
"description": "Scanner identity."
},
"suppressedCount": {
"type": "integer",
"minimum": 0,
"description": "Number of findings suppressed by pre-filters."
},
"materialChanges": {
"type": "array",
"items": {
"$ref": "#/$defs/MaterialChange"
},
"minItems": 0,
"description": "Optional list of material changes."
}
},
"$defs": {
"ImageReference": {
"type": "object",
"additionalProperties": false,
"description": "Reference to a container image.",
"required": [
"digest"
],
"properties": {
"digest": {
"type": "string",
"pattern": "^sha256:[A-Fa-f0-9]{64}$",
"description": "Image digest."
},
"name": {
"type": "string",
"description": "Image name."
},
"tag": {
"type": "string",
"description": "Image tag."
}
}
},
"DiffHunk": {
"type": "object",
"additionalProperties": false,
"description": "Single diff hunk for a file change.",
"required": [
"startLine",
"lineCount"
],
"properties": {
"startLine": {
"type": "integer",
"minimum": 0,
"description": "Start line number."
},
"lineCount": {
"type": "integer",
"minimum": 0,
"description": "Number of lines in the hunk."
},
"content": {
"type": "string",
"description": "Optional hunk content."
}
}
},
"FileChange": {
"type": "object",
"additionalProperties": false,
"description": "File-level delta captured by Smart-Diff.",
"required": [
"path"
],
"properties": {
"path": {
"type": "string",
"description": "File path."
},
"hunks": {
"type": "array",
"items": {
"$ref": "#/$defs/DiffHunk"
},
"minItems": 0,
"description": "Optional hunks describing the file change."
},
"fromHash": {
"type": "string",
"description": "Previous file hash (when available)."
},
"toHash": {
"type": "string",
"description": "Current file hash (when available)."
}
}
},
"LicenseDelta": {
"type": "object",
"additionalProperties": false,
"description": "License delta for a package or file.",
"properties": {
"added": {
"type": "array",
"items": {
"type": "string"
},
"minItems": 0,
"description": "Licenses added."
},
"removed": {
"type": "array",
"items": {
"type": "string"
},
"minItems": 0,
"description": "Licenses removed."
}
}
},
"PackageChange": {
"type": "object",
"additionalProperties": false,
"description": "Package version change between the base and target scan.",
"required": [
"name",
"from",
"to"
],
"properties": {
"name": {
"type": "string",
"description": "Package name."
},
"from": {
"type": "string",
"description": "Previous package version."
},
"to": {
"type": "string",
"description": "Current package version."
},
"purl": {
"type": "string",
"description": "Package URL (purl)."
},
"licenseDelta": {
"$ref": "#/$defs/LicenseDelta",
"description": "License delta between versions."
}
}
},
"PackageRef": {
"type": "object",
"additionalProperties": false,
"description": "Package reference used in diffs.",
"required": [
"name",
"version"
],
"properties": {
"name": {
"type": "string",
"description": "Package name."
},
"version": {
"type": "string",
"description": "Package version."
},
"purl": {
"type": "string",
"description": "Package URL (purl)."
}
}
},
"DiffPayload": {
"type": "object",
"additionalProperties": false,
"description": "Diff payload describing file and package deltas.",
"properties": {
"filesAdded": {
"type": "array",
"items": {
"type": "string"
},
"minItems": 0,
"description": "Paths of files added."
},
"filesRemoved": {
"type": "array",
"items": {
"type": "string"
},
"minItems": 0,
"description": "Paths of files removed."
},
"filesChanged": {
"type": "array",
"items": {
"$ref": "#/$defs/FileChange"
},
"minItems": 0,
"description": "Collection of file changes."
},
"packagesChanged": {
"type": "array",
"items": {
"$ref": "#/$defs/PackageChange"
},
"minItems": 0,
"description": "Collection of package changes."
},
"packagesAdded": {
"type": "array",
"items": {
"$ref": "#/$defs/PackageRef"
},
"minItems": 0,
"description": "Packages added."
},
"packagesRemoved": {
"type": "array",
"items": {
"$ref": "#/$defs/PackageRef"
},
"minItems": 0,
"description": "Packages removed."
}
}
},
"UserContext": {
"type": "object",
"additionalProperties": false,
"description": "Runtime user context for the image.",
"properties": {
"uid": {
"type": "integer",
"minimum": 0,
"description": "User ID."
},
"gid": {
"type": "integer",
"minimum": 0,
"description": "Group ID."
},
"caps": {
"type": "array",
"items": {
"type": "string"
},
"minItems": 0,
"description": "Linux capabilities (string names)."
}
}
},
"RuntimeContext": {
"type": "object",
"additionalProperties": false,
"description": "Runtime context used for reachability gating and policy decisions.",
"properties": {
"entrypoint": {
"type": "array",
"items": {
"type": "string"
},
"minItems": 0,
"description": "Entrypoint command array."
},
"env": {
"type": "object",
"additionalProperties": {
"type": "string"
},
"description": "Environment variables map."
},
"user": {
"$ref": "#/$defs/UserContext",
"description": "Runtime user context."
}
}
},
"ReachabilityGate": {
"type": "object",
"additionalProperties": false,
"description": "3-bit reachability gate derived from the 7-state lattice.",
"required": [
"class"
],
"properties": {
"reachable": {
"type": "boolean",
"description": "True/false if reachability is known; absent indicates unknown."
},
"configActivated": {
"type": "boolean",
"description": "True if configuration activates the finding."
},
"runningUser": {
"type": "boolean",
"description": "True if running user enables the finding."
},
"class": {
"type": "integer",
"minimum": -1,
"maximum": 7,
"description": "Derived 3-bit class (0..7), or -1 if any bit is unknown."
},
"rationale": {
"type": "string",
"description": "Optional human-readable rationale for the gate."
}
}
},
"ScannerInfo": {
"type": "object",
"additionalProperties": false,
"description": "Scanner identity and ruleset information.",
"required": [
"name",
"version"
],
"properties": {
"name": {
"type": "string",
"description": "Scanner name."
},
"version": {
"type": "string",
"description": "Scanner version string."
},
"ruleset": {
"type": "string",
"description": "Optional ruleset identifier."
}
}
},
"FindingKey": {
"type": "object",
"additionalProperties": false,
"description": "Unique identifier for a vulnerability finding.",
"required": [
"componentPurl",
"componentVersion",
"cveId"
],
"properties": {
"componentPurl": {
"type": "string",
"description": "Component package URL (purl)."
},
"componentVersion": {
"type": "string",
"description": "Component version string."
},
"cveId": {
"type": "string",
"description": "Vulnerability identifier (e.g., CVE)."
}
}
},
"MaterialChangeType": {
"type": "string",
"description": "Material change types emitted by Smart-Diff.",
"enum": [
"reachability_flip",
"vex_flip",
"range_boundary",
"intelligence_flip"
]
},
"VexStatusType": {
"type": "string",
"description": "VEX status values captured in Smart-Diff risk state.",
"enum": [
"affected",
"not_affected",
"fixed",
"under_investigation",
"unknown"
]
},
"RiskState": {
"type": "object",
"additionalProperties": false,
"description": "Risk state captured for a finding at a point in time.",
"required": [
"vexStatus",
"kev"
],
"properties": {
"reachable": {
"type": "boolean",
"description": "Reachability flag (null/absent indicates unknown)."
},
"vexStatus": {
"$ref": "#/$defs/VexStatusType",
"description": "VEX status value."
},
"inAffectedRange": {
"type": "boolean",
"description": "True if the component version is within the affected range."
},
"kev": {
"type": "boolean",
"description": "True if the vulnerability is in the KEV catalog."
},
"epssScore": {
"type": "number",
"minimum": 0,
"maximum": 1,
"description": "EPSS score (0..1)."
},
"policyFlags": {
"type": "array",
"items": {
"type": "string"
},
"minItems": 0,
"description": "Policy flags contributing to the decision."
}
}
},
"MaterialChange": {
"type": "object",
"additionalProperties": false,
"description": "Single material change detected for a finding.",
"required": [
"findingKey",
"changeType",
"reason"
],
"properties": {
"findingKey": {
"$ref": "#/$defs/FindingKey",
"description": "Finding key for the change."
},
"changeType": {
"$ref": "#/$defs/MaterialChangeType",
"description": "Type of material change detected."
},
"reason": {
"type": "string",
"description": "Human-readable reason for the change."
},
"previousState": {
"$ref": "#/$defs/RiskState",
"description": "Previous risk state (when available)."
},
"currentState": {
"$ref": "#/$defs/RiskState",
"description": "Current risk state (when available)."
},
"priorityScore": {
"type": "integer",
"minimum": 0,
"description": "Priority score derived from change rules and intelligence."
}
}
}
}
}

View File

@@ -0,0 +1,100 @@
using System.Text.Json;
using FluentAssertions;
using Json.Schema;
using Xunit;
namespace StellaOps.Attestor.Types.Tests;
public sealed class SmartDiffSchemaValidationTests
{
[Fact]
public void SmartDiffSchema_ValidatesSamplePredicate()
{
var schemaPath = Path.Combine(AppContext.BaseDirectory, "schemas", "stellaops-smart-diff.v1.schema.json");
File.Exists(schemaPath).Should().BeTrue($"schema file should be copied to '{schemaPath}'");
var schema = JsonSchema.FromText(File.ReadAllText(schemaPath));
using var doc = JsonDocument.Parse("""
{
"schemaVersion": "1.0.0",
"baseImage": {
"name": "example/base",
"digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"tag": "1.0"
},
"targetImage": {
"name": "example/target",
"digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"tag": "2.0"
},
"diff": {
"filesAdded": ["./a.txt"],
"packagesChanged": [
{
"name": "openssl",
"purl": "pkg:deb/openssl@3.0.14",
"from": "1.1.1u",
"to": "3.0.14"
}
]
},
"context": {
"entrypoint": ["/app/start"],
"env": {
"FEATURE_X": "true"
},
"user": {
"uid": 1001,
"caps": ["NET_BIND_SERVICE"]
}
},
"reachabilityGate": {
"reachable": true,
"configActivated": true,
"runningUser": false,
"class": 6,
"rationale": "sample"
},
"scanner": {
"name": "StellaOps.Scanner",
"version": "2025.12.0",
"ruleset": "reachability-2025.12"
}
}
""");
var result = schema.Evaluate(doc.RootElement, new EvaluationOptions
{
OutputFormat = OutputFormat.List,
RequireFormatValidation = true
});
result.IsValid.Should().BeTrue();
}
[Fact]
public void SmartDiffSchema_RejectsInvalidReachabilityClass()
{
var schemaPath = Path.Combine(AppContext.BaseDirectory, "schemas", "stellaops-smart-diff.v1.schema.json");
var schema = JsonSchema.FromText(File.ReadAllText(schemaPath));
using var doc = JsonDocument.Parse("""
{
"schemaVersion": "1.0.0",
"baseImage": { "digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" },
"targetImage": { "digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" },
"diff": { },
"reachabilityGate": { "class": 99 },
"scanner": { "name": "StellaOps.Scanner", "version": "2025.12.0" }
}
""");
var result = schema.Evaluate(doc.RootElement, new EvaluationOptions
{
OutputFormat = OutputFormat.List,
RequireFormatValidation = true
});
result.IsValid.Should().BeFalse();
}
}