Document object design guidelines for SpEL expressions

Property accessors resolved via SpEL's ReflectivePropertyAccessor and
DataBindingPropertyAccessor may be JavaBean-style accessors or plain
accessor methods used to support data classes such as Java records and
Kotlin data classes. However, neither accessor can determine, via
reflection, whether such a method is a side-effect-free read or an
action that happens to return a value. For example, File.delete() is a
public method that returns a boolean and therefore looks like a
plain "property".

To better inform users, this commit updates the Javadoc for
ReflectivePropertyAccessor, DataBindingPropertyAccessor, and
SimpleEvaluationContext (as well as in the SpEL reference
documentation) to clarify that restricting a SimpleEvaluationContext to
read-only data binding governs only whether assignment to a property is
permitted and does not guarantee that reading a property is free of
side effects. The reference documentation's "Security Considerations"
section now also defines what makes a method "accessor-shaped", with
concrete examples of safe versus side-effecting methods that share that
shape (for example, File.delete(), Queue.poll(), and
AtomicInteger.incrementAndGet()).

Building on that clarification, this commit introduces a new "Object
Design" section to the SpEL reference documentation, analogous to the
"Model Design" guidance for web data binding. This new section
recommends that any object reachable from an untrusted SpEL expression
(not only the root object) be a purpose-built, immutable type with a
deliberately limited surface area, and that its accessor-shaped methods
be audited for unsafe side effects. The new section also notes that
reachability is transitive through both property navigation and
indexing (for example, rootObject.child.grandchild or
rootObject.items[0]).

In any case, it remains the responsibility of the code that exposes a
root object or other reachable object to an expression from an
untrusted source to ensure that none of its accessor-shaped methods
perform an unsafe action.

Closes gh-37102
This commit is contained in:
Sam Brannen
2026-08-03 11:35:05 +03:00
parent 05619b7450
commit 0c966029e2
4 changed files with 131 additions and 4 deletions
@@ -29,6 +29,16 @@ import java.lang.reflect.Method;
* resolve technical properties on {@code java.lang.Object} or {@code java.lang.Class}.
* For unrestricted resolution, choose {@link ReflectivePropertyAccessor} instead.
*
* <p><strong>WARNING</strong>: Configuring this accessor for read-only access &mdash;
* for example, via {@link #forReadOnlyAccess()} or
* {@link SimpleEvaluationContext#forReadOnlyDataBinding()} &mdash; disallows
* <em>assignment</em> to a property but does not verify that reading a property is
* free of side effects. See the
* <a href="https://docs.spring.io/spring-framework/reference/core/expressions/evaluation.html#expressions-evaluation-context-security"
* >Security Considerations</a> section of the Spring Framework reference
* documentation, as well as the class-level documentation for
* {@link ReflectivePropertyAccessor}, for details.
*
* @author Juergen Hoeller
* @since 4.3.15
* @see #forReadOnlyAccess()
@@ -58,6 +58,19 @@ import org.springframework.util.StringUtils;
*
* <p>A property can be referenced through a public getter method (when being read)
* or a public setter method (when being written), and also through a public field.
* A getter method may be a JavaBean-style accessor (for example, {@code getName()}
* or {@code isActive()}) or a plain accessor method used to support data classes
* such as Java records and Kotlin data classes (for example, {@code name()}).
*
* <p><strong>WARNING</strong>: This accessor cannot determine, via reflection, whether
* a candidate getter method is a side-effect-free read of an underlying property or an
* action that happens to return a value &mdash; for example, {@code File.delete()}
* matches the same shape as a plain accessor method. See the
* <a href="https://docs.spring.io/spring-framework/reference/core/expressions/evaluation.html#expressions-evaluation-context-security"
* >Security Considerations</a> and
* <a href="https://docs.spring.io/spring-framework/reference/core/expressions/evaluation.html#expressions-evaluation-context-object-design"
* >Object Design</a> sections of the Spring Framework reference documentation for
* details.
*
* @author Andy Clement
* @author Juergen Hoeller
@@ -381,7 +394,9 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
method = findMethodForProperty(methodSuffixes,
"is", clazz, mustBeStatic, 0, BOOLEAN_TYPES);
if (method == null) {
// Record-style plain accessor method, for example, name()
// Plain accessor method for a data class, for example, a Java
// record component accessor or a Kotlin data class accessor
// such as name()
method = findMethodForProperty(new String[] {propertyName},
"", clazz, mustBeStatic, 0, ANY_TYPES);
}
@@ -92,10 +92,16 @@ import org.springframework.expression.spel.SpelMessage;
* the SpEL language to a subset of its features, that restriction is provided on
* a best-effort basis and does not guarantee that evaluation of an expression is
* safe; {@code SimpleEvaluationContext} must not be considered safe for evaluating
* a SpEL expression obtained from an untrusted source. See the
* a SpEL expression obtained from an untrusted source. This responsibility extends
* to property accessors: restricting a {@code SimpleEvaluationContext} to read-only
* data binding governs only whether <em>assignment</em> to a property is permitted
* and does not verify that reading a property is free of side effects. See the
* <a href="https://docs.spring.io/spring-framework/reference/core/expressions/evaluation.html#expressions-evaluation-context-security"
* >Security Considerations</a> section of the Spring Framework reference
* documentation for details.
* >Security Considerations</a> and
* <a href="https://docs.spring.io/spring-framework/reference/core/expressions/evaluation.html#expressions-evaluation-context-object-design"
* >Object Design</a> sections of the Spring Framework reference documentation, as well
* as the class-level documentation for {@link ReflectivePropertyAccessor} and
* {@link DataBindingPropertyAccessor}, for details.
*
* <p>Because a parsed {@code Expression} may cache accessor and executor state
* resolved against a particular {@code EvaluationContext} configuration, a parsed