CVE-2026-34204: Replication Metadata Injection
Status: Released
First containing release: RELEASE.2026-04-17T00-00-00Z
GitHub issue: pgsty/minio#24
Ordinary PUT and COPY requests could smuggle X-Minio-Replication-* headers into internal X-Minio-Internal-* SSE metadata, creating objects whose replication state did not match the authorized path and could even make them unreadable. The final fix stopped accepting replication-only metadata by default, restored it only in a trusted flow authorized for ReplicateObjectAction, and sanitized CopyObject before any header consumer ran.
Threat model
An attacker needed only ordinary object-write permission, not internode credentials. The input came entirely from client-controlled X-Minio-Replication-* headers, but metadata extraction converted it into internal replication or SSE state.
Later read paths interpreted the object according to that false internal state. The result could be an unreadable object: an integrity and availability failure. Almost every production server accepting untrusted writes needed to be treated as affected.
The root problem was not the header name. It was that data from an untrusted source acquired internal semantics without passing replication authorization.
Reject the whole request, or sanitize precisely?
Rejecting an ordinary request whenever it contains a replication header is the most obvious fix. It would also turn a header clients were previously allowed to send and have ignored into a hard failure. The final design was more precise:
- the default extraction path does not accept replication-only metadata;
- ordinary
PUTandCOPYstrip those fields first; - only a path authorized for
ReplicateObjectActionrestores them; - replica-status writes use the same trusted condition;
- legitimate multipart and Snowball replication flows explicitly restore the SSE metadata they require.
That keeps the compatibility change inside internal semantics instead of expanding it to every client carrying an extraneous header.
Why CopyObject had to sanitize early
CopyObject headers are not used only for the final metadata map. They can be consumed earlier by precondition logic and SSE-C source handling. Removing them immediately before the object write is too late: earlier consumers have already been contaminated.
The final sanitization occurs before those consumers. “Untrusted replication headers never enter internal semantics” becomes one invariant instead of a convention every downstream function must remember to enforce.
Implementation and verification
The change covered handler utilities, object handlers, and multipart handlers, with tests at several layers:
- trusted and untrusted metadata extraction at the helper layer;
- malicious
PUTandCOPYcases at the handler layer; CopyObjectheader sanitization;- red/green comparison between the vulnerable parent and the patched tree;
- live-server before/after behavior showing that a malicious header no longer made an object unreadable;
- continued operation of legitimate replication, multipart, and Snowball flows.
The public fix is fcb8f24. This article preserves the historical verification boundary; no live server was started again while preparing the chronicle.
Cost and residual risk
- Internal replication headers supplied by ordinary clients are now ignored or stripped.
- Replication-only metadata must be restored explicitly inside an authorized branch.
- If a future replication entry point forgets to restore it, the result should be a functional regression rather than another untrusted write path.
- The audit focused on replication headers; it does not establish that every
X-Minio-Internal-*field has undergone the same trust review.
This incident leaves a simple review question: a field that looks “internal” is not necessarily trusted. Ask where it came from and which authorization decision allowed it to acquire internal meaning.