Redact secret values from conversion-failure exceptions (S1) - #27
Merged
Conversation
A bound configuration value that failed type conversion could reach logs two ways: our own SettingsPropertyValueException message interpolated the raw value, and the failing converter's framework inner exception (FormatException/ArgumentException for int/enum/DateTime) embeds the raw input and was chained in. A secret on a typed property (e.g. a credentialed Uri, or a secret mis-bound to int/enum) therefore leaked into logs. Fix (full — plan review chose this over redact-message-only and over an opt-in "restore diagnostics" flag, which was rejected as insecure-by-config): - Resources.PropertySetterExceptionMessage: no longer includes the value; reports property name, target type, and the failing converter's exception type name (a compile-time identifier that cannot carry a secret). Fixes the "to to" double-word typo. - SettingsPropertyValueException: ctor drops the value parameter and no longer chains the framework inner exception. Invariant: this type never carries a value and never chains an inner, so it is auditably leak-proof. - New SettingsPropertyNullException (internal): the "AllowEmpty = false with no value" path is value-free, so it keeps its full, useful message instead of being redacted into a value-conversion exception; ConvertPropertyValue rethrows it unredacted. - ISectionBinder: doc note that custom binders must not throw exceptions whose message embeds a fetched value (the public SettingsBindingException chains the binder's inner). The built-in binders don't. Both changed exceptions are internal, so this is non-breaking. Tests: new Conversion/ExceptionRedactionTests.cs binds a sentinel secret to int/enum/DateTime/Uri and to a hostile custom converter, asserting the secret is absent from the entire exception ToString() chain while the property name and target type remain; the existing null-not-allowed test now expects SettingsPropertyNullException and asserts its message. Suite 76 net10 (was 71). Also refreshes FIX-PLAN.md (S1 marked done) and SESSION-HANDOFF.md.
guy-lud
commented
Jul 13, 2026
| { | ||
| return propertyPlan.Conversion.Convert(value); | ||
| } | ||
| catch (SettingsPropertyNullException) |
Contributor
Author
There was a problem hiding this comment.
We can say is not SettingsPropertyNullException in the catch(exception).
Contributor
Author
There was a problem hiding this comment.
Done in 3d0e9b8 — switched to catch (Exception e) when (e is not SettingsPropertyNullException). Same behavior, and the filter skips the catch entirely so the null exception's throw context is untouched (no rethrow). Thanks!
Address PR #27 review: replace the separate catch(SettingsPropertyNullException) { throw; } with a filter on the general catch — catch (Exception e) when (e is not SettingsPropertyNullException). Same behavior, more concise, and the filter never unwinds into the catch so the null exception's throw context is untouched.
This was referenced Jul 13, 2026
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.
S1 — Redact secret values from conversion-failure exceptions
Found during the P5 security review. A bound configuration value that fails type conversion could reach logs two ways:
SettingsPropertyValueExceptionmessage interpolated the raw value ([{value}]).FormatException/ArgumentExceptionforint/enum/DateTime) and was chained in, soException.ToString()/ILoggerprinted it.Realistic trigger: a secret on a typed property (a credentialed
Urithat fails to parse, or a secret mis-bound toint/enum). A secret bound tostringnever fails conversion, so it doesn't hit this.Fix (full — Rec 1)
The three-specialist plan review chose the full fix and rejected an opt-in "restore diagnostics" flag as insecure-by-configuration.
Resources.PropertySetterExceptionMessage— no longer includes the value; reports property name, target type, and the failing converter's exception type name (a compile-time identifier, cannot carry a secret). Fixes theto totypo.SettingsPropertyValueException— ctor drops thevalueparam and no longer chains the framework inner. Invariant: never carries a value, never chains an inner → auditably leak-proof.SettingsPropertyNullException(internal) — the value-free "AllowEmpty = false, no value" path keeps its full, useful message instead of being redacted;ConvertPropertyValuerethrows it unredacted.ISectionBinder— doc note that custom binders must not throw value-bearing messages (thepublicSettingsBindingExceptionchains the binder's inner; the four built-ins don't).Both changed exceptions are
internal→ non-breaking.Tests
Conversion/ExceptionRedactionTests.cs(+5): binds a sentinel secret toint/enum/DateTime/Uriand to a hostile custom converter, asserting the sentinel is absent from the entireex.ToString()chain while property name + target type remain. Existing null-path test updated toSettingsPropertyNullException+ message assertion. Suite 76 net10 (was 71).Note (modern .NET):
UriFormatExceptionis generic (no URI), so theUrileak was via our message, not the inner — theUritest targets our message accordingly.Tradeoff
A non-secret misconfiguration now loses the framework message/stack (gets property + target + failure type instead). Deliberate security-over-diagnostics call — worth a release-note mention.
Review
security-auditor(ENDORSE-WITH-CHANGES; all 3 required changes folded in), architecture + performance lenses (off the hot path — failure-only)./code-review(high) + Roslyndetect_antipatterns(0) — blast radius is only the two throw sites + tests.