Skip to content

Instantly share code, notes, and snippets.

@ppkarwasz
Created May 13, 2026 16:36
Show Gist options
  • Select an option

  • Save ppkarwasz/53b0a3c07a9e44aa945726138f67d11c to your computer and use it in GitHub Desktop.

Select an option

Save ppkarwasz/53b0a3c07a9e44aa945726138f67d11c to your computer and use it in GitHub Desktop.
Log4j draft threat model

Apache Log4j 2 — Threat Model

§4.1 Header

  • 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-model skill 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.org per the project security page; findings closed by this document cite the section here, e.g. "see Log4j 2 threat model §4.9".

Provenance legend

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.

Draft confidence

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.)

Relationship to the website threat model

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)

Purpose, in one paragraph

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.


§4.2 Scope and intended use

Intended use

  • 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

Component-family table

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.


§4.3 Out of scope (explicit non-goals)

  • 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-test is OUT-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. The log4j-1.2-api bridge 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)

§4.4 Trust boundaries and data flow

Where the boundary sits

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)

Data flow

[ 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.

Reachability preconditions per component

  • 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)

§4.5 Assumptions about the environment

  • 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 Logger methods 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.

Negative inventory — what Log4j 2 does NOT do to its host

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 SecurityManager or Policy.
  • 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 monitorInterval watcher, and any appender's destination file.
  • Does not mutate process-wide state (locale, default TimeZone, default charset, FPU state).
  • Does not use java.lang.instrument or 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)


§4.5a Build-time and configuration variants

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 the java: protocol is hard-coded.
  • log4j2.jmxNotifyAsync — perf, not security.

§4.6 Assumptions about inputs

What Log4j accepts

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.

Per-parameter trust table

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)

Size, shape, rate

  • 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)

§4.7 Adversary model

In-scope adversary

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 5424 MSG/PARAM-VALUE, or a Throwable'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.

Out-of-scope adversaries

  • 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)


§4.8 Security properties the project provides

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)

§4.9 Security properties the project does NOT provide

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-VALUE fields 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)

False friends (features that look like security properties but are not)

  • 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 with enableJndiLookup=true and the java:-protocol restriction, this is a designed-for-trusted-input feature, not a defensive layer against untrusted input.
  • monitorInterval config 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

Well-known attack classes the project leaves to the caller

  • 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)

§4.10 Downstream responsibilities

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=true and 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 from OUT-OF-MODEL to 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)

§4.11 Known misuse patterns

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 Logger or Marker names 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-api as if it gave 1.x guarantees; it is a bridge, not a re-implementation.

§4.11a Known non-findings (recurring false positives)

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.


§4.12 Conditions that would change this model

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 sslVerifyHostName when 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.


§4.13 Triage dispositions

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

§4.14 Open questions for the maintainers

Wave-4 questions, prioritized. Every (inferred) tag in the body above maps to one of these.

Q1 — Are application-server / container deployment shapes invariant?

§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).

Q2 — Trusted-classpath is the right framing for plugin loading?

§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.

Q3 — Are side-channel adversaries fully out of scope, or only most of them?

§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.

Q4 — Reachability precondition wording for Family A.

§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)?

Q5 — Memory-model assumption explicitness.

§4.5. Proposed answer: Log4j assumes the standard Java Memory Model; no other claim. Confirm.

Q6 — Clock assumption.

§4.5. Proposed answer: wall-clock used for timestamps; monotonicity is not assumed; clock skew between hosts is the operator's problem. Confirm.

Q7 — Throwable's message + stack trace as untrusted.

§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.

Q8 — Documented input size caps.

§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.

Q9 — Partial / chunked log events.

§4.6. Proposed answer: no streaming-event API; each log.*() call is a single event. Confirm.

Q10 — P4 thread safety blanket.

§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.

Q11 — P7 garbage-free invariance — edge probe.

§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)?

Q12 — P8 plugin discovery determinism — edge probe.

§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?

Q13 — Ordering guarantee absence.

§4.9. Proposed answer: no ordering guarantee across threads or across appenders. Confirm.

Q14 — StatusLogger as not-a-structured-channel.

§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?

Q15 — Misuse-pattern ratification.

§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.

Q16 — Further known non-findings (§4.11a).

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.

Q17 — Coexistence publication plan.

§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.


§4.15 Machine-readable companion (sketch)

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.


§4.16 Back-map to the website security artifacts

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).

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