Files
git.stella-ops.org/docs/api/vexlens-openapi.yaml
StellaOps Bot efd6850c38 Add unit tests for VexLens normalizer, CPE parser, product mapper, and PURL parser
- Implemented comprehensive tests for VexLensNormalizer including format detection and normalization scenarios.
- Added tests for CpeParser covering CPE 2.3 and 2.2 formats, invalid inputs, and canonical key generation.
- Created tests for ProductMapper to validate parsing and matching logic across different strictness levels.
- Developed tests for PurlParser to ensure correct parsing of various PURL formats and validation of identifiers.
- Introduced stubs for Monaco editor and worker to facilitate testing in the web application.
- Updated project file for the test project to include necessary dependencies.
2025-12-06 16:28:12 +02:00

1051 lines
30 KiB
YAML

# OpenAPI 3.1 specification for StellaOps VexLens WebService
openapi: 3.1.0
info:
title: StellaOps VexLens API
version: 0.1.0-draft
description: |
VexLens Consensus Engine API for computing VEX (Vulnerability Exploitability eXchange)
status consensus from multiple sources. Supports weighted voting, lattice-based consensus,
and authoritative-first resolution modes.
Uses the platform error envelope and tenant header `X-StellaOps-Tenant`.
servers:
- url: https://api.stellaops.example.com
description: Production
- url: https://api.dev.stellaops.example.com
description: Development
security:
- oauth2: [vexlens.viewer]
- oauth2: [vexlens.operator]
- oauth2: [vexlens.admin]
tags:
- name: Consensus
description: Compute and query VEX consensus
- name: Projections
description: Query stored consensus projections
- name: Issuers
description: Manage trusted VEX document issuers
- name: Statistics
description: Consensus statistics and analytics
paths:
/api/v1/vexlens/consensus:
post:
summary: Compute consensus for a vulnerability-product pair
description: |
Computes VEX status consensus from all available statements for a vulnerability-product pair.
Applies trust weighting, conflict detection, and returns a rationale for the decision.
tags: [Consensus]
operationId: computeConsensus
parameters:
- $ref: '#/components/parameters/Tenant'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ComputeConsensusRequest'
examples:
basic:
summary: Basic consensus request
value:
vulnerabilityId: CVE-2024-1234
productKey: pkg:npm/lodash@4.17.21
with-options:
summary: With consensus options
value:
vulnerabilityId: CVE-2024-1234
productKey: pkg:npm/lodash@4.17.21
mode: WeightedVote
minimumWeightThreshold: 0.2
storeResult: true
emitEvent: true
responses:
'200':
description: Consensus computed successfully
content:
application/json:
schema:
$ref: '#/components/schemas/ComputeConsensusResponse'
examples:
unanimous:
summary: Unanimous consensus
value:
vulnerabilityId: CVE-2024-1234
productKey: pkg:npm/lodash@4.17.21
status: not_affected
justification: vulnerable_code_not_present
confidenceScore: 0.95
outcome: Unanimous
rationale:
summary: "Unanimous consensus from 3 authoritative sources"
factors:
- "All statements agree on not_affected status"
- "Vendor statement with weight 0.98"
statusWeights:
not_affected: 2.85
contributions:
- statementId: stmt-vendor-001
issuerId: npm-security
status: not_affected
justification: vulnerable_code_not_present
weight: 0.98
contribution: 0.34
isWinner: true
conflicts: null
projectionId: proj-abc123
computedAt: "2025-12-06T12:00:00Z"
'400':
$ref: '#/components/responses/BadRequest'
'404':
$ref: '#/components/responses/NotFound'
default:
$ref: '#/components/responses/Error'
/api/v1/vexlens/consensus/batch:
post:
summary: Compute consensus for multiple pairs in batch
description: |
Computes VEX status consensus for multiple vulnerability-product pairs in a single request.
Useful for bulk processing during ingestion or policy evaluation.
tags: [Consensus]
operationId: computeConsensusBatch
parameters:
- $ref: '#/components/parameters/Tenant'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ComputeConsensusBatchRequest'
examples:
batch:
summary: Batch of 3 targets
value:
targets:
- vulnerabilityId: CVE-2024-1234
productKey: pkg:npm/lodash@4.17.21
- vulnerabilityId: CVE-2024-5678
productKey: pkg:npm/express@4.18.2
- vulnerabilityId: CVE-2024-9012
productKey: pkg:maven/org.apache.logging.log4j/log4j-core@2.17.0
mode: WeightedVote
storeResults: true
emitEvents: true
responses:
'200':
description: Batch consensus computed
content:
application/json:
schema:
$ref: '#/components/schemas/ComputeConsensusBatchResponse'
default:
$ref: '#/components/responses/Error'
/api/v1/vexlens/projections:
get:
summary: Query consensus projections
description: |
Lists stored consensus projections with filtering and pagination.
Projections are immutable snapshots of consensus computation results.
tags: [Projections]
operationId: queryProjections
parameters:
- $ref: '#/components/parameters/Tenant'
- name: vulnerabilityId
in: query
description: Filter by vulnerability ID (partial match)
schema:
type: string
- name: productKey
in: query
description: Filter by product key (partial match)
schema:
type: string
- name: status
in: query
description: Filter by consensus status
schema:
$ref: '#/components/schemas/VexStatus'
- name: outcome
in: query
description: Filter by consensus outcome
schema:
$ref: '#/components/schemas/ConsensusOutcome'
- name: minimumConfidence
in: query
description: Minimum confidence score
schema:
type: number
minimum: 0
maximum: 1
- name: computedAfter
in: query
description: Filter projections computed after this time
schema:
type: string
format: date-time
- name: computedBefore
in: query
description: Filter projections computed before this time
schema:
type: string
format: date-time
- name: statusChanged
in: query
description: Filter to only projections where status changed
schema:
type: boolean
- $ref: '#/components/parameters/Limit'
- $ref: '#/components/parameters/Offset'
- name: sortBy
in: query
description: Field to sort by
schema:
type: string
enum: [ComputedAt, StoredAt, VulnerabilityId, ProductKey, ConfidenceScore]
default: ComputedAt
- name: sortDescending
in: query
description: Sort in descending order
schema:
type: boolean
default: true
responses:
'200':
description: Paginated projection list
content:
application/json:
schema:
$ref: '#/components/schemas/QueryProjectionsResponse'
default:
$ref: '#/components/responses/Error'
/api/v1/vexlens/projections/{projectionId}:
get:
summary: Get a projection by ID
tags: [Projections]
operationId: getProjection
parameters:
- $ref: '#/components/parameters/Tenant'
- $ref: '#/components/parameters/ProjectionId'
responses:
'200':
description: Projection details
content:
application/json:
schema:
$ref: '#/components/schemas/ProjectionDetailResponse'
'404':
$ref: '#/components/responses/NotFound'
default:
$ref: '#/components/responses/Error'
/api/v1/vexlens/projections/latest:
get:
summary: Get the latest projection for a vulnerability-product pair
tags: [Projections]
operationId: getLatestProjection
parameters:
- $ref: '#/components/parameters/Tenant'
- name: vulnerabilityId
in: query
required: true
description: Vulnerability ID
schema:
type: string
- name: productKey
in: query
required: true
description: Product key (PURL or CPE)
schema:
type: string
responses:
'200':
description: Latest projection
content:
application/json:
schema:
$ref: '#/components/schemas/ProjectionDetailResponse'
'404':
$ref: '#/components/responses/NotFound'
default:
$ref: '#/components/responses/Error'
/api/v1/vexlens/projections/history:
get:
summary: Get projection history for a vulnerability-product pair
description: Returns the history of consensus projections in chronological order.
tags: [Projections]
operationId: getProjectionHistory
parameters:
- $ref: '#/components/parameters/Tenant'
- name: vulnerabilityId
in: query
required: true
schema:
type: string
- name: productKey
in: query
required: true
schema:
type: string
- name: limit
in: query
description: Maximum number of history entries
schema:
type: integer
minimum: 1
maximum: 100
default: 10
responses:
'200':
description: Projection history
content:
application/json:
schema:
$ref: '#/components/schemas/ProjectionHistoryResponse'
default:
$ref: '#/components/responses/Error'
/api/v1/vexlens/issuers:
get:
summary: List registered issuers
tags: [Issuers]
operationId: listIssuers
parameters:
- $ref: '#/components/parameters/Tenant'
- name: category
in: query
description: Filter by issuer category
schema:
$ref: '#/components/schemas/IssuerCategory'
- name: minimumTrustTier
in: query
description: Minimum trust tier
schema:
$ref: '#/components/schemas/TrustTier'
- name: status
in: query
description: Filter by issuer status
schema:
$ref: '#/components/schemas/IssuerStatus'
- name: search
in: query
description: Search term for name or ID
schema:
type: string
- $ref: '#/components/parameters/Limit'
- $ref: '#/components/parameters/Offset'
responses:
'200':
description: Issuer list
content:
application/json:
schema:
$ref: '#/components/schemas/IssuerListResponse'
default:
$ref: '#/components/responses/Error'
post:
summary: Register a new issuer
tags: [Issuers]
operationId: registerIssuer
parameters:
- $ref: '#/components/parameters/Tenant'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/RegisterIssuerRequest'
examples:
vendor:
summary: Register a vendor issuer
value:
issuerId: npm-security
name: npm Security Team
category: Vendor
trustTier: Authoritative
initialKeys:
- fingerprint: ABCD1234EFGH5678
keyType: Pgp
algorithm: EdDSA
metadata:
description: Official npm security advisories
uri: https://www.npmjs.com/advisories
email: security@npmjs.com
responses:
'201':
description: Issuer registered
content:
application/json:
schema:
$ref: '#/components/schemas/IssuerDetailResponse'
'409':
description: Issuer already exists
$ref: '#/components/responses/Error'
default:
$ref: '#/components/responses/Error'
/api/v1/vexlens/issuers/{issuerId}:
get:
summary: Get issuer details
tags: [Issuers]
operationId: getIssuer
parameters:
- $ref: '#/components/parameters/IssuerId'
responses:
'200':
description: Issuer details
content:
application/json:
schema:
$ref: '#/components/schemas/IssuerDetailResponse'
'404':
$ref: '#/components/responses/NotFound'
default:
$ref: '#/components/responses/Error'
delete:
summary: Revoke an issuer
tags: [Issuers]
operationId: revokeIssuer
parameters:
- $ref: '#/components/parameters/IssuerId'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/RevokeRequest'
responses:
'204':
description: Issuer revoked
'404':
$ref: '#/components/responses/NotFound'
default:
$ref: '#/components/responses/Error'
/api/v1/vexlens/issuers/{issuerId}/keys:
post:
summary: Add a key to an issuer
tags: [Issuers]
operationId: addIssuerKey
parameters:
- $ref: '#/components/parameters/IssuerId'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/RegisterKeyRequest'
responses:
'200':
description: Key added
content:
application/json:
schema:
$ref: '#/components/schemas/IssuerDetailResponse'
'404':
$ref: '#/components/responses/NotFound'
default:
$ref: '#/components/responses/Error'
/api/v1/vexlens/issuers/{issuerId}/keys/{fingerprint}:
delete:
summary: Revoke an issuer key
tags: [Issuers]
operationId: revokeIssuerKey
parameters:
- $ref: '#/components/parameters/IssuerId'
- name: fingerprint
in: path
required: true
description: Key fingerprint
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/RevokeRequest'
responses:
'204':
description: Key revoked
'404':
$ref: '#/components/responses/NotFound'
default:
$ref: '#/components/responses/Error'
/api/v1/vexlens/statistics:
get:
summary: Get consensus statistics
tags: [Statistics]
operationId: getStatistics
parameters:
- $ref: '#/components/parameters/Tenant'
responses:
'200':
description: Consensus statistics
content:
application/json:
schema:
$ref: '#/components/schemas/ConsensusStatisticsResponse'
default:
$ref: '#/components/responses/Error'
components:
parameters:
Tenant:
name: X-StellaOps-Tenant
in: header
description: Tenant identifier
schema:
type: string
ProjectionId:
name: projectionId
in: path
required: true
description: Projection ID
schema:
type: string
IssuerId:
name: issuerId
in: path
required: true
description: Issuer ID
schema:
type: string
Limit:
name: limit
in: query
description: Maximum number of items to return
schema:
type: integer
minimum: 1
maximum: 100
default: 50
Offset:
name: offset
in: query
description: Number of items to skip
schema:
type: integer
minimum: 0
default: 0
schemas:
VexStatus:
type: string
enum: [not_affected, affected, fixed, under_investigation]
description: VEX status per OpenVEX specification
VexJustification:
type: string
enum:
- component_not_present
- vulnerable_code_not_present
- vulnerable_code_not_in_execute_path
- vulnerable_code_cannot_be_controlled_by_adversary
- inline_mitigations_already_exist
description: Justification for not_affected status
ConsensusMode:
type: string
enum: [HighestWeight, WeightedVote, Lattice, AuthoritativeFirst]
description: |
- HighestWeight: Single highest-weighted statement wins
- WeightedVote: Weighted voting among all statements
- Lattice: Most conservative status wins (affected > under_investigation > not_affected > fixed)
- AuthoritativeFirst: Authoritative sources override others
ConsensusOutcome:
type: string
enum: [Unanimous, Majority, Plurality, ConflictResolved, NoData, Indeterminate]
description: Outcome of consensus computation
IssuerCategory:
type: string
enum: [Vendor, Distributor, Community, Internal, Aggregator]
description: Category of VEX document issuer
TrustTier:
type: string
enum: [Authoritative, Trusted, Untrusted, Unknown]
description: Trust level for an issuer
IssuerStatus:
type: string
enum: [Active, Suspended, Revoked]
description: Status of an issuer
ComputeConsensusRequest:
type: object
required: [vulnerabilityId, productKey]
properties:
vulnerabilityId:
type: string
description: CVE or other vulnerability identifier
productKey:
type: string
description: Product identifier (PURL or CPE)
mode:
$ref: '#/components/schemas/ConsensusMode'
minimumWeightThreshold:
type: number
minimum: 0
maximum: 1
description: Minimum trust weight threshold for statements
storeResult:
type: boolean
description: Store the result as a projection
emitEvent:
type: boolean
description: Emit an event for the consensus result
ComputeConsensusResponse:
type: object
required:
- vulnerabilityId
- productKey
- status
- confidenceScore
- outcome
- rationale
- contributions
- computedAt
properties:
vulnerabilityId:
type: string
productKey:
type: string
status:
$ref: '#/components/schemas/VexStatus'
justification:
$ref: '#/components/schemas/VexJustification'
confidenceScore:
type: number
minimum: 0
maximum: 1
outcome:
$ref: '#/components/schemas/ConsensusOutcome'
rationale:
$ref: '#/components/schemas/ConsensusRationale'
contributions:
type: array
items:
$ref: '#/components/schemas/Contribution'
conflicts:
type: array
items:
$ref: '#/components/schemas/Conflict'
projectionId:
type: string
description: ID of stored projection (if storeResult was true)
computedAt:
type: string
format: date-time
ConsensusRationale:
type: object
required: [summary, factors, statusWeights]
properties:
summary:
type: string
description: Human-readable summary of the decision
factors:
type: array
items:
type: string
description: List of factors that influenced the decision
statusWeights:
type: object
additionalProperties:
type: number
description: Total weight per status
Contribution:
type: object
required: [statementId, status, weight, contribution, isWinner]
properties:
statementId:
type: string
issuerId:
type: string
status:
$ref: '#/components/schemas/VexStatus'
justification:
$ref: '#/components/schemas/VexJustification'
weight:
type: number
contribution:
type: number
description: Proportional contribution to consensus
isWinner:
type: boolean
description: Whether this statement won the consensus
Conflict:
type: object
required: [statement1Id, statement2Id, status1, status2, severity, resolution]
properties:
statement1Id:
type: string
statement2Id:
type: string
status1:
$ref: '#/components/schemas/VexStatus'
status2:
$ref: '#/components/schemas/VexStatus'
severity:
type: string
enum: [Low, Medium, High, Critical]
resolution:
type: string
description: How the conflict was resolved
ComputeConsensusBatchRequest:
type: object
required: [targets]
properties:
targets:
type: array
items:
type: object
required: [vulnerabilityId, productKey]
properties:
vulnerabilityId:
type: string
productKey:
type: string
minItems: 1
maxItems: 100
mode:
$ref: '#/components/schemas/ConsensusMode'
storeResults:
type: boolean
emitEvents:
type: boolean
ComputeConsensusBatchResponse:
type: object
required: [results, totalCount, successCount, failureCount, completedAt]
properties:
results:
type: array
items:
$ref: '#/components/schemas/ComputeConsensusResponse'
totalCount:
type: integer
successCount:
type: integer
failureCount:
type: integer
completedAt:
type: string
format: date-time
QueryProjectionsResponse:
type: object
required: [projections, totalCount, offset, limit]
properties:
projections:
type: array
items:
$ref: '#/components/schemas/ProjectionSummary'
totalCount:
type: integer
offset:
type: integer
limit:
type: integer
ProjectionSummary:
type: object
required:
- projectionId
- vulnerabilityId
- productKey
- status
- confidenceScore
- outcome
- statementCount
- conflictCount
- computedAt
- statusChanged
properties:
projectionId:
type: string
vulnerabilityId:
type: string
productKey:
type: string
status:
$ref: '#/components/schemas/VexStatus'
justification:
$ref: '#/components/schemas/VexJustification'
confidenceScore:
type: number
outcome:
type: string
statementCount:
type: integer
conflictCount:
type: integer
computedAt:
type: string
format: date-time
statusChanged:
type: boolean
ProjectionDetailResponse:
allOf:
- $ref: '#/components/schemas/ProjectionSummary'
- type: object
properties:
tenantId:
type: string
rationaleSummary:
type: string
storedAt:
type: string
format: date-time
previousProjectionId:
type: string
ProjectionHistoryResponse:
type: object
required: [vulnerabilityId, productKey, history, totalCount]
properties:
vulnerabilityId:
type: string
productKey:
type: string
history:
type: array
items:
$ref: '#/components/schemas/ProjectionSummary'
totalCount:
type: integer
IssuerListResponse:
type: object
required: [issuers, totalCount]
properties:
issuers:
type: array
items:
$ref: '#/components/schemas/IssuerSummary'
totalCount:
type: integer
IssuerSummary:
type: object
required: [issuerId, name, category, trustTier, status, keyCount, registeredAt]
properties:
issuerId:
type: string
name:
type: string
category:
$ref: '#/components/schemas/IssuerCategory'
trustTier:
$ref: '#/components/schemas/TrustTier'
status:
$ref: '#/components/schemas/IssuerStatus'
keyCount:
type: integer
registeredAt:
type: string
format: date-time
IssuerDetailResponse:
allOf:
- $ref: '#/components/schemas/IssuerSummary'
- type: object
properties:
keyFingerprints:
type: array
items:
$ref: '#/components/schemas/KeyFingerprint'
metadata:
$ref: '#/components/schemas/IssuerMetadata'
lastUpdatedAt:
type: string
format: date-time
revokedAt:
type: string
format: date-time
revocationReason:
type: string
KeyFingerprint:
type: object
required: [fingerprint, keyType, status, registeredAt]
properties:
fingerprint:
type: string
keyType:
type: string
enum: [Pgp, X509, Jwk, Ssh, Sigstore]
algorithm:
type: string
status:
type: string
enum: [Active, Expired, Revoked]
registeredAt:
type: string
format: date-time
expiresAt:
type: string
format: date-time
IssuerMetadata:
type: object
properties:
description:
type: string
uri:
type: string
format: uri
email:
type: string
format: email
tags:
type: array
items:
type: string
RegisterIssuerRequest:
type: object
required: [issuerId, name, category, trustTier]
properties:
issuerId:
type: string
name:
type: string
category:
$ref: '#/components/schemas/IssuerCategory'
trustTier:
$ref: '#/components/schemas/TrustTier'
initialKeys:
type: array
items:
$ref: '#/components/schemas/RegisterKeyRequest'
metadata:
$ref: '#/components/schemas/IssuerMetadata'
RegisterKeyRequest:
type: object
required: [fingerprint, keyType]
properties:
fingerprint:
type: string
keyType:
type: string
enum: [Pgp, X509, Jwk, Ssh, Sigstore]
algorithm:
type: string
expiresAt:
type: string
format: date-time
RevokeRequest:
type: object
required: [reason]
properties:
reason:
type: string
minLength: 1
maxLength: 500
ConsensusStatisticsResponse:
type: object
required:
- totalProjections
- byStatus
- byOutcome
- averageConfidence
- projectionsWithConflicts
- statusChangesLast24h
- computedAt
properties:
totalProjections:
type: integer
byStatus:
type: object
additionalProperties:
type: integer
byOutcome:
type: object
additionalProperties:
type: integer
averageConfidence:
type: number
projectionsWithConflicts:
type: integer
statusChangesLast24h:
type: integer
computedAt:
type: string
format: date-time
Error:
type: object
required: [code, message]
properties:
code:
type: string
message:
type: string
details:
type: object
traceId:
type: string
responses:
Error:
description: Error response
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
BadRequest:
description: Invalid request
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
examples:
validation:
value:
code: VALIDATION_ERROR
message: Invalid request parameters
details:
field: vulnerabilityId
error: Required field missing
NotFound:
description: Resource not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
examples:
notFound:
value:
code: NOT_FOUND
message: Requested resource not found
securitySchemes:
oauth2:
type: oauth2
flows:
authorizationCode:
authorizationUrl: https://auth.stellaops.example.com/oauth/authorize
tokenUrl: https://auth.stellaops.example.com/oauth/token
scopes:
vexlens.viewer: Read access to consensus projections
vexlens.operator: Compute consensus and manage projections
vexlens.admin: Full access including issuer management