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.
Related Documentation
- API Overview - Shared conventions for all StellaOps APIs
- Signals API - Signal collection and correlation
- OpenAPI Spec - Full OpenAPI 3.0 specification
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 formatCVE-ID@pkg:PURLforceRecalculate(body, optional): Bypass cache, defaultfalseincludeBreakdown(body, optional): Include detailed inputs/weights, defaulttruepolicyVersion(body, optional): Specific policy version,nullfor 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 receivedpolicy_change- Policy weights changedscheduled- Periodic recalculationmanual- 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 calculationEWS.CalculateBatch- Batch calculationEWS.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) |