This is an automated email from the ASF dual-hosted git repository.
lukaszlenart pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/struts.git
The following commit(s) were added to refs/heads/main by this push:
new dc90e4890 Add draft security threat model (THREAT_MODEL.md) +
discoverability pointer (#1756)
dc90e4890 is described below
commit dc90e489099c47b6d5367302e0344dc878d3a352
Author: Jarek Potiuk <[email protected]>
AuthorDate: Mon Jun 29 21:10:42 2026 +0200
Add draft security threat model (THREAT_MODEL.md) + discoverability pointer
(#1756)
* Add draft security threat model (THREAT_MODEL.md)
Generated-by: Claude Opus 4.8 (1M context)
* SECURITY.md: link to THREAT_MODEL.md for scanner/triager discoverability
Generated-by: Claude Opus 4.8 (1M context)
* Fix dangling §14 refs and tighten provenance in threat model
Address code-review findings on the THREAT_MODEL.md draft:
- Add the missing §14 Q-env and Q-egress open questions, so every
*(inferred)* claim that cites them now resolves (restores the
"each inferred claim has a matching §14 question" invariant).
- Tag the two previously bare *(inferred)* claims (examples/showcase,
on-path attacker) with their matching question IDs.
- Soften the §1 header: drop the ASF Security team / PMC authorship
attribution on an unratified draft; state it is drafted for PMC review.
- Correct the OGNL Java Security Manager wording: SecurityManager is
deprecated for removal since JDK 17 and permanently disabled in JDK 24,
not simply "does not work on JDK 21+".
- AGENTS.md: point the Assess step at THREAT_MODEL.md's disposition guide
so the triage wrapper links the model directly.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
---------
Co-authored-by: Lukasz Lenart <[email protected]>
Co-authored-by: Claude Opus 4.8 <[email protected]>
---
AGENTS.md | 5 +-
SECURITY.md | 9 ++
THREAT_MODEL.md | 439 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 451 insertions(+), 2 deletions(-)
diff --git a/AGENTS.md b/AGENTS.md
index 088a58dd8..947f929a7 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -13,8 +13,9 @@ Before drafting any report, opening an issue, posting
publicly, or reaching a se
- read the Struts security policy,
- read the Struts security guidelines,
- check previously disclosed vulnerabilities and Security Bulletins.
-2. **Assess** the finding against the questions in [`SECURITY.md` §
Assessment](SECURITY.md#assessment). If the answers do not still point to a
likely new
- framework vulnerability, stop and explain — do not draft a new report.
+2. **Assess** the finding against the questions in [`SECURITY.md` §
Assessment](SECURITY.md#assessment), and route it through the disposition guide
in
+ [`THREAT_MODEL.md`](THREAT_MODEL.md) (scope, trust boundaries, in-model
security properties, and the known-non-findings list). If the answers do not
still
+ point to a likely new framework vulnerability, stop and explain — do not
draft a new report.
3. **Report privately** to `[email protected]` following
[`SECURITY.md` § Private Report
Requirements](SECURITY.md#private-report-requirements) and
[§ Report Quality Rules](SECURITY.md#report-quality-rules).
diff --git a/SECURITY.md b/SECURITY.md
index 8c69b08e8..3337c131c 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -1,5 +1,14 @@
# Security Policy
+## Threat Model
+
+A structured threat model for the Apache Struts framework — scope, adversary
model,
+the security properties the framework provides vs. leaves to the application,
and a
+triage-disposition guide for inbound reports and automated-scanner findings —
is
+maintained in [`THREAT_MODEL.md`](THREAT_MODEL.md). It is additive to this
policy:
+this `SECURITY.md` and the [security
guidance](https://struts.apache.org/security/)
+remain canonical for the reporting process and configuration details.
+
## Supported Versions
Please visit the
[Releases](https://struts.apache.org/releases.html#prior-releases) page to see
full information about each version
diff --git a/THREAT_MODEL.md b/THREAT_MODEL.md
new file mode 100644
index 000000000..4e3e490f1
--- /dev/null
+++ b/THREAT_MODEL.md
@@ -0,0 +1,439 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+# Apache Struts — Threat Model (v0 draft)
+
+## §1 Header
+
+- **Project:** Apache Struts (`apache/struts`), `main` @ HEAD (2026-06).
Scope: the
+ Struts framework in `apache/struts` only (the core MVC framework, its
+ interceptors, tags, and the plugins shipped in this repo).
+- **Date:** 2026-06-24. **Drafted for PMC review** via the
threat-model-producer
+ rubric (Scovetta). This is an unratified proposal, not an ASF Security team
or
+ PMC position; authorship and sponsorship are settled only once the PMC
adopts it
+ (see Status below and §14).
+- **Status:** DRAFT — not yet reviewed by the Struts PMC. Built as a strict
+ superset of the existing [`SECURITY.md`](SECURITY.md) and the published
+ [Struts security guidance](https://struts.apache.org/security/); every
+ load-bearing claim is tagged for provenance (see §14 for open questions).
+- **Version binding:** versioned with the project; a report against version *N*
+ is triaged against the model as it stood at *N*. The security envelope
changed
+ materially at **7.0** (several hardening knobs flipped to secure-by-default —
+ §5a), so the version is itself load-bearing.
+- **Reporting cross-reference:** §8-property violations → report privately per
+ [`SECURITY.md`](SECURITY.md) (`[email protected]`); §3/§9/§11a
findings
+ are closed citing this document and the existing `SECURITY.md` "Before
+ Reporting" checks.
+- **Provenance legend:** *(documented)* = Struts' own
docs/`SECURITY.md`/security
+ site; *(maintainer)* = confirmed by a Struts PMC member through this process;
+ *(inferred)* = reasoned from architecture/docs, not yet PMC-ratified — each
has
+ a matching §14 open question.
+- **Draft confidence:** the bulk is *(documented)* — Struts has an unusually
rich
+ published security policy — with a handful of *(inferred)* scoping calls for
the
+ PMC to ratify.
+
+**What Struts is.** Apache Struts 2 is a **Java MVC web framework** for
building
+server-side web applications. A request flows: servlet filter → action mapping
→
+**interceptor stack** (parameter population, validation, etc.) → **Action** →
+**result** (typically a JSP/FreeMarker view). Request parameters are bound onto
+action properties via setters, and view/configuration expressions are evaluated
+through **OGNL (Object-Graph Navigation Language)** against the **ValueStack**.
+*(documented — struts.apache.org)*
+
+**The framework's own security philosophy (load-bearing).** Struts
+**"doesn't provide any security mechanism — it is just a pure web framework."**
+*(documented — [security guidance](https://struts.apache.org/security/))* It is
+not an authentication, authorization, session-security, or input-sanitisation
+layer; those are the embedding application's responsibility (§3/§10). What
Struts
+*does* take an active stance on is **not letting its own machinery — chiefly
OGNL
+expression evaluation and request-parameter binding — become an injection
vector**.
+That single sentence shapes the whole model: most "Struts is insecure" reports
are
+either OGNL-injection-class (in model, §8) or application-responsibility (out
of
+model, §3/§11a).
+
+## §2 Scope and intended use
+
+Intended deployment: the Struts JARs are a **dependency embedded inside a web
+application** (a WAR) that the application developer writes, configures, and
+deploys into a servlet container (Tomcat, Jetty, …) behind the operator's
+perimeter. Struts is **in-process** with the application; it has no daemon, no
+listening socket of its own, and no trust boundary against the application code
+it runs inside. *(documented — it is a framework, not a server.)*
+
+**Caller roles.**
+
+- **Untrusted HTTP client** — sends requests (parameters, headers, cookies,
+ multipart uploads) to a Struts-backed endpoint. **The primary untrusted
boundary.**
+ Struts must treat all request-derived values as hostile. *(documented — the
+ parameter/OGNL hardening exists precisely for this actor.)*
+- **Application developer** — writes the actions, JSPs, struts.xml/annotations,
+ and chooses the hardening settings (§5a). **Trusted by the framework** —
their
+ code and configuration run with the application's privileges. A finding that
+ requires the developer to write unsafe code or disable a default protection
is
+ the application's bug, not Struts' (§3). *(documented — the
developer-responsibility
+ section of the security guidance.)*
+- **Operator** — deploys the WAR, sets `devMode` off, restricts dev-only
plugins,
+ configures the container and JVM. **Trusted.** *(documented.)*
+
+**Component families.**
+
+| Family | Entry point | Touches | In model? |
+| --- | --- | --- | --- |
+| OGNL evaluation + ValueStack | expression eval for params, tags, results |
in-JVM code paths | **In — the central attack surface** *(documented)* |
+| Parameter binding (`ParametersInterceptor`, `@StrutsParameter`) | request
params → action setters | reflection into app objects | **In — primary
boundary** *(documented)* |
+| Interceptor stack (cookie, fileupload, fetch-metadata, COOP/COEP, …) |
per-request processing | request data | **In** *(documented)* |
+| Tag library / JSP & FreeMarker integration | view rendering, expression
output | template eval | **In — output-side OGNL/EL** *(documented)* |
+| File upload (Jakarta multipart) | multipart request parsing | temp files |
**In — historical CVE surface** *(documented — S2 bulletins)* |
+| Bundled plugins (REST, JSON, Convention, …) in this repo | extra
mappers/result types | request data | **In — same request-trust surface**
*(inferred — §14 Q-plugins)* |
+| Config Browser Plugin | exposes internal config | dev-only diagnostic | **In
as dev-only** — exposure in prod is operator misconfig (§3/§11a) *(documented)*
|
+| Embedding application's own actions/JSPs/config | the developer's code | as
the app | **Out — application responsibility (§3)** *(documented)* |
+| Examples / showcase / test apps | demo code | n/a | **Out** *(see §3)* |
+
+## §3 Out of scope (explicit non-goals)
+
+The detailed lists of developer anti-patterns and insecure configurations are
+maintained in the project's own docs and are **not duplicated here** — this
model
+links to them and assigns each a triage disposition (§13):
+
+- **Anything the application developer is responsible for.** Struts provides no
+ security mechanism of its own *(documented)*. The full enumeration —
+ developer-exposed unsafe setters, request parameters used in localization or
+ forced OGNL evaluation, raw `${...}` JSP-EL over untrusted values, direct JSP
+ access, mixing security levels in one namespace — is in the
+ [security guidance](https://struts.apache.org/security/) and
+ [`SECURITY.md`](SECURITY.md). All are `OUT-OF-MODEL:
application-responsibility`.
+- **Findings that only manifest with a documented-insecure / non-default
setting**
+ (`devMode=true`, Config Browser Plugin exposed in production, DMI enabled,
or a
+ §5a hardening knob turned off) → `OUT-OF-MODEL: non-default-config`.
*(documented.)*
+- **The servlet container, JVM, JDK, and OS**, and the application's own
+ authentication, authorization, session management, CSRF token storage, and
+ transport (TLS). Struts is "a pure web framework," not a security framework.
+ *(documented / inferred — §14 Q-env.)*
+- **Generic denial of service.** Per [`SECURITY.md`](SECURITY.md), generic
flooding
+ or large-body streaming is not accepted; only *super-linear* amplification
inside
+ framework code may be in model (§8 / §14 Q-dos). *(documented.)*
+- **Already-disclosed S2-series vulnerabilities** — a duplicate of an existing
+ Security Bulletin/CVE is closed by reference (the
+ [`SECURITY.md` "Before Reporting"](SECURITY.md) checks), not re-triaged.
+- **Examples, showcase, and test applications** shipped in the repo.
*(inferred — §14 Q-scope.)*
+
+## §4 Trust boundaries and data flow
+
+```
+Untrusted HTTP request
+ │ params, headers, cookies, multipart
+ ▼
+Servlet filter ─► action mapping ─► Interceptor stack ─► Action ─► Result
(JSP/FreeMarker)
+ │ │
+ ParametersInterceptor tag/result OGNL
eval
+ binds params to setters against
ValueStack
+ │ │
+ ▼ ▼
+ OGNL evaluation against the ValueStack ◄── the
trust boundary
+ (allowlist / excluded classes+packages /
+ expression length / @StrutsParameter)
+```
+
+- **HTTP client → framework** is the one boundary Struts owns. Every
request-derived
+ string (parameter *names* as well as *values*, cookie names/values, header
values,
+ multipart filenames) is untrusted and may carry an OGNL payload. The
framework's
+ job at this boundary is to bind parameters and evaluate expressions **without
+ letting attacker input reach an OGNL evaluation that creates or changes
executable
+ code**. *(documented.)*
+- **Framework → application code** is *not* a trust boundary — Struts runs the
+ developer's actions and templates in-process, fully trusted. *(documented.)*
+
+**Reachability precondition (triager's test).** A finding is in-model only if
it is
+reachable by an **untrusted HTTP client against a Struts application that
follows the
+documented secure configuration** (current-version defaults, `devMode` off,
dev-only
+plugins restricted, no developer anti-patterns from §3). A finding that needs
+`devMode`, a disabled default protection, a developer-introduced unsafe
setter, or a
+documented anti-pattern is `OUT-OF-MODEL`. *(documented/inferred — §14
Q-default.)*
+
+## §5 Assumptions about the environment
+
+- A servlet container and a JVM the operator maintains; Struts does not patch
or
+ harden them. *(inferred — §14 Q-env.)*
+- The application is deployed with the **current supported version** (7.x or
6.x per
+ `SECURITY.md`); 2.x is end-of-life and out of support. *(documented —
Supported
+ Versions table.)*
+- The operator runs production with `devMode=false` and dev-only diagnostics
(Config
+ Browser Plugin) disabled or access-controlled. *(documented.)*
+- Struts opens no sockets and makes no outbound connections of its own; any
network
+ egress is the application's. *(inferred — §14 Q-egress.)*
+
+## §5a Build-time and configuration variants — **the central knob set**
+
+Struts' security envelope is set almost entirely by **runtime configuration**.
The
+**authoritative, current list of every hardening setting (purpose + secure
default)
+lives in the [security guidance](https://struts.apache.org/security/) and is
not
+reproduced here.** Only the triage-load-bearing facts:
+
+- The security posture **changed materially at 7.0**, where a cluster of
+ OGNL-injection and parameter-binding defences became **secure-by-default** —
+ notably the OGNL allowlist (`struts.allowlist.enable`), the
`@StrutsParameter`
+ annotation requirement (`struts.parameters.requireAnnotations`), excluded
+ classes/packages, the expression-length cap
(`struts.ognl.expressionMaxLength`,
+ default 256), and the static-field/proxy/default-package/custom-map
disallows.
+- `struts.devMode` (must be `false` in production) and Dynamic Method
Invocation
+ (gated by Strict Method Invocation since 2.5) are the two settings whose
*insecure*
+ value most often turns a non-finding into an apparent finding.
+- The **FetchMetadata / COOP / COEP** interceptors (6.0+) are opt-in
cross-origin
+ defences (§8.5).
+
+**Insecure-default question (wave 1).** Because the secure posture is the **7.0
+default set**, the triage rule needs ratifying: is "a finding that only works
with a
+pre-7.0 default, or with a 7.0 hardening knob turned off" `OUT-OF-MODEL:
+non-default-config`, with §10 carrying "deploy current version with defaults"?
— §14
+Q-default. The OGNL **Java Security Manager sandbox**
(`-Dognl.security.manager`) is a
+separate, opt-in defence built on the JDK `SecurityManager`, which has been
+**deprecated for removal since JDK 17 (JEP 411), disabled by default since JDK
18,
+and permanently disabled in JDK 24 (JEP 486)** *(documented — JDK release
notes)* —
+so on modern JDKs the model cannot treat it as a relied-upon control (§14
Q-jsm).
+
+## §6 Assumptions about inputs
+
+| Surface | Input | Attacker-controllable? | Concern |
+| --- | --- | --- | --- |
+| Parameter binding | request parameter **names and values** | **yes** | OGNL
injection via crafted names; binding to unsafe setters |
+| Cookies | cookie names/values (Cookie Interceptor) | **yes** | same
OGNL/parameter concerns; checked by accepted/excluded patterns |
+| Headers | request headers | **yes** | header-driven expression/log paths |
+| Multipart upload | file content, filename, content-type | **yes** | parser
robustness, temp-file handling (S2 history) |
+| Expression context | values that reach an OGNL eval (tags, results, forced
eval) | **yes if developer feeds untrusted input in** | the core RCE channel |
+| struts.xml / annotations / action code | framework + app configuration |
**no — developer-trusted** | not an attacker surface (§3) |
+
+The accepted/excluded pattern checkers (`AcceptedPatternsChecker` /
+`ExcludedPatternsChecker`, since 2.3.20) validate parameter names/values for
the
+Parameters and Cookie interceptors; a custom override that drops below the
framework
+defaults is a developer error, not a framework flaw. *(documented.)*
+
+## §7 Adversary model
+
+- **In scope:** an **untrusted remote HTTP client** with no credentials, able
to send
+ arbitrary parameters, headers, cookies, and multipart uploads to any
+ Struts-handled endpoint. Capabilities: craft parameter names/values carrying
OGNL,
+ attempt to reach executable-code creation through the ValueStack, pollute
+ parameter binding, exploit a file-upload or multipart parsing bug, or
trigger a
+ super-linear resource path in framework code. Goal: **remote code execution
via
+ OGNL** (the dominant Struts threat), and secondarily data disclosure, SSRF
through
+ framework features, or DoS amplification. *(documented — the OGNL lineage is
the
+ framework's stated central concern.)*
+- **On-path network attacker** — only where the application/operator has not
deployed
+ TLS; transport security is the app's, so this is largely out of model (§3).
*(inferred — §14 Q-env.)*
+- **Out of scope:** the application developer (writes trusted code/config); the
+ operator (deploys, sets devMode/plugins); anyone with container/host/JVM
control;
+ and a developer who disables a default protection or follows a documented
+ anti-pattern (§3). *(documented.)*
+
+## §8 Security properties the framework provides
+
+*(In the current-version, default-hardening posture; each lists violation
symptom +
+severity. Most are documented controls — the OGNL-injection defences are the
core of
+Struts' security work.)*
+
+1. **OGNL injection containment.** Attacker-supplied request data (parameter
names/
+ values, cookies, headers) must not reach an OGNL evaluation that creates or
alters
+ executable code. Enforced in depth by the default controls listed in §5a /
the
+ [security guidance](https://struts.apache.org/security/) (allowlist,
excluded
+ classes/packages, expression-length cap, static-field/proxy/default-package/
+ custom-map disallows, excluded node types). *Violation:* a crafted request
+ achieving OGNL-driven code execution (or class-loader/member access beyond
the
+ allowlist) on a default-configured current-version app. *Severity:*
+ security-critical (the S2-RCE class). *(documented.)*
+2. **Parameter-binding safety (7.0).** Request parameters bind only to setters
the
+ developer marked `@StrutsParameter` (to the declared depth); arbitrary
deep/nested
+ property traversal is not reachable by default. *Violation:* parameters
reaching
+ an unannotated setter, or nesting beyond the declared depth, on a default
7.0 app.
+ *Severity:* critical. *(documented.)*
+3. **Method-invocation control.** Dynamic Method Invocation is gated by Strict
Method
+ Invocation; a client cannot invoke arbitrary action methods by name when
DMI is at
+ its recommended (off/strict) setting. *Violation:* arbitrary method
invocation on a
+ default app. *Severity:* high–critical. *(documented.)*
+4. **Expression-length and node-type bounds.** OGNL expressions over the
configured
+ length (default 256) and forbidden node types are rejected before
evaluation.
+ *Violation:* bypass of these bounds. *Severity:* high. *(documented.)*
+5. **Cross-origin / fetch-metadata defences (opt-in).** When the
FetchMetadata, COOP,
+ and COEP interceptors are enabled, the framework emits/enforces the
corresponding
+ `Sec-Fetch-*` and cross-origin isolation behaviour. *Violation:* the
interceptor
+ failing to enforce its documented behaviour when enabled. *Severity:*
medium–high.
+ *(documented — opt-in since 6.0.)*
+
+## §9 Security properties the framework does *not* provide
+
+- **No security mechanism in the general sense.** Struts provides no
authentication,
+ authorization, session security, CSRF token store, input sanitisation, or
output
+ encoding *for the application's own data* — "it is just a pure web
framework."
+ *(documented.)*
+ - *False friend:* "Struts has no built-in login/access control" is **by
design**,
+ not a vulnerability.
+- **No protection against developer anti-patterns or non-default config** —
unsafe
+ setters, raw `${}` on user input, request params in localization/forced eval,
+ direct JSP access, `devMode` on, disabled hardening (§3/§5a).
+- **No defence once OGNL evaluation is fed untrusted input by the application
+ itself** (forced expression evaluation on a request value) — that is the
developer
+ handing OGNL the attacker's string. *(documented.)*
+- **No hard anti-DoS guarantee** beyond the "avoid super-linear in input size"
+ philosophy; generic flooding/streaming DoS is the operator's to absorb.
*(documented.)*
+- **The OGNL Java Security Manager sandbox is not a relied-upon control on
modern
+ JDKs** (the underlying `SecurityManager` is deprecated for removal since JDK
17 and
+ permanently disabled in JDK 24; see §5a). *(documented.)*
+- **Auto-generated error pages do not escape action names** (historical
S2-006) — the
+ app must define custom error pages; XSS in the default error page is a
documented
+ hardening item, not a defended property. *(documented.)*
+- **Well-known classes (framework):** OGNL/expression injection,
multipart/file-upload
+ parsing bugs, and parameter-pollution are the framework's recurring risk
classes;
+ reflected XSS, CSRF token management, and transport security are the
application's.
+
+## §10 Downstream (developer + operator) responsibilities
+
+The full, authoritative how-to is the [security
guidance](https://struts.apache.org/security/)
+and [`SECURITY.md`](SECURITY.md); in one line: **deploy a current supported
version
+with the default hardening left on, `devMode` off, dev-only plugins restricted,
+parameter setters annotated, JSPs hidden behind actions, and the application's
own
+authn/authz/CSRF/TLS supplied** (Struts provides none of those). The
threat-model
+value is only that a finding requiring the developer to *violate* one of these
is
+`OUT-OF-MODEL` (§3/§13), not that this list is novel.
+
+## §11 Known misuse patterns
+
+These are the §3 application-responsibility / non-default-config items viewed
as
+"things integrators get wrong" — running `devMode=true` in production or
exposing the
+Config Browser Plugin; disabling a default OGNL/binding protection "to make
something
+work"; exposing unsafe setters to binding; feeding request parameters into
forced
+OGNL evaluation or localization; allowing direct `*.jsp` access or raw `${}`
EL on
+untrusted values; relying on the OGNL Java Security Manager sandbox on modern
JDKs. Each
+is documented in the [security guidance](https://struts.apache.org/security/);
the
+disposition mapping is §11a/§13.
+
+## §11a Known non-findings (recurring false positives)
+
+*(Seeded directly from `SECURITY.md` "Before Reporting" — the PMC owns the
+authoritative list; §14 Q12.)*
+
+- **"OGNL/RCE that only works with `devMode=true`."** `OUT-OF-MODEL:
non-default-config`
+ — devMode is a development-only setting documented as unsafe for production.
+- **"An action setter lets me inject a value / reach a dangerous method."**
When the
+ setter is developer-exposed without `@StrutsParameter` (7.0), or performs an
unsafe
+ side effect, this is `OUT-OF-MODEL: application-responsibility`. In-model
only if it
+ bypasses the framework's *default* binding/OGNL protections.
+- **"Direct JSP access discloses X / executes Y."** App-deployment
misconfiguration —
+ JSPs must be hidden behind actions. `OUT-OF-MODEL:
application-responsibility`.
+- **"Raw `${}` EL / forced OGNL eval on my request parameter is
exploitable."** The
+ application fed untrusted input to expression evaluation — documented
anti-pattern,
+ not a framework flaw.
+- **"Config Browser Plugin exposes internal configuration."** Dev-only
diagnostic;
+ exposing it in production is operator misconfiguration. `OUT-OF-MODEL:
non-default-config`.
+- **"I can enumerate / pass arbitrary parameters."** Parameter binding is the
point of
+ the framework; in-model only when it crosses the default annotation/allowlist
+ protections.
+- **"Generic DoS: I streamed a huge body / hammered a URL."** Not accepted per
+ `SECURITY.md`; only super-linear amplification inside framework code is
considered.
+- **Duplicate of a disclosed S2-series bulletin/CVE** — closed by reference.
+- **Dependency-tail CVEs** (a transitive jar, e.g. a logging or XML library)
from an
+ SCA scan — triage upstream unless Struts' own code reaches the vulnerable
path with
+ untrusted input.
+
+## §12 Conditions that would change this model
+
+- A change to the default-hardening set (e.g. a new secure-by-default knob, or
a
+ default flipped) — re-baseline §5a/§8/§11a.
+- A new request-facing surface, a new bundled plugin, or a new
expression/templating
+ integration with its own trust surface.
+- A change to how OGNL evaluation, the allowlist, or parameter binding works.
+- A report that cannot be routed to a §13 disposition → revise §8/§9.
+
+## §13 Triage dispositions
+
+| Disposition | Meaning | Licensed by |
+| --- | --- | --- |
+| `VALID` | A §8 property breaks via an untrusted HTTP client on a
current-version, default-hardened app. | §8, §6, §7 |
+| `VALID-HARDENING` | A §11 misuse is too easy, or a default could be
tightened. | §11/§5a |
+| `OUT-OF-MODEL: application-responsibility` | Requires a developer
anti-pattern (unsafe setter, raw EL, forced eval, direct JSP) or the app's own
authn/authz. | §3/§10 |
+| `OUT-OF-MODEL: non-default-config` | Only manifests with `devMode`, a
dev-only plugin, DMI, or a disabled default protection. | §5a |
+| `OUT-OF-MODEL: adversary-not-in-scope` | Requires
container/host/JVM/developer control. | §7 |
+| `OUT-OF-MODEL: unsupported-version` | Only affects an end-of-life (2.x)
version. | §5 |
+| `BY-DESIGN: property-disclaimed` | Concerns a property §9 disclaims (no
built-in authn/authz/encoding; generic DoS; JSM on JDK21+). | §9 |
+| `KNOWN-NON-FINDING` | Matches §11a. | §11a |
+| `DUPLICATE` | Matches a disclosed S2-series bulletin/CVE. | §3 |
+| `MODEL-GAP` | Unroutable. | triggers §12 |
+
+## §14 Open questions for the maintainers
+
+**Wave 1 — scope, defaults, intended use**
+
+- **Q-default.** Confirm the triage baseline is "current supported version
(7.x/6.x)
+ with the documented default hardening on, `devMode` off, dev-only plugins
+ restricted" — and that a finding requiring a pre-7.0 default or a disabled
hardening
+ knob is `OUT-OF-MODEL: non-default-config`. (§5a/§13.)
+- **Q-scope.** Confirm the in-scope surface is the framework in `apache/struts`
+ (core + interceptors + tags + bundled plugins), with the embedding
application's own
+ actions/JSPs/config, and examples/showcase, out of scope. (§2/§3.)
+- **Q-philosophy.** Confirm the framing that Struts provides **no security
mechanism
+ of its own** beyond OGNL/parameter-binding injection containment — i.e.
authn,
+ authz, session security, CSRF token storage, output encoding, and transport
are the
+ application's. (§9.)
+- **Q-env.** Confirm the servlet container, JVM, JDK, and OS are out of scope
— Struts
+ does not patch or harden them, and the operator maintains them. (§3/§5.)
+- **Q-egress.** Confirm Struts opens no sockets and makes no outbound
connections of
+ its own, so any network egress (and the SSRF surface it implies) is the
+ application's. (§5/§7.)
+
+**Wave 2 — mechanism confirmations**
+
+- **Q-ognl.** Confirm the §8.1 list is the authoritative set of default
OGNL-injection
+ defences (allowlist, excluded classes/packages/patterns, expression length,
+ static-field/proxy/default-package/custom-map disallows, excluded node
types) and
+ that a bypass of any on a default app is `VALID`. (§8.)
+- **Q-jsm.** Confirm the OGNL Java Security Manager sandbox is **not** a
relied-upon
+ control (opt-in, and non-functional on modern JDKs — see §5a), so a report
premised
+ on its absence is not a finding. (§5a/§9.)
+- **Q-dos.** Where is the line between "generic DoS we don't accept" and
"super-linear
+ amplification inside framework code we do"? Confirm the §3/§8 wording. (§3.)
+
+**Wave 3 — surfaces & false-friends**
+
+- **Q-plugins.** Which bundled plugins (REST, JSON, Convention, …) are in
scope at the
+ same request-trust level, and are any (e.g. REST/XML) historically
higher-risk and
+ worth their own §8 note? (§2.)
+- **Q-upload.** Confirm the multipart/file-upload surface (Jakarta) and what
the
+ framework guarantees vs. leaves to the container/app. (§2/§6.)
+- **Q12.** Beyond the `SECURITY.md` "Before Reporting" list already folded
into §11a,
+ what do scanners/researchers most often report against Struts that you
consider a
+ non-finding? (Feeds §11a.)
+
+## §15 Appendix — existing-policy back-map
+
+This `THREAT_MODEL.md` is **additive** — it does not replace
+[`SECURITY.md`](SECURITY.md) (reporting process, supported versions, "Before
+Reporting" checks) or the published [security
guidance](https://struts.apache.org/security/);
+both are preserved and remain canonical for the reporting workflow. The
discoverability
+chain is `AGENTS.md` → `SECURITY.md` → this model. Mapping of existing-policy
claims to
+sections:
+
+| Existing-policy statement | Threat-model § |
+| --- | --- |
+| "Struts doesn't provide any security mechanism — pure web framework" | §1,
§9, §13 (`BY-DESIGN`) |
+| OGNL is the central historical vuln class | §1, §7, §8.1 |
+| devMode / Config Browser Plugin are dev-only | §3, §5a, §11a |
+| `@StrutsParameter` / unsafe setters | §6, §8.2, §10, §11a |
+| Direct JSP access / raw `${}` EL / forced eval / localization | §3, §10,
§11a |
+| Allowlist / excluded classes/packages / expression length (7.0 defaults) |
§5a, §8.1 |
+| DMI / Strict Method Invocation | §5a, §8.3 |
+| FetchMetadata / COOP / COEP | §5a, §8.5 |
+| OGNL JSM sandbox (modern-JDK limitation) | §5a, §9 |
+| Generic DoS not accepted; non-linear-in-input philosophy | §3, §8, §9 |
+| "Before Reporting" duplicate/known-config checks | §3, §11a, §13
(`DUPLICATE`) |
+| Supported versions (2.x EOL) | §5, §13 (`OUT-OF-MODEL: unsupported-version`)
|