- Project: Apache Log4j 2 (
apache/logging-log4j2) - Version pinned: 2.26.0 (latest GA at time of writing; HEAD = 2.27.0-SNAPSHOT)
- Java baseline: Java 17
- Date: 2026-05-13
- Author: drafted by the
produce-threat-modelskill in interview-first mode with a maintainer in the loop - Status: draft v1, under maintainer review
- Reporting cross-reference: vulnerabilities go to
security@logging.apache.orgper the project security page; findings closed by this document cite the section here, e.g. "see Log4j 2 threat model §4.9".
| Tag | Meaning |
|---|---|
| (documented) | Stated in a project artifact: the website's _threat-model-common.adoc and security/faq.html, the 2.x-specific security.html, the manual/systemproperties.html manual page, the repo's SECURITY.adoc, AGENTS.md, or FUZZING.adoc. |
| (maintainer) | Stated by a Log4j maintainer in response to this drafting process. Date appended. |
| (inferred) | Reasoned from code structure, absence of a feature, or domain knowledge — not yet confirmed. Every (inferred) tag has a matching open question in §4.14. |
| Documented | Maintainer | Inferred |
|---|---|---|
| 20 | 35 | 17 |
(Counts are literal tag occurrences, including a few in the legend and §4.14 prose. 15 substantive (inferred) claims map 1:1 to §4.14 questions; 2 of the occurrences are meta references. Goal: drive (inferred) to zero in the next review round.)
Apache Logging Services publishes a cross-project threat model at
_threat-model-common.adoc,
shared across Log4cxx, Log4j, and Log4net. This document is the
Log4j-2.x-specific model. It is a strict superset of the common doc:
every claim in the common doc maps to a section here (see §4.16 back-map),
nothing is silently dropped or weakened, and where this document adds
claims (per-parameter trust table, component-family carving, triage
dispositions, etc.) they extend the common doc rather than contradict it.
(maintainer, 2026-05-13)
Log4j 2 is a Java logging API and backend. An application instantiates one
or more Loggers, calls log.info(...) / log.error(...) / etc., and a
configured set of appenders writes formatted records somewhere (a file, a
socket, a database, a mail server, a JMS topic). The library is loaded
in-process with the application and configured by trusted files,
system properties, and environment variables. It is not a service: it does
not listen on any socket, does not start before the JVM, and offers no
network surface of its own. The threat model below describes what Log4j
takes responsibility for in that setting and what it explicitly leaves to
the embedding application and operator.
- In-process Java logging library, loaded as Maven/Gradle artifacts
under
org.apache.logging.log4j:log4j-*. (documented) - Callers are the application code (developers) and, via configuration, the operator (administrators / deployers). Both are Trusted Users in the common threat model. (documented)
- Deployment shapes: standalone JVMs, application servers, Spring Boot apps, containers. The library is the same artifact in all of them; the threat profile is invariant. (inferred) → §4.14 Q1
| Family | Modules | Touches outside the process? | In model? |
|---|---|---|---|
| A — Core | log4j-api, log4j-core, log4j-api-java9, log4j-core-java9 |
No (only via configured appenders) | Yes |
| B — Bridges & adapters | log4j-1.2-api, log4j-jcl, log4j-jul, log4j-jpl, log4j-slf4j-impl, log4j-slf4j2-impl, log4j-to-jul, log4j-to-slf4j, log4j-iostreams, log4j-jpa, log4j-taglib, log4j-web, log4j-jakarta-web, log4j-appserver, log4j-spring-boot, log4j-spring-cloud-config-client |
No new surface; adapts other APIs to Log4j or vice versa | Yes, same trust profile as A |
| C — Optional appenders / integrations | log4j-jakarta-jms, log4j-jakarta-smtp, log4j-jdbc-dbcp2, log4j-mongodb, log4j-mongodb4, log4j-cassandra, log4j-couchdb, log4j-docker, log4j-layout-template-json |
Yes — outbound to a trusted operator-supplied endpoint | Yes, with §4.10 obligation that the endpoint is trusted |
| D — Test / build / perf harness | log4j-*-fuzz-test, log4j-*-test, log4j-*-its, log4j-perf-test, log4j-osgi-test, log4j-parent, src/site |
n/a | No — not shipped to users (-test / -fuzz-test / -its / -perf-test) or build infrastructure |
(maintainer, 2026-05-13) for the carving.
- Family D modules as above: test, fuzz, integration-test, performance,
and build modules are not part of the shipped surface. A vulnerability
in
log4j-perf-testisOUT-OF-MODEL: unsupported-component. (maintainer, 2026-05-13) - Log4j 1.x. Lives in a separate repository (
apache/logging-log4j), is EOL, and is not covered here. Thelog4j-1.2-apibridge in Log4j 2 IS covered (Family B). (maintainer, 2026-05-13) - 3.x development branch, until it has a GA release. (maintainer, 2026-05-13)
- Untrusted configuration. Configuration files, system properties, and environment variables are trusted resources of the deployer; an attack that requires writing log4j configuration is out of scope. The reclassification of CVE-2018-1285 (Log4net XXE via attacker-controlled config) is the worked example. (documented)
- Untrusted plugin classpath. Anyone who can add a class to the classpath has already won; plugin-loading paths are not a security boundary. (inferred) → §4.14 Q2
- Side-channel attackers (timing observers, memory observers, co-tenant observers). Log4j does not provide constant-time anything. (inferred — companion to the explicit "no constant-time guarantees" in §4.9) → §4.14 Q3
- Log masking. Sensitive-data redaction is the application's job
before it reaches a
log.*()call. (documented) - Log file access control. It is the deployer's job to keep written log files away from unauthorized readers. (documented)
- Log delivery reliability. The project explicitly does not guarantee delivery of every event. (See §4.9 P6.) (documented + maintainer, 2026-05-13)
The API surface is the trust boundary on the input side. Anything
that arrives at a public method of org.apache.logging.log4j.Logger,
ThreadContext, etc., is the data we model. Below that, internal
collaboration between Log4j classes is not a security boundary —
in-process code is fully trusted.
The appender's outbound destination is the trust boundary on the output side, but in the opposite direction: Log4j trusts its operator-supplied destination (mail server, JMS broker, JDBC database, etc.). The destination is not an adversary of Log4j; it is an extension of the operator. (maintainer, 2026-05-13)
[ application code ]
| (1) log.*(message, params, throwable)
v
[ Logger ]----(2) reads ThreadContext (Map + Stack)
|
v
[ LogEvent assembly ]
|
v
[ Filters ] -- drop or pass
|
v
[ Layouts ] (Pattern | XML | JSON | RFC 5424 | template-json)
|
v
[ Appenders ] --> [ operator-trusted destination ]
(file | socket | DB | JMS | SMTP | console | ...)
(1) is the adversary-controllable entry path (for message text,
parameter toString(), ThreadContext values, ThreadContext stack
frames, Throwable contents).
(2) is partly adversary-controllable (TC values, stack frames) and partly trusted (TC keys, per the wave-1 maintainer decision).
(3) (downstream of the appender) is trusted by virtue of being operator-configured.
- Family A finding is in-model only if reachable from input that the adversary model marks attacker-controllable (per §4.6) on a default 2.x build (per §4.5a). (inferred) → §4.14 Q4
- Family B finding inherits Family A's preconditions.
- Family C finding is in-model only if reachable from in-scope input AND does not require manipulating the trusted destination. A finding that requires a malicious JMS broker to send back a crafted message is out of model. (maintainer, 2026-05-13)
- JVM: Java 17 or later for the 2.26 line. (documented —
.java-version,pom.xml) - Memory model: standard JMM; allocator behavior per the platform. (inferred) → §4.14 Q5
- Concurrency: the caller can invoke
Loggermethods from any thread; the library handles synchronization internally. (See §4.8 P4.) - Time / clock: Log4j reads wall-clock time for timestamps; monotonicity is not required. (inferred) → §4.14 Q6
- Filesystem & network: see negative inventory below.
In a default-configured Log4j 2.x (Family A only loaded, no Family C modules referenced from config): (maintainer, 2026-05-13)
- Does not open any inbound listener (no TCP/UDP server socket, no HTTP server, no JMS consumer).
- Does not spawn child processes.
- Does not install signal handlers (uses the JVM shutdown hook only,
via
ShutdownCallbackRegistry). - Does not modify the
SecurityManagerorPolicy. - Does not touch the system class loader or boot classpath.
- Reads environment variables only during configuration resolution and
through the
env:lookup (which is config-resolved, not message-resolved, by default since 2.16). - Reads filesystem only at: the configured config file, its
monitorIntervalwatcher, and any appender's destination file. - Does not mutate process-wide state (locale, default
TimeZone, default charset, FPU state). - Does not use
java.lang.instrumentor attach an agent. - Does not use
Unsafe/sun.misc/ JNI of its own.
Family C modules extend this set: log4j-jakarta-jms opens an
outbound JMS connection, log4j-jakarta-smtp opens an outbound SMTP
session, etc. — all outbound. No optional module opens an inbound
listener. (maintainer, 2026-05-13)
Log4j 2 has no compile-time flags that change the security envelope
(no #ifdef-style switches). The runtime knobs that do are: (documented
— manual/systemproperties.html)
| Property | Default | Env. variable | Posture | Maintainer stance |
|---|---|---|---|---|
log4j2.enableJndiLookup |
false |
LOG4J_ENABLE_JNDI_LOOKUP |
${jndi:…} lookup will not resolve unless flipped; even then only the java: protocol is permitted. |
Supported off. |
log4j2.enableJndiJms |
false |
LOG4J_ENABLE_JNDI_JMS |
JMS Appender will not resolve JNDI without it. | Supported off. |
log4j2.enableJndiJdbc |
false |
LOG4J_ENABLE_JNDI_JDBC |
JDBC Appender will not resolve a DataSource via JNDI without it. |
Supported off. |
log4j2.enableJndiContextSelector |
false |
LOG4J_ENABLE_JNDI_CONTEXT_SELECTOR |
JndiContextSelector only enabled when flipped. |
Supported off. |
log4j2.disableJmx |
true (since 2.24.0) |
LOG4J_DISABLE_JMX |
JMX MBean registration off by default. | Supported off. |
log4j2.scriptEnableLanguages |
empty | LOG4J_SCRIPT_ENABLE_LANGUAGES |
Scripting is disabled until the operator lists script engines. | Supported off. |
log4j2.sslVerifyHostName |
false |
(n/a) | TLS hostname verification off by default; mutual TLS not required. (documented — manual) (maintainer, 2026-05-13) | Supported posture, with hardening planned. A report against this default is VALID-HARDENING per §4.13. |
Insecure-default analysis (§6.8 Q23a). All JNDI / JMX / scripting
knobs ship in their safer setting and the operator must explicitly opt
in. The one remaining insecure default is log4j2.sslVerifyHostName=false
and the absence of mutual-TLS requirements on the Socket appender;
maintainers consider this the currently-supported production posture
but plan to harden in a future version (cf. CVE-2026-34477 and
CVE-2025-68161). Triage: see §4.13. (maintainer, 2026-05-13)
Properties NOT in this table that producers commonly mention but should not appear here:
log4j2.formatMsgNoLookups— irrelevant; lookups in message text were removed in 2.16.log4j2.enableJndi— no such master switch exists.log4j2.allowedLdapClasses/allowedJndiProtocols/allowedLdapHosts— do not exist; the JNDI restriction to thejava:protocol is hard-coded.log4j2.jmxNotifyAsync— perf, not security.
A Logger method call carries up to four kinds of data: format-string
(trusted), parameter values (untrusted), optional Throwable
(untrusted), and implicit context (ThreadContext Map + Stack;
Marker; logger name). The per-parameter trust table below names each.
| Entry point | Parameter | Adversary-controllable? | Caller / operator must enforce |
|---|---|---|---|
Logger.info(String fmt, Object... params) |
fmt |
No — should be a compile-time constant | Never source format strings from input (documented) |
| same | params[i] (and their toString()) |
Yes | Mask sensitive data before logging (documented) |
Logger.info(String fmt, Throwable t) |
t (message + stack trace) |
Yes | nothing — Log4j must safely render (inferred) → §4.14 Q7 |
Logger.info(Marker m, ...) |
m.getName() |
No by recommendation; project will not fail if app derives a marker name from user input, but cardinality is the app's problem (maintainer, 2026-05-13) | Don't generate unbounded unique markers from user input |
LogManager.getLogger(name) |
name |
No by recommendation; same cardinality caveat as Marker (maintainer, 2026-05-13) | Don't generate unbounded unique loggers from user input |
ThreadContext.put(k, v) |
k (key) |
No — trusted per recent decision (maintainer, 2026-05-13), superseding the common doc | — |
| same | v (value) |
Yes (documented) | If your layout interpolates TC values, do not put attacker-controlled data into them, especially with enableJndiLookup=true |
ThreadContext.push(message) |
stack-frame contents | Yes (documented) | — |
| RFC 5424 layout fields | MSGID, SD-ID, SD-PARAM-NAME, APP-NAME, HOSTNAME, PROCID |
No — trusted identifiers per maintainer decision (maintainer, 2026-05-13), superseding the common doc | — |
| same | MSG, PARAM-VALUE |
Yes (documented) | — |
- No documented hard cap on message length or parameter count; per-call resource use is expected to be linear (§4.8 P3). (inferred) → §4.14 Q8
- Streaming: a single log call is processed in one shot; there is no partial / chunked event API. (inferred) → §4.14 Q9
- Rate: backpressure is the operator's concern (async appender, log level, rate limit). (documented)
Anyone whose data flows into a log.*() call via the untrusted
columns of the §4.6 table. Capabilities the in-scope adversary has:
- Submit arbitrary byte sequences (any encoding, including malformed
UTF-8) and control characters (CR, LF, NUL, etc.) via message text,
parameter
toString(), TC values, TC stack frames, RFC 5424MSG/PARAM-VALUE, or aThrowable's message and stack. - Use lookup syntax
${...}(which since 2.16 does not expand in message text by default, but the operator may still expose it via TC value + a layout that interpolates). - Submit very long strings (within whatever upstream limits the application enforces).
- Trigger recursive references in their input.
- Anyone who can write
log4j2.{xml,properties,yaml,json}or any other configuration file. - Anyone who can set environment variables or system properties on the JVM.
- Anyone running code in the same JVM (in-process callers, malicious plugins, JVM agents).
- Anyone who can add a plugin / class to the classpath.
- Anyone who controls the JVM bytecode itself.
- Side-channel observers (timing, memory).
- A malicious destination for any appender (a malicious mail server, JMS broker, DB engine, etc.). These are operator-trusted endpoints. A "the JMS broker sent me a hostile reply" report is out of model. (maintainer, 2026-05-13)
This adversary model supersedes the website common doc on three specific points: (a) ThreadContext Map keys are trusted (the common doc says keys+values are untrusted; the recent maintainer decision elevates keys), (b) RFC 5424 MSGID and SD-ID are trusted, (c) RFC 5424 SD-PARAM-NAME, APP-NAME, HOSTNAME, PROCID are trusted. All other points remain as the common doc has them. (maintainer, 2026-05-13)
| ID | Property | Conditions | Violation symptom | Severity tier | Provenance |
|---|---|---|---|---|---|
| P1 | No RCE from in-scope input. No code outside Log4j and its declared dependencies executes as a consequence of in-scope input alone. | Default 2.x build (per §4.5a). | Arbitrary code executes from log message, parameter, or ThreadContext value. | SECURITY-CRITICAL | (maintainer, 2026-05-13); consistent with the post-2.17 reshape after CVE-2021-44228. |
| P2 | Structured-layout injection prevention. XML, JSON, and RFC 5424 layouts produce well-formed output that cannot be made to inject extra fields, break framing, or split records by in-scope input. | Layout is one of the structured layouts; output is consumed as that format. CRLF in unstructured layouts is out (§4.9). | Injected field, broken framing, attacker-controlled record boundary. | SECURITY-CRITICAL | (documented — common threat model) |
| P3 | Bounded resource use per log call. Per-call CPU and memory use is linear (or better) in the size of a single in-scope input; no unbounded recursion reachable from in-scope input. | Configuration is trusted. Input is in-scope. | Hang, OOM, or StackOverflowError originating from a Log4j frame during a logger call. |
SECURITY-CRITICAL if super-linear or unbounded; correctness-only for constant-factor regressions. | (maintainer, 2026-05-13) — explicit threshold not previously written down. |
| P4 | Thread safety of the public API. Concurrent log.*() calls from any thread on any number of Logger instances do not crash, corrupt internal state, or deadlock. |
Caller uses the public API. | Crash, internal-state corruption, deadlock originating in a Log4j frame. | CORRECTNESS, unless it enables P1/P2/P3. | (inferred) → §4.14 Q10 |
| P5 | No deserialization of in-scope bytes. Log4j Core does not pass bytes obtained from any in-scope source to ObjectInputStream.readObject() or equivalent. No Family C module deserializes its backing transport's bytes (maintainer, 2026-05-13). |
Production code path; default modules. | In-scope bytes reach a deserializer controlled by Log4j. | SECURITY-CRITICAL | (documented — FAQ; maintainer 2026-05-13 for Family C extension) |
| P6 | Best-effort delivery with documented escape. Events are delivered as configured; when an async queue fills, events are written to the internal StatusLogger rather than dropped silently. | Async appender configured; queue full. | Async event vanishes without surfacing on StatusLogger. | CORRECTNESS | (maintainer, 2026-05-13) |
| P7 | Garbage-free-mode invariance. Enabling garbage-free mode does not weaken P1–P5. | Garbage-free mode is enabled per log4j2.enable.threadlocals and related properties. |
Any of P1–P5 holds in default mode but not in garbage-free mode. | Same as the underlying property. | (maintainer, 2026-05-13) → §4.14 Q11 (edge probe) |
| P8 | Plugin discovery determinism. Plugin discovery is deterministic given a fixed classpath. | Classpath is fixed for the lifetime of the JVM (the trusted-classpath assumption of §4.3). | Two runs with the same classpath produce different plugin resolutions. | CORRECTNESS (supports supply-chain reasoning). | (maintainer, 2026-05-13) → §4.14 Q12 (edge probe) |
The companion to §4.8. Stated plainly so a downstream integrator does not import an assumption Log4j refuses to make.
- No log-injection protection in unstructured layouts. Pattern Layout has no field structure and makes no attempt to neutralize control characters; CRLF in Pattern Layout output is not a vulnerability. (documented — security FAQ)
- No CRLF escaping in RFC 5424
MSG/PARAM-VALUEfields by the layout itself. RFC 5424 §8.2 delegates control-character handling to the transport binding; Log4j follows the RFC. (documented — security FAQ) - No safety guarantee for deserialization performed by the application. Log4j Core itself does not deserialize (P5), but if your application serializes Log4j objects and deserializes them from an untrusted source, that is not Log4j's problem. Filtering by package or namespace is not sufficient to make deserialization safe; any hardening utilities Log4j ships are partial and not exhaustive. (documented — common threat model + FAQ)
- No log delivery guarantee. P6 covers the StatusLogger fallback; the project does not promise that every event reaches every appender's destination. (documented + maintainer, 2026-05-13)
- No ordering guarantee across threads or across appenders. (inferred) → §4.14 Q13
- No constant-time operations. Log4j does not implement side-channel-resistant primitives. Do not use Log4j to compare secrets, render secrets, or in any code path where timing is a security signal. (inferred — companion to §4.3) → §4.14 Q3
- No confidentiality of log content. Whatever the application
hands to
log.*()may end up in any appender's destination in plain text; redaction is the application's responsibility (§4.10). (documented) - No TLS hostname verification or mutual TLS by default on the
Socket appender (
log4j2.sslVerifyHostName=false). This is the one remaining insecure default; see §4.5a and §4.13. (maintainer, 2026-05-13)
- JSON / XML / RFC 5424 layouts ≠ a confidentiality boundary. They prevent injection (P2), not exposure. Sensitive data still has to be masked by the application.
- The
${jndi:…}lookup ≠ a sandboxed retrieval mechanism. Even withenableJndiLookup=trueand thejava:-protocol restriction, this is a designed-for-trusted-input feature, not a defensive layer against untrusted input. monitorIntervalconfig reload ≠ a hot-patching security mechanism. It is a developer-convenience reload. It does not gate what the new config can do.StatusLogger≠ a structured event channel. It writes to stderr by design and is for the operator's eyes. (inferred) → §4.14 Q14
- Log injection into downstream parsers (CWE-117) for unstructured layouts (Pattern Layout). Pick a structured layout, or sanitize at the parser side.
- Log forging beyond CRLF: timestamps, level fields, MDC contents in a Pattern Layout can be spoofed by message content.
- Compression-oracle-style attacks against any compressed appender destination. Not modeled.
- Decompression bombs on the source of the configuration only — but configuration is trusted, so this is N/A in practice.
- Resource exhaustion via cardinality (unbounded marker / logger names from user input) — see §4.10. (maintainer, 2026-05-13)
The contract the embedding application and operator must uphold for the §4.5–§4.7 assumptions to hold:
Application developer:
- Use compile-time-constant format strings; never source them from input. (documented)
- Mask sensitive data before the
log.*()call. (documented) - Don't put attacker-controlled data into ThreadContext values if your layout interpolates them. (documented)
- Don't derive Marker or Logger names from untrusted input; if you do, you own the cardinality / memory cost. (maintainer, 2026-05-13)
- Don't deserialize Log4j-emitted bytes from an untrusted source. (documented)
Operator / deployer:
- Keep configuration files, system properties, and environment variables out of attacker reach. (documented)
- Use confidential channels (TLS) for any non-local appender; disable HTTP / JMX on untrusted networks. (documented)
- Set
log4j2.sslVerifyHostName=trueand configure mutual TLS on any Socket appender exposed to an untrusted network — the default is unsafe and will remain so until a future hardening. (maintainer, 2026-05-13) - Restrict log-file read access to authorized readers. (documented)
- If you flip any §4.5a knob (
enableJndi*,disableJmx=false,scriptEnableLanguages), audit the downstream data flow first; having flipped one is sufficient to move a finding fromOUT-OF-MODELto operator responsibility. (maintainer, 2026-05-13)
Operator of a Family C destination:
- The destination (mail server, JMS broker, DB, Mongo, Cassandra, Couch, Docker daemon) is trusted; do not connect Log4j to one you do not operate. (maintainer, 2026-05-13)
The single highest-frequency misuse, per maintainer:
- Treating Pattern Layout output as machine-parseable. Pattern Layout is for human readers; it has no escaping rules. Downstream parsers that consume it as structured data and assume sanitization will be surprised. The fix is to switch to a structured layout (JSON, XML, RFC 5424) for the machine-consumed pipeline. (maintainer, 2026-05-13)
Other recurring misuses (lower frequency, all (inferred) until maintainer ratifies → §4.14 Q15):
- Putting attacker-controllable data into ThreadContext values and
using a layout that interpolates them, especially while also flipping
enableJndiLookup=true— the Log4Shell-shape misuse. - Deriving
LoggerorMarkernames from request-scoped user data and producing unbounded cardinality. - Logging the result of
Throwable.toString()of an exception whose message field carries user input, then parsing the rendered output. - Using
log4j-1.2-apias if it gave 1.x guarantees; it is a bridge, not a re-implementation.
Tool, fuzzer, AI, and human reports that match these patterns are not bugs under this model. Cite the row when closing.
| Pattern | Why it's not a finding | Suppression cite |
|---|---|---|
| CRLF (or any control char) appears in Pattern Layout output | Pattern Layout is unstructured by design; §4.9 disclaims log-injection protection there. | §4.9, security/faq.html#crlf-injection-cwe-93 |
CRLF (or any control char) appears in RFC 5424 MSG or PARAM-VALUE |
RFC 5424 §8.2 delegates this to the transport binding; Log4j follows the RFC. | §4.9, security/faq.html#crlf-injection-cwe-93 |
| Path-traversal-ish characters in a config file path | Config files are trusted; §4.3. | §4.3, security/faq.html#path-traversal-cwe-35 |
Static analyzer flags ObjectInputStream.readObject() somewhere in a Log4j module |
Log4j Core does not deserialize bytes from any in-scope source on the production path (P5); the call site reachable only from trusted sources (config) is not a finding. | §4.8 P5, security/faq.html#deserialization-of-untrusted-data-cwe-502 |
Anything in log4j-*-test, -fuzz-test, -its, -perf-test, -osgi-test |
Out of scope per §4.3. | §4.3 |
| "Lookups in message text are an RCE" | Removed in 2.16; reporter is reading pre-2.16 behavior. | §4.5a (formatMsgNoLookups note) |
A §4.14 question asks whether further patterns belong here.
A revision is required when any of these is true:
- A new public API surface is added (e.g. a new entry point that accepts data of a kind not yet in the §4.6 table).
- An existing entry point begins to accept input from a new source.
- A §4.5a default changes (especially
sslVerifyHostNamewhen it is hardened) or a knob is added/removed. - Log4j gains a network listener (it currently has none).
- A Family-C module begins to read inbound bytes from its backing transport (would require P5 to be re-examined).
- A bridge module (Family B) develops a trust profile distinct from Family A's.
- The 3.x branch reaches GA.
- A vulnerability report cannot be cleanly routed to any §4.13
disposition. (See §4.13
MODEL-GAP.)
Internal refactors that do not change any of the above do not require revision.
The closed set of outcomes for a vulnerability report, tool finding, or AI analysis judged against this model.
| Disposition | Meaning | Licensed by |
|---|---|---|
VALID |
Violates a §4.8 property via an in-scope adversary (§4.7) and an attacker-controllable input (§4.6) on a default 2.x build (§4.5a). | §4.6, §4.7, §4.8 |
VALID-HARDENING |
No §4.8 property is violated, but the API or default makes a §4.11 misuse easy enough that the project elects to harden. Reported privately; fixed at maintainer discretion; typically no CVE. The sslVerifyHostName=false default is currently in this category. (maintainer, 2026-05-13) |
§4.5a, §4.11 |
OUT-OF-MODEL: trusted-input |
Requires attacker control of a parameter the §4.6 table marks No (e.g. format string, TC key, logger name, RFC 5424 identifier fields). | §4.6 |
OUT-OF-MODEL: adversary-not-in-scope |
Requires a capability outside §4.7 (in-JVM execution, classpath control, side-channel observation, malicious appender destination). | §4.7 |
OUT-OF-MODEL: unsupported-component |
Lands in Family D (test/fuzz/perf harness) or in code shipped from a different repo (Log4j 1.x, 3.x pre-GA). | §4.3 |
OUT-OF-MODEL: non-default-build |
Manifests only under a §4.5a knob the operator flipped from its safe default (enableJndi*, scriptEnableLanguages, disableJmx=false). |
§4.5a |
BY-DESIGN: property-disclaimed |
Concerns a property §4.9 explicitly does not provide (CRLF in unstructured layouts, ordering, constant-time, deserialization-by-the-app). | §4.9 |
KNOWN-NON-FINDING |
Matches a §4.11a row. | §4.11a |
MODEL-GAP |
Cannot be cleanly routed to any of the above; triggers a §4.12 revision rather than an ad-hoc call. | §4.12 |
Wave-4 questions, prioritized. Every (inferred) tag in the body above maps to one of these.
§4.2 claims the threat profile is the same in standalone JVM, app server, Spring Boot, and container. Proposed answer: yes, because in all cases Log4j runs in-process with the application and the classpath is operator-trusted. Confirm or carve out any exception (e.g. shared-classloader app servers where another tenant's code is in the same JVM).
§4.3 places "anyone who can add a class to the classpath" out of scope. Proposed answer: yes, plugin loading uses standard classpath discovery; classpath is part of the deployment, which is operator-trusted. Confirm.
§4.3 + §4.9. Proposed answer: fully out of scope; Log4j makes no constant-time / side-channel-resistance claim. Confirm there is no appender or layout where you do claim some side-channel resistance.
§4.4. Proposed answer: "A Family A finding is in-model only if reachable from an attacker-controllable input on a default 2.x build." Is this the wording you want, or do you want to add "and through a public entry point" (i.e. excluding findings in internal collaborator classes reached only by reflection)?
§4.5. Proposed answer: Log4j assumes the standard Java Memory Model; no other claim. Confirm.
§4.5. Proposed answer: wall-clock used for timestamps; monotonicity is not assumed; clock skew between hosts is the operator's problem. Confirm.
§4.6 row. Proposed answer: yes — a Throwable whose message
field carries user input is in-scope adversary-controllable; Log4j
must render it safely (P1, P2 inside structured layouts). Confirm.
§4.6. Proposed answer: no hard caps; resource use is bounded per P3. Confirm there is no documented per-call message length, parameter count, or TC entry limit.
§4.6. Proposed answer: no streaming-event API; each log.*()
call is a single event. Confirm.
§4.8. Proposed answer: blanket thread safety of the public API;
non-public *Impl types are not part of the claim. Wave-2 answer
suggested the only carve-out is the async-full → StatusLogger
fallback, which is a P6 concern, not a P4 one. Confirm P4 stands.
§4.8. Proposed answer: P1–P5 hold identically in garbage-free mode. Any known case where garbage-free mode weakens a guarantee (e.g. a layout that falls back to a less-safe path under GC pressure)?
§4.8. Proposed answer: deterministic given a fixed classpath. Any known nondeterminism (e.g. file-system enumeration order on certain class loaders) that should be carved out?
§4.9. Proposed answer: no ordering guarantee across threads or across appenders. Confirm.
§4.9 false-friends row. Proposed answer: StatusLogger writes to stderr by design; reports about its destination or format are not findings. Confirm; should this also be a §4.11a row?
§4.11. The top misuse is "treat Pattern Layout output as machine-parseable" (maintainer, 2026-05-13). The four secondary patterns (TC-value-into-interpolating-layout, unbounded marker/logger cardinality, Throwable message echo, log4j-1.2-api expectations) are (inferred). Confirm or strike each.
Wave-3 answer said "none of these options have been reported lately." But the current §4.11a has only six rows (four from the FAQ, two from this drafting). Are there others scanners or researchers recurrently submit that should be on the list — e.g. specific CodeQL queries, specific FindSecBugs rules, specific OSS-Fuzz signal types? Without a list, triagers will keep re-deriving the calls.
§4.1 says this doc is the canonical Log4j-2 model and the website's
common doc remains the cross-project parent. Do you want this file
checked into apache/logging-log4j2 (and published as part of the
Log4j 2 docs site, replacing the brief link in SECURITY.adoc), or
checked into apache/logging-site next to _threat-model-common.adoc?
Affects where the back-map (§4.16) lives.
A sidecar log4j-threat-model.yaml should expose, at minimum:
families:
A: { modules: [log4j-api, log4j-core, ...], in_model: true }
B: { modules: [log4j-1.2-api, ...], in_model: true }
C: { modules: [log4j-jakarta-jms, ...], in_model: true, endpoint_trusted: true }
D: { modules: ["log4j-*-test", ...], in_model: false }
flags:
log4j2.enableJndiLookup: { default: false, security_relevant: true, posture: supported_off }
log4j2.sslVerifyHostName: { default: false, security_relevant: true, posture: supported_with_hardening }
...
trust_table:
- { entry: "Logger.info", param: "fmt", attacker_controllable: false }
- { entry: "Logger.info", param: "params[i]", attacker_controllable: true }
...
properties:
P1: { severity: critical, symptom: "RCE from in-scope input" }
P2: { severity: critical, symptom: "structured layout injection" }
P3: { severity: critical, symptom: "super-linear or unbounded resource use per call" }
...
disclaimed: [crlf_unstructured, crlf_rfc5424, deserialization_by_app, log_delivery, ordering, constant_time, confidentiality, tls_hostname_default]
non_findings: [crlf_pattern, crlf_rfc5424_msg, config_path_traversal, readobject_in_core, family_d_modules, pre_216_lookups]
dispositions: [VALID, VALID-HARDENING, OUT-OF-MODEL/trusted-input, OUT-OF-MODEL/adversary-not-in-scope, OUT-OF-MODEL/unsupported-component, OUT-OF-MODEL/non-default-build, BY-DESIGN/property-disclaimed, KNOWN-NON-FINDING, MODEL-GAP]Regenerate whenever this prose document changes.
Proof of the strict-superset property required by skill §3.1a. Every
statement in the website's _threat-model-common.adoc and
security/faq.html is reflected here:
| Website statement | This document |
|---|---|
| Trusted Users: developers + administrators with unrestricted access | §4.2 |
| Untrusted Users: all other parties | §4.7 |
| Trusted sources: env vars, config properties, config files | §4.3, §4.5a |
| Trusted: log-statement objects (safely convertible to strings) | §4.6 (params[i].toString() row — though Log4j must safely render the result) |
| Trusted: format strings in parameterized logging | §4.6 (first row), §4.10 (developer responsibility) |
| Untrusted: log messages, parameter string repr | §4.6 |
| Untrusted: thread context keys and values | §4.6 — superseded for keys (maintainer, 2026-05-13) |
| Deployer responsibility: prevent untrusted write access to config | §4.10 |
| Deployer responsibility: confidential channels; disable HTTP/JMX | §4.10 (JMX is now off-by-default per §4.5a) |
| Deployer responsibility: trusted data only with interpolation | §4.10 |
| Log injection (CWE-117): unstructured layouts NOT protected; structured MUST be | §4.8 P2, §4.9 |
| Supply chain (CWE-1357): deps verified, releases signed | (out of scope per skill §1 / §5; documented in SECURITY.adoc and ASF release procedures) |
| Information disclosure (CWE-200): deployer + developer responsibility | §4.10 |
| Log Reliability: events delivered by design; use reliable transports | §4.8 P6, §4.10 |
| DoS (CWE-779): configure levels and appenders | §4.10 (operator); §4.8 P3 adds maintainer-stated threshold |
| Deserialization (CWE-502): no guarantee | §4.8 P5, §4.9 |
| CVE-2018-1285 reclassified as non-vuln (config is trusted) | §4.3, §4.13 (OUT-OF-MODEL: trusted-input) |
| CRLF in Pattern Layout: not a vuln (unstructured by design) | §4.9, §4.11a |
| CRLF in RFC 5424: transport-binding's responsibility per RFC §8.2 | §4.9, §4.11a |
| Path traversal in config: config is trusted | §4.3, §4.11a |
| Deserialization FAQ: Log4j Core does not deserialize on its own | §4.8 P5, §4.11a |
Nothing in the common doc has been silently weakened or dropped. The three points where this document goes further than the common doc are clearly tagged (maintainer, 2026-05-13) and listed in §4.7.
Drop this appendix from the published version only when the maintainer agrees the Log4j-2 doc has become canonical for these statements (per §4.14 Q17).