feat(advisory): implement CSAF Vulnerabilities tab with per-CVE cards#1069
feat(advisory): implement CSAF Vulnerabilities tab with per-CVE cards#1069CryptoRodeo wants to merge 4 commits into
Conversation
Define TypeScript interfaces for the full CSAF 2.0 VEX document structure (CsafDocument, CsafVulnerability, CsafProductTree, etc.) and add useFetchAdvisoryCsafById query hook that fetches raw CSAF JSON via the downloadAdvisory endpoint and parses the Blob into typed data. Also add echarts and echarts-for-react dependencies for upcoming tree visualization components. Implements TC-4618 Assisted-by: Claude Code
When advisory labels.type === "csaf", render a CSAF-specific 5-tab layout (Overview, Vulnerabilities, Product Tree, Relationship Tree, Source) instead of the default 2-tab Info/Vulnerabilities view. The CsafAdvisoryDetails component fetches the parsed CSAF document via useFetchAdvisoryCsafById and provides placeholder content for each tab that subsequent tasks will replace with real implementations. Implements TC-4619 Assisted-by: Claude Code
Add CsafVulnerabilities tab component rendering per-CVE cards sorted by severity (critical first). Each card includes: - CVE ID linked to vulnerability details page - Severity shield with CVSS score - Expandable CVSS v3 breakdown (all metrics) - Expandable affected products list (first 5 + "+N more" toggle) - Expandable remediations grouped by category with linkified URLs - Expandable references as external links Product IDs resolved to display names via full_product_names. Graceful empty states for missing optional fields. Implements TC-4621 Assisted-by: Claude Code
Reviewer's GuideImplements a CSAF-specific advisory details view that, when an advisory is labeled type "csaf", switches to a dedicated 5-tab CSAF layout and a new Vulnerabilities tab rendering per-CVE cards with CVSS details, affected products, remediations, and references, backed by a typed CSAF model and a new query hook to fetch and parse the raw CSAF JSON document. Sequence diagram for CSAF advisory details renderingsequenceDiagram
actor User
participant AdvisoryDetails
participant CsafAdvisoryDetails
participant useFetchAdvisoryCsafById
participant downloadAdvisory
participant CsafVulnerabilities
participant CsafVulnerabilityCard
User->>AdvisoryDetails: open advisory details(advisoryId)
AdvisoryDetails->>AdvisoryDetails: check advisory.labels.type
AdvisoryDetails-->>User: render CsafAdvisoryDetails(advisoryId) [isCsaf]
CsafAdvisoryDetails->>useFetchAdvisoryCsafById: useFetchAdvisoryCsafById(advisoryId)
useFetchAdvisoryCsafById->>downloadAdvisory: downloadAdvisory({ client, path: { key: id } })
downloadAdvisory-->>useFetchAdvisoryCsafById: Blob
useFetchAdvisoryCsafById->>useFetchAdvisoryCsafById: blob.text() / JSON.parse(text)
useFetchAdvisoryCsafById-->>CsafAdvisoryDetails: csafDocument
CsafAdvisoryDetails-->>CsafVulnerabilities: CsafVulnerabilities(csafDocument)
CsafVulnerabilities->>CsafVulnerabilities: sortBySeverity(vulnerabilities)
CsafVulnerabilities->>CsafVulnerabilities: buildProductNameMap(csafDocument)
CsafVulnerabilities-->>CsafVulnerabilityCard: CsafVulnerabilityCard(vulnerability, productNameMap) * N
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 5 issues, and left some high level feedback:
- The CVSS baseSeverity values (e.g. HIGH, MEDIUM) are only lowercased and then cast to the internal severity union in CsafVulnerabilityCard, which expects values like 'important'/'moderate'; consider adding an explicit mapping from CVSS severities to your app severities instead of using a type cast to avoid incorrect shield rendering at runtime.
- In LinkifiedText, using a global regex with urlRegex.test inside the map will mutate lastIndex and can yield incorrect matches; it would be safer to either remove the global flag and re-run a fresh regex per part or infer link vs text by index (e.g. treating every odd segment from split as a URL).
- The new echarts and echarts-for-react dependencies added to client/package.json are not used in the current changes; if they are not needed yet, consider deferring their addition to the PR that actually uses them to keep the dependency set minimal.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The CVSS baseSeverity values (e.g. HIGH, MEDIUM) are only lowercased and then cast to the internal severity union in CsafVulnerabilityCard, which expects values like 'important'/'moderate'; consider adding an explicit mapping from CVSS severities to your app severities instead of using a type cast to avoid incorrect shield rendering at runtime.
- In LinkifiedText, using a global regex with urlRegex.test inside the map will mutate lastIndex and can yield incorrect matches; it would be safer to either remove the global flag and re-run a fresh regex per part or infer link vs text by index (e.g. treating every odd segment from split as a URL).
- The new echarts and echarts-for-react dependencies added to client/package.json are not used in the current changes; if they are not needed yet, consider deferring their addition to the PR that actually uses them to keep the dependency set minimal.
## Individual Comments
### Comment 1
<location path="client/src/app/pages/advisory-details/csaf-advisory-details.tsx" line_range="58-62" />
<code_context>
+ ],
+ });
+
+ const overviewTabRef = React.createRef<HTMLElement>();
+ const vulnerabilitiesTabRef = React.createRef<HTMLElement>();
+ const productTreeTabRef = React.createRef<HTMLElement>();
+ const relationshipTreeTabRef = React.createRef<HTMLElement>();
+ const sourceTabRef = React.createRef<HTMLElement>();
+
+ return (
</code_context>
<issue_to_address>
**issue (bug_risk):** Tab content refs should use useRef instead of React.createRef to avoid re-creating refs on each render.
Using `React.createRef` inside a function component creates new ref objects on every render, which breaks the stable refs PatternFly expects between `Tabs` and `TabContent`. Please switch these to `const overviewTabRef = React.useRef<HTMLElement | null>(null);` (and the same pattern for the other tabs) so the refs remain stable and avoid focus/scroll issues.
</issue_to_address>
### Comment 2
<location path="client/src/app/pages/advisory-details/components/csaf-vulnerability-card.tsx" line_range="71-73" />
<code_context>
+ </Link>
+ </FlexItem>
+ )}
+ {cvss && (
+ <FlexItem>
+ <SeverityShieldAndText
+ value={
+ cvss.baseSeverity.toLowerCase() as
</code_context>
<issue_to_address>
**issue (bug_risk):** CVSS baseSeverity values need a mapping to the SeverityShieldAndText value domain.
`cvss.baseSeverity` (CVSS v3) is `CRITICAL|HIGH|MEDIUM|LOW|NONE`, but here it’s lowercased and cast to `"critical" | "important" | "moderate" | "low" | "none"`. That means `"high"` and `"medium"` won’t match the expected union. Please add an explicit mapping (e.g. `HIGH → "important"`, `MEDIUM → "moderate"`) and fall back to a safe default (e.g. `"none"`) for unknown values instead of casting.
</issue_to_address>
### Comment 3
<location path="client/src/app/pages/advisory-details/components/csaf-vulnerability-card.tsx" line_range="135-137" />
<code_context>
+ ];
+
+ return (
+ <ExpandableSection
+ toggleText={isExpanded ? "Hide CVSS details" : "Show CVSS details"}
+ onToggle={(_event, expanded) => setIsExpanded(expanded)}
</code_context>
<issue_to_address>
**issue (bug_risk):** Affected products expandable section is always collapsed because its expanded state is not controlled.
Since the `ExpandableSection` is rendered with `isExpanded={false}` and no `onToggle` or local state, it can never be opened. Mirror the approach used in other sections by adding a `useState` flag for this panel and wiring it to `isExpanded` and `onToggle`.
</issue_to_address>
### Comment 4
<location path="client/src/app/pages/advisory-details/components/csaf-vulnerability-card.tsx" line_range="171-173" />
<code_context>
+ )}
+
+ {/* References */}
+ {vulnerability.references && vulnerability.references.length > 0 && (
+ <FlexItem>
+ <ExpandableSection toggleText="References" isExpanded={false}>
+ <List isPlain>
+ {vulnerability.references.map((ref, i) => (
</code_context>
<issue_to_address>
**issue (bug_risk):** References expandable section is also hard-coded to collapsed and cannot be opened.
Here, `ExpandableSection` is also rendered with `isExpanded={false}` and no state, so it will never open on click. Please add local state and an `onToggle` handler (as with the affected products section) so this section can expand and collapse.
</issue_to_address>
### Comment 5
<location path="client/src/app/pages/advisory-details/components/csaf-remediations.tsx" line_range="42-46" />
<code_context>
+
+/** Renders text with URLs converted to links. */
+const LinkifiedText: React.FC<{ text: string }> = ({ text }) => {
+ const urlRegex = /(https?:\/\/[^\s)]+)/g;
+ const parts = text.split(urlRegex);
+ return (
+ <>
+ {parts.map((part, i) =>
+ urlRegex.test(part) ? (
+ <a
</code_context>
<issue_to_address>
**issue (bug_risk):** Using a global RegExp with .test() inside map can misclassify parts due to the advancing lastIndex.
Because `urlRegex` is global, each call to `urlRegex.test(part)` advances `lastIndex`, so results depend on previous iterations and some URLs may not be linkified. Either derive link vs. text from the split index (e.g., odd indices are URLs) or use a non-global regex for `test` (e.g., `new RegExp(urlRegex.source).test(part)`).
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #1069 +/- ##
==========================================
- Coverage 49.17% 47.99% -1.18%
==========================================
Files 253 258 +5
Lines 5499 5632 +133
Branches 1660 1712 +52
==========================================
- Hits 2704 2703 -1
- Misses 2519 2654 +135
+ Partials 276 275 -1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
…ed import
- Remove isExpanded={false} from Affected Products, References, and
Remediations ExpandableSections — PF6 treats this as controlled mode
with no onToggle, permanently locking sections closed
- Fix severity cast to use correct Severity union values (high/medium)
instead of Red Hat labels (important/moderate)
- Remove unused CsafFullProductName import from csaf-remediations.tsx
Implements TC-4621
Assisted-by: Claude Code
Verification Report for TC-4621 (commit 88f74e8)
Overall: PASSAll 5 review comments from sourcery-ai addressed:
This comment was AI-generated by sdlc-workflow/verify-pr v0.9.1. |
Summary
CsafVulnerabilitiestab rendering per-CVE cards sorted by severity (critical first)CsafVulnerabilityCard— CVE ID linked to vulnerability details, severity shield, title, CWE, datesCsafCvssDetails— expandable CVSS v3 breakdown (attack vector, complexity, privileges, etc.)CsafRemediations— grouped by category (vendor_fix, workaround, etc.) with linkified URLs and expandable product listsTest plan
npm run build -w client)Implements TC-4621
Assisted-by: Claude Code
Summary by Sourcery
Introduce CSAF-specific advisory details view with a dedicated vulnerabilities tab and per-CVE cards for CSAF advisories.
New Features:
Enhancements:
Build: