< 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.74%
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
 115    private static readonly ReadOnlyDictionary<string, string> EmptyMetadata =
 116        new(new Dictionary<string, string>(StringComparer.Ordinal));
 17
 118    private static readonly Dictionary<string, string> AllowedKeys = new(StringComparer.OrdinalIgnoreCase)
 119    {
 120        ["provider_region"] = "provider_region",
 121        ["provider_zone"] = "provider_zone",
 122        ["provider_service"] = "provider_service",
 123        ["provider_request_id"] = "provider_request_id",
 124        ["provider_status_code"] = "provider_status_code",
 125        ["provider_key_state"] = "provider_key_state"
 126    };
 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    {
 4634        if (metadata is null || metadata.Count == 0)
 35        {
 1636            return EmptyMetadata;
 37        }
 38
 3039        Dictionary<string, string> filtered = new(StringComparer.Ordinal);
 3040        int aggregateLength = 0;
 41
 23642        foreach (KeyValuePair<string, string> item in metadata)
 43        {
 8844            if (filtered.Count >= MaxMetadataCount)
 45            {
 046                break;
 47            }
 48
 8849            string candidateKey = item.Key.Trim();
 8850            if (candidateKey.Length is 0 or > MaxKeyLength)
 51            {
 52                continue;
 53            }
 54
 8755            if (!AllowedKeys.TryGetValue(candidateKey, out string? canonicalKey))
 56            {
 57                continue;
 58            }
 59
 2060            string candidateValue = item.Value?.Trim() ?? string.Empty;
 2061            if (candidateValue.Length > MaxValueLength || ContainsControlCharacter(candidateValue))
 62            {
 63                continue;
 64            }
 65
 1866            int entryLength = canonicalKey.Length + candidateValue.Length;
 1867            if (aggregateLength + entryLength > MaxAggregateLength)
 68            {
 69                continue;
 70            }
 71
 1872            if (filtered.TryAdd(canonicalKey, candidateValue))
 73            {
 1874                aggregateLength += entryLength;
 75            }
 76        }
 77
 3078        return filtered.Count == 0
 3079            ? EmptyMetadata
 3080            : new ReadOnlyDictionary<string, string>(filtered);
 81    }
 82
 83    private static bool ContainsControlCharacter(string value)
 84    {
 332185        foreach (char character in value)
 86        {
 164287            if (char.IsControl(character))
 88            {
 189                return true;
 90            }
 91        }
 92
 1893        return false;
 94    }
 95}