December 2021 is when Log4Shell, CVE-2021-44228, went public with a severity score of 10.0, and scanners still flag it today. How to detect log4j vulnerability in your own app comes down to three checks: is a log4j-core JAR below the fixed version anywhere you run, including inside vendor software, does untrusted text reach its logger, and can that server call out to the internet.

What Log4Shell is, and when the Log4j vulnerability was discovered

Log4Shell is the name for CVE-2021-44228, a flaw in Apache Log4j’s log4j-core 2.x library, made public in December 2021 and scored 10.0 out of 10. A logged string could make the server fetch and run an attacker’s code. Later releases fixed it and three related CVEs.

Log4j is Apache’s logging library for Java, which the project describes as “a versatile, industrial-grade Java logging framework” made of an API and its implementation. The flaw sits in the implementation: Apache’s security page says “only the log4j-core JAR file is impacted”, and applications using only the log4j-api JAR are not. Checking for it is one item in web app security for an AI-built app, and it applies even to teams that have never written Java.

NVD’s entry for CVE-2021-44228 describes the Log4j vulnerability this way: an attacker who can control log messages or their parameters “can execute arbitrary code loaded from LDAP servers when message lookup substitution is enabled.” In plain words, a string the app wrote to its log could make the server fetch code from a machine the attacker runs and execute it, and NVD’s score vector lists no privileges required and no user interaction. The Cyber Safety Review Board’s Log4j report puts the reach in one line: Log4j “is a piece of open source software that developers have integrated into millions of systems.”

Log4Shell vs Log4j is a naming question: Log4j is the library and Log4Shell is one Log4j CVE in it, with three more following within weeks.

DateWhat happenedSource
November 24, 2021An Alibaba Cloud Security engineer reports the flaw to the Apache Software Foundation by emailCSRB report
December 9, 2021The Alibaba engineer tells Apache the flaw is being discussed on WeChat; the CSRB dates the disclosure to this dayCSRB report
December 10, 2021Apache makes 2.15.0 public and the CVE visible; NVD publishes it, adding its 10.0 score on December 13; CISA adds it to the Known Exploited Vulnerabilities catalogCSRB report, NVD, CISA
December 13, 20212.16.0 removes message lookups and turns JNDI off by defaultLog4j release notes
December 17, 20212.17.0 addresses CVE-2021-45105Log4j release notes
December 27, 20212.17.1 addresses CVE-2021-44832Log4j release notes
July 11, 2022The CSRB publishes its reviewCSRB report

The four CVEs of that month, as Apache Logging Services’ security page and NVD list them:

CVEWhat it wasSeverity (CVSS 3.x)Fixed in: Java 8+ / Java 7 / Java 6
CVE-2021-44228 (Log4Shell)JNDI lookup can be exploited to execute arbitrary code loaded from an LDAP server10.0 critical2.15.0 / 2.12.2 / 2.3.1
CVE-2021-45046The 2.15.0 fix was incomplete in certain non-default configurations that use a Thread Context Lookup9.0 critical2.16.0 / 2.12.3 / 2.3.1
CVE-2021-45105Infinite recursion in lookup evaluation, a denial of service5.9 medium2.17.0 / 2.12.3 / 2.3.1
CVE-2021-44832Remote code execution through the JDBC appender, for an attacker with write access to the logging configuration6.6 medium2.17.1 / 2.12.4 / 2.3.2 (NVD; Apache’s security page repeats the CVE-2021-45105 releases here)

The release that clears all four is 2.17.1 on Java 8 and later, 2.12.4 on Java 7 and 2.3.2 on Java 6, which is the upgrade CISA’s guidance repository urges. Where Apache’s security page and NVD disagree on the last row, Apache’s own 2.17.1 release notes side with NVD: that release “addresses CVE-2021-44832”.

How the Log4Shell exploit worked, in words

The Log4Shell exploit was a chain of four steps, and the chain can be broken at more than one of them:

  1. The attacker puts specially formed text into something the app logs: a header, a username, a search box.
  2. log4j-core 2.x, in the affected releases, treated part of that text as a lookup instruction instead of as text; before 2.15.0, Log4j “would automatically resolve Lookups contained in the message or its parameters in the Pattern Layout”.
  3. The lookup made the server contact an address the attacker chose.
  4. What came back from that server could be loaded and run as code.

The fixed release removes step 2: message lookup substitution has been off by default since 2.15.0 and was removed from 2.16.0. Keeping untrusted text out of your logs removes step 1. Blocking outbound traffic from the server narrows step 3, though a lookup can still leak data through the server’s DNS resolver. Those last two are my reading, not Apache’s guidance, and neither replaces the upgrade.

Why it matters for an app with no Java in it

My reading is that most builder-made apps are TypeScript or Python on a managed host, so their own code cannot contain log4j-core. The question still arrives: a row on a customer’s security questionnaire, a line in a scanner report, an item on an investor’s checklist. The honest answer needs a check, not a shrug, because Log4j can sit in places your own dependency tree never shows.

Where Log4j can sitExampleWho patches itHow you check
Your own Java service or Android buildA Java API or worker your team wroteYouThe build tool’s dependency tree
A fat JAR or WAR that bundles it inside another JAROne deployable archive with its libraries packed insideYou, by rebuilding with the fixed versionList the archive’s contents; a plain file search misses it
Self-hosted software beside the appA search server, a queue, a CI server, a log shipper; Elastic’s advisory ESA-2021-31 covers Elasticsearch and LogstashThe vendor, through an upgrade you installThe vendor’s advisory for your exact product version
A managed serviceA hosted database, search or CI serviceThe providerThe provider’s written statement, kept on file
A desktop or build tool on a developer machineAn IDE or build tool that runs on a JVMYou, by upgrading the toolThe tool’s version against its vendor’s notes

The vendor’s own words matter in row three. Elastic’s advisory ESA-2021-31 says supported Elasticsearch versions (6.8.9+, 7.8+) running on JDK 9 or later “are not susceptible to either remote code execution or information leakage” because of the Java Security Manager, and that the releases which fully mitigated both lookup CVEs “may trigger false positives in vulnerability scanners based on the bundled version of Log4j.” For other products, CISA’s affected-products list was the lookup during the incident; it has been archived and read-only since February 2, 2023, and it tells readers to ask the manufacturer for current information.

Take one case. A founder’s app is TypeScript on a managed host, and a customer’s security questionnaire asks whether the company is affected by Log4Shell; the app’s dependency tree holds no Java package, so the first answer drafted is “we don’t use Java”. The team also runs a self-hosted Java product beside the app, a search server or a CI server, whose distribution bundles log4j-core inside its own files. The app’s tree could never show it, and whether that product was affected, and which release fixes it, is stated in the product vendor’s own advisory. The answer is the product’s upgrade per that advisory, recorded with the four-condition worksheet further down. “We don’t use Java” was true of the code and not of what runs, and my take is that an inventory of what runs, not of what you wrote, is what answers the Log4j question.

Across the 21 third-party apps I audited in June and July 2026, the Dependencies and Supply Chain pillar averaged 34.5 out of 100, scored on 20 of those 21 apps because it did not apply to the other one. Those apps are a selected set, not a random sample, so the number describes those apps only and is not a rate for AI-built apps in general. My numbers for both sides of a scanner report are in the dependency-update article linked in the reachability section below; the point here is that neither “the scanner is red” nor “we don’t use Java” is an answer on its own.

How to detect log4j vulnerability: version check, scanners, and the reachability question

Detecting the Log4j vulnerability takes 3 steps: find every copy of log4j-core, including copies bundled inside other JARs and inside vendor software, read each version against Apache’s fixed releases, then decide whether the conditions for exploitation exist where it runs. A scanner can automate the first step; none decides the third.

The five parts below follow the order of the work: find it, scan for it, judge it, score it, then rule out the two lookalikes that share its name.

Log4j version check: find every copy, including the nested ones

A Log4j version check has 3 sources: the build tool’s dependency tree, which shows transitive copies, a file search for log4j-core JARs on the server or image, and the contents of each fat JAR or WAR, where a bundled copy has no file of its own.

Start in the browser if the code is on GitHub. Open the repository, click Insights, then Dependency graph, and search for log4j-core; each dependency shows its version, the manifest that brought it in and whether it has known vulnerabilities. GitHub’s dependency graph reads transitive dependencies from Maven’s pom.xml on its own; for Gradle, GitHub lists static reading of transitive dependencies as not supported and automatic dependency submission as supported. For the alerts, open the Security and quality tab and, under Findings, choose Dependabot, then Vulnerabilities. If the graph is empty, a repository administrator turns it on under Settings, Advanced Security, next to Dependency Graph; a paid-plan requirement for private repositories is not stated in GitHub’s docs.

  1. 01 Search the GitHub dependency graph for log4j-core and note each version
  2. 02 Print the build tool tree filtered to the org.apache.logging.log4j group
  3. 03 Search the server and each container image for log4j-core JAR files
  4. 04 List the contents of every fat JAR or WAR for log4j-core and JndiLookup.class
  5. 05 Read each version from the file name or the manifest and compare it with the fixed releases

The commands for steps 2 to 5, written from the Maven and Gradle documentation and the unzip and find manuals rather than from a run on a real system:

# Maven: the dependency tree, filtered to the Log4j group (transitive copies included)
mvn dependency:tree -Dincludes=org.apache.logging.log4j
# Gradle: the tree for all configurations, then why a version was chosen at runtime
./gradlew dependencies | grep log4j-core
./gradlew dependencyInsight --dependency log4j-core --configuration runtimeClasspath
# Files on a server or an unpacked image
find / -name 'log4j-core-*.jar' 2>/dev/null
# Inside a fat JAR or WAR: a bundled copy has no file of its own
unzip -l app.jar | grep -E 'log4j-core|JndiLookup.class'
# The version, from a log4j-core JAR's manifest
unzip -p log4j-core-2.17.1.jar META-INF/MANIFEST.MF | grep Implementation-Version

Maven’s dependency:tree goal takes a groupId:artifactId pattern in its includes filter, and Gradle’s dependency reports show the selected version of each dependency, which catches the copy a pom.xml or build file never names. To check the version of a JAR, read its manifest: log4j-core JARs carry an Implementation-Version line. Finding JndiLookup.class is not a verdict on its own, because the fixed 2.17.1 JAR still contains that class; the version decides. NVD’s entry for the last of the four CVEs covers Log4j 2 versions 2.0-beta7 through 2.17.0, excluding the security fix releases 2.3.2 and 2.12.4, so any other copy in that range needs the upgrade.

Log4j scanners: what a scan proves and what it cannot

Log4j scanners come in 3 kinds. Dependency and file scanners prove the library is present at a version. Network probes prove one input on one route did or did not trigger. Runtime agents watch a running process. None of them decides whether your app is exploitable.

A Log4j vulnerability scan of the first kind reads manifests, lockfiles, images or disks and reports the library and its version. That proves presence, not exploitability, and the same holds for the npm audit command and the other dependency scanners. Qualys published a disk scanner of this kind for Windows that searches “including archives (and nested JARs)”; its repository has been archived since December 10, 2025.

Network-based Log4j vulnerability scanning is the second kind: the probe sends a test string to a running service and waits for a callback. CISA’s log4j-scanner repository was built during the incident to find “potentially vulnerable web services” and credits FullHunt’s team, the makers of log4j-scan; it has been archived since December 6, 2022, and its README warns it “is not intended to be a 100% true positive solution; False negatives may occur.” A clean probe proves only that the tested input on the tested route did not trigger, which is my reading of what a probe can see. Run one only against systems you own or are authorized to test; FullHunt’s own disclaimer calls use against targets “without prior mutual consent” illegal.

Runtime and agent-based detection, the third kind, is enterprise tooling that watches the running JVM. As I read all three, no Log4j vulnerability scanner knows whether your logs carry outsider text.

Scanner kindWhat it looks atWhat a clean result provesWhat it misses
Dependency and file scannerManifests, lockfiles, images, disks and archivesNo matching library and version was in what it readCopies in places it was not pointed at; whether a found copy is reachable
Network probeOne running service, one input, one routeThat test string on that route did not triggerEvery other input, route and service
Runtime agentA running processNothing fired while it watchedServices it is not installed on

Reading any scanner’s output is a ladder of evidence, and the steps are in reading scanner findings as an evidence ladder. Which kind of test proves what, from a scan to a manual test, is the subject of pen test vs vulnerability assessment.

The reachability question: is this CVE exploitable in your app

Reachability for Log4Shell rests on 4 conditions: a vulnerable log4j-core release is loaded, the app logs text an outsider controls, message lookups are active, and the server can connect out to arbitrary hosts. An evidenced no on any of the first three changes the finding to present, not exploitable.

The general steps for any advisory, from confirming the affected range to testing the smallest fixed version, are in why npm audit fix is not a reachability decision. This section fills them in for CVE-2021-44228 as a worksheet you can copy.

ConditionHow to checkEvidence to keepYour answer
1. A vulnerable log4j-core release is loaded at runtimeThe version check above, against the affected ranges on Apache’s security pageThe tree output or file listing, with versionsYes / no
2. The app logs text an outsider controls through that libraryRead the logging calls near request handling: headers, form fields, usernames, anything from a requestThe file and line of each such call, or a note that none existYes / no
3. Message lookups are activeReleases before 2.15.0 resolve lookups in messages in the Pattern Layout by default; Apache’s security page lists only the upgrade as the Log4j 2 fixThe version, and the release notes line it matchesYes / no
4. The server can open outbound connections to arbitrary hostsThe firewall or egress rules for that hostThe rule set, datedYes / no

An evidenced no on condition 1, 2 or 3 turns “critical” into “present, not exploitable, upgrade on the normal schedule”. A no on condition 4 alone lowers the risk and does not clear it, because a lookup can still leak data through DNS (my reading). On condition 3, I would not record a no from a startup setting on an old release: the 2.15.0 release notes offered one for users who could not upgrade, but the current security page names only the upgrade for Log4j 2. The record says which condition failed and how it was checked. Then upgrade anyway: my working rule is that the upgrade is cheaper than defending the judgment later. The same worksheet works for the next CVE once you rewrite the four conditions from that advisory’s own text.

EPSS score meaning, next to CVSS and the KEV list

An EPSS score is the estimated probability, from 0 to 1, that a CVE will be exploited in the next 30 days, published daily by FIRST. CVSS rates how severe a flaw would be. CISA’s KEV catalog lists flaws already exploited. None of them knows your app.

ScoreWho publishes itWhat it answersWhat it does not
CVSS base scoreNVD (0 to 10)How severe the flaw would be if exploitedWhether anyone is exploiting it, or whether it reaches your code
EPSSFIRST, daily, as a probability with a percentileHow likely exploitation in the wild is in the next 30 daysWhether your copy is reachable
KEV catalogCISAWhether the flaw has been exploited in the wildWhether your copy is reachable

For CVE-2021-44228 the values come from NVD, FIRST’s EPSS page and CISA’s Known Exploited Vulnerabilities catalog, and the three agree: NVD’s CVSS base score is 10.0, KEV has listed it since December 10, 2021, and FIRST’s EPSS lookup on September 29, 2026 returned 0.99999, at the 100th percentile. Look the values up on the day you write the record and date them, because EPSS changes daily. None of the three says whether your app reaches the flaw, which is why the worksheet comes first. Scoring a whole report, and what EPSS value counts as high, belongs with vulnerability management tools.

Log4net vulnerability and Log4j 1.x: different libraries, different CVEs

The log4net vulnerability is not Log4Shell. log4net is a separate Apache library for .NET with CVEs of its own, listed on Apache’s security page; the newest, CVE-2026-40021, is silent log event loss in its XML layouts, fixed in 3.3.0. Log4j 1.x is a third code base, now end of life.

NVD states it directly: CVE-2021-44228 “is specific to log4j-core and does not affect log4net, log4cxx, or other Apache Logging Services projects.” Apache’s security page covers these log4net and Log4j 1 cases:

LibraryCVEWhat it isAffected / fixed
log4net (.NET)CVE-2026-40021Silent log event loss in XmlLayout and XmlLayoutSchemaLog4J due to unescaped XML 1.0 forbidden characters; 6.3 medium (CVSS 4.x)Before 3.3.0 / 3.3.0
log4net (.NET)CVE-2018-1285XXE via attacker-controlled log4net config files; Apache now says that under its current threat model this “is no longer considered a vulnerability”Before 2.0.10 / 2.0.10
Log4j 1.x (Java)CVE-2021-4104Vulnerable to the JNDI attack only when JNDI is used in its configuration, through a JMSAppenderNot fixed (end of life); remove any JMSAppender from the configuration

NVD’s entry for CVE-2018-1285 scores it 9.8 critical and describes applications “that accept attacker-controlled log4net configuration files.” Log4j 1 is a different code base: Apache says it “has reached End of Life in 2015,” that vulnerabilities reported after August 2015 “are not checked and will not be fixed,” and that “Log4j 1 does not have Lookups, so the risk is lower.” The answer there is migration to Log4j 2, not a patch. A scanner that matches on the string “log4” flags all three libraries, so the record should name the library and the CVE for each finding.

How to check your own app

A Log4j check is complete after 6 steps: an inventory of everything that could run a JVM, a version check on each, vendor statements for managed services, the four-condition worksheet, the upgrade with a regression test, and dependency alerts turned on.

Each check can fail, and each leaves evidence:

  1. 01 Inventory everything you run that could contain a JVM: your services, self-hosted products and CI. Evidence: the list with an owner per item
  2. 02 Run the version check on each item. Evidence: the dependency tree or file search output, versions included
  3. 03 Collect vendor statements for managed services and products. Evidence: the advisory URL and the date you read it
  4. 04 Fill in the four-condition worksheet for every copy found. Evidence: the completed table
  5. 05 Upgrade, then re-run the version check and a regression test. Evidence: before and after versions and the test run
  6. 06 Keep it from coming back: dependency alerts on, update pull requests automated, raw request input kept out of logs. Evidence: the settings, dated

For step 6, automated update pull requests come from a bot such as Renovate or Dependabot, and what Renovate bot is and how it opens them is a short setup of its own; npm malware packages are a separate threat; and what to keep out of logs is part of how to do logging. The one-page record of date, what was checked, what was found and the decision per copy is what answers a questionnaire row. Deliverable 3.5 in the sprint is verified this way: Record the dependency scan, remediation, and regression checks after changes.

Where the sprint fits

The nearest sprint deliverable is 3.5, Dependency remediation: we audit dependencies, upgrade or remove known-vulnerable packages, and remove unused packages. Deliverable 7.9 configures Renovate, Dependabot, or an equivalent to propose dependency updates with checks, and deliverable 3.10 tests the five highest-risk externally reachable attack surfaces, remediates findings, and delivers the methods and evidence. The sprint covers one codebase. Formal third-party certifications and independent audit opinions are separate from these engineering deliverables. Every deliverable is listed in the published sprint scope.

Common questions about Log4j and Log4Shell

Is Log4j vulnerability fixed?

Yes, in the library: all four December 2021 CVEs are fixed in 2.17.1 for Java 8 and later, in 2.12.4 for Java 7 and in 2.3.2 for Java 6. It is not fixed in every deployment, because an old copy keeps running until someone upgrades it, which is why the version check exists.

Is Log4j 2.17 vulnerable?

Not to Log4Shell: 2.17.0 is past the fixes for CVE-2021-44228, CVE-2021-45046 and CVE-2021-45105, but NVD lists 2.17.0 as affected by CVE-2021-44832, fixed in 2.17.1. Apache’s security page also lists later log4j-core flaws whose affected range covers 2.17.x, such as CVE-2026-34480 (a medium-severity log event loss in XmlLayout, fixed in 2.25.4), so the current release is the safer target.

Is Log4j deprecated?

Log4j 1 is: Apache says it “has reached End of Life in 2015, and is no longer supported.” Log4j 2 is not; the project says it is “actively maintained by a team of volunteers”, and its release notes list 2.26.1 (June 29, 2026) and 2.25.5 (July 1, 2026) as the newest releases. Only the latest minor release of the latest major version stays in active maintenance; for an older line past maintenance, Apache says new releases, “including security fixes, are very unlikely.”

Is Log4j 1.2 vulnerable?

Not to Log4Shell itself, but Apache says Log4j 1 is vulnerable to the JNDI attack when JNDI is used in its configuration, filed separately as CVE-2021-4104, and that vulnerabilities reported after August 2015 against Log4j 1 “are not checked and will not be fixed.” The fix is migration to Log4j 2.

Is Log4Shell still a threat?

Only where an unpatched copy still runs. CISA’s KEV catalog has listed CVE-2021-44228 as exploited in the wild since December 10, 2021, with known ransomware campaign use, and the CSRB expects vulnerable instances to “remain in systems for many years to come, perhaps a decade or longer.” An inventory, a version check and an upgrade take it off your list.

What can I use as a replacement for Log4j?

A replacement is not required: an upgraded Log4j 2 clears the December 2021 CVEs. If you want to move anyway, Apache notes that code written against the Log4j API can switch to other implementations “such as Logback or JUL (java.util.logging)”.

The version history is in Log4j 2’s release notes.

How to get rid of Log4j vulnerability?

Upgrade every log4j-core copy to the fixed release for its Java line (2.17.1, 2.12.4 or 2.3.2), upgrade vendor products per their own advisories, then re-run the version check to confirm no old copy is left. For the original CVE, Apache’s security page gives the upgrade as the Log4j 2 mitigation and names no configuration workaround.