feat(rate-limiting): Implement core rate limiting functionality with configuration, decision-making, metrics, middleware, and service registration
- 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.
This commit is contained in:
334
docs/api/unknowns-api.md
Normal file
334
docs/api/unknowns-api.md
Normal file
@@ -0,0 +1,334 @@
|
||||
# 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 <token>
|
||||
```
|
||||
|
||||
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)
|
||||
Reference in New Issue
Block a user