- Add RateLimitConfig for configuration management with YAML binding support. - Introduce RateLimitDecision to encapsulate the result of rate limit checks. - Implement RateLimitMetrics for OpenTelemetry metrics tracking. - Create RateLimitMiddleware for enforcing rate limits on incoming requests. - Develop RateLimitService to orchestrate instance and environment rate limit checks. - Add RateLimitServiceCollectionExtensions for dependency injection registration.
7.6 KiB
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:
Authorization: Bearer <token>
Required scope: scanner:unknowns:read
Endpoints
List Unknowns
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
{
"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
# 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
GET /api/v1/unknowns/{id}
Returns detailed information about a specific unknown.
Response
{
"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
GET /api/v1/unknowns/{id}/proof
Returns the proof tree explaining the ranking decision.
Response
{
"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
POST /api/v1/unknowns/batch
Get multiple unknowns by ID in a single request.
Request Body
{
"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
GET /api/v1/unknowns/summary
Returns aggregate statistics about unknowns.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
artifact |
string | Filter by artifact digest |
Response
{
"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