# Vulnerability Triage UX & VEX-First Decisioning **Version:** 1.0 **Date:** 2025-11-28 **Status:** Canonical This advisory defines the **end-to-end UX and data contracts** for vulnerability triage, VEX decisioning, evidence/explainability views, and audit export in Stella Ops. It synthesizes patterns from Snyk, GitLab SCA, Harbor/Trivy, and Anchore Enterprise into a converged UX layer. --- ## 1. Scope This spec covers: 1. **Vulnerability triage** (first touch) 2. **Suppression / "Not Affected"** (VEX-aligned) 3. **Evidence & explainability views** 4. **Audit export** (immutable bundles) 5. **Attestations** as the backbone of evidence and gating Stella Ops is the **converged UX layer** over scanner backends (Snyk, Trivy, GitLab, Anchore, or others). --- ## 2. Industry Pattern Analysis ### 2.1 Triage (First Touch) | Tool | Pattern | Stella Ops Mirror | |------|---------|-------------------| | **Snyk** | PR checks show before/after diffs; Fix PRs directly from Issues list | Evidence-first cards with "Fix PR" CTA | | **GitLab SCA** | Vulnerability Report with `Needs triage` default state | Status workflow starting at `DETECTED` | | **Harbor/Trivy** | Project -> Artifacts -> Vulnerabilities panel with Rescan CTA | Artifact-centric navigation with scan badges | | **Anchore** | Images -> Vulnerabilities aligned to Policies (pass/fail) | Policy gate indicators on all finding views | **UI pattern to reuse:** An **evidence-first card** per finding (CVE, package, version, path) with primary actions (Fix PR, Dismiss/Not Affected, View Evidence). ### 2.2 Suppression / "Not Affected" (VEX-Aligned) | Tool | Pattern | Stella Ops Mirror | |------|---------|-------------------| | **Snyk** | "Ignore" with reason + expiry; org-restricted; PR checks skip ignored | VEX `statusJustification` with validity window | | **GitLab** | `Dismissed` status with required comment; activity log | VEX decisions with actor/timestamp/audit trail | | **Anchore** | Allowlists + Policy Gates + VEX annotations | Allowlist integration + VEX buttons | | **Harbor/Trivy** | No native VEX; store as in-toto attestation | Attestation-backed VEX decisions | **UI pattern to reuse:** An **Actionable VEX** button (`Not Affected`, `Affected - mitigated`, `Fixed`) that opens a compact form: justification, evidence links, scope, expiry -> generates/updates a signed VEX note. ### 2.3 Evidence View (Explainability) | Tool | Pattern | Stella Ops Mirror | |------|---------|-------------------| | **Snyk** | PR context + Fix PR evidence + ignore policy display | Explainability panel with PR/commit links | | **GitLab** | Vulnerability Report hub with lifecycle activity | Decision history timeline | | **Anchore** | Policy Gates breakdown showing which trigger caused fail/pass | Gate evaluation with trigger explanations | | **Harbor/Trivy** | Scanner DB date, version, attestation links | Scanner metadata + attestation digest | **UI pattern to reuse:** An **Explainability panel** on the right: "Why this is flagged / Why it passed" with timestamps, rule IDs, feed freshness, and the **Attestation digest**. ### 2.4 Audit Export (Immutable) | Tool | Export Contents | |------|-----------------| | **Snyk** | PR check results + Ignore ledger + Fix PRs | | **GitLab** | Vulnerability Report with status history | | **Anchore** | Policy Bundle eval JSON as primary audit unit | | **Harbor/Trivy** | Trivy report + signed attestation | **UI pattern to reuse:** **"Create immutable audit bundle"** CTA that writes a ZIP/OCI artifact containing reports, VEX, policy evals, and attestations, plus a top-level manifest with hashes. --- ## 3. Core Data Model ### 3.1 Artifact ```text Artifact - id (string, stable) - type (IMAGE | REPO | SBOM | FUNCTION | HOST) - displayName - coordinates (registry/repo URL, tag, branch, env, etc.) - digests[] (e.g. sha256 for OCI images, commit SHA for repos) - latestScanAttestations[] (AttestationRef) - riskSummary (openCount, totalCount, maxSeverity, lastScanAt) ``` ### 3.2 VulnerabilityFinding ```text VulnerabilityFinding - id (string, internal stable ID) - sourceFindingId (string, from Snyk/Trivy/etc.) - scanner (name, version) - artifactId - vulnerabilityId (CVE, GHSA, etc.) - title - severity (CRITICAL | HIGH | MEDIUM | LOW | INFO) - package (name, version, ecosystem) - location (filePath, containerLayer, function, callPath[]) - introducedBy (commitId?, imageDigest?, buildId?) - firstSeenAt - lastSeenAt - status (DETECTED | RESOLVED | NO_LONGER_DETECTED) - currentVexDecisionId? (if a VEX decision is attached) - evidenceAttestationRefs[] (AttestationRef[]) ``` ### 3.3 VEXDecision Represents a **VEX-style statement** attached to a finding + subject. ```text VEXDecision - id - vulnerabilityId (CVE, etc.) - subject (ArtifactRef / SBOM node ref) - status (NOT_AFFECTED | AFFECTED_MITIGATED | AFFECTED_UNMITIGATED | FIXED) - justificationType (enum; see section 7.3) - justificationText (free text) - evidenceRefs[] (links to PRs, commits, tickets, docs, etc.) - scope (envs/projects where this decision applies) - validFor (notBefore, notAfter?) - attestationRef? (AttestationRef) - supersedesDecisionId? - createdBy (id, displayName) - createdAt - updatedAt ``` ### 3.4 Attestation / AttestationRef ```text AttestationRef - id - type (VULN_SCAN | SBOM | VEX | POLICY_EVAL | OTHER) - statementId (if DSSE/Intoto) - subjectName - subjectDigest (e.g. sha256) - predicateType (URI) - createdAt - signer (name, keyId) - storage (ociRef | bundlePath | url) ``` ### 3.5 PolicyEvaluation ```text PolicyEvaluation - id - subject (ArtifactRef) - policyBundleVersion - overallResult (PASS | WARN | FAIL) - gates[] (GateResult) - attestationRef? (AttestationRef) - evaluatedAt ``` ### 3.6 AuditBundle Represents a **downloadable immutable bundle** (ZIP or OCI artifact). ```text AuditBundle - bundleId - version - createdAt - createdBy - subject (ArtifactRef) - index (AuditBundleIndex) <- JSON index inside the bundle ``` --- ## 4. Primary UX Surfaces ### 4.1 Artifacts List **Goal:** High-level "what's risky?" view and entry point into triage. **Columns:** - Artifact - Type - Environment(s) - Open / Total vulns - Max severity - **Attestations** (badge w/ count) - Last scan (timestamp + scanner) **Actions:** - View vulnerabilities (primary) - View attestations - Create audit bundle ### 4.2 Vulnerability Workspace (per Artifact) **Split layout:** **Left: Vulnerability list** - Filters: severity, status, VEX status, scanner, package, introducedBy, env - Sort: severity, recency, package, path - Badges for: - `New` (first seen in last N scans) - `VEX: Not affected` - `Policy: blocked` / `Policy: allowed` **Right: Evidence / Explainability panel** Tabs: 1. **Overview** - Title, severity, package, version, path - Scanner + db date - Finding history timeline - Current VEX decision summary (if any) 2. **Reachability** - Call path, modules, runtime usage info (when available) 3. **Policy** - Policy evaluation: which gate caused pass/fail - Links to gate definitions 4. **Attestations** - All attestations that mention: - this artifact - this vulnerabilityId - this scan result **Primary actions per finding:** - **VEX: Set status** -> opens VEX Modal (see 4.3) - **Open Fix PR / View Fix** (if available from Snyk/GitLab) - **Attach Evidence** (link tickets / docs) - **Copy audit reference** (findingId + attestation digest) ### 4.3 VEX Modal - "Affect & Justification" **Entry points:** - From a finding row ("VEX" button) - From a policy failure explanation - From a bulk action on multiple findings **Fields (backed by `VEXDecision`):** - Status (radio buttons): - `Not affected` - `Affected - mitigated` - `Affected - not mitigated` - `Fixed` - Justification type (select - see section 7.3) - Justification text (multi-line) - Scope: - Environments (multi-select) - Projects / services (multi-select) - Validity: - Start (defaults now) - Optional expiry (recommended) - Evidence: - Add links (PR, ticket, doc, commit) - Attach attestation (optional; pick from list) - Review: - Summary of what will be written to the VEX statement - "Will generate signed attestation" note (if enabled) **Actions:** - Save (creates or updates VEXDecision, writes VEX attestation) - Cancel - View raw JSON (for power users) ### 4.4 Attestations View Per artifact, tab: **Attestations** Table of attestations: - Type (vuln scan, SBOM, VEX, policy) - Subject name (shortened) - Predicate type (URI) - Scanner / policy engine (derived from predicate) - Signer (keyId, trusted/not-trusted badge) - Created at - Verified (yes/no) Click to open: - Header: statement id, subject, signer - Predicate preview: - For vuln scan: counts, scanner version, db date - For SBOM: bomRef, component counts - For VEX: decision status, vulnerabilityId, scope ### 4.5 Policy & Gating View Per environment / pipeline: - Matrix of **gates** vs **subject types**: - e.g. `CI Build`, `Registry Admission`, `Runtime Admission` - Each gate shows: - Rule description (severity thresholds, allowlist usage, required attestations) - Last evaluation stats (pass/fail counts) - Clicking a gate shows: - Recent evaluations (with link to artifact & policy attestation) - Which condition failed ### 4.6 Audit Export - Bundle Creation **From:** - Artifact page (button: "Create immutable audit bundle") - Pipeline run detail - Policy evaluation detail **Workflow:** 1. User selects: - Subject artifact + digest - Time window (e.g. "last 7 days of scans & decisions") - Included content (checklist): - Vuln reports - SBOM - VEX decisions - Policy evaluations - Raw attestations 2. Backend generates: - ZIP or OCI artifact - `audit-bundle-index.json` at root 3. UI shows: - Bundle ID & hash - Download button - OCI reference (if pushed to registry) --- ## 5. State Model ### 5.1 Finding Status vs VEX Status Two separate but related states: **Finding.status:** - `DETECTED` - currently reported by at least one scanner - `NO_LONGER_DETECTED` - was present, not in latest scan for this subject - `RESOLVED` - confirmed removed (e.g. package upgraded, image replaced) **VEXDecision.status:** - `NOT_AFFECTED` - `AFFECTED_MITIGATED` - `AFFECTED_UNMITIGATED` - `FIXED` **UI rules:** - If `Finding.status = NO_LONGER_DETECTED` and a VEXDecision still exists: - Show badge: "Historical VEX decision (finding no longer detected)" - If `VEXDecision.status = NOT_AFFECTED`: - Policy engines may treat this as **non-blocking** (configurable) --- ## 6. Interaction Patterns to Mirror ### 6.1 From Snyk - PR checks show **before/after** and don't fail on ignored issues - Action: "Fix PR" from a finding - Mapping: - Stella Ops should show "Fix PR" and "Compare before/after" where data exists - VEX `NOT_AFFECTED` should make **future checks ignore** that finding for that subject/scope ### 6.2 From GitLab SCA - `Dismissed` with reasons and activity log - Mapping: - VEX decisions must have reason + actor + timestamp - The activity log should show a full **decision history** ### 6.3 From Anchore - Policy gates & allowlists - Mapping: - Gate evaluation screen with clear "this gate failed because..." explanation --- ## 7. Enumerations & Conventions ### 7.1 VEX Status ```text NOT_AFFECTED AFFECTED_MITIGATED AFFECTED_UNMITIGATED FIXED ``` ### 7.2 VEX Scope - `envs[]`: e.g. `["prod", "staging"]` - `projects[]`: service / app names - Default: applies to **all** unless restricted ### 7.3 Justification Type (inspired by CSAF/VEX) ```text CODE_NOT_PRESENT CODE_NOT_REACHABLE VULNERABLE_CODE_NOT_IN_EXECUTE_PATH CONFIGURATION_NOT_AFFECTED OS_NOT_AFFECTED RUNTIME_MITIGATION_PRESENT COMPENSATING_CONTROLS ACCEPTED_BUSINESS_RISK OTHER ``` --- ## 8. Attestation Placement ### 8.1 Trivy + Cosign Generate **vulnerability-scan attestation** and SBOM attestation; attach to image via OCI referrers. These attestations become the source of truth for evidence and audit export. ### 8.2 Harbor Treat attestations as first-class accessories/refs to the image. Surface them next to the Vulnerabilities tab. Link them into the explainability panel. ### 8.3 Anchore Reference attestation digests inside **Policy evaluation** output so pass/fail is traceable to signed inputs. ### 8.4 Snyk/GitLab Surface attestation presence in PR/Security dashboards to prove findings came from a **signed** scan; link out to the OCI digest. **UI pattern:** Small **"Signed evidence"** pill on each finding; clicking opens the attestation JSON (human-readable view) + verify command snippet. --- ## 9. Gating Controls | Tool | Mechanism | Stella Ops Mirror | |------|-----------|-------------------| | **Anchore** | Policy Gates/Triggers model for hard gates | Gates per environment with trigger explainability | | **Snyk** | PR checks + Auto Fix PRs as soft gates | PR integration with soft/hard gate toggles | | **GitLab** | MR approvals + Security Policies; auto-resolve on no-longer-detected | Status-aware policies with auto-resolution | | **Harbor** | External policy engines (Kyverno/OPA) verify signatures/attestations | Admission controller integration | --- ## 10. Minimal UI Wireframe ### 10.1 Artifacts List | Image | Tag | Risk (open/total) | Attestations | Last scan | |-------|-----|-------------------|--------------|-----------| | app/service | v1.2.3 | 3/47 | 4 | 2h ago (Trivy) | ### 10.2 Artifact -> Vulnerabilities Tab (Evidence-First) ``` +----------------------------------+-----------------------------------+ | Finding Cards (scrollable) | Explainability Panel | | | | | [CVE-2024-1234] CRITICAL | Overview | Reachability | Policy | | openssl 3.0.14 -> 3.0.15 | | | [Fix PR] [VEX: Not Affected] | Scanner: Trivy 0.53.0 | | [Attach Evidence] | DB: 2025-11-27 | | | Attestation: sha256:2e61... | | [CVE-2024-5678] HIGH | | | log4j 2.17.0 | [Why flagged] | | [VEX: Mitigated] | - version.match: 2.17.0 < 2.17.1 | | | - gate: severity >= HIGH | +----------------------------------+-----------------------------------+ ``` ### 10.3 Policy View Gate rules (like Anchore) with preview + dry-run; show which triggers cause failure. ### 10.4 Audit **"Create immutable audit bundle"** -> produces ZIP/OCI artifact with reports, VEX JSON, policy evals, and in-toto/DSSE attestations. ### 10.5 Registry/Admission "Ready to deploy" badge when all gates met and required attestations verified. --- ## 11. API Endpoints (High-Level) ```text GET /artifacts GET /artifacts/{id}/vulnerabilities GET /vulnerabilities/{id} POST /vex-decisions PATCH /vex-decisions/{id} GET /artifacts/{id}/attestations POST /audit-bundles GET /audit-bundles/{bundleId} ``` --- ## 12. JSON Schema Locations The following schemas should be created/maintained: - `docs/schemas/vex-decision.schema.json` - VEX decision form schema - `docs/schemas/attestation-vuln-scan.schema.json` - Vulnerability scan attestation - `docs/schemas/audit-bundle-index.schema.json` - Audit bundle manifest --- ## 13. Related Advisories - `27-Nov-2025 - Explainability Layer for Vulnerability Verdicts.md` - Evidence chain model - `27-Nov-2025 - Making Graphs Understandable to Humans.md` - Graph navigation UX - `25-Nov-2025 - Define Safe VEX 'Not Affected' Claims with Proofs.md` - VEX proof requirements --- ## 14. Sprint Integration This advisory maps to: - **SPRINT_0215_0001_0001_vuln_triage_ux.md** (NEW) - UI triage workspace implementation - **SPRINT_210_ui_ii.md** - VEX tab tasks (UI-LNM-22-003) - **SPRINT_0334_docs_modules_vuln_explorer.md** - Module documentation updates --- *Last updated: 2025-11-28*