{"record":{"id":"4e7926d8e8ad3cf7","repo":"mgth/LittleBigMouse","slug":"protected-payload-has-no-scheme","errorCode":null,"errorMessage":"Protected payload has no scheme.","messagePattern":"Protected payload has no scheme\\.","errorType":"exception","errorClass":"CryptographicException","httpStatus":null,"severity":"error","filePath":"LittleBigMouse.Plugins/LittleBigMouse.Plugins.Core/SecretProtector.cs","lineNumber":92,"sourceCode":"            payload.AsSpan(NonceLength + TagLength),\n            payload.AsSpan(NonceLength, TagLength));\n\n        return Prefix + AesGcmScheme + \".\" + Convert.ToBase64String(payload);\n    }\n\n    /// <summary>\n    /// Reverses <see cref=\"Protect\"/>. Throws — on a foreign scheme, a wrong key, a truncated\n    /// or tampered payload — rather than returning something plausible; callers treat that the\n    /// same way they already treat unreadable settings, by starting fresh.\n    /// </summary>\n    public string Unprotect(string envelope)\n    {\n        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                {","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/mgth/LittleBigMouse/blob/7a42f01d47d99d223b8ee33ba4019af82adf1c48/LittleBigMouse.Plugins/LittleBigMouse.Plugins.Core/SecretProtector.cs#L74-L110","documentation":"Unprotect strips the envelope Prefix and then looks for a '.' separating the scheme name (e.g. DPAPI or AES-GCM) from the base64 payload. If the body contains no dot, the envelope is malformed and this CryptographicException is thrown. The scheme selector is mandatory, so an envelope without it cannot be decrypted.","triggerScenarios":"Passing a string that has the envelope prefix but only a bare base64 body with no scheme tag — e.g. an envelope built by hand, produced by an incompatible app version, or corrupted so the dot was lost.","commonSituations":"Settings files edited or migrated between app versions with different envelope layouts, secrets copied from another tool's format, or partial string truncation that removed the scheme separator.","solutions":["Re-encrypt the secret with the current SecretProtector.Protect so a valid scheme-prefixed envelope is produced.","Inspect the envelope string and restore the missing '<scheme>.' segment between the prefix and the base64 payload.","Delete the stored value and re-enter the secret so the app saves it in the current format.","Catch CryptographicException on load and treat the value as unreadable, starting fresh per the API contract."],"exampleFix":"// before (no scheme)\nenc:QmFzZTY0UGF5bG9hZA==\n// after (scheme separated by dot)\nenc:aesgcm.QmFzZTY0UGF5bG9hZA==","handlingStrategy":"validation","validationCode":"// envelope must be prefix + scheme + '.' + base64\nvar body = envelope.Substring(SecretProtector.Prefix.Length);\nif (!body.Contains('.'))\n    throw new FormatException(\"Envelope is missing its scheme segment; re-protect the value.\");","typeGuard":"static bool HasSchemeSegment(string envelope, string prefix)\n{\n    if (!envelope.StartsWith(prefix, StringComparison.Ordinal)) return false;\n    var idx = envelope.IndexOf('.', prefix.Length);\n    return idx > prefix.Length && idx < envelope.Length - 1;\n}","tryCatchPattern":"try\n{\n    secret = protector.Unprotect(envelope);\n}\ncatch (CryptographicException ex) when (ex.Message.Contains(\"no scheme\"))\n{\n    Log.LogWarning(\"Malformed envelope (missing scheme); re-protecting required.\");\n    secret = null;\n}","preventionTips":["Only construct envelopes via SecretProtector.Protect, never by string concatenation.","Validate envelope shape (prefix, scheme, dot, base64) before storing it.","Treat any envelope failing shape checks as unreadable and re-enter the secret."],"tags":["cryptography","envelope-format","dpapi","aes-gcm"],"backgroundTag":"invalid-argument-format","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"}