Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
- Introduced `ui_bench_driver.mjs` to read scenarios and fixture manifest, generating a deterministic run plan. - Created `ui_bench_plan.md` outlining the purpose, scope, and next steps for the benchmark. - Added `ui_bench_scenarios.json` containing various scenarios for graph UI interactions. - Implemented tests for CLI commands, ensuring bundle verification and telemetry defaults. - Developed schemas for orchestrator components, including replay manifests and event envelopes. - Added mock API for risk management, including listing and statistics functionalities. - Implemented models for risk profiles and query options to support the new API.
5.1 KiB
5.1 KiB
Proof-Linked VEX UI Developer Guidelines
Compiled: 2025-12-01 (UTC)
Purpose
Any VEX-influenced verdict a user sees (Findings, Advisories & VEX, Vuln Explorer, etc.) must be directly traceable to concrete evidence: normalized VEX claims, their DSSE/signatures, and the policy explain trace. Every "Not Affected" badge is a verifiable link to the proof.
What this solves (in one line)
Every "Not Affected" badge becomes a verifiable link to why it is safe.
UX pattern (at a glance)
- Badge:
Not Affected(green pill) always renders as a link. - On click: open a right-side drawer with three tabs:
- Evidence (DSSE / in-toto / Sigstore)
- Attestation (predicate details + signer)
- Reasoning Graph (the node + edges that justify the verdict)
- Hover state: mini popover showing proof types available (e.g., "DSSE, SLSA attestation, Graph node").
Data model (API & DB)
Canonical object returned by VEX API for each finding:
{
"findingId": "vuln:CVE-2024-12345@pkg:docker/alpine@3.19",
"status": "not_affected",
"justificationCode": "vex:not_present",
"proof": {
"dsse": {
"envelopeDigest": "sha256-…",
"rekorEntryId": "e3f1…",
"downloadUrl": "https://…/dsse/e3f1…",
"signer": { "name": "StellaOps Authority", "keyId": "SHA256:…" }
},
"attestation": {
"predicateType": "slsa/v1",
"attestationId": "att:01H…",
"downloadUrl": "https://…/att/01H…"
},
"graph": {
"nodeId": "gx:NA-78f…",
"revision": "gx-r:2025-11-30T12:01:22Z",
"explainUrl": "https://…/graph?rev=gx-r:…&node=NA-78f…"
}
},
"receipt": {
"algorithm": "CVSS:4.0",
"inputsHash": "sha256-…",
"computedAt": "2025-11-30T12:01:22Z"
}
}
Suggested Postgres tables:
- vex_findings(finding_id, status, justification_code, graph_node_id, graph_rev, dsse_digest, rekor_id, attestation_id, created_at, updated_at)
- proof_artifacts(id, type, digest, url, signer_keyid, meta jsonb)
- graph_revisions(revision_id, created_at, root_hash)
API contract (minimal)
- GET /vex/findings/:id -> returns the object above.
- GET /proofs/:type/:id -> streams artifact (with Content-Disposition: attachment).
- GET /graph/explain?rev=…&node=… -> returns a JSON justification subgraph.
- Security headers: Content-SHA256, Digest, X-Proof-Root (graph root hash), and X-Signer-KeyId.
Angular UI spec (drop-in)
Component: FindingStatusBadge
<button class="badge badge--ok" (click)="drawer.open(finding.proof)">
Not Affected
</button>
Drawer layout (3 tabs):
- Evidence
- DSSE digest (copy button)
- Rekor entry (open in new tab)
- "Download envelope"
- Attestation
- Predicate type
- Attestation ID
- "Download attestation"
- Reasoning Graph
- Node ID + Revision
- Inline explainer ("Why safe?" bullets)
- "Open full graph" (routes to /graph?rev=…&node=…)
Micro-interactions:
- Copy-to-clipboard with toast ("Digest copied").
- If any artifact missing, show a yellow "Partial Proof" ribbon listing what is absent.
Visual language:
- Badges: Not Affected = solid green; Partial Proof = olive with warning dot; No Proof = gray outline (still clickable, explains absence).
- Proof chips: small caps labels
DSSE,ATTESTATION,GRAPH; each chip opens its subsection.
Validation (trust & integrity):
- On drawer open, the UI calls HEAD /proofs/... to fetch Digest header and X-Proof-Root; compare to stored digests. If mismatch, show red "Integrity Mismatch" banner with retry and report.
Telemetry (debugging):
- Emit events: proof_drawer_opened, proof_artifact_downloaded, graph_explain_viewed (include findingId, artifactType, latencyMs, integrityStatus).
Developer checklist:
- Every not_affected status must include at least one proof artifact.
- Badge is never a dead label; always clickable.
- Drawer validates artifact digests before rendering contents.
- "Open full graph" deep-links with rev + node (stable and shareable).
- Graceful partials: show what is present and what is missing.
- Accessibility: focus trap in drawer, aria-labels for chips, keyboard nav.
Test cases (quick):
- Happy path: all three proofs present; digests match; downloads work.
- Partial proof: DSSE present, attestation missing; drawer shows warning ribbon.
- Integrity fail: server returns different digest; red banner appears; badge stays clickable.
- Graph only: reasoning node present; DSSE/attestation absent; explains rationale clearly.
Optional nice-to-haves:
- Permalinks: copyable URL that re-opens the drawer to the same tab.
- QR export: downloadable "proof card" PNG with digests + signer (for audit packets).
- Offline kit: bundle DSSE, attestation, and a compact graph slice in a .tar.zst for air-gapped review.
If needed, this can be turned into:
- A small Angular module (ProofDrawerModule) + styles.
- A .NET 10 controller stub with integrity headers.
- Fixture JSON so teams can wire it up quickly.
Context links (from source conversation)
- docs/ui/console-overview.md
- docs/ui/advisories-and-vex.md
- docs/ui/findings.md
- src/VexLens/StellaOps.VexLens/AGENTS.md and TASKS.md
- docs/policy/overview.md