# Unknowns API Reference **Sprint:** SPRINT_3600_0002_0001 **Task:** UNK-RANK-011 - Update unknowns API documentation ## Overview The Unknowns API provides access to items that could not be fully classified due to missing evidence, ambiguous data, or incomplete intelligence. Unknowns are ranked by blast radius, exploit pressure, and containment signals. ## Base URL ``` /api/v1/unknowns ``` ## Authentication All endpoints require Bearer token authentication: ```http Authorization: Bearer ``` Required scope: `scanner:unknowns:read` ## Endpoints ### List Unknowns ```http GET /api/v1/unknowns ``` Returns paginated list of unknowns, optionally sorted by score. #### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `sort` | string | `score` | Sort field: `score`, `created_at`, `blast_dependents` | | `order` | string | `desc` | Sort order: `asc`, `desc` | | `page` | int | 1 | Page number (1-indexed) | | `pageSize` | int | 50 | Items per page (max 200) | | `artifact` | string | - | Filter by artifact digest | | `reason` | string | - | Filter by reason code | | `minScore` | float | - | Minimum score threshold (0-1) | | `maxScore` | float | - | Maximum score threshold (0-1) | | `kev` | bool | - | Filter by KEV status | | `seccomp` | string | - | Filter by seccomp state: `enforced`, `permissive`, `unknown` | #### Response ```json { "items": [ { "id": "unk-12345678-abcd-1234-5678-abcdef123456", "artifactDigest": "sha256:abc123...", "artifactPurl": "pkg:oci/myapp@sha256:abc123", "reasons": ["missing_vex", "ambiguous_indirect_call"], "blastRadius": { "dependents": 15, "netFacing": true, "privilege": "user" }, "evidenceScarcity": 0.7, "exploitPressure": { "epss": 0.45, "kev": false }, "containment": { "seccomp": "enforced", "fs": "ro" }, "score": 0.62, "proofRef": "proofs/unknowns/unk-12345678/tree.json", "createdAt": "2025-01-15T10:30:00Z", "updatedAt": "2025-01-15T10:30:00Z" } ], "pagination": { "page": 1, "pageSize": 50, "totalItems": 142, "totalPages": 3 } } ``` #### Example ```bash # Get top 10 highest-scored unknowns curl -H "Authorization: Bearer $TOKEN" \ "https://scanner.example.com/api/v1/unknowns?sort=score&order=desc&pageSize=10" # Filter by KEV and minimum score curl -H "Authorization: Bearer $TOKEN" \ "https://scanner.example.com/api/v1/unknowns?kev=true&minScore=0.5" # Filter by artifact curl -H "Authorization: Bearer $TOKEN" \ "https://scanner.example.com/api/v1/unknowns?artifact=sha256:abc123" ``` ### Get Unknown by ID ```http GET /api/v1/unknowns/{id} ``` Returns detailed information about a specific unknown. #### Response ```json { "id": "unk-12345678-abcd-1234-5678-abcdef123456", "artifactDigest": "sha256:abc123...", "artifactPurl": "pkg:oci/myapp@sha256:abc123", "reasons": ["missing_vex", "ambiguous_indirect_call"], "reasonDetails": [ { "code": "missing_vex", "message": "No VEX statement found for CVE-2024-1234", "component": "pkg:npm/lodash@4.17.20" }, { "code": "ambiguous_indirect_call", "message": "Indirect call target could not be resolved", "location": "src/utils.js:42" } ], "blastRadius": { "dependents": 15, "netFacing": true, "privilege": "user" }, "evidenceScarcity": 0.7, "exploitPressure": { "epss": 0.45, "kev": false }, "containment": { "seccomp": "enforced", "fs": "ro" }, "score": 0.62, "scoreBreakdown": { "blastComponent": 0.35, "scarcityComponent": 0.21, "pressureComponent": 0.26, "containmentDeduction": -0.20 }, "proofRef": "proofs/unknowns/unk-12345678/tree.json", "createdAt": "2025-01-15T10:30:00Z", "updatedAt": "2025-01-15T10:30:00Z" } ``` ### Get Unknown Proof ```http GET /api/v1/unknowns/{id}/proof ``` Returns the proof tree explaining the ranking decision. #### Response ```json { "version": "1.0", "unknownId": "unk-12345678-abcd-1234-5678-abcdef123456", "nodes": [ { "kind": "input", "hash": "sha256:abc...", "data": { "reasons": ["missing_vex"], "evidenceScarcity": 0.7 } }, { "kind": "delta", "hash": "sha256:def...", "factor": "blast_radius", "contribution": 0.35 }, { "kind": "delta", "hash": "sha256:ghi...", "factor": "containment_seccomp", "contribution": -0.10 }, { "kind": "score", "hash": "sha256:jkl...", "finalScore": 0.62 } ], "rootHash": "sha256:mno..." } ``` ### Batch Get Unknowns ```http POST /api/v1/unknowns/batch ``` Get multiple unknowns by ID in a single request. #### Request Body ```json { "ids": [ "unk-12345678-abcd-1234-5678-abcdef123456", "unk-87654321-dcba-4321-8765-654321fedcba" ] } ``` #### Response Same format as list response with matching items. ### Get Unknowns Summary ```http GET /api/v1/unknowns/summary ``` Returns aggregate statistics about unknowns. #### Query Parameters | Parameter | Type | Description | |-----------|------|-------------| | `artifact` | string | Filter by artifact digest | #### Response ```json { "totalCount": 142, "byReason": { "missing_vex": 45, "ambiguous_indirect_call": 32, "incomplete_sbom": 28, "unknown_platform": 15, "other": 22 }, "byScoreBucket": { "critical": 12, // score >= 0.8 "high": 35, // 0.6 <= score < 0.8 "medium": 48, // 0.4 <= score < 0.6 "low": 47 // score < 0.4 }, "byContainment": { "enforced": 45, "permissive": 32, "unknown": 65 }, "kevCount": 8, "avgScore": 0.52 } ``` ## Reason Codes | Code | Description | |------|-------------| | `missing_vex` | No VEX statement for vulnerability | | `ambiguous_indirect_call` | Indirect call target unresolved | | `incomplete_sbom` | SBOM missing component data | | `unknown_platform` | Platform not recognized | | `missing_advisory` | No advisory data for CVE | | `conflicting_evidence` | Multiple conflicting data sources | | `stale_data` | Data exceeds freshness threshold | ## Score Calculation The unknown score is calculated as: ``` score = 0.60 × blast + 0.30 × scarcity + 0.30 × pressure + containment_deduction ``` Where: - `blast` = normalized blast radius (0-1) - `scarcity` = evidence scarcity factor (0-1) - `pressure` = exploit pressure (EPSS + KEV factor) - `containment_deduction` = -0.10 for enforced seccomp, -0.10 for read-only FS ### Blast Radius Normalization ``` dependents_normalized = min(dependents / 50, 1.0) net_factor = 0.5 if net_facing else 0.0 priv_factor = 0.5 if privilege == "root" else 0.0 blast = min((dependents_normalized + net_factor + priv_factor) / 2, 1.0) ``` ### Exploit Pressure ``` epss_normalized = epss ?? 0.35 // Default if unknown kev_factor = 0.30 if kev else 0.0 pressure = min(epss_normalized + kev_factor, 1.0) ``` ## Error Responses | Status | Code | Description | |--------|------|-------------| | 400 | `INVALID_PARAMETER` | Invalid query parameter | | 401 | `UNAUTHORIZED` | Missing or invalid token | | 403 | `FORBIDDEN` | Insufficient permissions | | 404 | `NOT_FOUND` | Unknown not found | | 429 | `RATE_LIMITED` | Too many requests | ## Rate Limits - List: 100 requests/minute - Get by ID: 300 requests/minute - Summary: 60 requests/minute ## Related Documentation - [Unknowns Ranking Technical Reference](../product-advisories/14-Dec-2025%20-%20Triage%20and%20Unknowns%20Technical%20Reference.md) - [Scanner Architecture](../modules/scanner/architecture.md) - [Proof Bundle Format](../api/proof-bundle-format.md)