Table of contents
Best practices for dealing with Log4j in 2026
When the Log4Shell vulnerability (CVE-2021-44228) was disclosed in December 2021, four separate Log4j vulnerabilities were published in the span of twenty days. Engineers who worked long hours to upgrade to version 2.15.0 were told three days later to do it all over again for 2.16.0. That firefighting cycle taught the industry a lesson that still applies in 2026: patching alone is a treadmill, not a strategy.
Quick answer: how to deal with Log4j in 2026
- Upgrade to a current Log4j 2.x release (2.17.1 was the last version to address the 2021 wave; always run the latest available).
- Find every copy of Log4j, including transitive and bundled copies, with software composition analysis.
- Sanitize or encode all user input before it reaches any logger, which protects you against the next logging vulnerability, not just the last one.
The vulnerability had existed since 2013 and sat unmitigated in production systems for eight years. As we noted at the time, Log4j includes an abundance of features and configuration options, all of which introduce possible breaches. The Center for Internet Security (CIS) predicted: “We expect this cycle of vulnerability-fix, vulnerability-fix will continue as attackers and researchers continue to focus on Log4j.” That prediction held up. The rest of this article explains why dependency updates alone are not enough, and how to sanitize logger input so the next Log4Shell is harmless to your applications.
Log4j in 2026: an endemic vulnerability
More than four years after disclosure, Log4Shell has never really gone away. CISA has repeatedly listed it among the most routinely exploited vulnerabilities, and industry telemetry keeps confirming the long tail:
- CISA’s guidance page for Log4j remains active and continues to be updated.
The reason is structural. Log4j hides in transitive dependencies, bundled JARs, vendor appliances, and archived build artifacts. Organizations patched what they could see in 2021, but many never built a repeatable process for finding what they could not see. That is exactly the gap that continuous software composition analysis and the practices below are designed to close.
Dependency update is not enough to thwart future risks
We believe that a reactive approach, upgrading dependencies only when vulnerabilities like this are disclosed, is not a best practice for the code that you control. To prevent attacks now and in the future, and to avoid the endless fire drills we saw in the weeks after disclosure, applications need to be designed with user input sanitization.
To understand why, let’s first look at how data flows in a Log4j attack (Fig 1).
Figure 1: Log4j attacker to logger dataflow
The illustration above shows how the data flows from the attacker to the logger. With Log4Shell, an attacker who can control log messages or log message parameters can execute arbitrary code loaded from LDAP servers when message lookup substitution is enabled.
Looking more closely at the flow in Figure 1, we notice that the API call to the logger is a call to the abstract logger class that is usually provided by log4j-api. The actual implementation is usually provided by log4j-core, which is the concrete implementation of the logger.
The importance of sanitizing user input
Although the example in Fig 1 is strongly associated with LDAP and JNDI protocols, in practice CVE-2021-44228 boils down to something as simple as user input not being sanitized or validated before being used in applications.
In Fig 1 the apiVersion is passed to the logger without any sanitization process. Sanitizing or validating user input is one of the key pillars of Application Security. This is how we developers mitigate many serious CWEs, such as injection-type attacks. Sanitizing input before sending it to logs is described in CWE-117, which has been around for ages. It was submitted as a CWE in 2006 and described in a 2005 paper, Seven Pernicious Kingdoms: A Taxonomy of Software Security Errors: If developers had learned to sanitize or validate user input going into logs, CVE-2021-44228 would be harmless to the world.
To summarize what we have learned so far, an application might have numerous call sites (access points) to the logger, however a call to the logger is at risk if both of the following conditions are met:
1) The version of the logger implementation is vulnerable (in the example from figure 1 Log4J-core is suspected to be vulnerable)
2) Unsanitized user input is passed to the logger.
How to gauge the risk to your app at every log attempt
How do you determine whether the two conditions above are present, and thus, whether you are at risk?
The first condition is easily determined simply by running any competent software composition analysis tool that includes dependency trace analysis, such as Mend SCA.
To explain the second condition, let’s first take a look at sanitized input. An example of sanitized input is a response to a map lookup, or any other safe API call. Here is an example of safe, sanitized code:
@GetMapping("/safe-sanitized")
public String safesanitized(@RequestHeader("X-Api-Version") String apiVersion) {
…
logger.info("Received a request for API version " + map.get(apiVersion));
…
}Figure 2: Safe, sanitized data sent to logger using a map.get API call
In the case described in Figure 2, the data that is logged is a result of a map API call, and the user input is just a key to that map. The data sent to the logger by the application are constant values inserted into the map object, not arbitrary user input. Therefore, this data input is safe, and no remediation is needed. However, since there is a data-flow path from the application input to the logger, most SAST tools will consider this flow as vulnerable. But a more advanced tool that includes data-flow analysis will not consider this code vulnerable.
In many cases, the log message will contain risky characters like JSON objects. Notice however that those will always be a result of some API call, hence condition 2 will not be met. To understand this scenario, consider the following Java code:
logger.info("My Json:" + new com.google.gson.Gson().toJson(myObject));Figure 3: Safe, sanitized data sent to logger using a toJson API call
In the case described in Figure 3, the log message will be a result of the Json API call. Condition 2 will not be met and this call to the logger will be considered as safe.
How to secure your user input to loggers
If both conditions mentioned above are met, your log access may be vulnerable, and a remediation is recommended. Upgrading to a newer version can only protect against known vulnerabilities. To future-proof your application security and prepare for emerging new vulnerabilities, we recommend another approach.
We suggest encoding all non-sanitized user data before it is passed to the logger, as was suggested long ago by CWE-117. For example, in the Java code in Fig 1, instead of submitting the apiVersion to the logger, the program can pass an encoded version of it.
In general, there are two options for encoding the user input: using a deny or allow list.
For demonstrating the deny list, consider the following Java code:
@GetMapping("/safe-deny-list")
public String safeindex(@RequestHeader("X-Api-Version") String apiVersion) {
…
logger.info("Received a request for API version " + log4jEncoder(apiVersion));
…
}
private String log4jEncoder(String apiVersion) {
String regex = "["+ "{}" + _risky_chars_set_ + "]";
apiVersion = apiVersion.replaceAll(regex,"_");
return apiVersion;Figure 4: Safe access to log using a deny list
In the example above the system has a set of predefined “bad” characters at risk. These characters will be replaced in the input string before sending them into the logger. For example, “{“ will be replaced by “_”, so a string of the form “{some data..” will be replaced by “_some data…”.
The drawback of this approach is that the characters at risk must be defined to cover all characters in all encodings formats such as utf-8, utf-7, … In this way, deny lists quickly become cumbersome. To overcome this problem, consider using an allow instead of a deny list. Here is an example:
@GetMapping("/safe-allow-list")
public String safeindexAlowList(@RequestHeader("X-Api-Version") String apiVersion) {
String regex = "[" + "^" + "a-zA-Z0-9" + whitespace_chars + "]";
…
logger.info("Received a request for API version " + apiVersion.replaceAll(regex,"_"));
…
}Figure 5: Safe access to the logger using an allow list
With the allow list approach, a set of “good” characters are defined. The logger can get any string containing these characters. However if a string contains a character which is excluded from this list, it will subsequently be replaced by a harmless symbol.
Optional: how to decode your safe encoding
The allow or deny list approach leads to losing some of the data when retrieving it from the log. For example, if the user sends some data such as “{ some data }” the logger will log the encoded version that will be “_some data _”. When retrieving the data from the log the curly brackets will be missing. Notice that this will happen only for the call sites that log a direct message from the user and not a message that is the response to an API call such as to Json(..). However, to preserve this data, the solution can be extended by replacing characters from the list with an encoded form of these characters. With this approach, when reading from the log a decode function can recover the original string.
Log4j best practices checklist
- Run the latest Log4j 2.x release everywhere, and remove Log4j 1.x entirely since it is end of life.
- Maintain a continuously updated inventory of every Log4j instance, direct and transitive, using software composition analysis.
- Sanitize or encode user input before it reaches any logger, using the allow-list approach described above.
- Automate dependency updates so the next emergency patch lands in hours, not weeks.
- Monitor for exploitation attempts, since JNDI lookup probes are still a daily occurrence on internet-facing applications.
Conclusion
Traditional SAST tools are commonly flagging CWE-117 as low severity and, as a result, many developers are sending user input straight into logs without any sanitization or validation. If developers would have to sanitize/validate user input before passing it into the logs, vulnerabilities like CVE-2021-44228 would be harmless. Some of the ‘blame’ for this can be attributed to the low severity flagging of CWE-117, which means that developers are not aware of this problem.
When and where will the next Log4Shell be found? The problem extends far beyond one logger. Non-Java loggers and other frameworks share the same input handling weakness, which is why sanitizing any user input that flows into a logger, using allow or deny lists, remains the single most durable defense. Pair that coding discipline with continuous software composition analysis to find vulnerable components, and automated dependency updates to remediate them fast, and the next logging CVE becomes a routine ticket instead of a fire drill. Fold Log4j checks into your broader vulnerability management program so they never fall off the radar.