From 94753be77904d61f920b8aab8f0018186c8e68e0 Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Thu, 3 Sep 2026 20:43:05 +0200 Subject: [PATCH] docs: scope the @StrutsParameter "never set" claim to named properties The annotation page told readers that JSON and REST deserialization means unauthorized fields are never set on the target object. That is true of the properties the deserializer binds by name, but a Jackson any-setter is a separate sink that the REST plugin's authorization wrapper never wraps, so unknown keys routed to it are bound with no @StrutsParameter check at all -- in the same request in which an ordinary unannotated setter on the same class is correctly rejected. Qualify the bullet and document the gap in its own section, alongside the existing creator-bound-properties note that covers the same class of problem. The JSON plugin is unaffected: it does not use Jackson, so its own page's identical wording stays accurate and is left alone. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_012HF8BGrYmCVnqUdQJJ1XPM --- .../struts-parameter-annotation.md | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/source/core-developers/struts-parameter-annotation.md b/source/core-developers/struts-parameter-annotation.md index 916607c7b..c9a1642c2 100644 --- a/source/core-developers/struts-parameter-annotation.md +++ b/source/core-developers/struts-parameter-annotation.md @@ -27,7 +27,10 @@ channel that can populate an action from request data: action chaining (opt-in via `struts.chaining.requireAnnotations`). - [Cookie Interceptor](cookie-interceptor.html) — cookie values. - [JSON](../../plugins/json) and [REST](../../plugins/rest) plugins — per-property - authorization performed during deserialization, so unauthorized fields are never set. + authorization performed during deserialization, so an unauthorized property is not set on + the target object. This covers the properties the deserializer binds **by name**; in the + REST plugin a Jackson any-setter is a separate sink that is not covered — see + [Jackson any-setters](#jackson-any-setters) below. ### Creator-bound properties @@ -48,6 +51,27 @@ the same way as any nested object: `@StrutsParameter(depth = ...)` on the getter model. Otherwise those values silently stop arriving. {:.alert .alert-warning} +### Jackson any-setters + +A class that declares a Jackson any-setter — `@JsonAnySetter` on a method, on a field, or on a +`@JsonCreator` parameter — tells Jackson to route **every otherwise-unknown key** in the request body +to that member. The REST plugin's authorization wrapper covers the properties Jackson binds by name; +an any-setter is a separate sink and is not wrapped. Keys arriving through it are therefore set +without an `@StrutsParameter` check, even with `struts.parameters.requireAnnotations` enabled, and +even in the same request in which an ordinary unannotated setter on the same class is correctly +rejected. + +Two limits are worth knowing. An any-setter beneath an **unauthorized parent** is still unreachable: +the parent is rejected first and its whole subtree is skipped. And `@JsonUnwrapped` is a named +property, so it is unaffected by this. + +Declaring an any-setter on a class bound from a REST request body is the application accepting +arbitrary names and values off the wire — the same decision as binding a `Map`, and it deserves the +same scrutiny. Where that is not what you want, do not declare one on a request-bound class, or +narrow what the method accepts before storing it. Tracked as +[WW-5712](https://issues.apache.org/jira/browse/WW-5712). +{:.alert .alert-warning} + ## ModelDriven actions When an action implements `ModelDriven` and the [Model Driven