From d4f11afe4b357da1045acba1c6c2e7c8e0727ac4 Mon Sep 17 00:00:00 2001 From: Eljees <3.14hell@gmail.com> Date: Sun, 9 Aug 2026 16:27:05 +0300 Subject: [PATCH 1/2] fix(core): parse single letter version suffixes without a period DependencyVersionUtil uses two expressions to extract a version from a name: RX_VERSION, which requires a period, and RX_SINGLE_VERSION, which does not. Only the first one accepts a single trailing letter, so a version written as one digit followed by one letter - the scheme libjpeg uses (8a, 9d, 9e) - loses the letter and is parsed as "9". The CPE analyzer then matches every CPE for version 9, among them the vulnerable 9a and 9c, and reports false positives; the reporter of the issue had to work around it with a hint file. This adds the missing alternative to RX_SINGLE_VERSION so that both expressions accept the same suffixes. The flags of the pattern are left alone on purpose, so the change is limited to the case described above. fixes #4139 --- .../utils/DependencyVersionUtil.java | 2 +- .../utils/DependencyVersionUtilTest.java | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/owasp/dependencycheck/utils/DependencyVersionUtil.java b/core/src/main/java/org/owasp/dependencycheck/utils/DependencyVersionUtil.java index eb943c8eade..18af1ae9244 100644 --- a/core/src/main/java/org/owasp/dependencycheck/utils/DependencyVersionUtil.java +++ b/core/src/main/java/org/owasp/dependencycheck/utils/DependencyVersionUtil.java @@ -45,7 +45,7 @@ public final class DependencyVersionUtil { * version number using the previous regex. */ private static final Pattern RX_SINGLE_VERSION = Pattern.compile( - "\\d+(\\.\\d+){0,6}([._-]?(snapshot|release|final|alpha|beta|rc$|[a-zA-Z]{1,3}[_-]?\\d{1,8}))?"); + "\\d+(\\.\\d+){0,6}([._-]?(snapshot|release|final|alpha|beta|rc$|[a-zA-Z]{1,3}[_-]?\\d{1,8}|[a-zA-Z]\\b))?"); /** * Regular expression to extract the part before the version numbers if diff --git a/core/src/test/java/org/owasp/dependencycheck/utils/DependencyVersionUtilTest.java b/core/src/test/java/org/owasp/dependencycheck/utils/DependencyVersionUtilTest.java index 23f89847e71..e7d43310f8e 100644 --- a/core/src/test/java/org/owasp/dependencycheck/utils/DependencyVersionUtilTest.java +++ b/core/src/test/java/org/owasp/dependencycheck/utils/DependencyVersionUtilTest.java @@ -60,6 +60,28 @@ void testParseVersion_String() { } } + /** + * Test of parseVersion method, of class DependencyVersionUtil, for versions + * that consist of a number directly followed by a single letter and no + * period - the scheme used by libjpeg (8a, 9d, 9e). + * + * See https://github.com/dependency-check/DependencyCheck/issues/4139 + */ + @Test + void testParseVersion_singleLetterSuffixWithoutPeriod() { + final String[] names = {"9e", "9d", "8a", "libjpeg-9e", "jpegsr9e"}; + final String[] expected = {"9e", "9d", "8a", "9e", "9e"}; + + for (int i = 0; i < names.length; i++) { + final DependencyVersion version = DependencyVersionUtil.parseVersion(names[i]); + String result = null; + if (version != null) { + result = version.toString(); + } + assertEquals(expected[i], result, "Failed extraction on \"" + names[i] + "\"."); + } + } + /** * Test of parseVersion method, of class DependencyVersionUtil. */ From 995b8b30f005034ddb1a818269a750369214bd9b Mon Sep 17 00:00:00 2001 From: Eljees <3.14hell@gmail.com> Date: Sun, 9 Aug 2026 21:55:13 +0300 Subject: [PATCH 2/2] test(core): assert single letter releases are no longer conflated The PR only showed the parser output, not what it changes for a user. The comparison that matters is the one CPEAnalyzer performs when it looks for an exact CPE match: parseVersion(evidence).equals(parseVersion(cpe.getVersion())) Both sides went through the same expression, so dropping the suffix did not stop libjpeg from matching - it made 8a and 8b indistinguishable. A dependency at 8a matched the CPE of 8b and the identified version printed in the report was "8". Measured on this branch and on its parent: parseVersion("libjpeg-turbo-8a.tar.gz") before: 8 after: 8a parseVersion("8b") before: 8 after: 8b 8a evidence equals 8b CPE before: true after: false The new test pins that down. Signed-off-by: Eljees <3.14hell@gmail.com> --- .../utils/DependencyVersionUtilTest.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/core/src/test/java/org/owasp/dependencycheck/utils/DependencyVersionUtilTest.java b/core/src/test/java/org/owasp/dependencycheck/utils/DependencyVersionUtilTest.java index e7d43310f8e..c7038a0d284 100644 --- a/core/src/test/java/org/owasp/dependencycheck/utils/DependencyVersionUtilTest.java +++ b/core/src/test/java/org/owasp/dependencycheck/utils/DependencyVersionUtilTest.java @@ -21,6 +21,7 @@ import org.owasp.dependencycheck.BaseTest; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNull; /** @@ -82,6 +83,23 @@ void testParseVersion_singleLetterSuffixWithoutPeriod() { } } + /** + * The user visible consequence of dropping the suffix: 8a and 8b both collapse onto "8", so + * the version CPEAnalyzer derives from the evidence compares equal to the version of a CPE + * for a different release. Its exact match is + * {@code parseVersion(evidence).equals(parseVersion(cpe.getVersion()))}, so a dependency at + * 8a matched the CPE of 8b, and the identified version printed in the report was "8". + */ + @Test + void testParseVersion_singleLetterReleasesAreNotConflated() { + final DependencyVersion evidence = DependencyVersionUtil.parseVersion("libjpeg-turbo-8a.tar.gz", true); + + assertEquals("8a", evidence.toString()); + assertEquals(DependencyVersionUtil.parseVersion("8a"), evidence); + assertNotEquals(DependencyVersionUtil.parseVersion("8b"), evidence); + assertNotEquals(DependencyVersionUtil.parseVersion("9e"), DependencyVersionUtil.parseVersion("9d")); + } + /** * Test of parseVersion method, of class DependencyVersionUtil. */