FiloSottile/age · error
trailing data after end of encrypted file
Error message
trailing data after end of encrypted file
What it means
After the stream reader consumed the final (last-chunk-flagged) chunk, it expects an immediate EOF from the underlying source. Any additional byte after the last chunk means the encrypted file has extra trailing data, so the reader reports this error instead of silently ending. Trailing bytes may indicate concatenation of files, corruption, or tampering.
Source
Thrown at internal/stream/stream.go:103
return 0, nil
}
last, err := r.readChunk()
if err != nil {
r.err = err
return 0, err
}
n := copy(p, r.unread)
r.unread = r.unread[n:]
if last {
// Ensure there is an EOF after the last chunk as expected. In other
// words, check for trailing data after a full-length final chunk.
// Hopefully, the underlying reader supports returning EOF even if it
// had previously returned an EOF to ReadFull.
if _, err := io.ReadFull(r.src, make([]byte, 1)); err == nil {
r.err = errors.New("trailing data after end of encrypted file")
} else if err != io.EOF {
r.err = fmt.Errorf("non-EOF error reading after end of encrypted file: %w", err)
} else {
r.err = io.EOF
}
}
return n, nil
}
// readChunk reads the next chunk of ciphertext from r.src and makes it available
// in r.unread. last is true if the chunk was marked as the end of the message.
// readChunk must not be called again after returning a last chunk or an error.
func (r *DecryptReader) readChunk() (last bool, err error) {
if len(r.unread) != 0 {
panic("stream: internal error: readChunk called with dirty buffer")
}
View on GitHub (pinned to b74dce4cdb)
Solutions
- Verify the ciphertext is not being written after encryption completes (close the writer before sharing the file).
- Re-encrypt from the original plaintext to produce a clean file.
- Check for accidental file concatenation or appending logic (e.g. os.O_APPEND on an existing file) and open with O_TRUNC instead.
Example fix
// before
f, _ := os.OpenFile("file.age", os.O_APPEND|os.O_WRONLY, 0) // appends to existing ciphertext
// after
f, _ := os.OpenFile("file.age", os.O_TRUNC|os.O_WRONLY, 0) Defensive patterns
Strategy: try-catch
Try / catch
n, err := io.Copy(out, r)
if err != nil && strings.Contains(err.Error(), "trailing data after end of encrypted file") {
// ciphertext has extra bytes: treat as corrupted, do not trust partial output
return fmt.Errorf("ciphertext malformed: %w", err)
} Prevention
- Close the stream.Writer and confirm no error before using/sharing the ciphertext.
- Never append to an existing .age file; always write to a fresh file.
- Verify checksums of ciphertext after transport.
When it happens
Trigger: Reading an age stream (stream.NewReader) where the ciphertext source contains one or more bytes after the terminal full-length final chunk, detected via io.ReadFull(src, 1) returning no error.
Common situations: Appending to an already-complete encrypted file; concatenating two age files; a wrapper storing extra padding/records after the payload; truncated re-uploads that added junk bytes.
Related errors
- trailing data after armored file
- last chunk is empty, try age v1.0.0, and please consider rep
- stream.Writer is already closed
- invalid ciphertext size
AI-assisted analysis of FiloSottile/age@b74dce4cdb (2026-08-31).
Data as JSON: /api/errors/4f91a8f918db1b29.
Report an issue: GitHub.