镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

Refuse a lone surrogate when saving instead of storing U+FFFD [patch] - #218

Open
matt-edmondson wants to merge 2 commits into
mainfrom
fix/215-refuse-lone-surrogates
Open

matt-edmondson wants to merge 2 commits into
mainfrom
fix/215-refuse-lone-surrogates

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Fixes #215

What changed

Utf8JsonWriter silently writes U+FFFD in place of a lone surrogate. So a credential holding ill-formed UTF-16 was saved as a different secret, and the cache kept returning the original until the process restarted.

  • WellFormedTextConverterFactory (new, internal) wraps RoundTripStringJsonConverterFactory and adds a string converter. Before writing a string, a SemanticString (by its string form), or a dictionary key of either, it throws JsonException if the value has an unpaired surrogate. Serialize/SerializeToString already turn that into an ArgumentException. The message does not quote the value, since it is usually a secret. Reading is unchanged.
  • Every store refuses it. InMemoryCredentialStore.Save and CredentialCache.AddOrReplace now call a new ThrowIfNotSerializable helper (serialize, then zero the bytes), so the in-memory store refuses the same credentials as the platform stores. The cache and the store stay unchanged.
  • Well-formed supplementary characters (emoji, CJK Extension B) still save and load unchanged.

Overlap with #212

#212 adds the same ThrowIfNotSerializable helper and the same in-memory/AddOrReplace wiring, for null non-nullable properties. The code is identical, but the XML doc text differs, so whichever PR merges second needs a small doc-only conflict fix. Once both merge, the in-memory store refuses null properties and lone surrogates.

Tests

IllFormedTextTests:

  • Serialize and SerializeToString refuse lone high and lone low surrogates, and a reversed pair, in a SemanticString, a plain string, and a list of strings. The error message does not include the value.
  • AddOrReplace refuses the credential and stores nothing on a serializing store (RawBlobCredentialStore) and on InMemoryCredentialStore, and so does InMemoryCredentialStore.Save.
  • Emoji and U+20000 still round-trip through a store across cache instances.

With the source changes reverted, 4 of the 5 tests fail. The fifth, the round-trip test, passes either way, as it should. The inputs are built in code, not with [DataRow], because attribute strings are stored as UTF-8 and a lone surrogate would already be U+FFFD by the time the test ran.

Full suite: 108 passed, 5 skipped (native-store and Windows-only tests), 0 failed.


Generated by Claude Code

Utf8JsonWriter silently writes U+FFFD for a lone surrogate, so a
credential holding ill-formed UTF-16 was persisted as a different
secret, while the cache kept returning the original until a restart.
Serialize and SerializeToString now refuse such a value, in a plain
string or a SemanticString, anywhere in the credential, with an
ArgumentException that does not quote it. The in-memory store and
AddOrReplace run the same check, so every store refuses it.

Fixes #215

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017vfJW6R8MN1ipg19Mih4Z3
Covers the property-name paths of the well-formed text converters for
string and SemanticString keys, and IsWellFormed directly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017vfJW6R8MN1ipg19Mih4Z3
@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