Mass assignment is formally tracked as CWE-915, "Improperly Controlled Modification of Dynamically-Determined Object Attributes," and it is one of the easiest bugs to introduce in a Spring Boot codebase and one of the hardest to spot in review. The mechanism is simple: @RequestBody and @ModelAttribute binding hand Jackson (or Spring's data binder) a JSON payload and a target Java object, and Jackson happily populates every public setter it can match to a key in that payload — whether or not the developer intended that field to be user-writable. If a controller binds directly to a JPA entity like User, a request body of {"name":"bob","isAdmin":true} can flip a boolean the UI form never rendered. This is the textbook "over-posting" attack, and it doesn't require any injection or memory-corruption skill — just knowledge of a field name, which attackers routinely get from decompiled mobile apps, leaked schema docs, or trial and error. The problem is old enough to predate Spring itself, but it keeps resurfacing because the framework's convenience — bind the request straight onto your domain model — is also the vulnerability. This post covers how the binding actually works, why @JsonIgnore is a weaker fix than most teams assume, and what a DTO-first architecture looks like in practice — one of the more common Spring vulnerabilities that scanners tuned only for known CVEs will never surface, because it's a code pattern, not a dependency issue.
How does Spring actually let this happen?
Spring MVC's job is to deserialize a request body into whatever type a controller method declares as its parameter, and by default it does that with no awareness of your authorization model. When that parameter type is @RequestBody User user, and User is the same class Spring Data JPA persists to the users table, every setter on User becomes a request-writable field — role, accountBalance, emailVerified, whatever exists. Jackson's ObjectMapper performs this binding by matching JSON keys to bean properties (getters/setters or public fields) using reflection, with no distinction between "fields we render in a form" and "fields that exist on the entity." Spring's WebDataBinder (used by @ModelAttribute) has the same exposure through form-encoded parameters. Nothing in a default Spring Boot setup stops this — it is a consequence of binding directly to persistence entities rather than of any single misconfiguration, which is why the same bug pattern shows up across unrelated Spring codebases that never call each other's code.
Why isn't @JsonIgnore a complete fix?
@JsonIgnore and @JsonIgnoreProperties(access = JsonProperty.Access.WRITE_ONLY) work by telling Jackson to skip a given property during deserialization, and for a long time that was the standard advice for locking down sensitive entity fields. But annotation-based ignore rules live in the same deserialization pipeline they're supposed to constrain, and that pipeline has had real bypasses. CVE-2026-54515, tracked against FasterXML jackson-databind, describes a flaw in BeanDeserializerBase.createContextual() where the deserializer rebuilds its property map from the original, unfiltered property list after per-property @JsonIgnoreProperties exclusions have already been applied — restoring access to fields that were supposed to stay write-protected when combined with @JsonFormat(ACCEPT_CASE_INSENSITIVE_PROPERTIES). A related flaw, CVE-2026-54516, shows the same class of problem from a different angle: pairing @JsonProperty on a getter with @JsonIgnore on the setter can leave the underlying private field writable via its renamed key when MapperFeature.INFER_PROPERTY_MUTATORS is enabled (the default), again letting an attacker write a field the annotations were meant to block. The lesson isn't "patch and move on" — it's that a security control implemented as annotations on the same object Jackson deserializes is only as strong as that deserialization code, and a future parser bug can silently undo it. A DTO that never contains the sensitive field in the first place has no such dependency.
What does a DTO-based fix actually look like?
The fix OWASP's Web Security Testing Guide recommends for mass assignment, and the one Spring's own documentation points toward, is binding requests to a dedicated request DTO rather than the entity, then mapping that DTO onto the entity explicitly. A UpdateProfileRequest class declares only name and email — no role, no isAdmin, no balance — so there is nothing for an attacker to over-post even if they guess the internal field name, because the field doesn't exist on the type Jackson is populating. The service layer then does an explicit, field-by-field copy (or uses MapStruct or ModelMapper to generate that mapping) from DTO to entity, so every write path is a line of code a reviewer can see and reason about instead of an implicit reflection pass. This also solves the inverse problem — accidentally serializing internal fields back out in a response — since response DTOs are equally explicit about what leaves the service. The tradeoff is boilerplate: a CRUD-heavy app might need two or three classes per entity instead of one, which is exactly why teams reach for annotation shortcuts in the first place.
What should teams do if a full DTO rewrite isn't feasible right now?
For codebases where binding directly to entities is already widespread, Spring exposes narrower controls that reduce risk without a full DTO migration. WebDataBinder.setAllowedFields() lets a controller declare an explicit allowlist of bindable property names per request, which is safer than a denylist because newly added entity fields default to closed rather than open. @InitBinder methods can apply this per-controller or globally via a ControllerAdvice. For JSON bodies specifically, @JsonIgnoreProperties(ignoreUnknown = true) combined with per-field @JsonProperty(access = Access.WRITE_ONLY) reduces exposure, but given the 2026 jackson-databind CVEs above, treat it as a stopgap, not a permanent control — track it and schedule the DTO migration for the fields it protects. Static analysis and secure code review that specifically flags @RequestBody parameters typed as @Entity classes is a practical way to find every instance of this pattern across a large codebase without relying on manual audit, since the vulnerable pattern is structurally identical everywhere it appears.
How does Safeguard help with Spring vulnerabilities like this one?
Mass assignment doesn't show up as a CVE in a dependency scan — it's an architectural pattern in code your own team wrote, which is exactly the kind of finding static application security testing needs to catch during code review rather than after deployment. Safeguard's SAST engine performs source-to-sink taint analysis across Java codebases, the same technique that surfaces risky data flows generally — so binding code that carries untrusted request data into a persistence entity is exactly the kind of pattern it's built to trace, rather than something that depends on an annotation holding under future parser changes. Pairing that with reachability analysis means a flagged code path that's actually wired to an authenticated write endpoint gets prioritized over one that's dead code, so the team fixing findings spends time on the DTOs that matter first.