WW-5747 fix(rest): leave the parser at the end of an object the redaction wrapper drops - #1946
Merged
Merged
Conversation
…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>
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Fixes WW-5747
Problem
RedactionAwareDeserializerdrops a bean whose construction fails after a@StrutsParameterredaction by catching theJsonMappingExceptionand returningnull. 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@JsonIdentityInfoid 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
nullit 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@JsonUnwrappedand 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.visible = true) straddles the splice and cannot be followed: it is the value entered mid-object on a buffer context while the parser is aJsonParserSequence, 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'readerForUpdatingroot 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 throughreaderForUpdating; 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 afterclearCurrentToken(); drop inside an@JsonUnwrappedreplay stops at the record's end.mvn test -DskipAssembly -pl plugins/rest: 210 tests, 0 failures.🤖 Generated with Claude Code