Java deserialization is dangerous because constructing an object graph can invoke application code. An untrusted stream can select classes the receiver did not intend to expose. ObjectInputFilter and the process-wide jdk.serialFilter property give applications a way to constrain classes, array sizes, graph depth, and byte counts before the full object stream is consumed.

The filter is most useful when the application has a known serialization contract. A blanket denial of untrusted streams is stronger than trying to enumerate every dangerous class. Where serialization is required for trusted internal traffic, the filter should describe the classes and packages the protocol needs. An allow-all rule with a short blocklist is fragile because it depends on knowing every gadget class in the runtime and every dependency.

Set the policy at the right layer

The Java documentation supports command-line or security-property filters, a filter factory, and stream-specific filters. Use a process-wide baseline for defense in depth, then apply a narrower stream filter at each deserialization boundary. A library that accepts serialized objects should let its caller provide or inherit a restrictive policy. A custom filter can also impose limits on depth and array length.

Test the filter against the real serialization format. A class name may be loaded through a dependency, an array may be serialized differently, or an internal class may need access to a package that the policy omitted. Start with an allowlist derived from the protocol and representative test data. Keep rejection behavior observable without logging the complete stream or sensitive object fields.

Treat compatibility and bypasses seriously

Upgrading a library can change the object graph even when the application code is untouched. Add serialization round-trip tests to dependency updates. Review filter factories for ordering and composition mistakes. A factory that returns UNDECIDED for unknown classes is not the same as one that rejects them. Confirm the effective filter with a small test that attempts a class outside the policy.

Do not assume a filter protects every parser. JSON, YAML, XML, and custom binary formats have their own object-construction behavior. Those paths need format-specific controls and input limits. Also avoid creating a deserialization endpoint for data that can be represented with a simpler schema. The safest serialized stream is one the application never needs to accept.

Incident readiness

Inventory endpoints that use ObjectInputStream, Java remoting, or libraries that deserialize opaque objects. Record the filter configuration, the network path to the endpoint, and the process identity. Alert on unexpected serialization errors or class-rejection events. A repeated rejection can be a vulnerability scan, a broken integration, or an attempted exploit, so pair the alert with request context.

The proof of completion is a denied test class, a successful approved round trip, and a reviewed configuration path for every relevant process. The filter reduces the reachable class graph and resource consumption. It does not make an arbitrary deserialization endpoint safe to expose to the public internet.

References