Files
git.stella-ops.org/docs/modules/excititor/vex_linksets_api.md
StellaOps Bot 3b96b2e3ea
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
up
2025-11-27 23:45:09 +02:00

8.1 KiB

Excititor VEX Observation & Linkset APIs

Implementation reference for Sprint 121 (EXCITITOR-LNM-21-201, EXCITITOR-LNM-21-202). Documents the REST endpoints implemented in src/Excititor/StellaOps.Excititor.WebService/Endpoints/ObservationEndpoints.cs and LinksetEndpoints.cs.

Authentication & Headers

All endpoints require:

  • Authorization: Bearer token with vex.read scope
  • X-Stella-Tenant: Tenant identifier (required)

/vex/observations

List observations with filters

GET /vex/observations?vulnerabilityId=CVE-2024-0001&productKey=pkg:maven/org.demo/app@1.2.3&limit=50
GET /vex/observations?providerId=ubuntu-csaf&limit=50

Query Parameters:

  • vulnerabilityId + productKey (required together) - Filter by vulnerability and product
  • providerId - Filter by provider
  • limit (optional, default: 50, max: 100) - Number of results
  • cursor (optional) - Pagination cursor from previous response

Response 200:

{
  "items": [
    {
      "observationId": "vex:obs:sha256:abc123...",
      "tenant": "default",
      "providerId": "ubuntu-csaf",
      "vulnerabilityId": "CVE-2024-0001",
      "productKey": "pkg:maven/org.demo/app@1.2.3",
      "status": "affected",
      "createdAt": "2025-11-18T12:34:56Z",
      "lastObserved": "2025-11-18T12:34:56Z",
      "purls": ["pkg:maven/org.demo/app@1.2.3"]
    }
  ],
  "nextCursor": "MjAyNS0xMS0xOFQxMjozNDo1NlonfHZleDpvYnM6c2hhMjU2OmFiYzEyMy4uLg=="
}

Error Responses:

  • 400 ERR_PARAMS - At least one filter is required
  • 400 ERR_TENANT - X-Stella-Tenant header is required
  • 403 - Missing required scope

Get observation by ID

GET /vex/observations/{observationId}

Response 200:

{
  "observationId": "vex:obs:sha256:abc123...",
  "tenant": "default",
  "providerId": "ubuntu-csaf",
  "streamId": "ubuntu-csaf-vex",
  "upstream": {
    "upstreamId": "USN-9999-1",
    "documentVersion": "2024.10.22",
    "fetchedAt": "2025-11-18T12:34:00Z",
    "receivedAt": "2025-11-18T12:34:05Z",
    "contentHash": "sha256:...",
    "signature": {
      "type": "cosign",
      "keyId": "ubuntu-vex-prod",
      "issuer": "https://token.actions.githubusercontent.com",
      "verifiedAt": "2025-11-18T12:34:10Z"
    }
  },
  "content": {
    "format": "csaf",
    "specVersion": "2.0"
  },
  "statements": [
    {
      "vulnerabilityId": "CVE-2024-0001",
      "productKey": "pkg:maven/org.demo/app@1.2.3",
      "status": "affected",
      "lastObserved": "2025-11-18T12:34:56Z",
      "locator": "#/statements/0",
      "justification": "component_not_present",
      "introducedVersion": null,
      "fixedVersion": "1.2.4"
    }
  ],
  "linkset": {
    "aliases": ["USN-9999-1"],
    "purls": ["pkg:maven/org.demo/app@1.2.3"],
    "cpes": [],
    "references": [{"type": "advisory", "url": "https://ubuntu.com/security/notices/USN-9999-1"}]
  },
  "createdAt": "2025-11-18T12:34:56Z"
}

Error Responses:

  • 404 ERR_NOT_FOUND - Observation not found

Count observations

GET /vex/observations/count

Response 200:

{
  "count": 12345
}

/vex/linksets

List linksets with filters

At least one filter is required: vulnerabilityId, productKey, providerId, or hasConflicts=true.

GET /vex/linksets?vulnerabilityId=CVE-2024-0001&limit=50
GET /vex/linksets?productKey=pkg:maven/org.demo/app@1.2.3&limit=50
GET /vex/linksets?providerId=ubuntu-csaf&limit=50
GET /vex/linksets?hasConflicts=true&limit=50

Query Parameters:

  • vulnerabilityId - Filter by vulnerability ID
  • productKey - Filter by product key
  • providerId - Filter by provider
  • hasConflicts - Filter to linksets with disagreements (true/false)
  • limit (optional, default: 50, max: 100) - Number of results
  • cursor (optional) - Pagination cursor

Response 200:

{
  "items": [
    {
      "linksetId": "sha256:tenant:CVE-2024-0001:pkg:maven/org.demo/app@1.2.3",
      "tenant": "default",
      "vulnerabilityId": "CVE-2024-0001",
      "productKey": "pkg:maven/org.demo/app@1.2.3",
      "providerIds": ["ubuntu-csaf", "suse-csaf"],
      "statuses": ["affected", "fixed"],
      "aliases": [],
      "purls": [],
      "cpes": [],
      "references": [],
      "disagreements": [
        {
          "providerId": "suse-csaf",
          "status": "fixed",
          "justification": null,
          "confidence": 0.85
        }
      ],
      "observations": [
        {"observationId": "vex:obs:...", "providerId": "ubuntu-csaf", "status": "affected", "confidence": 0.9},
        {"observationId": "vex:obs:...", "providerId": "suse-csaf", "status": "fixed", "confidence": 0.85}
      ],
      "createdAt": "2025-11-18T12:34:56Z"
    }
  ],
  "nextCursor": null
}

Error Responses:

  • 400 ERR_AGG_PARAMS - At least one filter is required

Get linkset by ID

GET /vex/linksets/{linksetId}

Response 200:

{
  "linksetId": "sha256:...",
  "tenant": "default",
  "vulnerabilityId": "CVE-2024-0001",
  "productKey": "pkg:maven/org.demo/app@1.2.3",
  "providerIds": ["ubuntu-csaf", "suse-csaf"],
  "statuses": ["affected", "fixed"],
  "confidence": "low",
  "hasConflicts": true,
  "disagreements": [
    {
      "providerId": "suse-csaf",
      "status": "fixed",
      "justification": null,
      "confidence": 0.85
    }
  ],
  "observations": [
    {"observationId": "vex:obs:...", "providerId": "ubuntu-csaf", "status": "affected", "confidence": 0.9},
    {"observationId": "vex:obs:...", "providerId": "suse-csaf", "status": "fixed", "confidence": 0.85}
  ],
  "createdAt": "2025-11-18T12:00:00Z",
  "updatedAt": "2025-11-18T12:34:56Z"
}

Error Responses:

  • 400 ERR_AGG_PARAMS - linksetId is required
  • 404 ERR_AGG_NOT_FOUND - Linkset not found

Lookup linkset by vulnerability and product

GET /vex/linksets/lookup?vulnerabilityId=CVE-2024-0001&productKey=pkg:maven/org.demo/app@1.2.3

Response 200: Same as Get linkset by ID

Error Responses:

  • 400 ERR_AGG_PARAMS - vulnerabilityId and productKey are required
  • 404 ERR_AGG_NOT_FOUND - No linkset found for the specified vulnerability and product

Count linksets

GET /vex/linksets/count

Response 200:

{
  "total": 5000,
  "withConflicts": 127
}

List linksets with conflicts (shorthand)

GET /vex/linksets/conflicts?limit=50

Response 200: Same format as List linksets

Error Codes

Code Description
ERR_PARAMS Missing or invalid query parameters (observations)
ERR_TENANT X-Stella-Tenant header is required
ERR_NOT_FOUND Observation not found
ERR_AGG_PARAMS Missing or invalid query parameters (linksets)
ERR_AGG_NOT_FOUND Linkset not found

Pagination

  • Uses cursor-based pagination with base64-encoded timestamp|id cursors
  • Default limit: 50, Maximum limit: 100
  • Cursors are opaque; treat as strings and pass back unchanged

Determinism

  • Results are sorted by timestamp (descending), then by ID
  • Array fields are sorted lexicographically
  • Status enums are lowercase strings

SDK Example (TypeScript)

const listObservations = async (
  baseUrl: string,
  token: string,
  tenant: string,
  vulnerabilityId: string,
  productKey: string
) => {
  const params = new URLSearchParams({
    vulnerabilityId,
    productKey,
    limit: "100"
  });

  const response = await fetch(`${baseUrl}/vex/observations?${params}`, {
    headers: {
      Authorization: `Bearer ${token}`,
      "X-Stella-Tenant": tenant
    }
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(`${error.error.code}: ${error.error.message}`);
  }

  return response.json();
};

const getLinksetWithConflicts = async (
  baseUrl: string,
  token: string,
  tenant: string
) => {
  const response = await fetch(`${baseUrl}/vex/linksets/conflicts?limit=50`, {
    headers: {
      Authorization: `Bearer ${token}`,
      "X-Stella-Tenant": tenant
    }
  });

  return response.json();
};
  • vex_observations.md - VEX Observation domain model and storage schema
  • evidence-contract.md - Evidence bundle format and attestation
  • AGENTS.md - Component development guidelines