{"record":{"id":"d4f94b3a4330f4bb","repo":"mgth/LittleBigMouse","slug":"protected-payload-is-truncated","errorCode":null,"errorMessage":"Protected payload is truncated.","messagePattern":"Protected payload is truncated\\.","errorType":"exception","errorClass":"CryptographicException","httpStatus":null,"severity":"error","filePath":"LittleBigMouse.Plugins/LittleBigMouse.Plugins.Core/SecretProtector.cs","lineNumber":105,"sourceCode":"        if (!IsProtected(envelope))\n            throw new CryptographicException(\"Not a protected payload.\");\n\n        var body = envelope.AsSpan(Prefix.Length);\n        var separator = body.IndexOf('.');\n        if (separator < 0) throw new CryptographicException(\"Protected payload has no scheme.\");\n\n        var scheme = body[..separator].ToString();\n        var payload = Convert.FromBase64String(body[(separator + 1)..].ToString());\n\n        switch (scheme)\n        {\n            case DpapiScheme when OperatingSystem.IsWindows():\n                return Encoding.UTF8.GetString(\n                    ProtectedData.Unprotect(payload, null, DataProtectionScope.CurrentUser));\n\n            case AesGcmScheme:\n                if (payload.Length < NonceLength + TagLength)\n                    throw new CryptographicException(\"Protected payload is truncated.\");\n\n                var plain = new byte[payload.Length - NonceLength - TagLength];\n                using (var aes = new AesGcm(ReadKey() ?? throw new CryptographicException(\n                           $\"No key at {_keyFilePath} to read this payload with.\"), TagLength))\n                {\n                    aes.Decrypt(\n                        payload.AsSpan(0, NonceLength),\n                        payload.AsSpan(NonceLength + TagLength),\n                        payload.AsSpan(NonceLength, TagLength),\n                        plain);\n                }\n                return Encoding.UTF8.GetString(plain);\n\n            default:\n                throw new CryptographicException(\n                    $\"Payload was protected with '{scheme}', unreadable on this system.\");\n        }\n    }","sourceCodeStart":87,"sourceCodeEnd":123,"githubUrl":"https://github.com/mgth/LittleBigMouse/blob/7a42f01d47d99d223b8ee33ba4019af82adf1c48/LittleBigMouse.Plugins/LittleBigMouse.Plugins.Core/SecretProtector.cs#L87-L123","documentation":"For AES-GCM envelopes, the payload must contain at least NonceLength bytes of nonce plus TagLength bytes of authentication tag, followed by the ciphertext. If the base64-decoded payload is shorter than that combined minimum, decryption is impossible and this CryptographicException is thrown. It almost always means the encrypted payload was truncated or corrupted.","triggerScenarios":"Calling Unprotect on an AES-GCM envelope whose decoded payload is shorter than NonceLength + TagLength — e.g. a truncated settings value, a partially copied string, or a payload produced with different nonce/tag sizes by another version.","commonSituations":"Secret cut off during manual copy/paste, settings file corruption, or an app/config version change that altered nonce or tag lengths so old payloads no longer meet the size minimum.","solutions":["Re-encrypt the secret with SecretProtector.Protect to regenerate a complete nonce+ciphertext+tag payload.","Verify the stored envelope is complete — re-copy the whole string with no truncation and re-check the base64 decodes cleanly.","Delete the unreadable stored value and re-enter the secret so the app saves a fresh valid envelope.","Catch CryptographicException during settings load and fall back to prompting the user for the secret again."],"exampleFix":"// before (truncated payload)\nvar secret = protector.Unprotect(storedTruncatedEnvelope);\n// after\ntry { var secret = protector.Unprotect(storedEnvelope); }\ncatch (CryptographicException) { secret = PromptUserForSecret(); }","handlingStrategy":"try-catch","validationCode":"// rough pre-check: decoded payload must exceed nonce+tag length\nvar body = envelope.Substring(SecretProtector.Prefix.Length);\nvar payload = Convert.FromBase64String(body[(body.IndexOf('.') + 1)..]);\nif (payload.Length < SecretProtector.NonceLength + SecretProtector.TagLength)\n    throw new FormatException(\"Payload too short to contain nonce and tag.\");","typeGuard":"static bool PayloadIsPlausible(string envelope, string prefix)\n{\n    try\n    {\n        var body = envelope.AsSpan(prefix.Length);\n        var dot = body.IndexOf('.');\n        if (dot < 0) return false;\n        var payload = Convert.FromBase64String(body[(dot + 1)..].ToString());\n        return payload.Length >= SecretProtector.NonceLength + SecretProtector.TagLength;\n    }\n    catch (FormatException) { return false; }\n}","tryCatchPattern":"try\n{\n    secret = protector.Unprotect(envelope);\n}\ncatch (CryptographicException ex) when (ex.Message.Contains(\"truncated\"))\n{\n    Log.LogWarning(\"Encrypted payload truncated; prompting for secret again.\");\n    secret = PromptUserForSecret();\n}","preventionTips":["Copy/store encrypted envelopes as whole strings; truncation makes them undecryptable.","Keep nonce and tag lengths consistent across app versions or migrate old payloads explicitly.","On load failure, treat the secret as lost and re-prompt rather than retrying decryption."],"tags":["cryptography","aes-gcm","truncated-payload","settings"],"backgroundTag":"checksum-mismatch","analyzedSha":"7a42f01d47d99d223b8ee33ba4019af82adf1c48","analyzedAt":"2026-09-16T00:35:00.514Z","contentChangedAt":"2026-09-16T00:35:00.514Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}