Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsCommunityBlog
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

Back to skills

Java Serialization Hardening

ASecurity

Java built-in serialization as an attack surface and a permanent API commitment: why readObject is an extra constructor that accepts arbitrary bytes, gadget chains and what deserialization filters (JEP 290/415) can and cannot do, the cost of implementing Serializable, serialVersionUID and the custom serialized form, validating and defensively copying in readObject, the serialization proxy pattern, why records are different, and the same risk in JSON polymorphic typing. Use when Serializable, ...

2 stars
0 votes
0 copies
0 views
Added 9/19/2026
developmentrustgojavatestingapisecurityperformance

Works with

cliapi

Security Analysis

A100/100

Scanned 9/19/2026

Install to Claude Code

$npx -y skills add robsonkades/agent-skills --skill java-serialization-hardening --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Java Serialization Hardening?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Java Serialization Hardening
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/robsonkades-java-serialization-hardening/badge)](https://www.skillsdirectory.com/skills/robsonkades-java-serialization-hardening)

More formats (shields.io, HTML) on the badges page.

Download Zip
Files
SKILL.md
---
name: java-serialization-hardening
description: >
  Java built-in serialization as an attack surface and a permanent API commitment: why
  readObject is an extra constructor that accepts arbitrary bytes, gadget chains and what
  deserialization filters (JEP 290/415) can and cannot do, the cost of implementing
  Serializable, serialVersionUID and the custom serialized form, validating and defensively
  copying in readObject, the serialization proxy pattern, why records are different, and the
  same risk in JSON polymorphic typing. Use when Serializable, readObject, readResolve or
  Externalizable appears, when ObjectInputStream reads bytes from a cache, queue, session
  store, RMI or JMX, when Jackson default typing is enabled, or when a mixed-version deploy
  breaks a serialized cache. Format cost is serialization-performance, contract evolution is
  rpc-and-api-contracts, and the reflective access underneath is
  java-reflection-and-method-handles.
---

# Java Serialization Hardening

## Purpose

Keep the built-in serialization mechanism away from anything an attacker can influence, and —
where it cannot be removed — make each `Serializable` class defend its own invariants. Two
failure modes: `ObjectInputStream` on attacker-influenced data, which instantiates arbitrary
allowed object graphs and invokes serialization hooks before the caller can validate the result;
with a reachable gadget this can become code execution, and without one it can still exhaust
resources or violate invariants. The second is `implements Serializable` added
casually, which publishes the class's private field layout as a compatibility contract nobody
intended to maintain.

## Workflow

Inspect compiler release/toolchains, exact JDK build, active filters/factory, mapper/provider
versions and released serialized forms before edits. No single authoring baseline is declared;
references use Java SE 25. Standard `ObjectInputFilter` needs Java 9+ (older vendor backports
can differ), `@Serial` Java 14+, records Java 16+, and the filter factory Java 17+.
Use target-supported mechanisms without upgrading or enabling preview. Missing forms/config
mean compatibility or filter coverage remains unverified; state the needed evidence.

1. **Find every deserialization entry point.** Direct `ObjectInputStream`, RMI/JMX paths,
   JNDI providers that can return serialized state, a distributed cache configured with a JDK serializer, session replication, a
   message consumer, and any framework that stores state as bytes. List the ones whose bytes
   are not exclusively produced by trusted code you deploy together.
2. **Remove the mechanism where the data is not trusted.** Replace with JSON, protobuf or Avro
   parsed into explicit types. This is the fix; everything below is mitigation.
3. **Where it must stay, filter and compose deliberately.** Use a context-specific
   `ObjectInputFilter` for expected classes and graph limits. Configure a JVM-wide backstop, then
   verify how the active filter factory combines it with stream filters—the built-in factory can
   replace the static filter when a stream-specific filter is set rather than intersecting both.
4. **For each `Serializable` class you own**, decide the serialized form deliberately: default
   or custom, `serialVersionUID` declared, invariants revalidated on read, mutable components
   copied.
5. **Prefer a design where deserialization runs a constructor** — a record, or a serialization
   proxy — so the owning type's constructor invariants run. This does not prevent hooks in
   component graphs from executing first or eliminate size, aliasing and replacement-object risks.
6. **Test compatibility and rollout explicitly**: released golden data must still deserialize.
   If old readers coexist, keep writing the old/enveloped form or dual-write safely until they are
   gone; merely teaching the new release to read both formats does not protect the old release.

## Rules

- Default decision: do not use Java serialization for a new trust or persistence boundary. It is
  Java-specific, opaque, hard to govern as a schema and capable of executing class hooks. A closed,
  ephemeral in-process use may have different constraints, but convenience alone is insufficient.
  It is often slower/larger than schema formats, but measure the actual payload and workload.
  Choose a schema and parser based on interoperability, evolution, observability and measured
  cost—JSON, protobuf and Avro have different trade-offs (serialization-performance).
- **Never treat validation after `readObject()` as protection for hostile bytes.** Hooks and graph
  construction occur _during_ deserialization through methods such as `readObject` and
  `readResolve`; `finalize` is not a serialization callback. The gadget
  chain does not need your classes to be malicious — it needs a library on your classpath that
  can be driven into doing something, and popular libraries have repeatedly qualified.
- A filter is a mitigation, not a proof of safety. `ObjectInputFilter` (JEP 290) and the filter factory
  (JEP 415) let you allow-list classes and check array sizes, graph depth and observed bytes;
  string/primitive callback gaps require an independent input-byte cap. Set the
  narrowest filter that works and treat a rejection as a security event. An allow-list of exact
  classes is worth having; a deny-list of known gadget classes is not — the list of gadgets
  grows with every dependency.
- Implementing `Serializable` creates a compatibility commitment for as long as old bytes or
  consumers survive. Under the default form, field names/types, hierarchy and reachable types
  constrain evolution; a custom serial form or proxy can decouple representation. Add it only
  for a concrete requirement with retention and migration policy.
- Declare `private static final long serialVersionUID` explicitly on ordinary `Serializable`
  classes and annotate serialization declarations with `@Serial`. Enums have fixed UID `0L` and
  ignore such declarations; records default to `0L` and waive UID matching. For ordinary classes,
  the generated value depends on the compiler's view of the class — names, modifiers,
  members — so an innocuous edit changes it and produces `InvalidClassException` at runtime,
  in production, on a mixed-version deploy.
- Use the default serialized form only when the physical representation genuinely equals the
  logical content. Where it does not (a linked structure, a cache, a derived index, a doubly
  linked list), write a custom form: `writeObject`/`readObject` with `defaultWriteObject`/
  `defaultReadObject` plus the explicit fields, and mark the rest `transient`. The default form
  over a long linked structure also recurses per element and can overflow the stack.
- `readObject` is an additional construction path that accepts hostile input. Constructors and
  field initializers of serializable classes do not establish the object; the no-arg constructor
  of the first non-serializable superclass does run. Revalidate every invariant
  the real constructor enforces, and it must **defensively copy** every mutable component
  before validating — otherwise an attacker who appended extra bytes keeps a reference to the
  object's internals and mutates them after construction. Copy first, validate the copy, and
  never validate a field the attacker can still reach.
- Exclude derived caches, resource handles, credentials/capabilities and other non-logical state
  with `transient` or an explicit serial form. `transient` is not encryption and does not exclude
  the same object if it is reachable through another non-transient path. A transient field is
  restored as `null`/`0`, so `readObject` must reconstruct or revalidate it.
- Prefer the **serialization proxy pattern** when a `Serializable` class has non-trivial
  invariants: a private static nested class holding the logical state, `writeReplace` on the
  outer class, and a `readObject` on the outer class that throws `InvalidObjectException`.
  Deserialization then goes through the proxy's `readResolve`, which builds the object with its
  ordinary constructor — invariants enforced, no reflective back door. It does not work for
  classes extensible by clients, or for object graphs with cycles through the proxied class.
- A **record** is deserialized through its canonical constructor, so its compact-constructor
  validation and defensive copies apply to deserialised instances as well. That removes the
  `readObject` invariant-bypass hazard. Its `writeObject`, `readObject`, `readObjectNoData`,
  `writeExternal` and `readExternal` hooks are ignored, but `writeReplace`/`readResolve` may still
  substitute objects. Records also do not preserve cycles through their components.
- A class-based serializable singleton normally needs `readResolve` returning the canonical
  instance; review reference fields for back-reference theft before substitution and keep
  non-logical state out of the form. Primitive fields do not inherently require `transient`.
  An enum uses special name-based serialization, not these hooks—see java-object-construction.
- A related attack shape exists in JSON. Jackson's class-name default typing and broad
  `@JsonTypeInfo(use = Id.CLASS)` let the document name the class to instantiate, which is
  the JSON version of a gadget chain. Use logical type ids registered with
  a closed logical-id registry plus an explicit discriminator and strict mapper configuration.
  `Id.NAME` or a sealed interface alone is not an allow-list if other modules can register or
  discover subtypes. Never resolve arbitrary class names from a hostile payload.
- Serialized data crosses versions in time as well as space. A distributed cache, a session
  store or a queue holds bytes written by the previous release while the new one reads them,
  and vice versa during a rolling deploy. Decide the compatibility policy — and if the answer
  is to discard a cache, fence incompatible old writers/readers and verify that loss and bounded
  repopulation are operationally acceptable. An automatic flush alone does not coordinate rollout.

## References

Deliver the entry point and byte provenance, exact effective filter/outer limits, retained
construction invariants and migration policy. Report hostile-fixture and old/new-reader checks
executed, failures and missing coverage; a passing round trip does not establish safe deserialization.

- [Untrusted data and filters](references/untrusted-data-and-filters.md) — read when
  `ObjectInputStream` reads anything not produced by code you deploy, when configuring
  `ObjectInputFilter` or a filter factory, when auditing RMI/JMX/JNDI/cache exposure, or when
  polymorphic JSON typing is in use.
- [If you must implement Serializable](references/implementing-serializable.md) — read when
  writing or reviewing a `Serializable` class: choosing the serialized form,
  `serialVersionUID`, `readObject` validation and defensive copies, the serialization proxy,
  records, and compatibility testing across versions.

## Primary sources

- [Java SE 25 Serialization Filtering Guide](https://docs.oracle.com/en/java/javase/25/core/java-serialization-filters.html)
- [Java Object Serialization Specification](https://docs.oracle.com/en/java/javase/25/docs/specs/serialization/index.html)
- [Serializable API contract](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/io/Serializable.html)
- [JEP 290 — Filter Incoming Serialization Data](https://openjdk.org/jeps/290)
- [JEP 415 — Context-Specific Deserialization Filters](https://openjdk.org/jeps/415)

Attribution

robsonkadesrobsonkades
View sourceMore from robsonkades →
SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Related Skills

Browser Extension Developer

Use this skill when developing or maintaining browser extension code in the `browser/` directory, including Chrome/Firefox/Edge compatibility, content scripts, background scripts, or i18n updates.

281612 votes

Seo Optimizer

SEO optimization with keyword analysis, readability assessment, technical validation, content quality. Use for search rankings, blog posts, content audits, or encountering keyword density, readability scores, meta tags, schema markup errors.

2132 votes

Google Official Seo Guide

Official Google SEO guide covering search optimization, best practices, Search Console, crawling, indexing, and improving website search visibility based on official Google documentation

1862 votes

Tanstack Start

Build a full-stack TanStack Start app on Cloudflare Workers from scratch — SSR, file-based routing, server functions, D1+Drizzle, better-auth, Tailwind v4+shadcn/ui. Use whenever the user mentions TanStack Start, asks to scaffold a full-stack Cloudflare app with SSR, wants an SSR dashboard, or asks for a React 19 + Cloudflare Workers app with file-based routing and server functions — even if they don't name TanStack Start specifically. No template repo — Claude generates every file fresh per ...

9881 votes

Pentest

PTES-aligned adversarial security audit for backend, frontend, and mobile applications. Produces a CVSS-scored Hacker Report with verified PoCs and phased remediation.

5491 votes
View all in development →