What's wrong
Credential is a public abstract class. The library also exposes ICredentialFactory<T> where T : Credential, RegisterCredentialFactory and TryCreate<T>, which suggests callers can bring their own credential types.
However, polymorphic serialization is closed. Credential.cs lists exactly three [JsonDerivedType] attributes, and the options in Storage/CredentialSerialization.cs are private, with no registration hook. The README's "Credential types" recipe (README.md L91-110) only works by editing Credential.cs in this repository. A NuGet consumer cannot do that.
The result:
- Platform stores fail. Any subclass defined outside the library fails in
CredentialSerialization.Serialize, which the Windows, macOS and Linux stores all use. On Linux it goes through NativeSecretBuffer.OfCredential.
- In-memory hides the failure.
InMemoryCredentialStore keeps the object reference and never serializes, so a consumer's tests pass while production fails.
- The wrong exception escapes.
AddOrReplace lets the raw NotSupportedException out, not the documented CredentialStoreException.
Failure scenario
public sealed class CredentialWithCertificate : Credential { public string Thumbprint { get; init; } = ""; }
CredentialCache.Instance.AddOrReplace(persona, new CredentialWithCertificate { Thumbprint = "ab" });
→ System.NotSupportedException: Runtime type '...CredentialWithCertificate' is not supported by polymorphic type 'ktsu.CredentialCache.Credential'. Path: $.
The same call on new CredentialCache(new InMemoryCredentialStore()) succeeds, and TryGet returns the credential.
This was reproduced with a temporary MSTest test in CredentialCache.Test. CredentialSerialization.Serialize and NativeSecretBuffer.OfCredential both throw, while the in-memory round trip succeeds.
Suggested fix / acceptance criteria
Either:
- Open the type set. Add a public registration point, for example
CredentialSerialization.RegisterDerivedType<T>(string typeDiscriminator) where T : Credential. Back it with a DefaultJsonTypeInfoResolver modifier that appends to JsonPolymorphismOptions.DerivedTypes for Credential. Update the README recipe to use it.
- Or close it explicitly. Make the supported set explicit, e.g. no public way to subclass, and state in the README that the recipe is for contributors.
In both cases:
- An unregistered subclass passed to
AddOrReplace fails with a CredentialStoreException (or ArgumentException) that names the type. The cache is left unchanged.
- If the type set is opened: a subclass defined in the test assembly and registered via the new API round-trips through
Serialize/Deserialize and NativeSecretBuffer.OfCredential/ReadCredential.
- Minor: the README calls
Credential a "polymorphic record class", but it is a plain class.
What's wrong
Credentialis a public abstract class. The library also exposesICredentialFactory<T> where T : Credential,RegisterCredentialFactoryandTryCreate<T>, which suggests callers can bring their own credential types.However, polymorphic serialization is closed.
Credential.cslists exactly three[JsonDerivedType]attributes, and the options inStorage/CredentialSerialization.csare private, with no registration hook. The README's "Credential types" recipe (README.md L91-110) only works by editingCredential.csin this repository. A NuGet consumer cannot do that.The result:
CredentialSerialization.Serialize, which the Windows, macOS and Linux stores all use. On Linux it goes throughNativeSecretBuffer.OfCredential.InMemoryCredentialStorekeeps the object reference and never serializes, so a consumer's tests pass while production fails.AddOrReplacelets the rawNotSupportedExceptionout, not the documentedCredentialStoreException.Failure scenario
→
System.NotSupportedException: Runtime type '...CredentialWithCertificate' is not supported by polymorphic type 'ktsu.CredentialCache.Credential'. Path: $.The same call on
new CredentialCache(new InMemoryCredentialStore())succeeds, andTryGetreturns the credential.This was reproduced with a temporary MSTest test in
CredentialCache.Test.CredentialSerialization.SerializeandNativeSecretBuffer.OfCredentialboth throw, while the in-memory round trip succeeds.Suggested fix / acceptance criteria
Either:
CredentialSerialization.RegisterDerivedType<T>(string typeDiscriminator) where T : Credential. Back it with aDefaultJsonTypeInfoResolvermodifier that appends toJsonPolymorphismOptions.DerivedTypesforCredential. Update the README recipe to use it.In both cases:
AddOrReplacefails with aCredentialStoreException(orArgumentException) that names the type. The cache is left unchanged.Serialize/DeserializeandNativeSecretBuffer.OfCredential/ReadCredential.Credentiala "polymorphic record class", but it is a plain class.