Skip to content

Add an optional class filter to JavaSerializer deserialization - #1295

Open
Nexory wants to merge 1 commit into
EsotericSoftware:masterfrom
Nexory:hardening/javaserializer-classfilter
Open

Add an optional class filter to JavaSerializer deserialization#1295
Nexory wants to merge 1 commit into
EsotericSoftware:masterfrom
Nexory:hardening/javaserializer-classfilter

Conversation

@Nexory

@Nexory Nexory commented Aug 3, 2026

Copy link
Copy Markdown

What / Why

JavaSerializer.read(...) deserializes with ObjectInputStream.readObject(), and its ObjectInputStreamWithKryoClassLoader overrides only resolveClass(...) for classloader resolution. It applies no restriction on which classes may be deserialized. Kryo's own setRegistrationRequired(true) does not help on this path, because the bytes are handed to the JDK serialization mechanism rather than resolved through Kryo's class registration, so a Kryo stream that uses JavaSerializer for any field type deserializes arbitrary classes from the input.

This adds an opt-in class filter to JavaSerializer as defense-in-depth when reading serialized data from an untrusted source:

  • setClassFilter(Predicate<Class>) / getClassFilter().
  • The filter is checked in resolveClass(...): a class for which the predicate returns false is rejected with a KryoException before it is used.

Non-breaking

The default is unchanged. With no filter set (null, the default) behavior is exactly as before, and the existing classloader-resolution logic in resolveClass(...) is preserved.

Java 8 note

Kryo targets Java 8, so this uses a Predicate<Class> checked in the existing resolveClass(...) hook rather than the Java 9+ java.io.ObjectInputFilter (JEP-290). If you prefer, I am happy to additionally wire a real ObjectInputFilter on Java 9+ (resolved reflectively so the Java 8 build is unaffected), or to expose a Set<String> allowlist instead of a predicate. Whichever shape you prefer, the check stays in one place.

Tests

JavaSerializerTest adds two cases: a filter that disallows the class rejects deserialization (as KryoException), and a filter that allows it still round-trips. The existing tests are unchanged. Verified with mvn -Dtest=JavaSerializerTest test on JDK 11 (source/target 8).

JavaSerializer.read deserializes with ObjectInputStream.readObject and
previously applied no restriction on which classes may be deserialized;
Kryo's setRegistrationRequired does not apply on this path because the
bytes are handed to the JDK serialization mechanism. Add an opt-in
setClassFilter(Predicate<Class>), checked in resolveClass, so a class for
which the predicate returns false is rejected with a KryoException before
it is used. The default is unchanged (no filter) and the existing
classloader resolution is preserved.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant