abpframework/abp · error · AbpException
The encrypted BLOB is corrupted or has an invalid format: in
Error message
The encrypted BLOB is corrupted or has an invalid format: invalid terminal record!
What it means
In ChunkedDecryptingReadStream.ProduceNext, a zero-length prefix signals the terminal record. The codec then reads the terminal GCM tag; if ReadExactly returns null (stream ended before the tag) or extra bytes follow the tag, the terminal record is malformed and the BLOB cannot be authenticated as complete, so it throws.
Source
Thrown at framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/ChunkedDecryptingReadStream.cs:63
}
public ValueTask EnsureReadToAuthenticatedEndAsync(CancellationToken cancellationToken = default)
{
return EnsureReadToAuthenticatedEndCoreAsync(cancellationToken);
}
protected override byte[]? ProduceNext()
{
var cipherChunkSize = BlobEncryptionCodec.GetCipherChunkSize(
BlobEncryptionCodec.ReadUpTo(_cipherStream, BlobEncryptionCodec.ChunkLengthPrefixSize),
_chunkSize
);
if (cipherChunkSize == 0)
{
var terminalTag = BlobEncryptionCodec.ReadExactly(_cipherStream, BlobEncryptionCodec.GcmTagSize);
if (terminalTag == null || BlobEncryptionCodec.ReadUpTo(_cipherStream, 1).Length != 0)
{
throw new AbpException("The encrypted BLOB is corrupted or has an invalid format: invalid terminal record!");
}
SetChunkIndex(_chunkIndex);
BlobEncryptionCodec.VerifyTerminalRecordCore(_chunkCipher, _associatedData, _nonce, terminalTag);
return null;
}
return DecryptPayload(
BlobEncryptionCodec.ReadExactly(_cipherStream, cipherChunkSize),
BlobEncryptionCodec.ReadExactly(_cipherStream, BlobEncryptionCodec.GcmTagSize)
);
}
protected override async Task<byte[]?> ProduceNextAsync(CancellationToken cancellationToken)
{
var cipherChunkSize = BlobEncryptionCodec.GetCipherChunkSize(
await BlobEncryptionCodec.ReadUpToAsync(_cipherStream, BlobEncryptionCodec.ChunkLengthPrefixSize, cancellationToken),
_chunkSizeView on GitHub (pinned to 7ed43b1931)
Solutions
- Re-upload the BLOB from a known-good source.
- Verify the stored object length matches an expected ciphertext length.
- Check the storage backend for partial writes, replication lag, or appended junk.
- Ensure nothing else writes to the same BLOB key (no append/concat).
Example fix
// before — saving then appending to the same key await blob.SaveAsync(name, enc1); await AppendAsync(name, enc2); // trailing garbage after terminal record // after — one writer per key, no append await blob.SaveAsync(name, enc1); await blob.SaveAsync(name + ".2", enc2);
Defensive patterns
Strategy: try-catch
Validate before calling
// Verify the stored object length is consistent with the format before decrypting.
var info = await provider.GetOrNullAsync(name);
if (info == null) throw new FileNotFoundException(name);
// An encrypted BLOB is: magic + version + header + chunks + terminal record.
// Reject implausibly short or unexpectedly long objects.
if (info.ContentLength < 32)
throw new InvalidOperationException($"BLOB '{name}' too short to contain a valid terminal record."); Type guard
public sealed record VerifiedEncryptedBlob(string Name, long ContentLength)
{
public static VerifiedEncryptedBlob Check(string name, long len)
{
if (len < 32) throw new InvalidOperationException("blob too short / no terminal record");
return new VerifiedEncryptedBlob(name, len);
}
} Try / catch
try
{
return await blob.GetAllBytesAsync(name);
}
catch (AbpException ex) when (ex.Message.Contains("invalid terminal record"))
{
logger.LogError(ex, "BLOB '{Name}' has a malformed terminal record; re-upload.", name);
throw;
} Prevention
- Write each BLOB key exactly once; never append/concat.
- Verify upload completion (ETag/status) before reads.
- Store checksums and validate on read.
- Investigate trailing-bytes corruption in storage.
When it happens
Trigger: Decrypting a BLOB whose cipher stream ends before the terminal tag is fully present, or which has trailing garbage after the terminal tag (synchronous read path).
Common situations: Truncation that cuts off the terminal tag; concatenation of two BLOBs into one object; tampering that appended bytes; storage corruption.
Related errors
- The encrypted BLOB is corrupted or has an invalid format: mi
- The encrypted BLOB is corrupted or has an invalid format: tr
- The encrypted BLOB is corrupted or has an invalid format: in
- The encrypted BLOB is corrupted or has an invalid format: tr
- The stream can not be read anymore, because a previous read
AI-assisted analysis of abpframework/abp@7ed43b1931 (2026-08-13).
Data as JSON: /api/errors/6350ad6d958f05f2.
Report an issue: GitHub.