{"record":{"id":"81a75e825a835f94","repo":"mgth/LittleBigMouse","slug":"no-key-at-keyfilepath-to-read-this-payload-with","errorCode":null,"errorMessage":"No key at {_keyFilePath} to read this payload with.","messagePattern":"No key at (.+?) to read this payload with\\.","errorType":"exception","errorClass":"CryptographicException","httpStatus":null,"severity":"error","filePath":"LittleBigMouse.Plugins/LittleBigMouse.Plugins.Core/SecretProtector.cs","lineNumber":108,"sourceCode":"        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    }\n\n    byte[] GetOrCreateKey()\n    {","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/mgth/LittleBigMouse/blob/7a42f01d47d99d223b8ee33ba4019af82adf1c48/LittleBigMouse.Plugins/LittleBigMouse.Plugins.Core/SecretProtector.cs#L90-L126","documentation":"SecretProtector.Unprotect on Unix decrypts AES-GCM envelopes with a 32-byte key read from a 0600 key file (secrets.key) sitting next to the data file. This CryptographicException is thrown when the key file does not exist (or is unreadable/corrupt and therefore discarded) at the moment an already-protected envelope must be decrypted. Unlike Protect, which calls GetOrCreateKey and would create a fresh key, Unprotect deliberately fails loudly: decrypting with a new key would be impossible anyway, since the old key is gone.","triggerScenarios":"Calling Unprotect on a 'LBM1.aesgcm.' envelope when secrets.key is missing from the directory next to the data file, was deleted, or exists but is corrupt/wrong-length (ReadKey returns null after logging a warning to stderr).","commonSituations":"The configuration directory was copied or synced without the key file; a backup restore brought back the settings file but not secrets.key; the user manually cleaned 'mystery' files from ~/.config; the key file was truncated or edited; the data file was moved to another machine whose secrets.key differs or is absent.","solutions":["Restore the original secrets.key file into the directory next to the data file (from backup or the original machine) and retry.","If the key is unrecoverable, delete the protected values (or the whole store) and re-create them — paired televisions must be re-paired, since the secrets are unrecoverable by design.","Check the directory next to the data file (Path.GetDirectoryName(dataFilePath) + '/secrets.key') exists and contains a 32-byte base64 key readable by the running user; fix permissions to 0600 if needed.","On first run, call Protect (which creates the key) before any Unprotect of pre-existing envelopes, so a fresh install never tries to read a missing key."],"exampleFix":"// before\nvar token = protector.Unprotect(savedEnvelope); // throws if secrets.key missing\n// after\nvar token = SecretProtector.IsProtected(savedEnvelope) && File.Exists(keyPath)\n    ? protector.Unprotect(savedEnvelope)\n    : PairDeviceAgain(); // start fresh when the key is gone","handlingStrategy":"try-catch","validationCode":"// before calling Unprotect\nif (SecretProtector.IsProtected(envelope) &&\n    envelope.StartsWith(\"LBM1.aesgcm.\", StringComparison.Ordinal) &&\n    !File.Exists(keyFilePath))\n    return RePairDevice(); // key gone; cannot decrypt\nreturn protector.Unprotect(envelope);","typeGuard":"static bool CanUnprotectAes(string envelope, string keyFilePath) =>\n    envelope.StartsWith(\"LBM1.aesgcm.\", StringComparison.Ordinal) && File.Exists(keyFilePath);","tryCatchPattern":"try { return protector.Unprotect(envelope); }\ncatch (CryptographicException) { return null; /* treat as unreadable settings: start fresh / re-pair */ }","preventionTips":["Back up secrets.key together with any data files that contain LBM1.aesgcm envelopes.","Never sync or copy a config directory without its secrets.key sibling file.","Check stderr for 'Secret key ... is unreadable' warnings — the protector logs key replacement before failing.","Keep key file permissions at 0600 so cleanup tools or other users cannot remove or corrupt it."],"tags":["cryptography","missing-key-file","aes-gcm","decryption"],"backgroundTag":"missing-credentials","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"}