← All posts
Liferay DXPSecurityCVEVulnerability ManagementApplication Security

When a CVE Isn't a Liferay Vulnerability: Presence vs. Reachability

By LR Tools · August 23, 2026 · 7 min read

This is LR Tools’ take on a longer piece by David H Nebinger on Liferay.dev — a good explanation of a support-ticket pattern that comes up constantly: a scanner flags a CVE in a library Liferay bundles, and Liferay’s answer is “not affected,” which can look like a dodge until you understand why that’s often the technically correct answer.

Security vulnerabilities in third-party dependencies

Three different claims, one scanner report

A dependency scanner run against Liferay DXP produces a report that conflates three separate statements into one line item: a library is present at a given version, a CVE exists against that version, and — by implication — the CVE is exploitable through the application. The scanner can establish the first two reliably. It can’t establish the third, because doing that requires knowing whether the vulnerable code path is actually used, whether the feature it lives in is enabled, and whether attacker-controlled input can ever reach it.

How Software Composition Analysis tools identify dependency versions and match them against CVE databases

Software Composition Analysis tools are built to answer “is this library here,” not “is this feature reachable” — that second question needs someone who understands the application.

That gap between present and exploitable is usually called reachability, and it’s the entire reason a “Critical” scanner finding and an actual security incident aren’t the same thing. A JAR can contain a genuinely dangerous method in a class Liferay never loads, behind a feature Liferay never enables, with no path for outside input to reach it — and upgrading that JAR would change a version number without closing any real exposure.

A concrete example: Spring 6.2.9-era CVEs

Spring’s own security advisories illustrate this well. Several CVEs associated with Spring components around version 6.2.9 each come with a stated precondition for exploitability, not a blanket “any use of this version is vulnerable”:

CVE Component Condition Spring itself documents
CVE-2025-41242 Spring MVC static resource handling Requires a specific resource-handling configuration paired with a servlet container that doesn’t already reject the malicious path sequence — Spring notes default Tomcat/Jetty behavior blocks it.
CVE-2025-22228 Spring Security BCryptPasswordEncoder Only affects .matches() calls where the password exceeds 72 characters and the first 72 characters happen to match.
CVE-2025-41249 Spring Security annotation detection Relevant specifically when @EnableMethodSecurity is active and authorization annotations sit in certain parameterized type hierarchies.

None of these are hypothetical carve-outs Liferay invented — they’re conditions Spring’s advisories state directly. Whether any of them matter for a given Liferay installation depends on whether that specific configuration and code path is actually in play, which is exactly the kind of application-level detail a scan can’t see.

What Liferay’s security team actually checks

Liferay maintains a dedicated application security team that reviews vulnerabilities in both Liferay’s own code and the third-party software shipped with DXP — often before a customer ever reports one through Support. Their process goes past the CVE headline to the specifics: what triggers the vulnerable behavior, what’s required to exploit it, and how that compares against how Liferay actually uses the affected library. The team uses tooling — including Konvu and other AI-assisted analysis — to keep that review from being a rubber-stamp pass over the CVE description. Liferay publishes the outcomes of that work at support.liferay.com/security-vulnerabilities, including cases where the answer turned out to depend on configuration rather than being a flat yes or no.

The Log4j 1 years, as a case study

Anyone running enterprise scans through the long tail of Log4j 1’s end-of-life remembers this pattern well. Findings kept accumulating even after the project stopped being maintained:

CVE Component Attack surface
CVE-2019-17571 SocketServer Deserialization through Log4j’s network logging server
CVE-2021-4104 JMSAppender JNDI behavior tied to JMS logging configuration
CVE-2022-23302 JMSSink JNDI/deserialization through the JMS sink
CVE-2022-23305 JDBCAppender SQL injection through the database logging appender
CVE-2022-23307 Chainsaw Deserialization in the Chainsaw log viewer

Liferay never ran a Log4j SocketServer, never shipped Chainsaw, and never used the JDBCAppender or the vulnerable JMS appenders — so none of those attack surfaces existed in a standard DXP installation, no matter what the scanner counted.

A scanner's view of the Log4j 1 library cannot tell which features are actually in use

Still, “not exploitable through us” doesn’t make a Critical finding disappear from someone’s compliance dashboard, and forcing Log4j 1 straight to Log4j 2 across every supported release would have been a disproportionately large change to make purely to satisfy a scanner. Liferay’s actual path was reload4j — a binary-compatible fork of Log4j 1.2.17 built specifically to close off the SocketServer, JMSAppender, JMSSink, JDBCAppender, and Chainsaw issues without changing the logging API underneath every application built against it. Older DXP releases adopted reload4j as that stopgap, while DXP 7.4 made the larger jump to Log4j 2 on its own timeline.

Three outcomes, not one default answer

Once reachability is actually assessed, a finding lands in one of three places:

  1. Not affected. The vulnerable capability isn’t reachable through Liferay’s usage — no code change needed, though this conclusion should only be reached after genuinely tracing the exploit path, not asserted reflexively.
  2. Patch or harden without a full upgrade. Sometimes the vulnerability is real and matters, but a major-version upgrade isn’t practical — the upstream project may be abandoned, or the fixed release may carry breaking changes. A targeted patch, or outright removing unused vulnerable functionality, can close the exposure without the blast radius of a full upgrade.
  3. Upgrade. When a compatible release contains the fix and the upgrade path is clean, upgrading is genuinely the right call.

Why “just upgrade it” isn’t automatically the safer option

It’s tempting to treat upgrading as the risk-free default, but a major dependency bump carries its own risk: API incompatibilities, OSGi package version conflicts, classloading differences, transitive dependency shifts, and runtime regressions that don’t show up until after the change ships. If an upgrade closes a vulnerability that was never actually reachable, it’s trading a real (if smaller) compatibility and regression risk for essentially zero reduction in exploitable exposure. Security engineering that optimizes for “no CVEs listed” over “no exploitable paths” ends up spending real engineering time — development, compatibility testing, release engineering — on changes that don’t reduce risk, time that could otherwise go toward fixing things that do.

The part that’s on you: your own customizations

Liferay’s reachability analysis covers Liferay’s own usage of a library — it says nothing about what your OSGi modules do with that same library. If Liferay ships a dependency with a vulnerable feature it never enables, and your custom module turns that feature on, wires attacker-controlled input into it, or exposes it through a REST endpoint, you’ve built the attack surface Liferay’s own assessment correctly said didn’t exist out of the box. “Liferay isn’t vulnerable” and “your customization isn’t vulnerable” are two different claims, and a security review of a customized Liferay installation has to check both.

One more thing worth saying directly: don’t respond to a scanner finding by manually dropping a newer version of a bundled JAR into a running Liferay installation. Liferay manages its dependency versions, OSGi package relationships, and transitive dependencies as a coordinated whole — replacing one piece underneath it can produce failures that are hard to diagnose and moves the installation outside what Liferay Support has actually tested. Making a CVE disappear from a scan while introducing an unrelated production issue isn’t a net improvement.

A better question to bring to Support

“When will you upgrade this JAR” tends to get a less useful answer than asking directly: is the vulnerable functionality reachable in Liferay’s usage, under what configuration would it become exploitable, and does that assessment extend to custom OSGi modules or only to Liferay out of the box. Those questions get at the thing that actually matters — risk — instead of a version number.

A CVE in a scan report is the start of an investigation, not the verdict. Sometimes that investigation genuinely ends in an upgrade. Just as often, the correct, fully-considered answer is that the platform isn’t exposed — and that conclusion represents more security work having been done, not less.

This article is LR Tools’ rewrite of the original post by David H Nebinger — read it on Liferay.dev for the author’s own framing.

This article is adapted from: David H Nebinger, Liferay.dev

More from the blog

Liferay DXPPostgreSQLDatabase Migration

Migrating Liferay to PostgreSQL: A More Reliable Alternative to Liferay's Beta Tool

August 23, 2026 · 3 min read

Liferay's own database migration tool is still Beta and unreliable in practice. Here's a third-party alternative, the exact 8-step migration procedure, and links to the tool and its docs.

Liferay DXPFree TierLicensing

DXP Free Tier Licenses: Why the Product Version Doesn't Have to Match Your Runtime

August 23, 2026 · 3 min read

A DXP Free Tier activation key stamped with a quarterly release that hadn't shipped yet looked like a bug. It wasn't — here's what the product-version field in a Liferay license actually means, and why it isn't a strict compatibility gate.

Liferay DXPOSGiGradle

No More Guesswork: A Gradle Plugin That Deploys Liferay OSGi Modules in the Right Order

August 23, 2026 · 4 min read

A Gradle plugin that maps your Liferay workspace's module dependencies, deploys them in dependency-safe waves, and confirms each one reaches Active in the Gogo shell before moving on — so you stop hand-ordering deployments.