8.1 KiB
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 insrc/Excititor/StellaOps.Excititor.WebService/Endpoints/ObservationEndpoints.csandLinksetEndpoints.cs.
Authentication & Headers
All endpoints require:
- Authorization: Bearer token with
vex.readscope - 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 productproviderId- Filter by providerlimit(optional, default: 50, max: 100) - Number of resultscursor(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 required400 ERR_TENANT- X-Stella-Tenant header is required403- 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 IDproductKey- Filter by product keyproviderId- Filter by providerhasConflicts- Filter to linksets with disagreements (true/false)limit(optional, default: 50, max: 100) - Number of resultscursor(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 required404 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 required404 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|idcursors - 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();
};
Related Documentation
vex_observations.md- VEX Observation domain model and storage schemaevidence-contract.md- Evidence bundle format and attestationAGENTS.md- Component development guidelines