feat: Add VEX Status Chip component and integration tests for reachability drift detection
- Introduced `VexStatusChipComponent` to display VEX status with color coding and tooltips. - Implemented integration tests for reachability drift detection, covering various scenarios including drift detection, determinism, and error handling. - Enhanced `ScannerToSignalsReachabilityTests` with a null implementation of `ICallGraphSyncService` for better test isolation. - Updated project references to include the new Reachability Drift library.
This commit is contained in:
305
docs/modules/excititor/schemas/issuer_directory_contract.md
Normal file
305
docs/modules/excititor/schemas/issuer_directory_contract.md
Normal file
@@ -0,0 +1,305 @@
|
||||
# Issuer Directory Contract v1.0.0
|
||||
|
||||
**Status:** APPROVED
|
||||
**Version:** 1.0.0
|
||||
**Effective:** 2025-12-19
|
||||
**Owner:** VEX Lens Guild + Issuer Directory Guild
|
||||
**Sprint:** SPRINT_0129_0001_0001 (unblocks VEXLENS-30-003)
|
||||
|
||||
---
|
||||
|
||||
## 1. Purpose
|
||||
|
||||
The Issuer Directory provides a registry of known VEX statement issuers with trust metadata, signing key information, and provenance tracking.
|
||||
|
||||
## 2. Data Model
|
||||
|
||||
### 2.1 Issuer Entity
|
||||
|
||||
```csharp
|
||||
public sealed record Issuer
|
||||
{
|
||||
/// <summary>Unique issuer identifier (e.g., "vendor:redhat", "cert:cisa").</summary>
|
||||
public required string IssuerId { get; init; }
|
||||
|
||||
/// <summary>Issuer category.</summary>
|
||||
public required IssuerCategory Category { get; init; }
|
||||
|
||||
/// <summary>Display name.</summary>
|
||||
public required string DisplayName { get; init; }
|
||||
|
||||
/// <summary>Trust tier assignment.</summary>
|
||||
public required IssuerTrustTier TrustTier { get; init; }
|
||||
|
||||
/// <summary>Official website URL.</summary>
|
||||
public string? WebsiteUrl { get; init; }
|
||||
|
||||
/// <summary>Security advisory feed URL.</summary>
|
||||
public string? AdvisoryFeedUrl { get; init; }
|
||||
|
||||
/// <summary>Registered signing keys.</summary>
|
||||
public ImmutableArray<SigningKeyInfo> SigningKeys { get; init; }
|
||||
|
||||
/// <summary>Products/ecosystems this issuer is authoritative for.</summary>
|
||||
public ImmutableArray<string> AuthoritativeFor { get; init; }
|
||||
|
||||
/// <summary>When this issuer record was created.</summary>
|
||||
public DateTimeOffset CreatedAt { get; init; }
|
||||
|
||||
/// <summary>When this issuer record was last updated.</summary>
|
||||
public DateTimeOffset UpdatedAt { get; init; }
|
||||
|
||||
/// <summary>Whether issuer is active.</summary>
|
||||
public bool IsActive { get; init; } = true;
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 Issuer Category
|
||||
|
||||
```csharp
|
||||
public enum IssuerCategory
|
||||
{
|
||||
/// <summary>Software vendor/maintainer.</summary>
|
||||
Vendor = 0,
|
||||
|
||||
/// <summary>Linux distribution.</summary>
|
||||
Distribution = 1,
|
||||
|
||||
/// <summary>CERT/security response team.</summary>
|
||||
Cert = 2,
|
||||
|
||||
/// <summary>Security research organization.</summary>
|
||||
SecurityResearch = 3,
|
||||
|
||||
/// <summary>Community project.</summary>
|
||||
Community = 4,
|
||||
|
||||
/// <summary>Commercial security vendor.</summary>
|
||||
Commercial = 5
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 Signing Key Info
|
||||
|
||||
```csharp
|
||||
public sealed record SigningKeyInfo
|
||||
{
|
||||
/// <summary>Key fingerprint (SHA-256).</summary>
|
||||
public required string Fingerprint { get; init; }
|
||||
|
||||
/// <summary>Key type (pgp, x509, sigstore).</summary>
|
||||
public required string KeyType { get; init; }
|
||||
|
||||
/// <summary>Key algorithm (rsa, ecdsa, ed25519).</summary>
|
||||
public string? Algorithm { get; init; }
|
||||
|
||||
/// <summary>Key size in bits.</summary>
|
||||
public int? KeySize { get; init; }
|
||||
|
||||
/// <summary>Key creation date.</summary>
|
||||
public DateTimeOffset? CreatedAt { get; init; }
|
||||
|
||||
/// <summary>Key expiration date.</summary>
|
||||
public DateTimeOffset? ExpiresAt { get; init; }
|
||||
|
||||
/// <summary>Whether key is currently valid.</summary>
|
||||
public bool IsValid { get; init; } = true;
|
||||
|
||||
/// <summary>Public key location (URL or inline).</summary>
|
||||
public string? PublicKeyUri { get; init; }
|
||||
}
|
||||
```
|
||||
|
||||
## 3. Pre-Registered Issuers
|
||||
|
||||
### 3.1 Authoritative Tier (Trust Tier 0)
|
||||
|
||||
| Issuer ID | Display Name | Category | Authoritative For |
|
||||
|-----------|--------------|----------|-------------------|
|
||||
| `vendor:redhat` | Red Hat Product Security | Vendor | `pkg:rpm/redhat/*`, `pkg:oci/registry.redhat.io/*` |
|
||||
| `vendor:canonical` | Ubuntu Security Team | Distribution | `pkg:deb/ubuntu/*` |
|
||||
| `vendor:debian` | Debian Security Team | Distribution | `pkg:deb/debian/*` |
|
||||
| `vendor:suse` | SUSE Security Team | Distribution | `pkg:rpm/suse/*`, `pkg:rpm/opensuse/*` |
|
||||
| `vendor:microsoft` | Microsoft Security Response | Vendor | `pkg:nuget/*` (Microsoft packages) |
|
||||
| `vendor:oracle` | Oracle Security | Vendor | `pkg:maven/com.oracle.*/*` |
|
||||
| `vendor:apache` | Apache Security Team | Community | `pkg:maven/org.apache.*/*` |
|
||||
| `vendor:google` | Google Security Team | Vendor | `pkg:golang/google.golang.org/*` |
|
||||
|
||||
### 3.2 Trusted Tier (Trust Tier 1)
|
||||
|
||||
| Issuer ID | Display Name | Category |
|
||||
|-----------|--------------|----------|
|
||||
| `cert:cisa` | CISA | Cert |
|
||||
| `cert:nist` | NIST NVD | Cert |
|
||||
| `cert:github` | GitHub Security Advisories | SecurityResearch |
|
||||
| `cert:snyk` | Snyk Security | Commercial |
|
||||
| `research:oss-fuzz` | Google OSS-Fuzz | SecurityResearch |
|
||||
|
||||
### 3.3 Community Tier (Trust Tier 2)
|
||||
|
||||
| Issuer ID | Display Name | Category |
|
||||
|-----------|--------------|----------|
|
||||
| `community:osv` | OSV (Open Source Vulnerabilities) | Community |
|
||||
| `community:vulndb` | VulnDB | Community |
|
||||
|
||||
## 4. API Endpoints
|
||||
|
||||
### 4.1 List Issuers
|
||||
|
||||
```
|
||||
GET /api/v1/issuers
|
||||
```
|
||||
|
||||
Query Parameters:
|
||||
- `category`: Filter by category
|
||||
- `trust_tier`: Filter by trust tier
|
||||
- `active`: Filter by active status (default: true)
|
||||
- `limit`: Max results (default: 100)
|
||||
- `cursor`: Pagination cursor
|
||||
|
||||
### 4.2 Get Issuer
|
||||
|
||||
```
|
||||
GET /api/v1/issuers/{issuerId}
|
||||
```
|
||||
|
||||
### 4.3 Register Issuer (Admin)
|
||||
|
||||
```
|
||||
POST /api/v1/issuers
|
||||
Authorization: Bearer {admin_token}
|
||||
|
||||
{
|
||||
"issuerId": "vendor:acme",
|
||||
"category": "vendor",
|
||||
"displayName": "ACME Security",
|
||||
"trustTier": "trusted",
|
||||
"websiteUrl": "https://security.acme.example",
|
||||
"advisoryFeedUrl": "https://security.acme.example/feed.json",
|
||||
"authoritativeFor": ["pkg:npm/@acme/*"]
|
||||
}
|
||||
```
|
||||
|
||||
### 4.4 Register Signing Key (Admin)
|
||||
|
||||
```
|
||||
POST /api/v1/issuers/{issuerId}/keys
|
||||
Authorization: Bearer {admin_token}
|
||||
|
||||
{
|
||||
"fingerprint": "sha256:abc123...",
|
||||
"keyType": "pgp",
|
||||
"algorithm": "rsa",
|
||||
"keySize": 4096,
|
||||
"publicKeyUri": "https://security.acme.example/keys/signing.asc"
|
||||
}
|
||||
```
|
||||
|
||||
### 4.5 Lookup by Fingerprint
|
||||
|
||||
```
|
||||
GET /api/v1/issuers/by-fingerprint/{fingerprint}
|
||||
```
|
||||
|
||||
Returns the issuer associated with a signing key fingerprint.
|
||||
|
||||
## 5. Trust Tier Resolution
|
||||
|
||||
### 5.1 Automatic Assignment
|
||||
|
||||
When a VEX statement is received:
|
||||
|
||||
1. **Check signature:** If signed, lookup issuer by key fingerprint
|
||||
2. **Check domain:** Match issuer by advisory feed domain
|
||||
3. **Check authoritativeFor:** Match issuer by product PURL patterns
|
||||
4. **Fallback:** Assign `Unknown` tier if no match
|
||||
|
||||
### 5.2 Override Rules
|
||||
|
||||
Operators can configure trust overrides:
|
||||
|
||||
```yaml
|
||||
# etc/vexlens.yaml
|
||||
issuer_overrides:
|
||||
- issuer_id: "community:custom-feed"
|
||||
trust_tier: "trusted" # Promote community to trusted
|
||||
- issuer_id: "vendor:untrusted-vendor"
|
||||
trust_tier: "community" # Demote vendor to community
|
||||
```
|
||||
|
||||
## 6. Issuer Verification
|
||||
|
||||
### 6.1 PGP Signature Verification
|
||||
|
||||
```csharp
|
||||
public interface IIssuerVerifier
|
||||
{
|
||||
/// <summary>
|
||||
/// Verifies a VEX document signature against registered issuer keys.
|
||||
/// </summary>
|
||||
Task<IssuerVerificationResult> VerifyAsync(
|
||||
byte[] documentBytes,
|
||||
byte[] signatureBytes,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public sealed record IssuerVerificationResult
|
||||
{
|
||||
public bool IsValid { get; init; }
|
||||
public string? IssuerId { get; init; }
|
||||
public string? KeyFingerprint { get; init; }
|
||||
public IssuerTrustTier? TrustTier { get; init; }
|
||||
public string? VerificationError { get; init; }
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 Sigstore Verification
|
||||
|
||||
For Sigstore-signed documents:
|
||||
|
||||
1. Verify Rekor inclusion proof
|
||||
2. Extract OIDC identity from certificate
|
||||
3. Match identity to registered issuer
|
||||
4. Return issuer info with trust tier
|
||||
|
||||
## 7. Database Schema
|
||||
|
||||
```sql
|
||||
CREATE TABLE vex.issuers (
|
||||
issuer_id TEXT PRIMARY KEY,
|
||||
category TEXT NOT NULL,
|
||||
display_name TEXT NOT NULL,
|
||||
trust_tier INT NOT NULL DEFAULT 3,
|
||||
website_url TEXT,
|
||||
advisory_feed_url TEXT,
|
||||
authoritative_for TEXT[] DEFAULT '{}',
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE vex.issuer_signing_keys (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
issuer_id TEXT NOT NULL REFERENCES vex.issuers(issuer_id),
|
||||
fingerprint TEXT NOT NULL UNIQUE,
|
||||
key_type TEXT NOT NULL,
|
||||
algorithm TEXT,
|
||||
key_size INT,
|
||||
public_key_uri TEXT,
|
||||
is_valid BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMPTZ,
|
||||
expires_at TIMESTAMPTZ,
|
||||
registered_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_issuer_signing_keys_fingerprint ON vex.issuer_signing_keys(fingerprint);
|
||||
CREATE INDEX idx_issuers_trust_tier ON vex.issuers(trust_tier);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Changelog
|
||||
|
||||
| Version | Date | Changes |
|
||||
|---------|------|---------|
|
||||
| 1.0.0 | 2025-12-19 | Initial release |
|
||||
271
docs/modules/excititor/schemas/vex_normalization_contract.md
Normal file
271
docs/modules/excititor/schemas/vex_normalization_contract.md
Normal file
@@ -0,0 +1,271 @@
|
||||
# VEX Normalization Contract v1.0.0
|
||||
|
||||
**Status:** APPROVED
|
||||
**Version:** 1.0.0
|
||||
**Effective:** 2025-12-19
|
||||
**Owner:** VEX Lens Guild
|
||||
**Sprint:** SPRINT_0129_0001_0001 (unblocks VEXLENS-30-001 through 30-011)
|
||||
|
||||
---
|
||||
|
||||
## 1. Purpose
|
||||
|
||||
This contract defines the normalization rules for VEX (Vulnerability Exploitability eXchange) documents from multiple sources into a canonical StellaOps internal representation.
|
||||
|
||||
## 2. Supported Input Formats
|
||||
|
||||
| Format | Version | Parser |
|
||||
|--------|---------|--------|
|
||||
| OpenVEX | 0.2.0+ | `OpenVexParser` |
|
||||
| CycloneDX VEX | 1.5+ | `CycloneDxVexParser` |
|
||||
| CSAF VEX | 2.0 | `CsafVexParser` |
|
||||
|
||||
## 3. Canonical Representation
|
||||
|
||||
### 3.1 NormalizedVexStatement
|
||||
|
||||
```csharp
|
||||
public sealed record NormalizedVexStatement
|
||||
{
|
||||
/// <summary>Unique statement identifier (deterministic hash).</summary>
|
||||
public required string StatementId { get; init; }
|
||||
|
||||
/// <summary>CVE or vulnerability identifier.</summary>
|
||||
public required string VulnerabilityId { get; init; }
|
||||
|
||||
/// <summary>Normalized status (not_affected, affected, fixed, under_investigation).</summary>
|
||||
public required VexStatus Status { get; init; }
|
||||
|
||||
/// <summary>Justification code (when status = not_affected).</summary>
|
||||
public VexJustification? Justification { get; init; }
|
||||
|
||||
/// <summary>Human-readable impact statement.</summary>
|
||||
public string? ImpactStatement { get; init; }
|
||||
|
||||
/// <summary>Action statement for remediation.</summary>
|
||||
public string? ActionStatement { get; init; }
|
||||
|
||||
/// <summary>Products affected by this statement.</summary>
|
||||
public required ImmutableArray<ProductIdentifier> Products { get; init; }
|
||||
|
||||
/// <summary>Source document metadata.</summary>
|
||||
public required VexSourceMetadata Source { get; init; }
|
||||
|
||||
/// <summary>Statement timestamp (UTC, ISO-8601).</summary>
|
||||
public required DateTimeOffset Timestamp { get; init; }
|
||||
|
||||
/// <summary>Issuer information.</summary>
|
||||
public required IssuerInfo Issuer { get; init; }
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 VexStatus Enum
|
||||
|
||||
```csharp
|
||||
public enum VexStatus
|
||||
{
|
||||
/// <summary>Product is not affected by the vulnerability.</summary>
|
||||
NotAffected = 0,
|
||||
|
||||
/// <summary>Product is affected and vulnerable.</summary>
|
||||
Affected = 1,
|
||||
|
||||
/// <summary>Product was affected but is now fixed.</summary>
|
||||
Fixed = 2,
|
||||
|
||||
/// <summary>Impact is being investigated.</summary>
|
||||
UnderInvestigation = 3
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 VexJustification Enum
|
||||
|
||||
```csharp
|
||||
public enum VexJustification
|
||||
{
|
||||
/// <summary>Component is not present.</summary>
|
||||
ComponentNotPresent = 0,
|
||||
|
||||
/// <summary>Vulnerable code is not present.</summary>
|
||||
VulnerableCodeNotPresent = 1,
|
||||
|
||||
/// <summary>Vulnerable code is not in execute path.</summary>
|
||||
VulnerableCodeNotInExecutePath = 2,
|
||||
|
||||
/// <summary>Vulnerable code cannot be controlled by adversary.</summary>
|
||||
VulnerableCodeCannotBeControlledByAdversary = 3,
|
||||
|
||||
/// <summary>Inline mitigations exist.</summary>
|
||||
InlineMitigationsAlreadyExist = 4
|
||||
}
|
||||
```
|
||||
|
||||
## 4. Normalization Rules
|
||||
|
||||
### 4.1 Status Mapping
|
||||
|
||||
| Source Format | Source Value | Normalized Status |
|
||||
|---------------|--------------|-------------------|
|
||||
| OpenVEX | `not_affected` | NotAffected |
|
||||
| OpenVEX | `affected` | Affected |
|
||||
| OpenVEX | `fixed` | Fixed |
|
||||
| OpenVEX | `under_investigation` | UnderInvestigation |
|
||||
| CycloneDX | `notAffected` | NotAffected |
|
||||
| CycloneDX | `affected` | Affected |
|
||||
| CycloneDX | `resolved` | Fixed |
|
||||
| CycloneDX | `inTriage` | UnderInvestigation |
|
||||
| CSAF | `not_affected` | NotAffected |
|
||||
| CSAF | `known_affected` | Affected |
|
||||
| CSAF | `fixed` | Fixed |
|
||||
| CSAF | `under_investigation` | UnderInvestigation |
|
||||
|
||||
### 4.2 Justification Mapping
|
||||
|
||||
| Source Format | Source Value | Normalized Justification |
|
||||
|---------------|--------------|--------------------------|
|
||||
| OpenVEX | `component_not_present` | ComponentNotPresent |
|
||||
| OpenVEX | `vulnerable_code_not_present` | VulnerableCodeNotPresent |
|
||||
| OpenVEX | `vulnerable_code_not_in_execute_path` | VulnerableCodeNotInExecutePath |
|
||||
| OpenVEX | `vulnerable_code_cannot_be_controlled_by_adversary` | VulnerableCodeCannotBeControlledByAdversary |
|
||||
| OpenVEX | `inline_mitigations_already_exist` | InlineMitigationsAlreadyExist |
|
||||
| CycloneDX | Same as OpenVEX (camelCase) | Same mapping |
|
||||
| CSAF | `component_not_present` | ComponentNotPresent |
|
||||
| CSAF | `vulnerable_code_not_present` | VulnerableCodeNotPresent |
|
||||
| CSAF | `vulnerable_code_not_in_execute_path` | VulnerableCodeNotInExecutePath |
|
||||
| CSAF | `vulnerable_code_cannot_be_controlled_by_adversary` | VulnerableCodeCannotBeControlledByAdversary |
|
||||
| CSAF | `inline_mitigations_already_exist` | InlineMitigationsAlreadyExist |
|
||||
|
||||
### 4.3 Product Identifier Normalization
|
||||
|
||||
Products are normalized to PURL (Package URL) format:
|
||||
|
||||
```
|
||||
pkg:{ecosystem}/{namespace}/{name}@{version}?{qualifiers}#{subpath}
|
||||
```
|
||||
|
||||
| Source | Extraction Method |
|
||||
|--------|-------------------|
|
||||
| OpenVEX | Direct from `product.id` if PURL, else construct from `product.identifiers` |
|
||||
| CycloneDX | From `bom-ref` PURL or construct from `component.purl` |
|
||||
| CSAF | From `product_id` → `product_identification_helper.purl` |
|
||||
|
||||
### 4.4 Statement ID Generation
|
||||
|
||||
Statement IDs are deterministic SHA-256 hashes:
|
||||
|
||||
```csharp
|
||||
public static string GenerateStatementId(
|
||||
string vulnerabilityId,
|
||||
VexStatus status,
|
||||
IEnumerable<string> productPurls,
|
||||
string issuerId,
|
||||
DateTimeOffset timestamp)
|
||||
{
|
||||
var input = $"{vulnerabilityId}|{status}|{string.Join(",", productPurls.OrderBy(p => p))}|{issuerId}|{timestamp:O}";
|
||||
var hash = SHA256.HashData(Encoding.UTF8.GetBytes(input));
|
||||
return $"stmt:{Convert.ToHexString(hash).ToLowerInvariant()[..32]}";
|
||||
}
|
||||
```
|
||||
|
||||
## 5. Issuer Directory Integration
|
||||
|
||||
Normalized statements include issuer information from the Issuer Directory:
|
||||
|
||||
```csharp
|
||||
public sealed record IssuerInfo
|
||||
{
|
||||
/// <summary>Issuer identifier (e.g., "vendor:redhat", "vendor:canonical").</summary>
|
||||
public required string IssuerId { get; init; }
|
||||
|
||||
/// <summary>Display name.</summary>
|
||||
public required string DisplayName { get; init; }
|
||||
|
||||
/// <summary>Trust tier (authoritative, trusted, community, unknown).</summary>
|
||||
public required IssuerTrustTier TrustTier { get; init; }
|
||||
|
||||
/// <summary>Issuer's signing key fingerprints (if signed).</summary>
|
||||
public ImmutableArray<string> SigningKeyFingerprints { get; init; }
|
||||
}
|
||||
|
||||
public enum IssuerTrustTier
|
||||
{
|
||||
Authoritative = 0, // Vendor/maintainer of the product
|
||||
Trusted = 1, // Known security research org
|
||||
Community = 2, // Community contributor
|
||||
Unknown = 3 // Unverified source
|
||||
}
|
||||
```
|
||||
|
||||
## 6. API Governance
|
||||
|
||||
### 6.1 Endpoints
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
|----------|--------|-------------|
|
||||
| `/api/v1/vex/statements` | GET | Query normalized statements |
|
||||
| `/api/v1/vex/statements/{id}` | GET | Get specific statement |
|
||||
| `/api/v1/vex/normalize` | POST | Normalize a VEX document |
|
||||
| `/api/v1/vex/issuers` | GET | List known issuers |
|
||||
| `/api/v1/vex/issuers/{id}` | GET | Get issuer details |
|
||||
|
||||
### 6.2 Query Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `vulnerability` | string | Filter by CVE/vulnerability ID |
|
||||
| `product` | string | Filter by PURL (URL-encoded) |
|
||||
| `status` | enum | Filter by VEX status |
|
||||
| `issuer` | string | Filter by issuer ID |
|
||||
| `since` | datetime | Statements after timestamp |
|
||||
| `limit` | int | Max results (default: 100, max: 1000) |
|
||||
| `cursor` | string | Pagination cursor |
|
||||
|
||||
### 6.3 Response Format
|
||||
|
||||
```json
|
||||
{
|
||||
"statements": [
|
||||
{
|
||||
"statementId": "stmt:a1b2c3d4e5f6...",
|
||||
"vulnerabilityId": "CVE-2024-1234",
|
||||
"status": "not_affected",
|
||||
"justification": "vulnerable_code_not_in_execute_path",
|
||||
"products": ["pkg:npm/lodash@4.17.21"],
|
||||
"issuer": {
|
||||
"issuerId": "vendor:lodash",
|
||||
"displayName": "Lodash Maintainers",
|
||||
"trustTier": "authoritative"
|
||||
},
|
||||
"timestamp": "2024-12-19T10:30:00Z"
|
||||
}
|
||||
],
|
||||
"cursor": "next_page_token",
|
||||
"total": 42
|
||||
}
|
||||
```
|
||||
|
||||
## 7. Precedence Rules
|
||||
|
||||
When multiple statements exist for the same vulnerability+product:
|
||||
|
||||
1. **Timestamp:** Later statements supersede earlier ones
|
||||
2. **Trust Tier:** Higher trust tiers take precedence (Authoritative > Trusted > Community > Unknown)
|
||||
3. **Specificity:** More specific product matches win (exact version > version range > package)
|
||||
|
||||
## 8. Validation
|
||||
|
||||
All normalized statements must pass:
|
||||
|
||||
1. `vulnerabilityId` matches CVE/GHSA/vendor pattern
|
||||
2. `status` is a valid enum value
|
||||
3. `products` contains at least one valid PURL
|
||||
4. `timestamp` is valid ISO-8601 UTC
|
||||
5. `issuer.issuerId` exists in Issuer Directory or is marked Unknown
|
||||
|
||||
---
|
||||
|
||||
## Changelog
|
||||
|
||||
| Version | Date | Changes |
|
||||
|---------|------|---------|
|
||||
| 1.0.0 | 2025-12-19 | Initial release |
|
||||
Reference in New Issue
Block a user