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

Reply via email to