fix(secrets): ${secret:} expander skips comment keys (_-prefixed leaf) — 0.1.2

Live HistorianGateway boot caught that the expander scanned a '_secretsComment' appsettings
value documenting the ${secret:...} syntax, tried to resolve the literal example token, and
crashed boot. The family uses _*Comment keys pervasively, so skip any config key whose leaf
segment starts with '_' (the _comment convention). Functional keys never lead with '_'.
This commit is contained in:
Joseph Doherty
2026-07-16 04:16:25 -04:00
parent 76124590f0
commit 9bd3c76d96
3 changed files with 47 additions and 2 deletions
@@ -83,6 +83,13 @@ public sealed class SecretReferenceExpander
/// or <c>ValidateOnStart</c> executes; otherwise validators may see the unexpanded placeholder.
/// Fail-closed: a missing referenced secret propagates <see cref="SecretNotFoundException"/> out
/// of startup.
/// <para>
/// Documentation/comment keys are skipped: any entry whose leaf key segment (the part after the
/// last <c>:</c>) begins with an underscore — the widely-used <c>"_comment"</c> JSON convention —
/// is left untouched. This lets an appsettings file document the <c>${secret:...}</c> syntax in a
/// <c>_comment</c> value (even with a literal example token) without that example being treated as
/// a real reference to resolve. Functional keys are never named with a leading underscore.
/// </para>
/// </remarks>
/// <param name="config">The loaded configuration root to mutate.</param>
/// <param name="ct">A token to cancel the operation.</param>
@@ -95,7 +102,9 @@ public sealed class SecretReferenceExpander
var pending = new List<KeyValuePair<string, string>>();
foreach (KeyValuePair<string, string?> entry in config.AsEnumerable())
{
if (entry.Value is { } value && value.Contains(TokenMarker, StringComparison.Ordinal))
if (entry.Value is { } value
&& value.Contains(TokenMarker, StringComparison.Ordinal)
&& !IsCommentKey(entry.Key))
{
pending.Add(new KeyValuePair<string, string>(entry.Key, value));
}
@@ -106,4 +115,14 @@ public sealed class SecretReferenceExpander
config[entry.Key] = await ExpandAsync(entry.Value, ct).ConfigureAwait(false);
}
}
// A configuration key is a comment/documentation key when its leaf segment (after the last ':')
// starts with '_' — the conventional "_comment" marker. Such values may legitimately contain a
// literal ${secret:...} example and must not be resolved.
private static bool IsCommentKey(string key)
{
int lastSeparator = key.LastIndexOf(':');
string leaf = lastSeparator >= 0 ? key[(lastSeparator + 1)..] : key;
return leaf.StartsWith('_');
}
}