{"record":{"id":"d34390e13915acba","repo":"rustfs/rustfs","slug":"dare-stream-truncated-before-a-finalized-package","errorCode":null,"errorMessage":"DARE stream truncated before a finalized package","messagePattern":"DARE stream truncated before a finalized package","errorType":"exception","errorClass":"io::Error","httpStatus":null,"severity":"critical","filePath":"crates/rio-v2/src/encrypt_reader.rs","lineNumber":473,"sourceCode":"            }\n\n            while *this.header_read < DARE_HEADER_SIZE {\n                let mut read_buf = ReadBuf::new(&mut this.header_buf[*this.header_read..]);\n                match this.inner.as_mut().poll_read(cx, &mut read_buf) {\n                    Poll::Pending => return Poll::Pending,\n                    Poll::Ready(Ok(())) => {\n                        let n = read_buf.filled().len();\n                        if n == 0 {\n                            if *this.header_read == 0 {\n                                // Clean EOF at a package boundary. Execution only reaches here\n                                // with `finalized == false` (the finalized case is consumed at the\n                                // loop top). If at least one package of the current part has been\n                                // decrypted (`ref_nonce.is_some()`) but we never saw a final-flagged\n                                // package, the final package is missing => DARE truncation. Zero\n                                // decrypted packages (`ref_nonce.is_none()`) is a legitimately empty\n                                // object (encrypt emits no packages for empty plaintext), so accept.\n                                if this.ref_nonce.is_some() {\n                                    return Poll::Ready(Err(io::Error::new(\n                                        io::ErrorKind::UnexpectedEof,\n                                        \"DARE stream truncated before a finalized package\",\n                                    )));\n                                }\n                                *this.finished = true;\n                                return Poll::Ready(Ok(()));\n                            }\n                            return Poll::Ready(Err(io::Error::new(\n                                io::ErrorKind::UnexpectedEof,\n                                \"unexpected EOF while reading DARE header\",\n                            )));\n                        }\n                        *this.header_read += n;\n                    }\n                    Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),\n                }\n            }\n","sourceCodeStart":455,"sourceCodeEnd":491,"githubUrl":"https://github.com/rustfs/rustfs/blob/9e6e02ea09c86bedf44c7bd64a74ea02a0cff1de/crates/rio-v2/src/encrypt_reader.rs#L455-L491","documentation":"DecryptReader hit a clean EOF exactly at a DARE package boundary, but at least one package of the current part had already been decrypted (ref_nonce set) and none of them carried the final flag. DARE v2 streams must terminate with a final-flagged package; its absence means the encrypted object is truncated, so the reader returns UnexpectedEof. Zero decrypted packages is treated as a legitimately empty object and accepted.","triggerScenarios":"An interrupted encrypted upload committed without the final package; multipart assembly using a wrong or stale part list so the tail part carrying the final package is missing; the object truncated after write; a range/assembly path that drops the last package.","commonSituations":"Crash during PUT of an SSE-encrypted object; manual xl.meta or multipart surgery; completing a multipart upload with an outdated parts list; mixed-version clusters during rolling upgrades mishandling the final flag.","solutions":["Re-upload the object from a known-good copy — the plaintext is unrecoverable past the truncation point.","If multipart, verify the complete-multipart part list matches the actually uploaded parts (order and content) before retrying.","Check server logs around the original write for interrupted/crashed PUTs.","Audit any tooling that reassembles or copies encrypted objects part-by-part; ensure it preserves the final package."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"match r.read_buf(&mut buf).await {\n    Ok(n) => Ok(n),\n    Err(e) if e.kind() == io::ErrorKind::UnexpectedEof && e.to_string().contains(\"finalized package\") => {\n        // Encrypted object truncated before its final package: unrecoverable — re-upload required.\n        integrity::mark_unrecoverable(&object_id);\n        Err(e)\n    }\n    Err(e) => Err(e),\n}","preventionTips":["Verify multipart complete-multipart part lists against actually uploaded parts before committing encrypted objects.","Ensure writers emit the final-flagged package before an object becomes visible to readers.","Audit part-copy tooling to confirm it preserves the final DARE package."],"tags":["dare","encryption","eof","truncated-object","multipart"],"backgroundTag":"truncated-encrypted-stream","analyzedSha":"9e6e02ea09c86bedf44c7bd64a74ea02a0cff1de","analyzedAt":"2026-08-16T20:34:17.560Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}