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 <noreply@anthropic.com>

---------

Co-authored-by: Lukasz Lenart <lukaszlenart@apache.org>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Jarek Potiuk
2026-06-29 21:10:42 +02:00
committed by GitHub
parent 50c16e93b1
commit dc90e48909
3 changed files with 451 additions and 2 deletions
+3 -2
View File
@@ -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 `security@struts.apache.org` following [`SECURITY.md` § Private Report Requirements](SECURITY.md#private-report-requirements) and
[§ Report Quality Rules](SECURITY.md#report-quality-rules).
+9
View File
@@ -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
+439
View File
@@ -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) (`security@struts.apache.org`); §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:* highcritical. *(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:* mediumhigh.
*(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`) |