{"record":{"id":"0d70d528de01b619","repo":"siyuan-note/siyuan","slug":"invalid-encrypted-asset-plaintext-chunk-size","errorCode":null,"errorMessage":"invalid encrypted asset plaintext chunk size","messagePattern":"invalid encrypted asset plaintext chunk size","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/crypto.go","lineNumber":2476,"sourceCode":"\t\taad := encryptedAssetChunkAAD(aadPrefix, metadata.ContainerID, chunkIndex)\n\t\tif metadata.Spec == encryptedAssetLegacySpec {\n\t\t\taad = []byte(fmt.Sprintf(\"%s:content:%d\", aadPrefix, chunkIndex))\n\t\t}\n\t\tplainChunk, chunkErr := util.DecryptWithAAD(\n\t\t\tassetKey,\n\t\t\tencryptedChunk,\n\t\t\taad,\n\t\t)\n\t\tif chunkErr != nil {\n\t\t\treturn \"\", chunkErr\n\t\t}\n\t\texpectedSize := int64(encryptedAssetChunkSize)\n\t\tif remaining := metadata.Size - written; remaining < expectedSize {\n\t\t\texpectedSize = remaining\n\t\t}\n\t\tif int64(len(plainChunk)) != expectedSize {\n\t\t\tzeroAndClear(plainChunk)\n\t\t\treturn \"\", errors.New(\"invalid encrypted asset plaintext chunk size\")\n\t\t}\n\t\tn, writeErr := writer.Write(plainChunk)\n\t\twritten += int64(n)\n\t\tzeroAndClear(plainChunk)\n\t\tif writeErr != nil {\n\t\t\treturn \"\", writeErr\n\t\t}\n\t\tif n != len(plainChunk) {\n\t\t\treturn \"\", io.ErrShortWrite\n\t\t}\n\t}\n\tvar terminator uint32\n\tif err = binary.Read(reader, binary.BigEndian, &terminator); err != nil {\n\t\treturn \"\", err\n\t}\n\tif terminator != 0 || written != metadata.Size {\n\t\treturn \"\", errors.New(\"invalid encrypted asset content length\")\n\t}","sourceCodeStart":2458,"sourceCodeEnd":2494,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/crypto.go#L2458-L2494","documentation":"After decrypting a chunk, the code validates that the plaintext length equals the expected chunk size (full encryptedAssetChunkSize, or the remaining bytes for the final chunk). This error means decryption produced plaintext of the wrong length — strong evidence of tampering or AAD/context mismatch, since AES-GCM would normally fail authentication first. The chunk is zeroed before returning.","triggerScenarios":"In DecryptAssetToWriter's chunk loop, a decrypted plainChunk's length does not match metadata.Size - written (clamped to encryptedAssetChunkSize). Happens when the file's chunk boundaries were re-framed after encryption or metadata.Size does not match the actual content.","commonSituations":"A tool rewrote or re-chunked the encrypted file without updating metadata; corruption shifting bytes between chunks; an intentionally tampered ciphertext where GCM tags were also forged (extremely rare).","solutions":["Treat the file as tampered or corrupt; restore from a verified backup","Re-encrypt from the original plaintext if it still exists elsewhere","Compare metadata.Size and the sum of chunk sizes against the file length to locate the inconsistent region","Verify the file's integrity via sync history or the device it originated from"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"plain, err := model.DecryptAsset(box, disk, dek, ct)\nif err != nil {\n    if strings.Contains(err.Error(), \"plaintext chunk size\") {\n        // treat file as tampered: quarantine and restore from backup\n        return restoreFromBackup(disk)\n    }\n    return err\n}","preventionTips":["Never modify encrypted blobs or their metadata after creation","Keep backups taken before any encryption-parameter change"],"tags":["encryption","integrity","tampering"],"backgroundTag":"checksum-mismatch","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}