Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@

import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
Expand All @@ -39,6 +41,9 @@ public class MailProcessor {
private static final Pattern PSIRT_CLOSURE_PATTERN = Pattern.compile(
"Your request\\s+[A-Z]+-\\d+.*this request is now closed",
Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
private static final Comparator<String> CVE_COMPARATOR = Comparator
.comparingInt(MailProcessor::cveYear)
.thenComparingInt(MailProcessor::cveSequence);

@ConfigProperty(name = "google.group.target")
String targetGroupEmail;
Expand Down Expand Up @@ -322,10 +327,7 @@ void recordSecAlertThreadIdIfMissing(GHIssue issue, String threadId) throws IOEx
}

void applyCveIdFromSecAlert(GHIssue issue, String subject, String body) throws IOException {
String cveId = extractCveId(subject);
if (cveId == null) {
cveId = extractCveId(body);
}
String cveId = selectLatestCveId(subject, body);
if (cveId == null) return;

String title = issue.getTitle();
Expand All @@ -350,10 +352,29 @@ void applyCveIdFromSecAlert(GHIssue issue, String subject, String body) throws I
}
}

static String extractCveId(String text) {
if (text == null) return null;
static List<String> extractAllCveIds(String text) {
if (text == null) return List.of();
List<String> cves = new ArrayList<>();
Matcher matcher = Constants.CVE_PATTERN.matcher(text);
return matcher.find() ? matcher.group() : null;
while (matcher.find()) {
cves.add(matcher.group());
}
return List.copyOf(cves);
}

private static int cveYear(String cve) {
return Integer.parseInt(cve.substring(4, cve.lastIndexOf('-')));
}

private static int cveSequence(String cve) {
return Integer.parseInt(cve.substring(cve.lastIndexOf('-') + 1));
}

static String selectLatestCveId(String subject, String body) {
return Stream.concat(extractAllCveIds(subject).stream(), extractAllCveIds(body).stream())
.distinct()
.max(CVE_COMPARATOR)
.orElse(null);
}

private boolean isValidGroupMessage(Map<String, String> headers) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,31 +75,6 @@ void formatReplyComment_containsOnlyFromAndSeparator() {
assertEquals("From: bob@example.com\n\n---\n\nReply body", result);
}

@Test
void extractCveId_findsIdInSubject() {
assertEquals("CVE-2026-1234", MailProcessor.extractCveId("Re: CVE-2026-1234 XSS in admin console"));
}

@Test
void extractCveId_findsIdInBody() {
assertEquals("CVE-2026-45678", MailProcessor.extractCveId("The assigned CVE ID is CVE-2026-45678. Please confirm."));
}

@Test
void extractCveId_returnsNullWhenNoCvePresent() {
assertNull(MailProcessor.extractCveId("No CVE here, just a regular message."));
}

@Test
void extractCveId_returnsNullForNullInput() {
assertNull(MailProcessor.extractCveId(null));
}

@Test
void extractCveId_returnsFirstCveWhenMultiplePresent() {
assertEquals("CVE-2026-1111", MailProcessor.extractCveId("CVE-2026-1111 and CVE-2026-2222"));
}

@Test
void applyCveIdFromSecAlert_replacesTitleAndRemovesCveRequestLabel() throws Exception {
GHIssue issue = mock(GHIssue.class);
Expand Down Expand Up @@ -181,6 +156,87 @@ void applyCveIdFromSecAlert_doesNotAddCveKindWhenAlreadyPresent() throws Excepti
verify(issue, never()).addLabels(Kind.CVE.toLabel());
}

// --- extractAllCveIds ---

@Test
void extractAllCveIds_findsMultipleCves() {
List<String> result = MailProcessor.extractAllCveIds("CVE-2026-9083 and CVE-2026-19729");
assertEquals(List.of("CVE-2026-9083", "CVE-2026-19729"), result);
}

@Test
void extractAllCveIds_returnsEmptyForNull() {
assertTrue(MailProcessor.extractAllCveIds(null).isEmpty());
}

@Test
void extractAllCveIds_returnsEmptyWhenNoCve() {
assertTrue(MailProcessor.extractAllCveIds("No CVE here").isEmpty());
}

// --- selectLatestCveId ---

@Test
void selectLatestCveId_picksHighestFromSubjectAndBody() {
assertEquals("CVE-2026-19729",
MailProcessor.selectLatestCveId("Incomplete fix for CVE-2026-9083", "*Reference : CVE-2026-19729"));
}

@Test
void selectLatestCveId_picksHighestWhenMultipleInBody() {
assertEquals("CVE-2026-19729",
MailProcessor.selectLatestCveId(null, "Report about CVE-2026-9083. Assigned CVE-2026-19729."));
}

@Test
void selectLatestCveId_fallsBackToSubjectWhenBodyEmpty() {
assertEquals("CVE-2026-9083",
MailProcessor.selectLatestCveId("Fix for CVE-2026-9083", "No CVE in body"));
}

@Test
void selectLatestCveId_returnsNullWhenNoCveAnywhere() {
assertNull(MailProcessor.selectLatestCveId("No CVE", "Also no CVE"));
}

@Test
void selectLatestCveId_returnsNullWhenBothInputsNull() {
assertNull(MailProcessor.selectLatestCveId(null, null));
}

@Test
void selectLatestCveId_returnsSingleCveWhenSameInBoth() {
assertEquals("CVE-2026-9083",
MailProcessor.selectLatestCveId("CVE-2026-9083", "Reference: CVE-2026-9083"));
}

@Test
void selectLatestCveId_comparesAcrossYears() {
assertEquals("CVE-2026-1",
MailProcessor.selectLatestCveId("CVE-2025-999999", "CVE-2026-1"));
}

// --- applyCveIdFromSecAlert bug scenario ---

@Test
void applyCveIdFromSecAlert_picksNewCveOverOldInSubject() throws Exception {
GHIssue issue = mock(GHIssue.class);
when(issue.getTitle()).thenReturn("[CVE-TBD] Incomplete fix for CVE-2026-9083");
when(issue.getNumber()).thenReturn(992);

GHLabel cveRequestLabel = mock(GHLabel.class);
when(cveRequestLabel.getName()).thenReturn(Status.CVE_REQUEST.toLabel());
when(issue.getLabels()).thenReturn(List.of(cveRequestLabel));

MailProcessor processor = new MailProcessor();
processor.applyCveIdFromSecAlert(issue, "Incomplete fix for CVE-2026-9083 - #GHI-992",
"*Reference : CVE-2026-19729\n*Embargo status : Public");

verify(issue).setTitle("[CVE-2026-19729] Incomplete fix for CVE-2026-9083");
verify(issue).removeLabels(Status.CVE_REQUEST.toLabel());
verify(issue).addLabels(Kind.CVE.toLabel());
}

// --- resolveIssueByGhiTag ---

@Test
Expand Down
Loading