Skip to content

fix(core): parse single letter version suffixes without a period - #8728

Open
Eljees wants to merge 2 commits into
dependency-check:mainfrom
Eljees:fix/version-parse-single-letter-suffix
Open

fix(core): parse single letter version suffixes without a period#8728
Eljees wants to merge 2 commits into
dependency-check:mainfrom
Eljees:fix/version-parse-single-letter-suffix

Conversation

@Eljees

@Eljees Eljees commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Description of Change

DependencyVersionUtil has two expressions for pulling a version out of 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 libjpeg's scheme of one digit and one letter (8a, 9d, 9e) never reaches it — the name falls through to RX_SINGLE_VERSION, the letter is dropped and the version comes out as 9. The CPE analyzer then matches every CPE for version 9, among them the vulnerable 9a and 9c, which is the false positive described in the issue. The reporter had to work around it with a hint file.

The change adds the missing alternative to RX_SINGLE_VERSION so that both expressions accept the same suffixes. I left the pattern flags alone on purpose: RX_VERSION is CASE_INSENSITIVE and RX_SINGLE_VERSION is not, and adding the flag would also have changed how the other alternatives match, which is outside the scope of this issue.

Before changing anything I ran the current and the proposed expression side by side over the names used in the existing tests (commons-lang3-3.12.0.jar, spring-core-5.3.9.RELEASE.jar, openssl-1.0.2k, lib1.5r4-someflag-R26.jar, 6, utf8, ...). Only inputs of the "digit followed by a single letter" shape change; everything else parses exactly as before.

Related issues

Have test cases been added to cover the new functionality?

yes — DependencyVersionUtilTest.testParseVersion_singleLetterSuffixWithoutPeriod, covering 9e, 9d, 8a and the two names from the reporter's reproducer. On main it fails with expected: <9e> but was: <9>.

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 dependency-check#4139
@boring-cyborg boring-cyborg Bot added core changes to core tests test cases labels Aug 9, 2026
@chadlwilson
chadlwilson requested a balanced review from Copilot August 9, 2026 15:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates version parsing to preserve single-letter suffixes used by libjpeg.

Changes:

  • Extends RX_SINGLE_VERSION to recognize versions such as 8a and 9e.
  • Adds regression tests for standalone and filename-embedded versions.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
DependencyVersionUtil.java Adds single-letter suffix parsing.
DependencyVersionUtilTest.java Tests libjpeg version formats.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@chadlwilson chadlwilson left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably OK if the tests all pass?

Not really sure though, hard to understand what the consequence of changing this logic is downstream and whether it will lead to correct matching since the (presumably naive AI generated) PR does not demonstrate any end-user functionality improvement (being able to match libjpeg correctly to vulns), and the version parsing has additional risk given it is used in all sorts of places - sometimes needing consistency with separate CPE version sorting/parsing rules for version "piece" or range matching and also compatibility with weird hacks in CPEAnalyzer like:

final DependencyVersion evVer = DependencyVersionUtil.parseVersion(evidence.getValue(), true);
if (evVer == null) {
continue;
}
DependencyVersion evBaseVer = null;
String evBaseVerUpdate = null;
final int idx = evVer.getVersionParts().size() - 1;
if (evVer.getVersionParts().get(idx)
.matches("^(v|release|final|snapshot|beta|alpha|u|rc|m|20\\d\\d).*$")) {
//store the update version
final String checkUpdate = evVer.getVersionParts().get(idx);
if (checkUpdate.matches("^(v|release|final|snapshot|beta|alpha|u|rc|m|20\\d\\d).*$")) {
evBaseVerUpdate = checkUpdate;
evBaseVer = new DependencyVersion();
evBaseVer.setVersionParts(evVer.getVersionParts().subList(0, idx));
}
}

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>
@Eljees

Eljees commented Aug 9, 2026

Copy link
Copy Markdown
Contributor Author

Fair point — the tests asserted the parser output, not the thing that matters. Measured on this branch against its parent:

before after
parseVersion("libjpeg-turbo-8a.tar.gz") 8 8a
parseVersion("8b") 8 8b
8a evidence .equals() 8b CPE true false

The last row is the user-visible effect. CPEAnalyzer's exact match is parseVersion(evidence).equals(parseVersion(cpe.getVersion())), and both sides went through the same expression — so dropping the suffix never stopped libjpeg from matching, it made the releases indistinguishable. A dependency at 8a matched the CPE of another release (9a/9c in the reporter's case), and the identified version printed in the report was 8. So it is a false positive going away, not a new match appearing. Pinned by a test in 995b8b3.

On the downstream risk: any version that parsed before parses identically — RX_SINGLE_VERSION only gained one alternative for a trailing single letter, so the only inputs whose result changes are the ones that previously lost their suffix. I also checked the CPEAnalyzer block you linked: it peels a v/rc/20xx-style update off the last version part, and 8a does not match that pattern, so it takes the same path as before.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core changes to core tests test cases

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Incorrect DependencyVersion parsing for libjpeg library

3 participants