feat: lenient JSON options for secret payloads (enum names + case-insensitive)#48
Merged
Merged
Conversation
…ensitive) Secret<T> previously (de)serialized its payload with JsonSerializerOptions.Default (case-sensitive, enums as ordinals). Enum-as-ordinal is fragile: reordering the enum silently remaps stored values. Switch to a shared, lenient SecretValueSerialization.Options: - enums serialize as NAMES (round-trip-safe against reordering) - reading still accepts numeric enums AND any property casing This is strictly more permissive on read, so existing encrypted envelopes at rest stay fully readable -- no migration. Only the in-memory form of newly serialized typed secret values changes (enum name instead of ordinal). Only the type-aware path (Secret<T> ctor) serializes a typed value; external sources (CLI/browser/files) supply plain JSON, which a name-based, case-insensitive contract handles best. - SecretValueSerialization.Options applied to Secret<T> ctor serialize + Open deserialize - CLI encrypt --value help: pass enum names, not ordinals - 7 tests (enum name/number read, name write, case-insensitive object, FromPlain round-trips) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Secret<T>previously (de)serialized its payload withJsonSerializerOptions.Default— case-sensitive, enums as ordinals. Enum-as-ordinal is fragile: if someone later reorders the enum, the stored number silently maps to a different member. This switches secret payloads to a shared, lenientSecretValueSerialization.Options:Why it's safe for existing secrets
The change is strictly more permissive on read, so existing encrypted envelopes at rest (enum-as-number, PascalCase) stay fully readable — no migration. Only the in-memory form of newly serialized typed secret values changes (enum name instead of ordinal); the encrypted bytes at rest are produced by a separate encryptor path and are untouched.
Why a name/case-insensitive contract is the right default
Only the type-aware path inside the library (
Secret<T>ctor) ever serializes a typed value (enum/object). Everything from outside — the CLI, hand-edited files, or a future browser encryptor — supplies plain JSON and would naturally send an enum's name. A name-based, case-insensitive contract handles all of those robustly. (The plaintext-config read path already used the lenient pipeline options; this aligns the decrypted-envelope path with it.)Changes
SecretValueSerialization.Options(case-insensitive +JsonStringEnumConverter), applied toSecret<T>ctor serialize +Open()deserialize.encrypt --valuehelp: pass enum names, not ordinals.FromPlainround-trips (enum, record-with-enum, nullable enum).Verification
Notes
Independent of #47 (LocalStorage). This is a small, focused enabler that also makes the future browser-encrypted-secrets idea (and complex/enum secret authoring in general) far more forgiving.
🤖 Generated with Claude Code