Skip to content

Refuse a null non-nullable credential property when reading or saving [patch] - #204

Merged
matt-edmondson merged 2 commits into
mainfrom
fix/180-reject-null-credential-properties
Nov 11, 2026
Merged

matt-edmondson merged 2 commits into
mainfrom
fix/180-reject-null-credential-properties

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Fixes #180

What was wrong

CredentialSerialization.BuildOptions() did not set RespectNullableAnnotations. A stored {"$type":"CredentialWithToken","Token":null} (or Username/Password set to null) therefore deserialized into a credential with a null non-nullable property. TryGet returned true and cached it, and the caller's first credential.Token.… threw NullReferenceException. Save also persisted a credential built with Token = null!, so the bad entry round-tripped.

Change

  • BuildOptions sets RespectNullableAnnotations = true. A null non-nullable property now throws JsonException, which Deserialize and DeserializeFromString already map to null, so TryGet returns false. This matches UnknownPayloadTests.
  • Serialize and SerializeToString now report a credential they can't write as an ArgumentException that names the type and wraps the JsonException. Before, that credential was persisted with null in it. Every platform store goes through these helpers, so AddOrReplace throws before the store or cache changes. Its XML docs now list the exception.
  • Both helpers now check their argument for null, which CA1062 requires now that they have bodies.

Tests

New NullPropertyTests:

  • Deserialize and DeserializeFromString return null for Token, Username or Password set to null.
  • Complete CredentialWithToken, CredentialWithUsernamePassword and CredentialWithNothing payloads still deserialize.
  • TryGet returns false for each bad entry.
  • Serialize and SerializeToString throw ArgumentException for Token = null!.
  • AddOrReplace throws for Password = null! and leaves both the store and the cache empty.

Without the fix, 11 of these 14 fail. The 3 that pass are the complete-payload checks. The full suite on net10.0 (Linux): 99 tests, 94 passed, 0 failed, 5 skipped. The skips are the existing native-store tests that need libsecret or Windows.

This touches the same BuildOptions method as #203 (#175) but different lines. I checked that the two branches merge without conflict.


Generated by Claude Code

CredentialSerialization's options didn't respect nullable annotations, so a
stored "Token":null or "Password":null deserialized into a found credential
whose property was null, and TryGet handed it to a caller whose first read
threw NullReferenceException. Save also persisted a credential built with
null!, so the bad entry round-tripped.

Set RespectNullableAnnotations, so such an entry reads back as not found
through the existing JsonException path, and have Serialize and
SerializeToString report a credential that can't be read back as an
ArgumentException.

Fixes #180

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017vfJW3kaZKRNvfNtvM4CX6
Resolve the conflict with #207 in Serialize and SerializeToString by keeping
both checks: the unknown-type check runs first, then a JsonException from a
null non-nullable property is reported as an ArgumentException.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017vfJW3kaZKRNvfNtvM4CX6
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants