< Summary

Information
Class: AsiBackbone.Signing.ManagedKey.ManagedKeyProviderMetadataFilter
Assembly: AsiBackbone.Signing.ManagedKey
File(s): /home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.Signing.ManagedKey/ManagedKeyProviderMetadataFilter.cs
Line coverage
97%
Covered lines: 33
Uncovered lines: 1
Coverable lines: 34
Total lines: 95
Line coverage: 97%
Branch coverage
91%
Covered branches: 31
Total branches: 34
Branch coverage: 91.1%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
Filter(...)90%303094.73%
ContainsControlCharacter(...)100%44100%

File(s)

/home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.Signing.ManagedKey/ManagedKeyProviderMetadataFilter.cs

#LineLine coverage
 1using System.Collections.ObjectModel;
 2
 3namespace AsiBackbone.Signing.ManagedKey;
 4
 5/// <summary>
 6/// Minimizes untrusted metadata returned by host-owned managed-key clients.
 7/// </summary>
 8internal static class ManagedKeyProviderMetadataFilter
 9{
 10    internal const int MaxMetadataCount = 16;
 11    internal const int MaxKeyLength = 64;
 12    internal const int MaxValueLength = 256;
 13    internal const int MaxAggregateLength = 2048;
 14
 215    private static readonly ReadOnlyDictionary<string, string> EmptyMetadata =
 216        new(new Dictionary<string, string>(StringComparer.Ordinal));
 17
 218    private static readonly Dictionary<string, string> AllowedKeys = new(StringComparer.OrdinalIgnoreCase)
 219    {
 220        ["provider_region"] = "provider_region",
 221        ["provider_zone"] = "provider_zone",
 222        ["provider_service"] = "provider_service",
 223        ["provider_request_id"] = "provider_request_id",
 224        ["provider_status_code"] = "provider_status_code",
 225        ["provider_key_state"] = "provider_key_state"
 226    };
 27
 28    /// <summary>
 29    /// Returns a bounded, allowlisted copy of provider metadata.
 30    /// </summary>
 31    internal static IReadOnlyDictionary<string, string> Filter(
 32        IReadOnlyDictionary<string, string>? metadata)
 33    {
 8234        if (metadata is null || metadata.Count == 0)
 35        {
 2436            return EmptyMetadata;
 37        }
 38
 5839        Dictionary<string, string> filtered = new(StringComparer.Ordinal);
 5840        int aggregateLength = 0;
 41
 44842        foreach (KeyValuePair<string, string> item in metadata)
 43        {
 16644            if (filtered.Count >= MaxMetadataCount)
 45            {
 046                break;
 47            }
 48
 16649            string candidateKey = item.Key.Trim();
 16650            if (candidateKey.Length is 0 or > MaxKeyLength)
 51            {
 52                continue;
 53            }
 54
 16455            if (!AllowedKeys.TryGetValue(candidateKey, out string? canonicalKey))
 56            {
 57                continue;
 58            }
 59
 3860            string candidateValue = item.Value?.Trim() ?? string.Empty;
 3861            if (candidateValue.Length > MaxValueLength || ContainsControlCharacter(candidateValue))
 62            {
 63                continue;
 64            }
 65
 3466            int entryLength = canonicalKey.Length + candidateValue.Length;
 3467            if (aggregateLength + entryLength > MaxAggregateLength)
 68            {
 69                continue;
 70            }
 71
 3472            if (filtered.TryAdd(canonicalKey, candidateValue))
 73            {
 3474                aggregateLength += entryLength;
 75            }
 76        }
 77
 5878        return filtered.Count == 0
 5879            ? EmptyMetadata
 5880            : new ReadOnlyDictionary<string, string>(filtered);
 81    }
 82
 83    private static bool ContainsControlCharacter(string value)
 84    {
 659485        foreach (char character in value)
 86        {
 326287            if (char.IsControl(character))
 88            {
 289                return true;
 90            }
 91        }
 92
 3493        return false;
 94    }
 95}