Files
master cc69d332e3
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Add unit tests for RabbitMq and Udp transport servers and clients
- Implemented comprehensive unit tests for RabbitMqTransportServer, covering constructor, disposal, connection management, event handlers, and exception handling.
- Added configuration tests for RabbitMqTransportServer to validate SSL, durable queues, auto-recovery, and custom virtual host options.
- Created unit tests for UdpFrameProtocol, including frame parsing and serialization, header size validation, and round-trip data preservation.
- Developed tests for UdpTransportClient, focusing on connection handling, event subscriptions, and exception scenarios.
- Established tests for UdpTransportServer, ensuring proper start/stop behavior, connection state management, and event handling.
- Included tests for UdpTransportOptions to verify default values and modification capabilities.
- Enhanced service registration tests for Udp transport services in the dependency injection container.
2025-12-05 19:01:12 +02:00

7.5 KiB
Raw Permalink Blame History

VEX Lens Contract

Contract ID: CONTRACT-VEX-LENS-005 Version: 1.0 Status: Published Last Updated: 2025-12-05

Overview

This contract defines the VEX Lens (VexLinkset) data model used to correlate multiple VEX observations for a specific vulnerability and product. The VEX Lens captures provider agreement, disagreements, and calculates consensus confidence.

Implementation Reference

Source: src/Excititor/__Libraries/StellaOps.Excititor.Core/Observations/VexLinkset.cs

Data Model

VexLinkset

The core VEX Lens structure correlating observations.

public sealed record VexLinkset
{
    /// <summary>
    /// Unique identifier: SHA256(tenant|vulnerabilityId|productKey)
    /// </summary>
    public string LinksetId { get; }

    /// <summary>
    /// Tenant identifier (normalized to lowercase).
    /// </summary>
    public string Tenant { get; }

    /// <summary>
    /// The vulnerability identifier (CVE, GHSA, vendor ID).
    /// </summary>
    public string VulnerabilityId { get; }

    /// <summary>
    /// Product key (typically a PURL or CPE).
    /// </summary>
    public string ProductKey { get; }

    /// <summary>
    /// Canonical scope metadata for the product key.
    /// </summary>
    public VexProductScope Scope { get; }

    /// <summary>
    /// References to observations that contribute to this linkset.
    /// </summary>
    public ImmutableArray<VexLinksetObservationRefModel> Observations { get; }

    /// <summary>
    /// Conflict annotations capturing disagreements between providers.
    /// </summary>
    public ImmutableArray<VexObservationDisagreement> Disagreements { get; }

    /// <summary>
    /// When this linkset was first created.
    /// </summary>
    public DateTimeOffset CreatedAt { get; }

    /// <summary>
    /// When this linkset was last updated.
    /// </summary>
    public DateTimeOffset UpdatedAt { get; }
}

JSON Representation

{
  "linkset_id": "sha256:abc123...",
  "tenant": "default",
  "vulnerability_id": "CVE-2024-1234",
  "product_key": "pkg:npm/lodash@4.17.20",
  "scope": {
    "ecosystem": "npm",
    "namespace": null,
    "name": "lodash",
    "version": "4.17.20"
  },
  "observations": [
    {
      "observation_id": "obs-001",
      "provider_id": "github",
      "status": "affected",
      "confidence": 0.9
    },
    {
      "observation_id": "obs-002",
      "provider_id": "redhat",
      "status": "not_affected",
      "confidence": 0.85
    }
  ],
  "disagreements": [
    {
      "provider_id": "github",
      "status": "affected",
      "justification": null,
      "confidence": 0.9
    },
    {
      "provider_id": "redhat",
      "status": "not_affected",
      "justification": "vulnerable_code_not_in_execute_path",
      "confidence": 0.85
    }
  ],
  "created_at": "2025-12-05T10:00:00Z",
  "updated_at": "2025-12-05T10:00:00Z"
}

VexLinksetObservationRefModel

Reference to an observation contributing to the linkset.

{
  "observation_id": "obs-001",
  "provider_id": "github",
  "status": "affected",
  "confidence": 0.9
}
Field Type Description
observation_id string Unique observation identifier
provider_id string VEX provider identifier
status string VEX status claim
confidence double? Optional confidence [0.0-1.0]

VexObservationDisagreement

Captures conflict between providers.

{
  "provider_id": "github",
  "status": "affected",
  "justification": null,
  "confidence": 0.9
}

VEX Status Values

Status Description
affected Product is affected by vulnerability
not_affected Product is not affected
fixed Vulnerability has been fixed
under_investigation Status is being determined

VEX Justification Codes

When status is not_affected, justification may include:

Code Description
component_not_present Vulnerable component not present
vulnerable_code_not_present Vulnerable code not present
vulnerable_code_not_in_execute_path Code present but not reachable
vulnerable_code_cannot_be_controlled_by_adversary Not exploitable
inline_mitigations_already_exist Mitigations in place

Confidence Levels

VexLinksetConfidence

Computed confidence based on linkset state.

Level Conditions
Low Conflicts exist, or < 1 observation, or multiple distinct statuses
Medium Single provider, or consistent observations
High 2+ providers agree on status

Confidence Calculation

public VexLinksetConfidence Confidence
{
    get
    {
        if (HasConflicts)
            return VexLinksetConfidence.Low;

        if (Observations.Length == 0)
            return VexLinksetConfidence.Low;

        if (Statuses.Count > 1)
            return VexLinksetConfidence.Low;

        if (ProviderIds.Count >= 2)
            return VexLinksetConfidence.High;

        return VexLinksetConfidence.Medium;
    }
}

Linkset ID Generation

Deterministic ID from key components:

public static string CreateLinksetId(string tenant, string vulnerabilityId, string productKey)
{
    var input = $"{tenant.ToLowerInvariant()}|{vulnerabilityId}|{productKey}";
    var hash = SHA256.HashData(Encoding.UTF8.GetBytes(input));
    return $"sha256:{Convert.ToHexString(hash).ToLowerInvariant()}";
}

API Endpoints

Resolve VEX for Finding

POST /excititor/resolve
Content-Type: application/json

{
  "tenant_id": "default",
  "queries": [
    {
      "vulnerability_id": "CVE-2024-1234",
      "product_key": "pkg:npm/lodash@4.17.20"
    }
  ]
}

Response: 200 OK
{
  "results": [
    {
      "linkset_id": "sha256:...",
      "vulnerability_id": "CVE-2024-1234",
      "product_key": "pkg:npm/lodash@4.17.20",
      "rollup_status": "affected",
      "confidence": "medium",
      "has_conflicts": false,
      "provider_count": 1
    }
  ]
}

Get Linkset Details

GET /excititor/linksets/{linkset_id}

Response: 200 OK
{
  "linkset_id": "sha256:...",
  "vulnerability_id": "CVE-2024-1234",
  "observations": [...],
  "disagreements": [...],
  "confidence": "low"
}

Consensus Algorithm

The consensus rollup algorithm:

  1. Filter: Remove invalid statements by signature policy
  2. Score: score = weight(provider) × freshnessFactor(lastObserved)
  3. Aggregate: W(status) = Σ score per status
  4. Pick: rollupStatus = argmax_status W(status)
  5. Tie-breakers:
    • Higher max single provider score
    • More recent lastObserved
    • Lexicographic order (fixed > not_affected > under_investigation > affected)

Provider Weights

Provider Type Default Weight
Vendor 1.0
Distribution 0.9
Platform 0.7
Attestation 0.6
Hub 0.5

Freshness Factor

freshnessFactor = clamp(0.8, 1.0 - (age_days / 30), 1.0)

Determinism Guarantees

  1. Stable ID: LinksetId is deterministic from (tenant, vulnId, productKey)
  2. Sorted observations: Observations sorted by observationId
  3. Sorted disagreements: Disagreements sorted by (providerId, status)
  4. Immutable records: Linksets are immutable; updates create new versions

Unblocks

This contract unblocks the following tasks:

  • CONCELIER-VEXLENS-30-001
  • EXCITITOR-VEXLENS-30-001