fix(secrets): harden SecretName (reject rooted paths, fail-fast on default) + keep SecretDecryptionException sealed
This commit is contained in:
@@ -16,8 +16,9 @@ public readonly record struct SecretName
|
||||
/// <param name="value">The raw name to normalize and validate.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// The value is null, empty, or whitespace; contains the path-traversal sequence
|
||||
/// <c>..</c>; or contains any character outside <c>[a-z0-9._/-]</c> (after trim and
|
||||
/// lowercasing).
|
||||
/// <c>..</c>; is rooted (starts or ends with <c>/</c>) or contains an empty path
|
||||
/// segment (<c>//</c>); or contains any character outside <c>[a-z0-9._/-]</c> (after
|
||||
/// trim and lowercasing).
|
||||
/// </exception>
|
||||
public SecretName(string value)
|
||||
{
|
||||
@@ -37,6 +38,14 @@ public readonly record struct SecretName
|
||||
nameof(value));
|
||||
}
|
||||
|
||||
if (normalized.StartsWith('/') || normalized.EndsWith('/') ||
|
||||
normalized.Contains("//", StringComparison.Ordinal))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"Secret name must not be rooted or contain an empty path segment: it may not start or end with '/' or contain '//' (was '{value}').",
|
||||
nameof(value));
|
||||
}
|
||||
|
||||
foreach (char c in normalized)
|
||||
{
|
||||
if (!AllowedChars.Contains(c, StringComparison.Ordinal))
|
||||
@@ -47,17 +56,22 @@ public readonly record struct SecretName
|
||||
}
|
||||
}
|
||||
|
||||
Value = normalized;
|
||||
_value = normalized;
|
||||
}
|
||||
|
||||
private readonly string? _value;
|
||||
|
||||
/// <summary>The normalized secret name (trimmed, lowercased, allow-list validated).</summary>
|
||||
public string Value { get; }
|
||||
/// <exception cref="InvalidOperationException">The instance is the uninitialized <c>default</c> struct value.</exception>
|
||||
public string Value => _value ?? throw new InvalidOperationException("SecretName is uninitialized (default struct value).");
|
||||
|
||||
/// <summary>Returns the normalized <see cref="Value"/>.</summary>
|
||||
/// <returns>The normalized secret name.</returns>
|
||||
/// <exception cref="InvalidOperationException">The instance is the uninitialized <c>default</c> struct value.</exception>
|
||||
public override string ToString() => Value;
|
||||
|
||||
/// <summary>Implicitly converts a <see cref="SecretName"/> to its underlying string value.</summary>
|
||||
/// <param name="name">The secret name to convert.</param>
|
||||
/// <exception cref="InvalidOperationException">The instance is the uninitialized <c>default</c> struct value.</exception>
|
||||
public static implicit operator string(SecretName name) => name.Value;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user