Files
git.stella-ops.org/docs/api/findings-scoring.md
2025-12-25 23:10:09 +02:00

12 KiB

Evidence-Weighted Score (EWS) API

Last updated: 2025-12-25 (Sprint 8200.0012.0004)

Status

  • v1.0-beta (API endpoints complete; QA pending)

Scope

RESTful API for calculating, retrieving, and managing Evidence-Weighted Scores for vulnerability findings. Enables prioritization by aggregating multiple evidence signals into a single 0-100 score with action bucket classification.

Authentication & Authorization

Required Scopes

Scope Description Endpoints
read:scores Read score data GET endpoints
write:scores Calculate scores POST endpoints
admin:scoring Webhook management All webhook endpoints

Required Headers

Header Required Description
Authorization: Bearer <jwt> Yes RS256/ES256 token with appropriate scopes
X-Stella-Tenant Yes Tenant slug/UUID
Content-Type Yes (POST/PUT) application/json
traceparent Recommended W3C trace context

Endpoints Summary

Method Path Rate Limit Description
POST /api/v1/findings/{findingId}/score 100/min Calculate score for finding
GET /api/v1/findings/{findingId}/score 1000/min Get cached score
POST /api/v1/findings/scores 10/min Batch calculate (max 100)
GET /api/v1/findings/{findingId}/score-history 100/min Get score history
GET /api/v1/scoring/policy 100/min Get active policy
GET /api/v1/scoring/policy/{version} 100/min Get policy version
POST /api/v1/scoring/webhooks 10/min Register webhook
GET /api/v1/scoring/webhooks 10/min List webhooks
GET /api/v1/scoring/webhooks/{id} 10/min Get webhook
PUT /api/v1/scoring/webhooks/{id} 10/min Update webhook
DELETE /api/v1/scoring/webhooks/{id} 10/min Delete webhook

Score Calculation

Calculate Single Score

Request:

POST /api/v1/findings/{findingId}/score
Authorization: Bearer <token>
X-Stella-Tenant: acme-corp
Content-Type: application/json

{
  "forceRecalculate": false,
  "includeBreakdown": true,
  "policyVersion": null
}

Parameters:

  • findingId (path, required): Finding identifier in format CVE-ID@pkg:PURL
  • forceRecalculate (body, optional): Bypass cache, default false
  • includeBreakdown (body, optional): Include detailed inputs/weights, default true
  • policyVersion (body, optional): Specific policy version, null for latest

Response (200 OK):

{
  "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",
    "EPSS: 0.8% probability (High band)",
    "Source: Distro VEX signed (trust: 80%)",
    "Mitigations: seccomp profile active"
  ],
  "caps": {
    "speculativeCap": false,
    "notAffectedCap": false,
    "runtimeFloor": false
  },
  "policyDigest": "sha256:abc123def456...",
  "calculatedAt": "2026-01-15T14:30:00Z",
  "cachedUntil": "2026-01-15T15:30:00Z",
  "fromCache": false
}

Score Buckets

Bucket Score Range Action
ActNow 90-100 Immediate remediation required
ScheduleNext 70-89 Schedule for upcoming sprint
Investigate 40-69 Needs further analysis
Watchlist 0-39 Monitor, low priority

Evidence Inputs

Code Full Name Description
rch Reachability Static/dynamic code path analysis (0-1)
rts Runtime Signal Production observation frequency (0-1)
bkp Backport Vendor backport availability (0-1)
xpl Exploit EPSS/KEV exploit likelihood (0-1)
src Source Trust Advisory source trust level (0-1)
mit Mitigation Applied mitigations effectiveness (0-1)

Guardrails (Caps & Floors)

Guardrail Effect Condition
speculativeCap Max 45 No runtime evidence
notAffectedCap Max 15 VEX status = not_affected
runtimeFloor Min 60 Observed in production

Batch Calculation

Request:

POST /api/v1/findings/scores
Authorization: Bearer <token>
X-Stella-Tenant: acme-corp
Content-Type: application/json

{
  "findingIds": [
    "CVE-2024-1234@pkg:deb/debian/curl@7.64.0-4",
    "CVE-2024-5678@pkg:npm/lodash@4.17.20",
    "GHSA-abc123@pkg:pypi/requests@2.25.0"
  ],
  "forceRecalculate": false,
  "includeBreakdown": true
}

Response (200 OK):

{
  "results": [
    { "findingId": "...", "score": 78, "bucket": "ScheduleNext", ... },
    { "findingId": "...", "score": 45, "bucket": "Investigate", ... },
    { "findingId": "...", "score": 92, "bucket": "ActNow", ... }
  ],
  "summary": {
    "total": 3,
    "succeeded": 3,
    "failed": 0,
    "byBucket": {
      "actNow": 1,
      "scheduleNext": 1,
      "investigate": 1,
      "watchlist": 0
    },
    "averageScore": 71.7,
    "calculationTimeMs": 45
  },
  "errors": [],
  "policyDigest": "sha256:abc123...",
  "calculatedAt": "2026-01-15T14:30:00Z"
}

Limits:

  • Maximum batch size: 100 findings
  • Rate limit: 10 requests/minute

Score History

Request:

GET /api/v1/findings/{findingId}/score-history?from=2026-01-01&to=2026-01-15&limit=50
Authorization: Bearer <token>
X-Stella-Tenant: acme-corp

Parameters:

  • from (query, optional): Start date (ISO-8601)
  • to (query, optional): End date (ISO-8601)
  • limit (query, optional): Max entries (1-100, default 50)
  • cursor (query, optional): Pagination cursor

Response (200 OK):

{
  "findingId": "CVE-2024-1234@pkg:deb/debian/curl@7.64.0-4",
  "history": [
    {
      "score": 78,
      "bucket": "ScheduleNext",
      "policyDigest": "sha256:abc123...",
      "calculatedAt": "2026-01-15T14:30:00Z",
      "trigger": "evidence_update",
      "changedFactors": ["rts", "xpl"]
    },
    {
      "score": 65,
      "bucket": "Investigate",
      "policyDigest": "sha256:abc123...",
      "calculatedAt": "2026-01-10T09:15:00Z",
      "trigger": "scheduled",
      "changedFactors": []
    }
  ],
  "pagination": {
    "hasMore": true,
    "nextCursor": "eyJvZmZzZXQiOjUwfQ=="
  }
}

Trigger Types:

  • evidence_update - New evidence received
  • policy_change - Policy weights changed
  • scheduled - Periodic recalculation
  • manual - User-initiated recalculation

Scoring Policy

Get Active Policy

Request:

GET /api/v1/scoring/policy
Authorization: Bearer <token>
X-Stella-Tenant: acme-corp

Response (200 OK):

{
  "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
  }
}

Webhooks

Register Webhook

Request:

POST /api/v1/scoring/webhooks
Authorization: Bearer <token>
X-Stella-Tenant: acme-corp
Content-Type: application/json

{
  "url": "https://example.com/webhook/scores",
  "secret": "webhook-secret-key",
  "findingPatterns": ["CVE-*", "GHSA-*"],
  "minScoreChange": 10,
  "triggerOnBucketChange": true
}

Response (201 Created):

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "url": "https://example.com/webhook/scores",
  "hasSecret": true,
  "findingPatterns": ["CVE-*", "GHSA-*"],
  "minScoreChange": 10,
  "triggerOnBucketChange": true,
  "createdAt": "2026-01-15T14:30:00Z"
}

Webhook Payload

When a score change triggers a webhook:

{
  "event_type": "score.changed",
  "finding_id": "CVE-2024-1234@pkg:deb/debian/curl@7.64.0-4",
  "previous_score": 65,
  "current_score": 78,
  "previous_bucket": "Investigate",
  "current_bucket": "ScheduleNext",
  "score_change": 13,
  "bucket_changed": true,
  "policy_digest": "sha256:abc123...",
  "timestamp": "2026-01-15T14:30:00Z"
}

Webhook Signature

If a secret is configured, payloads include an HMAC-SHA256 signature:

X-Webhook-Signature: sha256=<hex-encoded-hmac>
X-Webhook-Id: <webhook-uuid>
X-Webhook-Timestamp: <unix-epoch-seconds>

Verify by computing: HMAC-SHA256(secret, payload_body)

Webhook Delivery

  • Retry policy: 4 attempts with exponential backoff (100ms, 500ms, 2s, 5s)
  • Timeout: 30 seconds per attempt
  • Success: HTTP 2xx response
  • Fire-and-forget: Does not block score calculation

Error Responses

All errors follow the standard envelope:

{
  "code": "SCORING_FINDING_NOT_FOUND",
  "message": "Finding 'CVE-2024-1234@...' not found or no evidence available",
  "traceId": "01HXYZABCD1234567890"
}

Error Codes

Code HTTP Status Description
SCORING_FINDING_NOT_FOUND 404 Finding doesn't exist
SCORING_EVIDENCE_NOT_AVAILABLE 404 No evidence for scoring
SCORING_POLICY_NOT_FOUND 404 Policy version doesn't exist
SCORING_CALCULATION_FAILED 400 Score calculation error
SCORING_BATCH_TOO_LARGE 400 Batch exceeds 100 findings
SCORING_RATE_LIMIT_EXCEEDED 429 Rate limit hit
SCORING_INVALID_REQUEST 400 Malformed request

Observability

Metrics

Metric Type Description
ews_calculations_total Counter Total calculations by bucket/result
ews_calculation_duration_seconds Histogram Calculation latency
ews_batch_calculations_total Counter Batch operations
ews_cache_hits_total Counter Cache hits
ews_cache_misses_total Counter Cache misses
ews_webhooks_delivered_total Counter Webhook deliveries
ews_bucket_distribution_* Gauge Findings per bucket

Tracing

All operations emit OpenTelemetry spans:

  • EWS.Calculate - Single score calculation
  • EWS.CalculateBatch - Batch calculation
  • EWS.WebhookDelivery - Webhook delivery

Span attributes include: finding_id, score, bucket, policy_digest, duration_ms, from_cache

Examples

CLI Usage

# Calculate score
curl -X POST "https://api.stellaops.local/api/v1/findings/CVE-2024-1234@pkg:npm/lodash@4.17.20/score" \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Stella-Tenant: acme-corp" \
  -H "Content-Type: application/json" \
  -d '{"includeBreakdown": true}'

# Get cached score
curl "https://api.stellaops.local/api/v1/findings/CVE-2024-1234@pkg:npm/lodash@4.17.20/score" \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Stella-Tenant: acme-corp"

# Batch calculation
curl -X POST "https://api.stellaops.local/api/v1/findings/scores" \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Stella-Tenant: acme-corp" \
  -H "Content-Type: application/json" \
  -d '{"findingIds": ["CVE-2024-1234@...", "CVE-2024-5678@..."]}'

Changelog

Version Date Changes
1.0-beta 2025-12-25 Initial API release (Sprint 8200.0012.0004)