Repository navigation
Filter Java deserialization on the RemoteObservable protocol - #878
Draft
ssatybaldiyev-ship-it wants to merge 1 commit into
Draft
ssatybaldiyev-ship-it wants to merge 1 commit into
ssatybaldiyev-ship-it wants to merge 1 commit into
Conversation
The subscribe-parameters map (opcode 4) and the error payload (opcode 2) were read with ObjectInputStream.readObject() and no class filter, so a peer could make the JVM instantiate any serializable class on the classpath. The server-side read happens before any authentication. Add FilteredDeserializer, which installs a JEP 290 ObjectInputFilter that allows only the classes each payload needs, and use it in BatchedRxEventPipelineConfigurator, LegacyTcpPipelineConfigurator and RemoteObservable. The wire format is unchanged. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
ssatybaldiyev-ship-it
requested review from
Andyz26,
calvin681,
dtrager02,
fdc-ntflx and
hellolittlej
as code owners
October 21, 2026 12:45
ssatybaldiyev-ship-it
marked this pull request as draft
October 26, 2026 10:27
This branch has not been deployed
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.
Context
BatchedRxEventPipelineConfigurator,LegacyTcpPipelineConfiguratorandRemoteObservabledeserialize bytes from the network withObjectInputStream.readObject()and no class filter:RemoteObservable.fromBytesToThrowableA peer can send a serialized object of any class on the classpath. Java runs that class's
readObjectduring deserialization, before the cast to the expected type, so an attacker-chosen class can execute code in the worker. The server-side read happens before any authentication.This adds
FilteredDeserializerinmantis-common. It installs a JEP 290ObjectInputFilterthat allows only the classes each payload needs:java.langclasses, andjava.utilMapimplementations, plus a few JDK-internal helper types the JDK collection code needs (Map.Entry,CollSer,Collections$UnmodifiableCollection).java.utilListimplementations and anyThrowablesubclass.Anything else is rejected with an
InvalidClassExceptionbefore that class's code runs. The three call sites now callFilteredDeserializer. The wire format is unchanged, so old and new nodes interoperate.Behavior change: a map or exception holding a field outside those types is now rejected. That includes a
ConcurrentHashMappassed as subscribe parameters, and a custom exception with a field of an unusual class. A rejected error payload makesfromBytesToThrowablethrow aRuntimeException.Not covered: the data events (opcode 1) are raw bytes decoded by the job's codec, so this does not affect them. Restricting
jdk.serialFilterprocess-wide is a deployment setting outside this repo.Tests
FilteredDeserializerTest: accepted and rejected cases, including a nested gadget class whosereadObjectmust not run.BatchedRxEventPipelineConfiguratorTest,LegacyTcpPipelineConfiguratorTest,RemoteObservableErrorDeserializationTest.mantis-remote-observabletests are not run by./gradlew test, becausemantis-remote-observable/build.gradleexcludes that whole package (some tests there need ports). They pass when run with that exclusion lifted. Narrowing the exclusion is left for a follow-up.Checklist
./gradlew buildcompiles code correctly./gradlew testpasses all testsThe new tests pass locally. A full local
./gradlew build akkaTestcould not be completed in my environment, because Mockito's agent self-attach is blocked there (the same tests fail on an unmodifiedmaster), so CI should confirm the rest.🤖 Generated with Claude Code