consolidation of some of the modules, localization fixes, product advisories work, qa work
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://stellaops.dev/schemas/excititor/connector-signer-metadata.schema.json",
|
||||
"title": "Excititor Connector Signer Metadata",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["schemaVersion", "generatedAt", "connectors"],
|
||||
"properties": {
|
||||
"schemaVersion": {
|
||||
"type": "string",
|
||||
"pattern": "^1\\.0\\.0$"
|
||||
},
|
||||
"generatedAt": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"connectors": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/$defs/connector"
|
||||
}
|
||||
}
|
||||
},
|
||||
"$defs": {
|
||||
"connector": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"connectorId",
|
||||
"provider",
|
||||
"issuerTier",
|
||||
"signers"
|
||||
],
|
||||
"properties": {
|
||||
"connectorId": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-z0-9:-\\.]+$"
|
||||
},
|
||||
"provider": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["name", "slug"],
|
||||
"properties": {
|
||||
"name": { "type": "string", "minLength": 3 },
|
||||
"slug": { "type": "string", "pattern": "^[a-z0-9-]+$" }
|
||||
}
|
||||
},
|
||||
"issuerTier": {
|
||||
"type": "string",
|
||||
"enum": ["tier-0", "tier-1", "tier-2", "untrusted"]
|
||||
},
|
||||
"signers": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": { "$ref": "#/$defs/signer" }
|
||||
},
|
||||
"bundle": { "$ref": "#/$defs/bundleRef" },
|
||||
"validFrom": { "type": "string", "format": "date" },
|
||||
"validTo": { "type": "string", "format": "date" },
|
||||
"revoked": { "type": "boolean", "default": false },
|
||||
"notes": { "type": "string", "maxLength": 2000 }
|
||||
}
|
||||
},
|
||||
"signer": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["usage", "fingerprints"],
|
||||
"properties": {
|
||||
"usage": {
|
||||
"type": "string",
|
||||
"enum": ["csaf", "oval", "openvex", "bulk-meta", "attestation"]
|
||||
},
|
||||
"fingerprints": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": { "$ref": "#/$defs/fingerprint" }
|
||||
},
|
||||
"keyLocator": {
|
||||
"type": "string",
|
||||
"description": "Path or URL (mirror/OCI/TUF) where the signing key or certificate chain can be retrieved in offline kits."
|
||||
},
|
||||
"certificateChain": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"description": "Optional PEM-encoded certificates for x509/cosign keys."
|
||||
}
|
||||
}
|
||||
},
|
||||
"fingerprint": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["alg", "value"],
|
||||
"properties": {
|
||||
"alg": {
|
||||
"type": "string",
|
||||
"enum": ["sha256", "sha512", "sha1"]
|
||||
},
|
||||
"format": {
|
||||
"type": "string",
|
||||
"enum": ["pgp", "x509-spki", "x509-ski", "cosign", "pem"]
|
||||
},
|
||||
"value": {
|
||||
"type": "string",
|
||||
"minLength": 16,
|
||||
"maxLength": 128
|
||||
}
|
||||
}
|
||||
},
|
||||
"bundleRef": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["kind", "uri"],
|
||||
"properties": {
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"enum": ["oci-referrer", "oci-tag", "file", "tuf"]
|
||||
},
|
||||
"uri": { "type": "string", "minLength": 8 },
|
||||
"digest": { "type": "string", "minLength": 32 },
|
||||
"publishedAt": { "type": "string", "format": "date-time" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 |
|
||||
82
docs-archived/modules/excititor/schemas/vex-chunk-api.yaml
Normal file
82
docs-archived/modules/excititor/schemas/vex-chunk-api.yaml
Normal file
@@ -0,0 +1,82 @@
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: StellaOps Excititor Chunk API
|
||||
version: "0.1.0"
|
||||
description: |
|
||||
Frozen for Sprint 110 (EXCITITOR-AIAI-31-002). Aligns with Evidence Locker attestation contract v1.
|
||||
servers:
|
||||
- url: https://excitor.local
|
||||
paths:
|
||||
/vex/evidence/chunks:
|
||||
post:
|
||||
summary: Submit VEX evidence chunk (aggregation-only)
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/x-ndjson:
|
||||
schema:
|
||||
$ref: '#/components/schemas/VexChunk'
|
||||
responses:
|
||||
'202':
|
||||
description: Accepted for processing
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [chunk_digest, queue_id]
|
||||
properties:
|
||||
chunk_digest:
|
||||
type: string
|
||||
description: sha256 of canonical chunk JSON
|
||||
queue_id:
|
||||
type: string
|
||||
description: Background job identifier
|
||||
'400':
|
||||
description: Validation error
|
||||
components:
|
||||
schemas:
|
||||
VexChunk:
|
||||
type: object
|
||||
required: [chunk_id, tenant, source, schema, items, provenance]
|
||||
properties:
|
||||
chunk_id:
|
||||
type: string
|
||||
format: uuid
|
||||
tenant:
|
||||
type: string
|
||||
source:
|
||||
type: string
|
||||
description: feed id (e.g., ghsa, nvd)
|
||||
schema:
|
||||
type: string
|
||||
enum: [stellaops.vex.chunk.v1]
|
||||
items:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required: [advisory_id, status, purl]
|
||||
properties:
|
||||
advisory_id:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
enum: [affected, unaffected, under_investigation, fixed, unknown]
|
||||
purl:
|
||||
type: string
|
||||
justification:
|
||||
type: string
|
||||
last_observed:
|
||||
type: string
|
||||
format: date-time
|
||||
provenance:
|
||||
type: object
|
||||
required: [fetched_at, artifact_sha]
|
||||
properties:
|
||||
fetched_at:
|
||||
type: string
|
||||
format: date-time
|
||||
artifact_sha:
|
||||
type: string
|
||||
signature:
|
||||
type: object
|
||||
nullable: true
|
||||
@@ -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 |
|
||||
149
docs-archived/modules/excititor/schemas/vex_overlay.schema.json
Normal file
149
docs-archived/modules/excititor/schemas/vex_overlay.schema.json
Normal file
@@ -0,0 +1,149 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://stellaops.dev/schemas/excititor/vex_overlay.schema.json",
|
||||
"title": "Excititor VEX Overlay",
|
||||
"description": "Graph-ready overlay built from Link-Not-Merge observations and linksets. Immutable and append-only; ordered for deterministic pagination and caching.",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"schemaVersion",
|
||||
"generatedAt",
|
||||
"tenant",
|
||||
"purl",
|
||||
"advisoryId",
|
||||
"source",
|
||||
"status",
|
||||
"observations",
|
||||
"provenance"
|
||||
],
|
||||
"properties": {
|
||||
"schemaVersion": {
|
||||
"type": "string",
|
||||
"enum": ["1.0.0"]
|
||||
},
|
||||
"generatedAt": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"tenant": {
|
||||
"type": "string",
|
||||
"description": "Tenant identifier used for storage partitioning."
|
||||
},
|
||||
"purl": {
|
||||
"type": "string",
|
||||
"description": "Normalized package URL for the component."
|
||||
},
|
||||
"advisoryId": {
|
||||
"type": "string",
|
||||
"description": "Upstream advisory identifier (e.g., GHSA, RHSA, CVE)."
|
||||
},
|
||||
"source": {
|
||||
"type": "string",
|
||||
"description": "Linkset source identifier (matches Concelier linkset source)."
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"affected",
|
||||
"not_affected",
|
||||
"under_investigation",
|
||||
"fixed",
|
||||
"unknown"
|
||||
]
|
||||
},
|
||||
"justifications": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["kind", "reason"],
|
||||
"properties": {
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"description": "Reason code aligned to VEX statement taxonomy."
|
||||
},
|
||||
"reason": {
|
||||
"type": "string",
|
||||
"description": "Human-readable justification text."
|
||||
},
|
||||
"evidence": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "Observation or linkset id contributing to this justification."
|
||||
}
|
||||
},
|
||||
"weight": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 1,
|
||||
"description": "Optional confidence weight."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"conflicts": {
|
||||
"type": "array",
|
||||
"description": "Conflicts detected in linkset normalization.",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["field", "reason"],
|
||||
"properties": {
|
||||
"field": { "type": "string" },
|
||||
"reason": { "type": "string" },
|
||||
"values": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"sourceIds": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"observations": {
|
||||
"type": "array",
|
||||
"description": "Ordered list of Link-Not-Merge observation references feeding this overlay.",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["id", "contentHash", "fetchedAt"],
|
||||
"properties": {
|
||||
"id": { "type": "string" },
|
||||
"contentHash": { "type": "string", "pattern": "^sha256:[A-Fa-f0-9]{64}$" },
|
||||
"fetchedAt": { "type": "string", "format": "date-time" }
|
||||
}
|
||||
},
|
||||
"minItems": 1
|
||||
},
|
||||
"provenance": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["linksetId", "linksetHash", "observationHashes"],
|
||||
"properties": {
|
||||
"linksetId": { "type": "string" },
|
||||
"linksetHash": { "type": "string", "pattern": "^sha256:[A-Fa-f0-9]{64}$" },
|
||||
"observationHashes": {
|
||||
"type": "array",
|
||||
"items": { "type": "string", "pattern": "^sha256:[A-Fa-f0-9]{64}$" },
|
||||
"minItems": 1
|
||||
},
|
||||
"policyHash": { "type": "string" },
|
||||
"sbomContextHash": { "type": "string" },
|
||||
"planCacheKey": { "type": "string" },
|
||||
"generatedBy": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"cache": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"cached": { "type": "boolean" },
|
||||
"cachedAt": { "type": "string", "format": "date-time" },
|
||||
"ttlSeconds": { "type": "integer", "minimum": 0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
37
docs-archived/modules/excititor/schemas/vex_raw.schema.json
Normal file
37
docs-archived/modules/excititor/schemas/vex_raw.schema.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$id": "https://stellaops.dev/schemas/excititor/vex_raw.schema.json",
|
||||
"title": "Excititor VEX Raw Document",
|
||||
"$comment": "Note (2025-12): The gridFsObjectId field is legacy. Since Sprint 4400, all large content is stored in PostgreSQL with RustFS. This field exists only for backward compatibility with migrated data.",
|
||||
"type": "object",
|
||||
"additionalProperties": true,
|
||||
"required": ["_id", "providerId", "format", "sourceUri", "retrievedAt", "digest"],
|
||||
"properties": {
|
||||
"_id": {
|
||||
"type": "string",
|
||||
"description": "Content-addressed digest; equals `digest`."
|
||||
},
|
||||
"providerId": { "type": "string", "minLength": 1 },
|
||||
"format": { "type": "string", "enum": ["csaf", "cyclonedx", "openvex"] },
|
||||
"sourceUri": { "type": "string", "minLength": 1 },
|
||||
"retrievedAt": { "type": "string", "format": "date-time" },
|
||||
"digest": { "type": "string", "minLength": 32 },
|
||||
"content": {
|
||||
"oneOf": [
|
||||
{ "type": "string", "contentEncoding": "base64" },
|
||||
{ "type": "string" }
|
||||
],
|
||||
"description": "Inline payload if below size threshold; may be empty when stored in RustFS (legacy: GridFS prior to Sprint 4400)."
|
||||
},
|
||||
"gridFsObjectId": {
|
||||
"anyOf": [
|
||||
{ "type": "string" },
|
||||
{ "type": "null" }
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"type": "object",
|
||||
"additionalProperties": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user