1499 lines
44 KiB
YAML
1499 lines
44 KiB
YAML
openapi: 3.0.3
|
|
info:
|
|
title: StellaOps Findings Ledger API
|
|
version: 1.0.0-beta1
|
|
description: >-
|
|
Canonical, aggregation-only surface for append-only findings events, projections, and
|
|
Merkle anchoring metadata. Aligns with schema in docs/modules/findings-ledger/schema.md.
|
|
servers:
|
|
- url: https://{env}.ledger.api.stellaops.local
|
|
description: Default environment-scoped host
|
|
variables:
|
|
env:
|
|
default: prod
|
|
enum: [dev, staging, prod, airgap]
|
|
- url: https://ledger.{region}.offline.bundle
|
|
description: Offline bundle host for air-gapped deployments
|
|
variables:
|
|
region:
|
|
default: local
|
|
enum: [local]
|
|
security:
|
|
- bearerAuth: []
|
|
- mTLS: []
|
|
paths:
|
|
/v1/ledger/events:
|
|
get:
|
|
summary: List ledger events
|
|
operationId: listLedgerEvents
|
|
tags: [ledger]
|
|
parameters:
|
|
- $ref: '#/components/parameters/TenantId'
|
|
- name: chainId
|
|
in: query
|
|
required: false
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
- name: sinceSequence
|
|
in: query
|
|
schema:
|
|
type: integer
|
|
minimum: 0
|
|
- name: limit
|
|
in: query
|
|
schema:
|
|
type: integer
|
|
default: 200
|
|
maximum: 1000
|
|
responses:
|
|
'200':
|
|
description: Paged ledger events in deterministic order (chainId, sequence_No asc)
|
|
headers:
|
|
Req-Cursor:
|
|
schema:
|
|
type: string
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/LedgerEventPage'
|
|
post:
|
|
summary: Append deterministic ledger event
|
|
operationId: appendLedgerEvent
|
|
tags: [ledger]
|
|
parameters:
|
|
- $ref: '#/components/parameters/TenantId'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/LedgerEventAppendRequest'
|
|
responses:
|
|
'201':
|
|
description: Event persisted
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/LedgerEvent'
|
|
'409':
|
|
description: Hash/sequence conflict (non-deterministic input)
|
|
/v1/ledger/projections/findings:
|
|
get:
|
|
summary: Get latest projection for findings
|
|
operationId: listFindingProjections
|
|
tags: [projections]
|
|
parameters:
|
|
- $ref: '#/components/parameters/TenantId'
|
|
- name: findingId
|
|
in: query
|
|
schema:
|
|
type: string
|
|
- name: policyVersion
|
|
in: query
|
|
schema:
|
|
type: string
|
|
- name: status
|
|
in: query
|
|
schema:
|
|
type: string
|
|
- name: limit
|
|
in: query
|
|
schema:
|
|
type: integer
|
|
default: 200
|
|
maximum: 1000
|
|
responses:
|
|
'200':
|
|
description: Projection rows with cycleHash for replay validation
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/FindingProjectionPage'
|
|
/v1/ledger/export/findings:
|
|
get:
|
|
summary: Export findings in deterministic order
|
|
operationId: exportFindings
|
|
tags: [export]
|
|
parameters:
|
|
- $ref: '#/components/parameters/TenantId'
|
|
- $ref: '#/components/parameters/Shape'
|
|
- $ref: '#/components/parameters/SinceSequence'
|
|
- $ref: '#/components/parameters/UntilSequence'
|
|
- $ref: '#/components/parameters/SinceObservedAt'
|
|
- $ref: '#/components/parameters/UntilObservedAt'
|
|
- name: finding_status
|
|
in: query
|
|
schema: { type: string }
|
|
- name: severity
|
|
in: query
|
|
schema: { type: number }
|
|
- name: risk_profile_version
|
|
in: query
|
|
schema: { type: string }
|
|
- $ref: '#/components/parameters/PageSize'
|
|
- $ref: '#/components/parameters/PageToken'
|
|
responses:
|
|
'200':
|
|
description: Paged findings export
|
|
headers:
|
|
X-Stella-Next-Page-Token:
|
|
schema: { type: string }
|
|
X-Stella-Result-Count:
|
|
schema: { type: integer }
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/FindingExportPage'
|
|
application/x-ndjson:
|
|
schema:
|
|
type: string
|
|
description: NDJSON stream of FindingExportItem
|
|
/v1/ledger/export/vex:
|
|
get:
|
|
summary: Export VEX statements
|
|
operationId: exportVex
|
|
tags: [export]
|
|
parameters:
|
|
- $ref: '#/components/parameters/TenantId'
|
|
- $ref: '#/components/parameters/Shape'
|
|
- $ref: '#/components/parameters/SinceSequence'
|
|
- $ref: '#/components/parameters/UntilSequence'
|
|
- $ref: '#/components/parameters/SinceObservedAt'
|
|
- $ref: '#/components/parameters/UntilObservedAt'
|
|
- name: product_id
|
|
in: query
|
|
schema: { type: string }
|
|
- name: advisory_id
|
|
in: query
|
|
schema: { type: string }
|
|
- name: status
|
|
in: query
|
|
schema: { type: string }
|
|
- name: statement_type
|
|
in: query
|
|
schema: { type: string }
|
|
- $ref: '#/components/parameters/PageSize'
|
|
- $ref: '#/components/parameters/PageToken'
|
|
responses:
|
|
'200':
|
|
description: Paged VEX export
|
|
headers:
|
|
X-Stella-Next-Page-Token:
|
|
schema: { type: string }
|
|
X-Stella-Result-Count:
|
|
schema: { type: integer }
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/VexExportPage'
|
|
application/x-ndjson:
|
|
schema:
|
|
type: string
|
|
description: NDJSON stream of VexExportItem
|
|
/v1/ledger/export/advisories:
|
|
get:
|
|
summary: Export advisories
|
|
operationId: exportAdvisories
|
|
tags: [export]
|
|
parameters:
|
|
- $ref: '#/components/parameters/TenantId'
|
|
- $ref: '#/components/parameters/Shape'
|
|
- $ref: '#/components/parameters/SinceSequence'
|
|
- $ref: '#/components/parameters/UntilSequence'
|
|
- $ref: '#/components/parameters/SinceObservedAt'
|
|
- $ref: '#/components/parameters/UntilObservedAt'
|
|
- name: severity
|
|
in: query
|
|
schema: { type: string }
|
|
- name: source
|
|
in: query
|
|
schema: { type: string }
|
|
- name: cwe_id
|
|
in: query
|
|
schema: { type: string }
|
|
- name: kev
|
|
in: query
|
|
schema: { type: boolean }
|
|
- name: cvss_version
|
|
in: query
|
|
schema: { type: string }
|
|
- name: cvss_score_min
|
|
in: query
|
|
schema: { type: number }
|
|
- name: cvss_score_max
|
|
in: query
|
|
schema: { type: number }
|
|
- $ref: '#/components/parameters/PageSize'
|
|
- $ref: '#/components/parameters/PageToken'
|
|
responses:
|
|
'200':
|
|
description: Paged advisory export
|
|
headers:
|
|
X-Stella-Next-Page-Token:
|
|
schema: { type: string }
|
|
X-Stella-Result-Count:
|
|
schema: { type: integer }
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/AdvisoryExportPage'
|
|
application/x-ndjson:
|
|
schema:
|
|
type: string
|
|
description: NDJSON stream of AdvisoryExportItem
|
|
/v1/ledger/export/sboms:
|
|
get:
|
|
summary: Export SBOMs
|
|
operationId: exportSboms
|
|
tags: [export]
|
|
parameters:
|
|
- $ref: '#/components/parameters/TenantId'
|
|
- $ref: '#/components/parameters/Shape'
|
|
- $ref: '#/components/parameters/SinceSequence'
|
|
- $ref: '#/components/parameters/UntilSequence'
|
|
- $ref: '#/components/parameters/SinceObservedAt'
|
|
- $ref: '#/components/parameters/UntilObservedAt'
|
|
- name: subject_digest
|
|
in: query
|
|
schema: { type: string }
|
|
- name: sbom_format
|
|
in: query
|
|
schema: { type: string }
|
|
- name: component_purl
|
|
in: query
|
|
schema: { type: string }
|
|
- name: contains_native
|
|
in: query
|
|
schema: { type: boolean }
|
|
- name: slsa_build_type
|
|
in: query
|
|
schema: { type: string }
|
|
- $ref: '#/components/parameters/PageSize'
|
|
- $ref: '#/components/parameters/PageToken'
|
|
responses:
|
|
'200':
|
|
description: Paged SBOM export
|
|
headers:
|
|
X-Stella-Next-Page-Token:
|
|
schema: { type: string }
|
|
X-Stella-Result-Count:
|
|
schema: { type: integer }
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/SbomExportPage'
|
|
application/x-ndjson:
|
|
schema:
|
|
type: string
|
|
description: NDJSON stream of SbomExportItem
|
|
/v1/ledger/attestations:
|
|
get:
|
|
summary: List attestation verifications
|
|
operationId: listLedgerAttestations
|
|
tags: [attestation]
|
|
parameters:
|
|
- $ref: '#/components/parameters/TenantId'
|
|
- name: artifactId
|
|
in: query
|
|
schema: { type: string }
|
|
- name: findingId
|
|
in: query
|
|
schema: { type: string }
|
|
- name: attestationId
|
|
in: query
|
|
schema: { type: string }
|
|
- name: status
|
|
in: query
|
|
schema:
|
|
type: string
|
|
enum: [verified, failed, unknown]
|
|
- name: sinceRecordedAt
|
|
in: query
|
|
schema: { type: string, format: date-time }
|
|
- name: untilRecordedAt
|
|
in: query
|
|
schema: { type: string, format: date-time }
|
|
- $ref: '#/components/parameters/AttestationLimit'
|
|
- $ref: '#/components/parameters/PageToken'
|
|
responses:
|
|
'200':
|
|
description: Paged attestation verifications
|
|
headers:
|
|
X-Stella-Next-Page-Token:
|
|
schema: { type: string }
|
|
X-Stella-Result-Count:
|
|
schema: { type: integer }
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/AttestationExportPage'
|
|
application/x-ndjson:
|
|
schema:
|
|
type: string
|
|
description: NDJSON stream of AttestationExportItem
|
|
/.well-known/openapi:
|
|
get:
|
|
summary: Serve Findings Ledger OpenAPI document
|
|
operationId: getOpenApi
|
|
tags: [metadata]
|
|
responses:
|
|
'200':
|
|
description: OpenAPI YAML document
|
|
content:
|
|
application/yaml:
|
|
schema:
|
|
type: string
|
|
|
|
# Evidence-Weighted Score (EWS) Endpoints - Sprint 8200.0012.0004
|
|
/api/v1/findings/{findingId}/score:
|
|
post:
|
|
summary: Calculate evidence-weighted score for a finding
|
|
description: >-
|
|
Calculates the Evidence-Weighted Score (EWS) for a finding by aggregating
|
|
reachability, runtime signals, exploit likelihood, source trust, and mitigation
|
|
effectiveness. Returns a 0-100 score and action bucket (ActNow, ScheduleNext,
|
|
Investigate, Watchlist).
|
|
operationId: calculateFindingScore
|
|
tags: [scoring]
|
|
security:
|
|
- bearerAuth: [write:scores]
|
|
parameters:
|
|
- name: findingId
|
|
in: path
|
|
required: true
|
|
description: Finding identifier in format CVE-ID@pkg:PURL
|
|
schema:
|
|
type: string
|
|
pattern: "^[A-Z]+-\\d+@pkg:.+$"
|
|
example: "CVE-2024-1234@pkg:deb/debian/curl@7.64.0-4"
|
|
requestBody:
|
|
required: false
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/CalculateScoreRequest'
|
|
example:
|
|
forceRecalculate: false
|
|
includeBreakdown: true
|
|
policyVersion: null
|
|
responses:
|
|
'200':
|
|
description: Score calculated successfully
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/EvidenceWeightedScoreResponse'
|
|
example:
|
|
findingId: "CVE-2024-1234@pkg:deb/debian/curl@7.64.0-4"
|
|
score: 78
|
|
bucket: "ScheduleNext"
|
|
inputs:
|
|
rch: 0.85
|
|
rts: 0.40
|
|
bkp: 0.00
|
|
xpl: 0.70
|
|
src: 0.80
|
|
mit: 0.10
|
|
weights:
|
|
rch: 0.30
|
|
rts: 0.25
|
|
bkp: 0.15
|
|
xpl: 0.15
|
|
src: 0.10
|
|
mit: 0.10
|
|
flags: ["live-signal", "proven-path"]
|
|
explanations:
|
|
- "Static reachability: path to vulnerable sink (confidence: 85%)"
|
|
- "Runtime: 3 observations in last 24 hours"
|
|
policyDigest: "sha256:abc123..."
|
|
calculatedAt: "2026-01-15T14:30:00Z"
|
|
cachedUntil: "2026-01-15T15:30:00Z"
|
|
'400':
|
|
description: Invalid request
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ScoringErrorResponse'
|
|
'404':
|
|
description: Finding not found or no evidence available
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ScoringErrorResponse'
|
|
'429':
|
|
description: Rate limit exceeded (100/min)
|
|
get:
|
|
summary: Get cached evidence-weighted score for a finding
|
|
description: Returns the most recently calculated score from cache. Returns 404 if no score has been calculated.
|
|
operationId: getFindingScore
|
|
tags: [scoring]
|
|
security:
|
|
- bearerAuth: [read:scores]
|
|
parameters:
|
|
- name: findingId
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
responses:
|
|
'200':
|
|
description: Cached score retrieved
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/EvidenceWeightedScoreResponse'
|
|
'404':
|
|
description: No cached score found
|
|
|
|
/api/v1/findings/scores:
|
|
post:
|
|
summary: Calculate evidence-weighted scores for multiple findings
|
|
description: Batch calculation of scores for up to 100 findings. Returns summary statistics and individual results.
|
|
operationId: calculateFindingScoresBatch
|
|
tags: [scoring]
|
|
security:
|
|
- bearerAuth: [write:scores]
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/CalculateScoresBatchRequest'
|
|
example:
|
|
findingIds:
|
|
- "CVE-2024-1234@pkg:deb/debian/curl@7.64.0-4"
|
|
- "CVE-2024-5678@pkg:npm/lodash@4.17.20"
|
|
forceRecalculate: false
|
|
includeBreakdown: true
|
|
responses:
|
|
'200':
|
|
description: Batch scores calculated
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/CalculateScoresBatchResponse'
|
|
'400':
|
|
description: Invalid request or batch too large (max 100)
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ScoringErrorResponse'
|
|
'429':
|
|
description: Rate limit exceeded (10/min)
|
|
|
|
/api/v1/findings/{findingId}/score-history:
|
|
get:
|
|
summary: Get score history for a finding
|
|
description: Returns historical score calculations with pagination. Tracks score changes, triggers, and which factors changed.
|
|
operationId: getFindingScoreHistory
|
|
tags: [scoring]
|
|
security:
|
|
- bearerAuth: [read:scores]
|
|
parameters:
|
|
- name: findingId
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
- name: from
|
|
in: query
|
|
description: Start of date range (inclusive)
|
|
schema:
|
|
type: string
|
|
format: date-time
|
|
- name: to
|
|
in: query
|
|
description: End of date range (inclusive)
|
|
schema:
|
|
type: string
|
|
format: date-time
|
|
- name: limit
|
|
in: query
|
|
description: Maximum entries to return (1-100, default 50)
|
|
schema:
|
|
type: integer
|
|
default: 50
|
|
minimum: 1
|
|
maximum: 100
|
|
- name: cursor
|
|
in: query
|
|
description: Pagination cursor from previous response
|
|
schema:
|
|
type: string
|
|
responses:
|
|
'200':
|
|
description: Score history retrieved
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ScoreHistoryResponse'
|
|
'404':
|
|
description: Finding not found
|
|
|
|
/api/v1/scoring/policy:
|
|
get:
|
|
summary: Get active scoring policy configuration
|
|
description: Returns the currently active evidence weight policy including weights, guardrails, and bucket thresholds.
|
|
operationId: getActiveScoringPolicy
|
|
tags: [scoring]
|
|
security:
|
|
- bearerAuth: [read:scores]
|
|
responses:
|
|
'200':
|
|
description: Active policy retrieved
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ScoringPolicyResponse'
|
|
example:
|
|
version: "ews.v1.2"
|
|
digest: "sha256:abc123..."
|
|
activeSince: "2026-01-01T00:00:00Z"
|
|
environment: "production"
|
|
weights:
|
|
rch: 0.30
|
|
rts: 0.25
|
|
bkp: 0.15
|
|
xpl: 0.15
|
|
src: 0.10
|
|
mit: 0.10
|
|
guardrails:
|
|
notAffectedCap: { enabled: true, maxScore: 15 }
|
|
runtimeFloor: { enabled: true, minScore: 60 }
|
|
speculativeCap: { enabled: true, maxScore: 45 }
|
|
buckets:
|
|
actNowMin: 90
|
|
scheduleNextMin: 70
|
|
investigateMin: 40
|
|
|
|
/api/v1/scoring/policy/{version}:
|
|
get:
|
|
summary: Get specific scoring policy version
|
|
description: Returns a specific version of the scoring policy for historical comparison or audit.
|
|
operationId: getScoringPolicyVersion
|
|
tags: [scoring]
|
|
security:
|
|
- bearerAuth: [read:scores]
|
|
parameters:
|
|
- name: version
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
example: "ews.v1.2"
|
|
responses:
|
|
'200':
|
|
description: Policy version retrieved
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ScoringPolicyResponse'
|
|
'404':
|
|
description: Policy version not found
|
|
|
|
/api/v1/scoring/webhooks:
|
|
post:
|
|
summary: Register a webhook for score change notifications
|
|
description: >-
|
|
Registers a webhook to receive notifications when finding scores change.
|
|
Supports filtering by finding patterns, minimum score change threshold,
|
|
and bucket changes. Webhook payloads are signed with HMAC-SHA256 if a
|
|
secret is provided.
|
|
operationId: registerScoringWebhook
|
|
tags: [scoring, webhooks]
|
|
security:
|
|
- bearerAuth: [admin:scoring]
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/RegisterWebhookRequest'
|
|
example:
|
|
url: "https://example.com/webhook/scores"
|
|
secret: "webhook-secret-key"
|
|
findingPatterns: ["CVE-*"]
|
|
minScoreChange: 10
|
|
triggerOnBucketChange: true
|
|
responses:
|
|
'201':
|
|
description: Webhook registered
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/WebhookResponse'
|
|
'400':
|
|
description: Invalid webhook URL or configuration
|
|
'429':
|
|
description: Rate limit exceeded (10/min)
|
|
get:
|
|
summary: List all registered webhooks
|
|
operationId: listScoringWebhooks
|
|
tags: [scoring, webhooks]
|
|
security:
|
|
- bearerAuth: [admin:scoring]
|
|
responses:
|
|
'200':
|
|
description: List of webhooks
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/WebhookListResponse'
|
|
|
|
/api/v1/scoring/webhooks/{id}:
|
|
get:
|
|
summary: Get a specific webhook by ID
|
|
operationId: getScoringWebhook
|
|
tags: [scoring, webhooks]
|
|
security:
|
|
- bearerAuth: [admin:scoring]
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
responses:
|
|
'200':
|
|
description: Webhook details
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/WebhookResponse'
|
|
'404':
|
|
description: Webhook not found
|
|
put:
|
|
summary: Update a webhook configuration
|
|
operationId: updateScoringWebhook
|
|
tags: [scoring, webhooks]
|
|
security:
|
|
- bearerAuth: [admin:scoring]
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/RegisterWebhookRequest'
|
|
responses:
|
|
'200':
|
|
description: Webhook updated
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/WebhookResponse'
|
|
'404':
|
|
description: Webhook not found
|
|
'400':
|
|
description: Invalid configuration
|
|
delete:
|
|
summary: Delete a webhook
|
|
operationId: deleteScoringWebhook
|
|
tags: [scoring, webhooks]
|
|
security:
|
|
- bearerAuth: [admin:scoring]
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
responses:
|
|
'204':
|
|
description: Webhook deleted
|
|
'404':
|
|
description: Webhook not found
|
|
|
|
components:
|
|
securitySchemes:
|
|
bearerAuth:
|
|
type: http
|
|
scheme: bearer
|
|
bearerFormat: JWT
|
|
mTLS:
|
|
type: mutualTLS
|
|
parameters:
|
|
TenantId:
|
|
name: X-Stella-Tenant
|
|
in: header
|
|
required: true
|
|
schema:
|
|
type: string
|
|
Shape:
|
|
name: shape
|
|
in: query
|
|
required: true
|
|
schema:
|
|
type: string
|
|
enum: [canonical, compact]
|
|
SinceSequence:
|
|
name: since_sequence
|
|
in: query
|
|
schema:
|
|
type: integer
|
|
minimum: 0
|
|
UntilSequence:
|
|
name: until_sequence
|
|
in: query
|
|
schema:
|
|
type: integer
|
|
minimum: 0
|
|
SinceObservedAt:
|
|
name: since_observed_at
|
|
in: query
|
|
schema:
|
|
type: string
|
|
format: date-time
|
|
UntilObservedAt:
|
|
name: until_observed_at
|
|
in: query
|
|
schema:
|
|
type: string
|
|
format: date-time
|
|
PageSize:
|
|
name: page_size
|
|
in: query
|
|
schema:
|
|
type: integer
|
|
default: 500
|
|
maximum: 5000
|
|
PageToken:
|
|
name: page_token
|
|
in: query
|
|
schema:
|
|
type: string
|
|
AttestationLimit:
|
|
name: limit
|
|
in: query
|
|
schema:
|
|
type: integer
|
|
default: 200
|
|
maximum: 1000
|
|
schemas:
|
|
# Evidence-Weighted Score (EWS) Schemas - Sprint 8200.0012.0004
|
|
CalculateScoreRequest:
|
|
type: object
|
|
description: Request to calculate score for a finding
|
|
properties:
|
|
forceRecalculate:
|
|
type: boolean
|
|
default: false
|
|
description: Force recalculation even if cached score exists
|
|
includeBreakdown:
|
|
type: boolean
|
|
default: true
|
|
description: Include detailed breakdown in response
|
|
policyVersion:
|
|
type: string
|
|
nullable: true
|
|
description: Specific policy version to use. Null = use latest
|
|
|
|
CalculateScoresBatchRequest:
|
|
type: object
|
|
required: [findingIds]
|
|
description: Request to calculate scores for multiple findings
|
|
properties:
|
|
findingIds:
|
|
type: array
|
|
maxItems: 100
|
|
items:
|
|
type: string
|
|
description: Finding IDs to calculate scores for. Max 100.
|
|
forceRecalculate:
|
|
type: boolean
|
|
default: false
|
|
includeBreakdown:
|
|
type: boolean
|
|
default: true
|
|
policyVersion:
|
|
type: string
|
|
nullable: true
|
|
|
|
EvidenceWeightedScoreResponse:
|
|
type: object
|
|
required: [findingId, score, bucket, policyDigest, calculatedAt]
|
|
description: Evidence-weighted score calculation result
|
|
properties:
|
|
findingId:
|
|
type: string
|
|
description: Finding identifier
|
|
score:
|
|
type: integer
|
|
minimum: 0
|
|
maximum: 100
|
|
description: Calculated score (0-100). Higher = more urgent.
|
|
bucket:
|
|
type: string
|
|
enum: [ActNow, ScheduleNext, Investigate, Watchlist]
|
|
description: Action bucket classification
|
|
inputs:
|
|
$ref: '#/components/schemas/EvidenceInputs'
|
|
weights:
|
|
$ref: '#/components/schemas/EvidenceWeights'
|
|
flags:
|
|
type: array
|
|
items:
|
|
type: string
|
|
enum: [live-signal, proven-path, vendor-na, speculative]
|
|
description: Active flags affecting the score
|
|
explanations:
|
|
type: array
|
|
items:
|
|
type: string
|
|
description: Human-readable explanations for each factor
|
|
caps:
|
|
$ref: '#/components/schemas/AppliedCaps'
|
|
policyDigest:
|
|
type: string
|
|
pattern: "^sha256:[a-f0-9]{64}$"
|
|
description: Policy digest used for calculation
|
|
calculatedAt:
|
|
type: string
|
|
format: date-time
|
|
description: When the score was calculated
|
|
cachedUntil:
|
|
type: string
|
|
format: date-time
|
|
description: When the cached score expires
|
|
fromCache:
|
|
type: boolean
|
|
description: Whether this result came from cache
|
|
|
|
EvidenceInputs:
|
|
type: object
|
|
description: Normalized evidence input values (0-1 scale)
|
|
properties:
|
|
rch:
|
|
type: number
|
|
format: double
|
|
minimum: 0
|
|
maximum: 1
|
|
description: Reachability
|
|
rts:
|
|
type: number
|
|
format: double
|
|
minimum: 0
|
|
maximum: 1
|
|
description: Runtime signal
|
|
bkp:
|
|
type: number
|
|
format: double
|
|
minimum: 0
|
|
maximum: 1
|
|
description: Backport availability
|
|
xpl:
|
|
type: number
|
|
format: double
|
|
minimum: 0
|
|
maximum: 1
|
|
description: Exploit likelihood
|
|
src:
|
|
type: number
|
|
format: double
|
|
minimum: 0
|
|
maximum: 1
|
|
description: Source trust
|
|
mit:
|
|
type: number
|
|
format: double
|
|
minimum: 0
|
|
maximum: 1
|
|
description: Mitigation effectiveness
|
|
|
|
EvidenceWeights:
|
|
type: object
|
|
description: Evidence weight configuration (weights must sum to 1.0)
|
|
properties:
|
|
rch:
|
|
type: number
|
|
format: double
|
|
description: Reachability weight
|
|
rts:
|
|
type: number
|
|
format: double
|
|
description: Runtime signal weight
|
|
bkp:
|
|
type: number
|
|
format: double
|
|
description: Backport weight
|
|
xpl:
|
|
type: number
|
|
format: double
|
|
description: Exploit weight
|
|
src:
|
|
type: number
|
|
format: double
|
|
description: Source trust weight
|
|
mit:
|
|
type: number
|
|
format: double
|
|
description: Mitigation weight
|
|
|
|
AppliedCaps:
|
|
type: object
|
|
description: Applied guardrail caps and floors
|
|
properties:
|
|
speculativeCap:
|
|
type: boolean
|
|
description: Speculative cap applied (no runtime evidence)
|
|
notAffectedCap:
|
|
type: boolean
|
|
description: Not-affected cap applied (VEX status)
|
|
runtimeFloor:
|
|
type: boolean
|
|
description: Runtime floor applied (observed in production)
|
|
|
|
CalculateScoresBatchResponse:
|
|
type: object
|
|
required: [results, summary, policyDigest, calculatedAt]
|
|
description: Batch score calculation result
|
|
properties:
|
|
results:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/EvidenceWeightedScoreResponse'
|
|
description: Individual score results
|
|
summary:
|
|
$ref: '#/components/schemas/BatchSummary'
|
|
errors:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/ScoringError'
|
|
description: Errors for failed calculations
|
|
policyDigest:
|
|
type: string
|
|
description: Policy digest used for all calculations
|
|
calculatedAt:
|
|
type: string
|
|
format: date-time
|
|
|
|
BatchSummary:
|
|
type: object
|
|
required: [total, succeeded, failed, byBucket, averageScore, calculationTimeMs]
|
|
properties:
|
|
total:
|
|
type: integer
|
|
description: Total findings processed
|
|
succeeded:
|
|
type: integer
|
|
description: Successful calculations
|
|
failed:
|
|
type: integer
|
|
description: Failed calculations
|
|
byBucket:
|
|
$ref: '#/components/schemas/BucketDistribution'
|
|
averageScore:
|
|
type: number
|
|
format: double
|
|
description: Average score across all findings
|
|
calculationTimeMs:
|
|
type: number
|
|
format: double
|
|
description: Total calculation time in milliseconds
|
|
|
|
BucketDistribution:
|
|
type: object
|
|
description: Score distribution by bucket
|
|
properties:
|
|
actNow:
|
|
type: integer
|
|
scheduleNext:
|
|
type: integer
|
|
investigate:
|
|
type: integer
|
|
watchlist:
|
|
type: integer
|
|
|
|
ScoreHistoryResponse:
|
|
type: object
|
|
required: [findingId, history, pagination]
|
|
description: Score history response
|
|
properties:
|
|
findingId:
|
|
type: string
|
|
description: Finding ID
|
|
history:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/ScoreHistoryEntry'
|
|
description: History entries
|
|
pagination:
|
|
$ref: '#/components/schemas/Pagination'
|
|
|
|
ScoreHistoryEntry:
|
|
type: object
|
|
required: [score, bucket, policyDigest, calculatedAt, trigger]
|
|
description: Score history entry
|
|
properties:
|
|
score:
|
|
type: integer
|
|
minimum: 0
|
|
maximum: 100
|
|
description: Score value at this point in time
|
|
bucket:
|
|
type: string
|
|
description: Bucket at this point in time
|
|
policyDigest:
|
|
type: string
|
|
description: Policy digest used
|
|
calculatedAt:
|
|
type: string
|
|
format: date-time
|
|
description: When calculated
|
|
trigger:
|
|
type: string
|
|
enum: [evidence_update, policy_change, scheduled, manual]
|
|
description: What triggered recalculation
|
|
changedFactors:
|
|
type: array
|
|
items:
|
|
type: string
|
|
description: Which factors changed since previous calculation
|
|
|
|
Pagination:
|
|
type: object
|
|
required: [hasMore]
|
|
description: Pagination metadata
|
|
properties:
|
|
hasMore:
|
|
type: boolean
|
|
description: Whether more results are available
|
|
nextCursor:
|
|
type: string
|
|
description: Cursor for next page. Null if no more pages.
|
|
|
|
ScoringPolicyResponse:
|
|
type: object
|
|
required: [version, digest, activeSince, environment, weights, guardrails, buckets]
|
|
description: Scoring policy response
|
|
properties:
|
|
version:
|
|
type: string
|
|
description: Policy version identifier
|
|
digest:
|
|
type: string
|
|
description: Policy content digest
|
|
activeSince:
|
|
type: string
|
|
format: date-time
|
|
description: When this policy became active
|
|
environment:
|
|
type: string
|
|
description: Environment (production, staging, etc.)
|
|
weights:
|
|
$ref: '#/components/schemas/EvidenceWeights'
|
|
guardrails:
|
|
$ref: '#/components/schemas/GuardrailsConfig'
|
|
buckets:
|
|
$ref: '#/components/schemas/BucketThresholds'
|
|
|
|
GuardrailsConfig:
|
|
type: object
|
|
description: Guardrail configuration
|
|
properties:
|
|
notAffectedCap:
|
|
$ref: '#/components/schemas/Guardrail'
|
|
runtimeFloor:
|
|
$ref: '#/components/schemas/Guardrail'
|
|
speculativeCap:
|
|
$ref: '#/components/schemas/Guardrail'
|
|
|
|
Guardrail:
|
|
type: object
|
|
required: [enabled]
|
|
description: Individual guardrail settings
|
|
properties:
|
|
enabled:
|
|
type: boolean
|
|
maxScore:
|
|
type: integer
|
|
minScore:
|
|
type: integer
|
|
|
|
BucketThresholds:
|
|
type: object
|
|
required: [actNowMin, scheduleNextMin, investigateMin]
|
|
description: Bucket threshold configuration
|
|
properties:
|
|
actNowMin:
|
|
type: integer
|
|
description: Minimum score for ActNow bucket
|
|
scheduleNextMin:
|
|
type: integer
|
|
description: Minimum score for ScheduleNext bucket
|
|
investigateMin:
|
|
type: integer
|
|
description: Minimum score for Investigate bucket
|
|
|
|
RegisterWebhookRequest:
|
|
type: object
|
|
required: [url]
|
|
description: Request to register a webhook for score changes
|
|
properties:
|
|
url:
|
|
type: string
|
|
format: uri
|
|
description: Webhook URL to call on score changes
|
|
secret:
|
|
type: string
|
|
description: Optional secret for HMAC-SHA256 signature
|
|
findingPatterns:
|
|
type: array
|
|
items:
|
|
type: string
|
|
description: Finding ID patterns to watch. Empty = all findings.
|
|
minScoreChange:
|
|
type: integer
|
|
default: 5
|
|
description: Minimum score change to trigger webhook
|
|
triggerOnBucketChange:
|
|
type: boolean
|
|
default: true
|
|
description: Whether to trigger on bucket changes
|
|
|
|
WebhookResponse:
|
|
type: object
|
|
required: [id, url, hasSecret, minScoreChange, triggerOnBucketChange, createdAt]
|
|
description: Webhook registration response
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
description: Webhook ID
|
|
url:
|
|
type: string
|
|
description: Webhook URL
|
|
hasSecret:
|
|
type: boolean
|
|
description: Whether secret is configured
|
|
findingPatterns:
|
|
type: array
|
|
items:
|
|
type: string
|
|
description: Finding patterns being watched
|
|
minScoreChange:
|
|
type: integer
|
|
description: Minimum score change threshold
|
|
triggerOnBucketChange:
|
|
type: boolean
|
|
description: Whether to trigger on bucket changes
|
|
createdAt:
|
|
type: string
|
|
format: date-time
|
|
description: When webhook was created
|
|
|
|
WebhookListResponse:
|
|
type: object
|
|
required: [webhooks, totalCount]
|
|
properties:
|
|
webhooks:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/WebhookResponse'
|
|
totalCount:
|
|
type: integer
|
|
|
|
ScoringError:
|
|
type: object
|
|
required: [findingId, code, message]
|
|
description: Scoring error information
|
|
properties:
|
|
findingId:
|
|
type: string
|
|
description: Finding ID that failed
|
|
code:
|
|
type: string
|
|
description: Error code
|
|
message:
|
|
type: string
|
|
description: Error message
|
|
|
|
ScoringErrorResponse:
|
|
type: object
|
|
required: [code, message]
|
|
description: Scoring error response
|
|
properties:
|
|
code:
|
|
type: string
|
|
description: Error code
|
|
message:
|
|
type: string
|
|
description: Error message
|
|
details:
|
|
type: object
|
|
additionalProperties: true
|
|
description: Additional details
|
|
traceId:
|
|
type: string
|
|
description: Trace ID for debugging
|
|
|
|
# Existing Ledger Schemas
|
|
LedgerEvent:
|
|
type: object
|
|
required: [event]
|
|
properties:
|
|
event:
|
|
type: object
|
|
required: [id, type, tenant, chainId, sequence, policyVersion, occurredAt, recordedAt, payload]
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
type:
|
|
type: string
|
|
description: ledger_event_type (see schema.md §2.2)
|
|
tenant:
|
|
type: string
|
|
chainId:
|
|
type: string
|
|
format: uuid
|
|
sequence:
|
|
type: integer
|
|
policyVersion:
|
|
type: string
|
|
finding:
|
|
type: object
|
|
properties:
|
|
id: { type: string }
|
|
artifactId: { type: string }
|
|
vulnId: { type: string }
|
|
actor:
|
|
type: object
|
|
properties:
|
|
id: { type: string }
|
|
type: { type: string }
|
|
occurredAt:
|
|
type: string
|
|
format: date-time
|
|
recordedAt:
|
|
type: string
|
|
format: date-time
|
|
payload:
|
|
type: object
|
|
additionalProperties: true
|
|
evidenceBundleRef:
|
|
type: string
|
|
eventHash:
|
|
type: string
|
|
previousHash:
|
|
type: string
|
|
merkleLeafHash:
|
|
type: string
|
|
LedgerEventPage:
|
|
type: object
|
|
required: [items]
|
|
properties:
|
|
items:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/LedgerEvent'
|
|
nextCursor:
|
|
type: string
|
|
LedgerEventAppendRequest:
|
|
type: object
|
|
required: [id, type, tenant, chainId, sequence, policyVersion, occurredAt, payload]
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
type:
|
|
type: string
|
|
tenant:
|
|
type: string
|
|
chainId:
|
|
type: string
|
|
format: uuid
|
|
sequence:
|
|
type: integer
|
|
policyVersion:
|
|
type: string
|
|
finding:
|
|
type: object
|
|
properties:
|
|
id: { type: string }
|
|
artifactId: { type: string }
|
|
vulnId: { type: string }
|
|
actor:
|
|
type: object
|
|
properties:
|
|
id: { type: string }
|
|
type: { type: string }
|
|
occurredAt:
|
|
type: string
|
|
format: date-time
|
|
payload:
|
|
type: object
|
|
additionalProperties: true
|
|
evidenceBundleRef:
|
|
type: string
|
|
previousHash:
|
|
type: string
|
|
description: Optional; validated if supplied
|
|
merkleLeafHash:
|
|
type: string
|
|
description: Optional; server recomputes to validate determinism
|
|
FindingProjection:
|
|
type: object
|
|
required: [tenantId, findingId, policyVersion, status, cycleHash]
|
|
properties:
|
|
tenantId: { type: string }
|
|
findingId: { type: string }
|
|
policyVersion: { type: string }
|
|
status: { type: string }
|
|
severity:
|
|
type: number
|
|
format: double
|
|
riskScore:
|
|
type: number
|
|
format: double
|
|
riskSeverity:
|
|
type: string
|
|
riskProfileVersion:
|
|
type: string
|
|
riskExplanationId:
|
|
type: string
|
|
format: uuid
|
|
labels:
|
|
type: object
|
|
additionalProperties: true
|
|
currentEventId:
|
|
type: string
|
|
format: uuid
|
|
cycleHash:
|
|
type: string
|
|
updatedAt:
|
|
type: string
|
|
format: date-time
|
|
FindingProjectionPage:
|
|
type: object
|
|
required: [items]
|
|
properties:
|
|
items:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/FindingProjection'
|
|
nextCursor:
|
|
type: string
|
|
ExportProvenance:
|
|
type: object
|
|
properties:
|
|
policyVersion: { type: string }
|
|
cycleHash: { type: string }
|
|
ledgerEventHash: { type: string }
|
|
FindingExportItem:
|
|
type: object
|
|
properties:
|
|
eventSequence: { type: integer }
|
|
observedAt: { type: string, format: date-time }
|
|
findingId: { type: string }
|
|
policyVersion: { type: string }
|
|
status: { type: string }
|
|
severity: { type: number, format: double }
|
|
cycleHash: { type: string }
|
|
evidenceBundleRef: { type: string }
|
|
provenance:
|
|
$ref: '#/components/schemas/ExportProvenance'
|
|
labels:
|
|
type: object
|
|
additionalProperties: true
|
|
VexExportItem:
|
|
type: object
|
|
properties:
|
|
eventSequence: { type: integer }
|
|
observedAt: { type: string, format: date-time }
|
|
vexStatementId: { type: string }
|
|
productId: { type: string }
|
|
status: { type: string }
|
|
statementType: { type: string }
|
|
knownExploited: { type: boolean }
|
|
cycleHash: { type: string }
|
|
provenance:
|
|
$ref: '#/components/schemas/ExportProvenance'
|
|
AdvisoryExportItem:
|
|
type: object
|
|
properties:
|
|
eventSequence: { type: integer }
|
|
published: { type: string, format: date-time }
|
|
advisoryId: { type: string }
|
|
source: { type: string }
|
|
title: { type: string }
|
|
severity: { type: string }
|
|
cvssScore: { type: number, format: double }
|
|
cvssVector: { type: string }
|
|
kev: { type: boolean }
|
|
cycleHash: { type: string }
|
|
provenance:
|
|
$ref: '#/components/schemas/ExportProvenance'
|
|
SbomExportItem:
|
|
type: object
|
|
properties:
|
|
eventSequence: { type: integer }
|
|
createdAt: { type: string, format: date-time }
|
|
sbomId: { type: string }
|
|
subjectDigest: { type: string }
|
|
sbomFormat: { type: string }
|
|
componentsCount: { type: integer }
|
|
hasVulnerabilities: { type: boolean }
|
|
cycleHash: { type: string }
|
|
provenance:
|
|
$ref: '#/components/schemas/ExportProvenance'
|
|
FindingExportPage:
|
|
type: object
|
|
properties:
|
|
items:
|
|
type: array
|
|
items: { $ref: '#/components/schemas/FindingExportItem' }
|
|
nextPageToken: { type: string }
|
|
VexExportPage:
|
|
type: object
|
|
properties:
|
|
items:
|
|
type: array
|
|
items: { $ref: '#/components/schemas/VexExportItem' }
|
|
nextPageToken: { type: string }
|
|
AdvisoryExportPage:
|
|
type: object
|
|
properties:
|
|
items:
|
|
type: array
|
|
items: { $ref: '#/components/schemas/AdvisoryExportItem' }
|
|
nextPageToken: { type: string }
|
|
SbomExportPage:
|
|
type: object
|
|
properties:
|
|
items:
|
|
type: array
|
|
items: { $ref: '#/components/schemas/SbomExportItem' }
|
|
nextPageToken: { type: string }
|
|
AttestationExportItem:
|
|
type: object
|
|
properties:
|
|
attestationId: { type: string }
|
|
artifactId: { type: string }
|
|
findingId: { type: string }
|
|
verificationStatus: { type: string }
|
|
verificationTime: { type: string, format: date-time }
|
|
dsseDigest: { type: string }
|
|
rekorEntryId: { type: string }
|
|
evidenceBundleRef: { type: string }
|
|
ledgerEventId: { type: string, format: uuid }
|
|
recordedAt: { type: string, format: date-time }
|
|
merkleLeafHash: { type: string }
|
|
rootHash: { type: string }
|
|
AttestationExportPage:
|
|
type: object
|
|
properties:
|
|
items:
|
|
type: array
|
|
items: { $ref: '#/components/schemas/AttestationExportItem' }
|
|
nextPageToken: { type: string }
|