Files
git.stella-ops.org/docs/api/findings-scoring.md

19 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
}

Attested-Reduction Mode (v1.1)

When attested-reduction scoring is enabled on the policy, the response includes additional fields for cryptographic attestation metadata and reduction profile information.

Extended Response (200 OK) with Reduction Mode:

{
  "findingId": "CVE-2024-1234@pkg:deb/debian/curl@7.64.0-4",
  "score": 0,
  "bucket": "Watchlist",
  "inputs": { "rch": 0.00, "rts": 0.00, "bkp": 1.00, "xpl": 0.30, "src": 0.90, "mit": 1.00 },
  "weights": { "rch": 0.30, "rts": 0.25, "bkp": 0.15, "xpl": 0.15, "src": 0.10, "mit": 0.10 },
  "flags": ["anchored-vex", "vendor-na", "attested-reduction"],
  "explanations": [
    "Anchored VEX statement: not_affected - score reduced to 0"
  ],
  "caps": { "speculativeCap": false, "notAffectedCap": false, "runtimeFloor": false },
  "policyDigest": "sha256:reduction123...",
  "calculatedAt": "2026-01-15T14:30:00Z",
  "cachedUntil": "2026-01-15T15:30:00Z",
  "fromCache": false,
  "reductionProfile": {
    "enabled": true,
    "mode": "aggressive",
    "profileId": "attested-verified",
    "maxReductionPercent": 100,
    "requireVexAnchoring": true,
    "requireRekorVerification": true
  },
  "hardFail": false,
  "shortCircuitReason": "anchored_vex_not_affected",
  "anchor": {
    "anchored": true,
    "envelopeDigest": "sha256:abc123def456...",
    "predicateType": "https://stellaops.io/attestation/vex/v1",
    "rekorLogIndex": 12345678,
    "rekorEntryId": "24296fb24b8ad77a7e...",
    "scope": "finding",
    "verified": true,
    "attestedAt": "2026-01-14T10:00:00Z"
  }
}

Attested-Reduction Fields

Field Type Description
reductionProfile object Reduction profile configuration (when enabled)
reductionProfile.enabled boolean Whether attested-reduction is active
reductionProfile.mode string "aggressive" or "conservative"
reductionProfile.profileId string Profile identifier for audit trail
reductionProfile.maxReductionPercent integer Maximum score reduction allowed (0-100)
reductionProfile.requireVexAnchoring boolean Whether VEX must be anchored to qualify
reductionProfile.requireRekorVerification boolean Whether Rekor verification is required
hardFail boolean true if anchored evidence confirms active exploitation
shortCircuitReason string Reason for short-circuit (if score was short-circuited)
anchor object Primary evidence anchor metadata (if available)

Short-Circuit Reasons

Reason Score Effect Condition
anchored_vex_not_affected Score = 0 Verified VEX not_affected/fixed attestation
anchored_affected_runtime_confirmed Score = 100 (hard fail) Anchored VEX affected + anchored runtime confirms vulnerability

Evidence Anchor Fields

Field Type Description
anchor.anchored boolean Whether evidence has cryptographic attestation
anchor.envelopeDigest string DSSE envelope digest (sha256 hex)
anchor.predicateType string Attestation predicate type URL
anchor.rekorLogIndex integer Sigstore Rekor transparency log index
anchor.rekorEntryId string Rekor entry UUID
anchor.scope string Attestation scope (finding, package, image)
anchor.verified boolean Whether attestation signature was verified
anchor.attestedAt string ISO-8601 attestation timestamp

Hard-Fail Response Example

When anchored evidence confirms active exploitation:

{
  "findingId": "CVE-2024-9999@pkg:npm/critical@1.0.0",
  "score": 100,
  "bucket": "ActNow",
  "flags": ["anchored-vex", "anchored-runtime", "hard-fail", "attested-reduction"],
  "explanations": [
    "Anchored VEX affected + runtime confirmed vulnerable path - hard fail"
  ],
  "hardFail": true,
  "shortCircuitReason": "anchored_affected_runtime_confirmed",
  "reductionProfile": {
    "enabled": true,
    "mode": "aggressive",
    "profileId": "attested-verified",
    "maxReductionPercent": 100,
    "requireVexAnchoring": true,
    "requireRekorVerification": true
  }
}

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

Attested-Reduction Scoring Profile

Sprint: SPRINT_20260112_004_LB_attested_reduction_scoring

When enabled, the attested-reduction profile applies precedence-based scoring using cryptographically anchored evidence:

Formula: score = clamp(base_epss * (1 + R + T) - P, 0, 1)

Where:

  • base_epss - EPSS score (exploit likelihood)
  • R - Reachability bonus (applied when anchored not-reachable evidence exists)
  • T - Telemetry bonus (applied when anchored no-observation evidence exists)
  • P - Patch proof reduction (applied when anchored backport/fix evidence exists)

Short-Circuit Rules:

  1. Anchored VEX not_affected/fixed → Score = 0 (immediate watchlist)
  2. Anchored VEX affected + runtime confirmed → Hard fail (Score = 100, ActNow bucket)

Configuration in Policy:

{
  "version": "ews.v1.3",
  "weights": { ... },
  "guardrails": { ... },
  "buckets": { ... },
  "attestedReduction": {
    "enabled": true,
    "precedenceList": [
      "vex.not_affected",
      "vex.fixed",
      "backport.signed_proof",
      "backport.vendor_vex",
      "reachability.not_reachable",
      "runtime.not_observed"
    ],
    "reachabilityBonus": 0.3,
    "telemetryBonus": 0.2,
    "patchProofReduction": 0.5,
    "clampMin": 0.0,
    "clampMax": 1.0,
    "hardFailOnAffectedWithRuntime": true,
    "hardFailScore": 1.0,
    "skipEpssWhenAnchored": true,
    "requiredVerificationStatus": "Verified"
  }
}

Anchor Metadata:

Evidence inputs can include anchor metadata for cryptographic attestation:

{
  "findingId": "CVE-2024-1234@pkg:test/lib@1.0.0",
  "xpl": 0.5,
  "vexStatus": "not_affected",
  "vexAnchor": {
    "isAnchored": true,
    "dsseEnvelopeDigest": "sha256:abc123...",
    "predicateType": "https://stellaops.io/attestation/vex-override/v1",
    "rekorLogIndex": 12345678,
    "rekorEntryId": "24296fb24b8ad77a...",
    "verificationStatus": "Verified",
    "attestationTimestamp": "2026-01-14T10:30:00Z"
  },
  "backportDetails": {
    "evidenceTier": "SignedProof",
    "status": "Fixed",
    "confidence": 0.95,
    "anchor": {
      "isAnchored": true,
      "dsseEnvelopeDigest": "sha256:def456...",
      "predicateType": "https://stellaops.io/attestation/backport/v1",
      "verificationStatus": "Verified"
    }
  }
}

Response Flags (when attested-reduction is active):

Flag Description
attested-reduction Attested-reduction scoring path was used
anchored-vex Anchored VEX evidence triggered precedence
anchored-backport Anchored backport evidence applied reduction
anchored-reachability Anchored reachability evidence applied bonus
anchored-runtime Anchored runtime evidence affected score
hard-fail Hard-fail triggered (affected + runtime confirmed)
epss-reduced EPSS influence reduced due to anchored evidence

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)