{"record":{"id":"db2145d5d1295126","repo":"abpframework/abp","slug":"the-encrypted-blob-was-not-read-to-its-authenticat","errorCode":null,"errorMessage":"The encrypted BLOB was not read to its authenticated end, so its completeness can not be verified (a content-pipeline contributor stopped reading the content before the end).","messagePattern":"The encrypted BLOB was not read to its authenticated end, so its completeness can not be verified \\(a content-pipeline contributor stopped reading the content before the end\\)\\.","errorType":"exception","errorClass":"AbpException","httpStatus":null,"severity":"error","filePath":"framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/ChunkedCryptoReadStream.cs","lineNumber":126,"sourceCode":"            ThrowIfMoreContent(await ProduceNextAsync(cancellationToken));\n        }\n        catch\n        {\n            MarkFaulted();\n            throw;\n        }\n    }\n\n    private bool IsAtAuthenticatedEnd()\n    {\n        if (_finished)\n        {\n            return true;\n        }\n\n        if (_outputBuffer != null && _outputBufferPosition < _outputBuffer.Length)\n        {\n            throw new AbpException(\n                \"The encrypted BLOB was not read to its authenticated end, so its completeness can not be verified \" +\n                \"(a content-pipeline contributor stopped reading the content before the end).\");\n        }\n\n        return false;\n    }\n\n    private void ThrowIfMoreContent(byte[]? next)\n    {\n        if (next != null)\n        {\n            throw new AbpException(\n                \"The encrypted BLOB was not read to its authenticated end, so its completeness can not be verified \" +\n                \"(a content-pipeline contributor stopped reading the content before the end).\");\n        }\n\n        SetOutputBuffer(null);\n    }","sourceCodeStart":108,"sourceCodeEnd":144,"githubUrl":"https://github.com/abpframework/abp/blob/7ed43b1931b9df46a50c0c59148a18645641d0df/framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/ChunkedCryptoReadStream.cs#L108-L144","documentation":"ChunkedCryptoReadStream.IsAtAuthenticatedEnd checks whether the BLOB has been fully consumed. If the producer is considered finished but plaintext still sits unconsumed in the output buffer, a content-pipeline contributor stopped reading before the authenticated end — so completeness cannot be verified and the stream throws.","triggerScenarios":"A content-pipeline contributor (custom IBlobProvider, content processor, size-limiting reader) reads only part of the decrypted content, then the framework's authenticated-end verification runs while buffered plaintext remains.","commonSituations":"A size-limiting or preview reader that stops early; a custom contributor that returns from its read loop before EOF; a thumbnail/preview pipeline consuming encrypted BLOBs.","solutions":["Ensure every content-pipeline contributor drains the decrypting stream to its authenticated end (read until 0 bytes returned).","Remove or relocate contributors that intentionally stop early (previews, size caps) so they run on a copy, not on the authenticated stream.","For preview use-cases, materialize the full plaintext first, then truncate the in-memory copy.","Verify your custom IBlobProvider/IBlobContentLazyProvider reads the stream to completion."],"exampleFix":"// before — contributor stops reading early\npublic async Task ProcessAsync(Stream s)\n{\n    var buf = new byte[1024];\n    await s.ReadAsync(buf, 0, 1024); // then returns\n}\n\n// after — drain to authenticated end\npublic async Task ProcessAsync(Stream s)\n{\n    var buf = new byte[81920];\n    int n;\n    while ((n = await s.ReadAsync(buf, 0, buf.Length)) > 0)\n    {\n        // process buf[0..n]\n    }\n}","handlingStrategy":"validation","validationCode":"// Guarantee a contributor drains the decrypting stream to its authenticated end.\nstatic async Task DrainToEndAsync(Stream s, CancellationToken ct)\n{\n    var buf = new byte[81920];\n    while (await s.ReadAsync(buf, 0, buf.Length, ct) > 0) { /* pass */ }\n}\n\n// Call from every contributor before it returns:\nawait DrainToEndAsync(decryptingStream, ct);","typeGuard":"// A wrapper type that proves the stream has been drained to its authenticated end.\npublic sealed class FullyConsumedStream : IDisposable\n{\n    private readonly Stream _inner;\n    public FullyConsumedStream(Stream s) { _inner = s; }\n    public static async Task<FullyConsumedStream> FromAsync(Stream s, CancellationToken ct)\n    {\n        var buf = new byte[81920];\n        while (await s.ReadAsync(buf, 0, buf.Length, ct) > 0) { }\n        return new FullyConsumedStream(s);\n    }\n    public void Dispose() => _inner.Dispose();\n}","tryCatchPattern":"try\n{\n    await contributor.ProcessAsync(decryptingStream);\n}\ncatch (AbpException ex) when (ex.Message.Contains(\"not read to its authenticated end\"))\n{\n    logger.LogError(ex, \"A pipeline contributor stopped reading early; remove or fix it.\");\n    throw;\n}","preventionTips":["Ensure every contributor reads the stream to 0-byte EOF before returning.","Run preview/size-limiting logic on a fully-materialized copy, not the authenticated stream.","Audit custom IBlobProvider/contributor implementations for early returns.","Do not dispose the decrypting stream before the framework's end-check runs."],"tags":["crypto","integrity","streams","pipeline","completeness"],"backgroundTag":null,"analyzedSha":"7ed43b1931b9df46a50c0c59148a18645641d0df","analyzedAt":"2026-08-13T16:26:11.351Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}