fix(security): relabel unchanged bucket for cross-image comparisons (#662)

When comparing scans from two different image refs, same-CVE+package
matches are not necessarily the same finding. Relabel the pill and
row status from 'Unchanged' to 'Shared' in that case to convey that
the caveat in the warning banner applies.
This commit is contained in:
Anso
2026-04-17 14:23:23 -04:00
committed by GitHub
parent 2fce1d3baf
commit e0f2e230a6
2 changed files with 42 additions and 2 deletions
@@ -241,9 +241,14 @@ export function ScanComparisonSheet({
size="sm"
className="h-7 text-xs px-2.5"
onClick={() => { setFilter('unchanged'); setPage(0); }}
title={
crossImage
? 'Same CVE and package name appear in both images. Because the images differ, this does not necessarily mean the finding is literally unchanged.'
: 'Findings present in both scans'
}
>
<Equal className="w-3 h-3 mr-1" strokeWidth={1.5} />
Unchanged ({data.unchanged.length})
{crossImage ? 'Shared' : 'Unchanged'} ({data.unchanged.length})
</Button>
{needsPagination && (
<div className="flex items-center gap-1 ml-auto">
@@ -349,7 +354,7 @@ export function ScanComparisonSheet({
{filter === 'unchanged' && (
<span className="inline-flex items-center gap-1 text-muted-foreground">
<Equal className="w-3 h-3" strokeWidth={1.5} />
Unchanged
{crossImage ? 'Shared' : 'Unchanged'}
</span>
)}
</TableCell>
@@ -171,6 +171,41 @@ describe('ScanComparisonSheet', () => {
expect(screen.queryByText(/findings per scan/i)).toBeNull();
});
it('relabels the unchanged bucket as "Shared" for cross-image comparisons', async () => {
mockedFetch.mockResolvedValueOnce(
jsonResponse(200, result({
scanB: { id: 2, image_ref: 'alpine:3.19', scanned_at: 1_700_000_010_000 },
unchanged: [vuln({ vulnerability_id: 'CVE-U', pkg_name: 'pu' })],
})),
);
const user = userEvent.setup();
render(<ScanComparisonSheet baselineScanId={1} currentScanId={2} onClose={() => {}} />);
await waitFor(() =>
expect(screen.getByRole('button', { name: /Shared \(1\)/ })).toBeInTheDocument(),
);
expect(screen.queryByRole('button', { name: /Unchanged/ })).toBeNull();
await user.click(screen.getByRole('button', { name: /Shared \(1\)/ }));
expect(screen.getByText('Shared')).toBeInTheDocument();
});
it('keeps the "Unchanged" label when both scans use the same image', async () => {
mockedFetch.mockResolvedValueOnce(
jsonResponse(200, result({
unchanged: [vuln({ vulnerability_id: 'CVE-U', pkg_name: 'pu' })],
})),
);
render(<ScanComparisonSheet baselineScanId={1} currentScanId={2} onClose={() => {}} />);
await waitFor(() =>
expect(screen.getByRole('button', { name: /Unchanged \(1\)/ })).toBeInTheDocument(),
);
expect(screen.queryByRole('button', { name: /Shared/ })).toBeNull();
});
it('reloads when the scan ids change', async () => {
mockedFetch.mockResolvedValue(jsonResponse(200, result()));