Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/heureka-mitigate-manually.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cloudoperators/juno-app-heureka": minor
---

Add Mitigate Manually action to vulnerability rows. Users can now mark a CVE as manually mitigated from the popup menu in both the Active and Remediated vulnerability tabs. The CVE moves to the Remediated tab immediately and can be reverted via the Remediation History Panel.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { ImageVulnerability } from "../../../../../Services/utils"
import { getSeverityColor, useTextOverflow } from "../../../../../../utils"
import { FalsePositiveModal } from "../../../FalsePositiveModal"
import { RiskAcceptanceModal } from "../../../RiskAcceptanceModal"
import { MitigateManuallyModal } from "../../../MitigateManuallyModal"
import { useRouteContext } from "@tanstack/react-router"
import { createRemediation } from "../../../../../../api/createRemediation"
import { RemediationInput } from "../../../../../../generated/graphql"
Expand All @@ -42,6 +43,7 @@ type IssuesDataRowProps = {
showFalsePositiveAction?: boolean
onFalsePositiveSuccess?: (cveNumber: string) => void | Promise<void>
onRiskAcceptanceSuccess?: (cveNumber: string) => void | Promise<void>
onMitigateManuallySuccess?: (cveNumber: string) => void | Promise<void>
}

export const IssuesDataRow = ({
Expand All @@ -51,10 +53,12 @@ export const IssuesDataRow = ({
showFalsePositiveAction = true,
onFalsePositiveSuccess,
onRiskAcceptanceSuccess,
onMitigateManuallySuccess,
}: IssuesDataRowProps) => {
const [isExpanded, setIsExpanded] = useState(false)
const [isModalOpen, setIsModalOpen] = useState(false)
const [isRiskAcceptanceModalOpen, setIsRiskAcceptanceModalOpen] = useState(false)
const [isMitigateManuallyModalOpen, setIsMitigateManuallyModalOpen] = useState(false)
const [isSubmitting, setIsSubmitting] = useState(false)
const { needsExpansion, textRef } = useTextOverflow(issue?.description || "")
const { apiClient, queryClient } = useRouteContext({ from: "/services/$service" })
Expand Down Expand Up @@ -158,6 +162,49 @@ export const IssuesDataRow = ({
}
}

const handleMitigateManuallyConfirm = async (input: RemediationInput): Promise<{ error: string } | void> => {
setIsSubmitting(true)
try {
const remediation = await createRemediation({ apiClient, input })
const cveNumber = issue?.name || "unknown"
if (remediation) {
queryClient.setQueriesData(
{
predicate: (query) => {
const [key, filter] = query.queryKey as [string, any]
if (key !== "remediations") return false
if (filter?.service && !filter.service.includes(service)) return false
if (filter?.image && !filter.image.includes(image)) return false
if (filter?.vulnerability && !filter.vulnerability.includes(cveNumber)) return false
return true
},
},
(old: any) => {
if (!old?.data?.Remediations) return old
const edges = old.data.Remediations.edges ?? []
if (edges.some((e: any) => e?.node?.id === remediation.id)) return old
return {
...old,
data: {
...old.data,
Remediations: {
...old.data.Remediations,
edges: [...edges, { node: remediation }],
totalCount: (old.data.Remediations.totalCount ?? 0) + 1,
},
},
}
}
)
}
await onMitigateManuallySuccess?.(cveNumber)
} catch (error) {
return { error: error instanceof Error ? error.message : "Failed to create remediation" }
} finally {
setIsSubmitting(false)
}
}
Comment thread
hodanoori marked this conversation as resolved.
Outdated

return (
<>
<DataGridRow>
Expand Down Expand Up @@ -207,6 +254,7 @@ export const IssuesDataRow = ({
<PopupMenuOptions>
<PopupMenuItem label="Mark False Positive" onClick={() => setIsModalOpen(true)} />
<PopupMenuItem label="Accept Risk" onClick={() => setIsRiskAcceptanceModalOpen(true)} />
<PopupMenuItem label="Mitigate Manually" onClick={() => setIsMitigateManuallyModalOpen(true)} />
</PopupMenuOptions>
</PopupMenu>
)}
Expand All @@ -233,6 +281,15 @@ export const IssuesDataRow = ({
service={service}
image={image}
/>
<MitigateManuallyModal
open={isMitigateManuallyModalOpen}
onClose={() => setIsMitigateManuallyModalOpen(false)}
onConfirm={handleMitigateManuallyConfirm}
vulnerability={issue.name}
severity={issue.severity}
service={service}
image={image}
/>
</>
)}
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ function renderWithRouter(
image="repo/image"
onFalsePositiveSuccess={() => {}}
onRiskAcceptanceSuccess={() => {}}
onMitigateManuallySuccess={() => {}}
/>
</Suspense>
),
Expand Down Expand Up @@ -169,6 +170,7 @@ describe("IssuesDataRows — active/remediated split", () => {
image="repo/image"
onFalsePositiveSuccess={() => {}}
onRiskAcceptanceSuccess={() => {}}
onMitigateManuallySuccess={() => {}}
/>
</Suspense>
)
Expand Down Expand Up @@ -209,6 +211,7 @@ describe("IssuesDataRows — active/remediated split", () => {
image="repo/image"
onFalsePositiveSuccess={() => {}}
onRiskAcceptanceSuccess={() => {}}
onMitigateManuallySuccess={() => {}}
/>
</Suspense>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type IssuesDataRowsProps = {
image: string
onFalsePositiveSuccess: (cveNumber: string) => void | Promise<void>
onRiskAcceptanceSuccess: (cveNumber: string) => void | Promise<void>
onMitigateManuallySuccess: (cveNumber: string) => void | Promise<void>
}

export const IssuesDataRows = ({
Expand All @@ -26,6 +27,7 @@ export const IssuesDataRows = ({
image,
onFalsePositiveSuccess,
onRiskAcceptanceSuccess,
onMitigateManuallySuccess,
}: IssuesDataRowsProps) => {
const { error, data } = use(issuesPromise)
const remediationsResult = use(remediationsPromise)
Expand Down Expand Up @@ -55,6 +57,7 @@ export const IssuesDataRows = ({
image={image}
onFalsePositiveSuccess={onFalsePositiveSuccess}
onRiskAcceptanceSuccess={onRiskAcceptanceSuccess}
onMitigateManuallySuccess={onMitigateManuallySuccess}
/>
))
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { ImageVulnerability } from "../../../../../Services/utils"
import { getSeverityColor, useTextOverflow } from "../../../../../../utils"
import { FalsePositiveModal } from "../../../FalsePositiveModal"
import { RiskAcceptanceModal } from "../../../RiskAcceptanceModal"
import { MitigateManuallyModal } from "../../../MitigateManuallyModal"
import { useRouteContext } from "@tanstack/react-router"
import { createRemediation } from "../../../../../../api/createRemediation"
import { RemediationInput, RemediationTypeValues } from "../../../../../../generated/graphql"
Expand Down Expand Up @@ -54,6 +55,7 @@ export const RemediatedIssueDataRow = ({
const [isExpanded, setIsExpanded] = useState(false)
const [isFalsePositiveModalOpen, setIsFalsePositiveModalOpen] = useState(false)
const [isRiskAcceptanceModalOpen, setIsRiskAcceptanceModalOpen] = useState(false)
const [isMitigateManuallyModalOpen, setIsMitigateManuallyModalOpen] = useState(false)
const [isSubmitting, setIsSubmitting] = useState(false)
const { needsExpansion, textRef } = useTextOverflow(issue?.description || "")
const { apiClient, queryClient } = useRouteContext({ from: "/services/$service" })
Expand Down Expand Up @@ -179,6 +181,7 @@ export const RemediatedIssueDataRow = ({
<PopupMenuOptions>
<PopupMenuItem label="Mark False Positive" onClick={() => setIsFalsePositiveModalOpen(true)} />
<PopupMenuItem label="Accept Risk" onClick={() => setIsRiskAcceptanceModalOpen(true)} />
<PopupMenuItem label="Mitigate Manually" onClick={() => setIsMitigateManuallyModalOpen(true)} />
</PopupMenuOptions>
</PopupMenu>
)}
Expand All @@ -202,6 +205,15 @@ export const RemediatedIssueDataRow = ({
service={service}
image={image}
/>
<MitigateManuallyModal
open={isMitigateManuallyModalOpen}
onClose={() => setIsMitigateManuallyModalOpen(false)}
onConfirm={handleRemediationConfirm}
vulnerability={issue.name}
severity={issue.severity}
service={service}
image={image}
/>
</>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ const VulnerabilitiesTabContent = ({
successMessage,
onFalsePositiveSuccess,
onRiskAcceptanceSuccess,
onMitigateManuallySuccess,
}: {
service: string
image: ServiceImage
Expand All @@ -64,6 +65,7 @@ const VulnerabilitiesTabContent = ({
successMessage: string | null
onFalsePositiveSuccess: (cveNumber: string) => void | Promise<void>
onRiskAcceptanceSuccess: (cveNumber: string) => void | Promise<void>
onMitigateManuallySuccess: (cveNumber: string) => void | Promise<void>
}) => {
return (
<>
Expand Down Expand Up @@ -107,6 +109,7 @@ const VulnerabilitiesTabContent = ({
image={image.repository}
onFalsePositiveSuccess={onFalsePositiveSuccess}
onRiskAcceptanceSuccess={onRiskAcceptanceSuccess}
onMitigateManuallySuccess={onMitigateManuallySuccess}
/>
</Suspense>
</ErrorBoundary>
Expand Down Expand Up @@ -349,10 +352,20 @@ export const ImageIssuesList = ({
)
}, [])

const handleMitigateManuallySuccess = useCallback((cveNumber: string) => {
setVulnerabilitiesSuccessMessage(
`Vulnerability ${cveNumber} has been manually mitigated and moved to the Remediated list.`
)
}, [])

const handleRemediatedTabRemediationSuccess = useCallback(
(cveNumber: string, remediationType: RemediationTypeValues) => {
const remediationTypeLabel =
remediationType === RemediationTypeValues.FalsePositive ? "a false positive" : "a risk acceptance"
remediationType === RemediationTypeValues.FalsePositive
? "a false positive"
: remediationType === RemediationTypeValues.Mitigation
? "manually mitigated"
: "a risk acceptance"
const text = `Vulnerability ${cveNumber} has been marked as ${remediationTypeLabel}.`
setRemediatedSuccessMessage(text)
},
Expand All @@ -377,6 +390,7 @@ export const ImageIssuesList = ({
successMessage={vulnerabilitiesSuccessMessage}
onFalsePositiveSuccess={handleFalsePositiveSuccess}
onRiskAcceptanceSuccess={handleRiskAcceptanceSuccess}
onMitigateManuallySuccess={handleMitigateManuallySuccess}
/>
</TabPanel>
<TabPanel>
Expand Down
Loading
Loading