mgth/LittleBigMouse · error · CryptographicException

Not a protected payload.

Error message

Not a protected payload.

What it means

SecretProtector.Unprotect decrypts envelope strings that were produced by Protect and therefore start with a known Prefix. If the input does not carry that prefix, IsProtected returns false and the method throws this CryptographicException instead of attempting to parse plaintext. Callers are expected to treat it as 'not encrypted yet' and start fresh, per the XML docs.

Solutions

  1. Call IsProtected(envelope) before Unprotect and skip/decrypt accordingly, treating plaintext as already-unprotected.
  2. Check the stored value actually includes the prefix (e.g. starts with 'enc:' or whatever Prefix is) and re-encrypt it with Protect.
  3. Re-enter the secret in the app so it is saved in the current protected envelope format.
  4. Wrap Unprotect in try-catch for CryptographicException and fall back to treating the value as plaintext.

Example fix

// before
var secret = protector.Unprotect(storedValue);
// after
var secret = SecretProtector.IsProtected(storedValue)
    ? protector.Unprotect(storedValue)
    : storedValue;
Defensive patterns

Strategy: type-guard

Validate before calling

// only unprotect envelopes that actually carry the prefix
if (!SecretProtector.IsProtected(envelope))
    return envelope; // already plaintext

Type guard

static bool IsProtectedEnvelope(string? s) =>
    !string.IsNullOrEmpty(s) && s.StartsWith(SecretProtector.Prefix, StringComparison.Ordinal);

Try / catch

try
{
    secret = protector.Unprotect(envelope);
}
catch (CryptographicException)
{
    secret = envelope; // treat as plaintext and start fresh
}

Prevention

When it happens

Trigger: Calling Unprotect on a plain (never-encrypted) string, on a value stored by an older app version before encryption was introduced, or on a value that lost its prefix through truncation/transformation.

Common situations: Upgrading from a pre-encryption version whose settings file holds plaintext secrets, manually copying a secret without its envelope prefix, or a config migration stripping the prefix.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of mgth/LittleBigMouse@7a42f01d47 (2026-09-16). Data as JSON: /api/errors/4a8cd1cd580efb68. Report an issue: GitHub.

Appendix: source

Thrown at LittleBigMouse.Plugins/LittleBigMouse.Plugins.Core/SecretProtector.cs:88

        using var aes = new AesGcm(GetOrCreateKey(), TagLength);
        aes.Encrypt(
            nonce,
            bytes,
            payload.AsSpan(NonceLength + TagLength),
            payload.AsSpan(NonceLength, TagLength));

        return Prefix + AesGcmScheme + "." + Convert.ToBase64String(payload);
    }

    /// <summary>
    /// Reverses <see cref="Protect"/>. Throws — on a foreign scheme, a wrong key, a truncated
    /// or tampered payload — rather than returning something plausible; callers treat that the
    /// same way they already treat unreadable settings, by starting fresh.
    /// </summary>
    public string Unprotect(string envelope)
    {
        if (!IsProtected(envelope))
            throw new CryptographicException("Not a protected payload.");

        var body = envelope.AsSpan(Prefix.Length);
        var separator = body.IndexOf('.');
        if (separator < 0) throw new CryptographicException("Protected payload has no scheme.");

        var scheme = body[..separator].ToString();
        var payload = Convert.FromBase64String(body[(separator + 1)..].ToString());

        switch (scheme)
        {
            case DpapiScheme when OperatingSystem.IsWindows():
                return Encoding.UTF8.GetString(
                    ProtectedData.Unprotect(payload, null, DataProtectionScope.CurrentUser));

            case AesGcmScheme:
                if (payload.Length < NonceLength + TagLength)
                    throw new CryptographicException("Protected payload is truncated.");

View on GitHub (pinned to 7a42f01d47)