16 KiB
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:
- Vulnerability triage (first touch)
- Suppression / "Not Affected" (VEX-aligned)
- Evidence & explainability views
- Audit export (immutable bundles)
- 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
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
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.
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
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
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).
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 affectedPolicy: blocked/Policy: allowed
Right: Evidence / Explainability panel
Tabs:
- Overview
- Title, severity, package, version, path
- Scanner + db date
- Finding history timeline
- Current VEX decision summary (if any)
- Reachability
- Call path, modules, runtime usage info (when available)
- Policy
- Policy evaluation: which gate caused pass/fail
- Links to gate definitions
- Attestations
- All attestations that mention:
- this artifact
- this vulnerabilityId
- this scan result
- All attestations that mention:
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 affectedAffected - mitigatedAffected - not mitigatedFixed
- 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
- e.g.
- 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:
- 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
- Backend generates:
- ZIP or OCI artifact
audit-bundle-index.jsonat root
- 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 scannerNO_LONGER_DETECTED- was present, not in latest scan for this subjectRESOLVED- confirmed removed (e.g. package upgraded, image replaced)
VEXDecision.status:
NOT_AFFECTEDAFFECTED_MITIGATEDAFFECTED_UNMITIGATEDFIXED
UI rules:
- If
Finding.status = NO_LONGER_DETECTEDand 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_AFFECTEDshould make future checks ignore that finding for that subject/scope
6.2 From GitLab SCA
Dismissedwith 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
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)
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)
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 schemadocs/schemas/attestation-vuln-scan.schema.json- Vulnerability scan attestationdocs/schemas/audit-bundle-index.schema.json- Audit bundle manifest
13. Related Advisories
27-Nov-2025 - Explainability Layer for Vulnerability Verdicts.md- Evidence chain model27-Nov-2025 - Making Graphs Understandable to Humans.md- Graph navigation UX25-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