Skip to content

WW-5747 fix(rest): leave the parser at the end of an object the redaction wrapper drops - #1946

Merged
lukaszlenart merged 1 commit into
mainfrom
WW-5747
Sep 15, 2026
Merged

lukaszlenart merged 1 commit into
mainfrom
WW-5747

Conversation

@lukaszlenart

Copy link
Copy Markdown
Member

Fixes WW-5747

Problem

RedactionAwareDeserializer drops a bean whose construction fails after a @StrutsParameter redaction by catching the JsonMappingException and returning null. It returned without moving the parser, so whatever tokens of the dropped object were still unread went to the enclosing bean: a creator-bound bean failing on its last creator parameter with fields after it, or a bean whose bean-typed @JsonIdentityInfo id failed to construct and could not be bound, left its remaining fields to the parent — a same-named parent property took the child's value, the parent's own later properties were lost, and an unknown field failed the parent inside a scope already marked redacted, dropping the root.

Change

  • On entry the wrapper records the context of the object or array it is entered on. Before returning null it skips to that value's end token, consuming nested structures whole. The end is recognised by context identity — the first end token whose context no longer descends from the recorded one — which every parser keeps, including the token buffers Jackson replays @JsonUnwrapped and any-setter values from (those report no nesting depth at all; a depth comparison drained them). A parser left without a current token, as Jackson does before splicing a late type id, is advanced first. A scalar entry needs nothing.
  • A polymorphic value Jackson reads from a spliced parser (type id not the first key, or visible = true) straddles the splice and cannot be followed: it is the value entered mid-object on a buffer context while the parser is a JsonParserSequence, and its drop is not attempted. The exception propagates with the enclosing scope marked, so the nearest enclosing bean that can leave its parser in order drops itself; through the handlers' readerForUpdating root the read fails. Either replaces a silent desynchronisation. A bean nested inside such a value lies wholly on one side and is dropped normally. The refusal logs a WARN naming the type, so the resulting Jackson error can be tied to authorization.

Review trail

Three code-review and two security-review passes, each finding a deeper parser shape: depth → identity (token buffers), cleared current token after Jackson's splice, refusal too broad (nested beans inside a spliced value), refusal too narrow (a splice whose base parser is itself a buffer), parent scope not marked on refusal. All are regression tests now. Security: nothing newly introduced; every direction is "consume more or fail", never "leave unread for the parent".

Tests

ParameterAuthorizingModuleTest, nine new cases: null-id drop with trailing fields; creator failure with a nested object and trailing fields; late-type-id drop fails the read through readerForUpdating; a bean nested in a late-type-id value still drops in place; a refused drop marks the parent so it drops itself; a late-type-id value nested in another's buffer escalates rather than leaking; parent swallowing a refused subtype drop after clearCurrentToken(); drop inside an @JsonUnwrapped replay stops at the record's end.

mvn test -DskipAssembly -pl plugins/rest: 210 tests, 0 failures.

🤖 Generated with Claude Code

…tion wrapper drops

RedactionAwareDeserializer drops a bean whose construction fails after
a @StrutsParameter redaction by catching the JsonMappingException and
returning null. It returned without moving the parser, so whatever
tokens of the dropped object were still unread went to the enclosing
bean: a creator-bound bean failing on its last creator parameter with
fields after it, or a bean whose bean-typed @JsonIdentityInfo id failed
to construct and could not be bound, left its remaining fields to the
parent - a same-named parent property took the child's value, the
parent's own later properties were lost, and an unknown field failed
the parent inside a scope already marked redacted, dropping the root.

The wrapper now records the context of the object or array it is
entered on and, before returning null, skips to that value's end token,
consuming nested structures whole. The end is recognised by context
identity - the first end token whose context no longer descends from
the recorded one - which every parser keeps, including the token
buffers Jackson replays unwrapped and any-setter values from; those
report no nesting depth at all, so a depth comparison drained them.
A parser left without a current token, as Jackson does before splicing
a late type id, is advanced first. A scalar entry needs nothing.

A polymorphic value whose type id is not the first key is read from
Jackson's JsonParserSequence, spliced from a buffer of the keys before
the id and the real parser, and that value alone straddles the splice.
The buffer holds no start token, so that value is the one entered
mid-object on a buffer context while the parser is a splice - a bean
wholly inside the buffer is entered on its start token - and its drop
is not attempted. The exception propagates with the enclosing scope marked, so
the nearest enclosing bean that can leave its parser in order drops
itself, and through the handlers' root the read fails; either replaces
a silent desynchronisation. A bean nested inside such a value lies
wholly on one side of the splice and is dropped normally.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@lukaszlenart
lukaszlenart marked this pull request as ready for review September 15, 2026 14:13
@lukaszlenart
lukaszlenart merged commit 0be1f3f into main Sep 15, 2026
13 checks passed
@lukaszlenart
lukaszlenart deleted the WW-5747 branch September 15, 2026 14:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant