# Risk Scoring Contract (66-002) **Contract ID:** `CONTRACT-RISK-SCORING-002` **Version:** 1.0 **Status:** Published **Last Updated:** 2025-12-05 ## Overview This contract defines the risk scoring interface used by the Policy Engine to calculate and prioritize vulnerability findings. It covers job requests, results, risk profiles, and signal definitions. ## Implementation References - **Scoring Models:** `src/Policy/StellaOps.Policy.Engine/Scoring/RiskScoringModels.cs` - **Risk Profile:** `src/Policy/StellaOps.Policy.RiskProfile/Models/RiskProfileModel.cs` - **Attestation Schema:** `src/Attestor/StellaOps.Attestor.Types/schemas/stellaops-risk-profile.v1.schema.json` ## Data Models ### RiskScoringJobRequest Request to create a risk scoring job. ```json { "tenant_id": "string", "context_id": "string", "profile_id": "string", "findings": [ { "finding_id": "string", "component_purl": "pkg:npm/lodash@4.17.20", "advisory_id": "CVE-2024-1234", "trigger": "created|updated|enriched|vex_applied" } ], "priority": "low|normal|high|emergency", "correlation_id": "string (optional)", "requested_at": "2025-12-05T00:00:00Z (optional)" } ``` ### RiskScoringJob A queued or completed risk scoring job. ```json { "job_id": "string", "tenant_id": "string", "context_id": "string", "profile_id": "string", "profile_hash": "sha256:...", "findings": [...], "priority": "normal", "status": "queued|running|completed|failed|cancelled", "requested_at": "2025-12-05T00:00:00Z", "started_at": "2025-12-05T00:00:01Z (optional)", "completed_at": "2025-12-05T00:00:02Z (optional)", "correlation_id": "string (optional)", "error_message": "string (optional)" } ``` ### RiskScoringResult Result of scoring a single finding. ```json { "finding_id": "string", "profile_id": "string", "profile_version": "1.0.0", "raw_score": 0.75, "normalized_score": 0.85, "severity": "high", "signal_values": { "cvss": 7.5, "kev": true, "reachability": 0.9 }, "signal_contributions": { "cvss": 0.4, "kev": 0.3, "reachability": 0.3 }, "override_applied": "kev-boost (optional)", "override_reason": "Known Exploited Vulnerability (optional)", "scored_at": "2025-12-05T00:00:02Z" } ``` ## Risk Profile Model ### RiskProfileModel Defines how findings are scored and prioritized. ```json { "id": "default-profile", "version": "1.0.0", "description": "Default risk profile for vulnerability prioritization", "extends": "base-profile (optional)", "signals": [ { "name": "cvss", "source": "nvd", "type": "numeric", "path": "/cvss/base_score", "transform": "normalize_10", "unit": "score" }, { "name": "kev", "source": "cisa", "type": "boolean", "path": "/kev/in_catalog" }, { "name": "reachability", "source": "scanner", "type": "numeric", "path": "/reachability/score" } ], "weights": { "cvss": 0.4, "kev": 0.3, "reachability": 0.3 }, "overrides": { "severity": [ { "when": { "kev": true }, "set": "critical" } ], "decisions": [ { "when": { "kev": true, "reachability": { "$gt": 0.8 } }, "action": "deny", "reason": "KEV with high reachability" } ] }, "metadata": {} } ``` ### Signal Types | Type | Description | Value Range | |------|-------------|-------------| | `boolean` | True/false signal | `true` / `false` | | `numeric` | Numeric signal | `0.0` to `1.0` (normalized) | | `categorical` | Categorical signal | String values | ### Severity Levels | Level | JSON Value | Priority | |-------|------------|----------| | Critical | `"critical"` | 1 (highest) | | High | `"high"` | 2 | | Medium | `"medium"` | 3 | | Low | `"low"` | 4 | | Informational | `"informational"` | 5 (lowest) | ### Decision Actions | Action | Description | |--------|-------------| | `allow` | Finding is acceptable, no action required | | `review` | Finding requires manual review | | `deny` | Finding is not acceptable, blocks promotion | ## Scoring Algorithm ### Score Calculation ``` raw_score = Σ(signal_value × weight) for all signals normalized_score = clamp(raw_score, 0.0, 1.0) ``` ### VEX Gate Provider The VEX gate provider short-circuits scoring when a VEX denial is present: ```csharp if (signals.HasVexDenial) return 0.0; // Fully mitigated return Math.Max(signals.Values); // Otherwise, max signal ``` ### CVSS + KEV Provider ```csharp score = clamp01((cvss / 10.0) + kevBonus) where kevBonus = kev ? 0.2 : 0.0 ``` ## API Endpoints ### Submit Scoring Job ``` POST /api/v1/risk/jobs Content-Type: application/json { "tenant_id": "...", "context_id": "...", "profile_id": "...", "findings": [...] } Response: 202 Accepted { "job_id": "...", "status": "queued" } ``` ### Get Job Status ``` GET /api/v1/risk/jobs/{job_id} Response: 200 OK { "job_id": "...", "status": "completed", "results": [...] } ``` ### Get Finding Score ``` GET /api/v1/risk/findings/{finding_id}/score Response: 200 OK { "finding_id": "...", "normalized_score": 0.85, "severity": "high", ... } ``` ## Finding Change Events Events that trigger rescoring: | Event | JSON Value | Description | |-------|------------|-------------| | Created | `"created"` | New finding discovered | | Updated | `"updated"` | Finding metadata changed | | Enriched | `"enriched"` | New signals available | | VEX Applied | `"vex_applied"` | VEX status changed | ## Determinism Guarantees 1. **Reproducible scores:** Same inputs always produce same outputs 2. **Profile versioning:** Profile hash included in results for traceability 3. **Signal ordering:** Signals processed in deterministic order 4. **Timestamp precision:** UTC ISO-8601 with millisecond precision ## Unblocks This contract unblocks the following tasks: - LEDGER-RISK-67-001 - LEDGER-RISK-68-001 - LEDGER-RISK-69-001 - POLICY-RISK-67-003 - POLICY-RISK-68-001 - POLICY-RISK-68-002 ## Related Contracts - [Advisory Key Contract](./advisory-key.md) - Advisory ID canonicalization - [VEX Lens Contract](./vex-lens.md) - VEX evidence for scoring - [Export Bundle Contract](./export-bundle.md) - Score digest in exports