1+ using System . Text . Json ;
2+ using System . Web ;
3+ using Microsoft . AspNetCore . Http ;
4+
5+ namespace SharedKernel . Logging . Helpers ;
6+
7+ internal static class RedactionHelper
8+ {
9+ private const int MaxPropertyLength = 1024 * 5 ; // 5 KB
10+
11+ private static readonly HashSet < string > SensitiveKeywords = new ( StringComparer . OrdinalIgnoreCase )
12+ {
13+ "pwd" ,
14+ "pass" ,
15+ "secret" ,
16+ "token" ,
17+ "cookie" ,
18+ "auth" ,
19+ "pan" ,
20+ "cvv" ,
21+ "cvc" ,
22+ "cardholder" ,
23+ "bindingid" ,
24+ "ssn" ,
25+ "tin" ,
26+ "iban" ,
27+ "swift" ,
28+ "bankaccount" ,
29+ "notboundcard"
30+ } ;
31+
32+ public static Dictionary < string , string > RedactHeaders ( IHeaderDictionary headers ) =>
33+ headers . ToDictionary (
34+ h => h . Key ,
35+ h => SensitiveKeywords . Any ( k => h . Key . IndexOf ( k , StringComparison . OrdinalIgnoreCase ) >= 0 )
36+ ? "[REDACTED]"
37+ : h . Value . ToString ( ) ! ) ;
38+
39+ public static Dictionary < string , string > RedactHeaders ( Dictionary < string , IEnumerable < string > > headers ) =>
40+ headers . ToDictionary (
41+ kvp => kvp . Key ,
42+ kvp => SensitiveKeywords . Any ( k => kvp . Key . Contains ( k , StringComparison . OrdinalIgnoreCase ) )
43+ ? "[REDACTED]"
44+ : string . Join ( ";" , kvp . Value ) ) ;
45+
46+ public static object RedactBody ( string ? mediaType , string raw )
47+ {
48+ if ( string . IsNullOrWhiteSpace ( raw ) )
49+ {
50+ return string . Empty ;
51+ }
52+
53+ if ( IsJsonMediaType ( mediaType ) )
54+ {
55+ try
56+ {
57+ var el = JsonSerializer . Deserialize < JsonElement > ( raw ) ;
58+ return RedactElement ( el ) ;
59+ }
60+ catch ( JsonException )
61+ {
62+ return "[INVALID_JSON]" ;
63+ }
64+ }
65+
66+ if ( mediaType ? . Equals ( "application/x-www-form-urlencoded" , StringComparison . OrdinalIgnoreCase ) == true )
67+
68+ {
69+ var nvc = HttpUtility . ParseQueryString ( raw ) ;
70+ var keys = nvc . AllKeys ;
71+
72+ var dict = new Dictionary < string , string > ( StringComparer . OrdinalIgnoreCase ) ;
73+ foreach ( var k in keys )
74+ {
75+ if ( string . IsNullOrEmpty ( k ) )
76+ continue ;
77+
78+ var v = nvc [ k ] ?? string . Empty ;
79+ dict [ k ] = SensitiveKeywords . Any ( s =>
80+ k . Contains ( s , StringComparison . OrdinalIgnoreCase ) ||
81+ v . Contains ( s , StringComparison . OrdinalIgnoreCase ) )
82+ ? "[REDACTED]"
83+ : v ;
84+ }
85+
86+ return dict ;
87+ }
88+
89+ if ( raw . Length <= MaxPropertyLength )
90+ {
91+ return SensitiveKeywords . Any ( s => raw . Contains ( s , StringComparison . OrdinalIgnoreCase ) )
92+ ? "[REDACTED]"
93+ : raw ;
94+ }
95+
96+ var maxKb = MaxPropertyLength / 1024 ;
97+ var actualKb = raw . Length / 1024 ;
98+ return $ "[OMITTED: max { maxKb } KB, actual { actualKb } KB]";
99+ }
100+
101+ private static bool IsJsonMediaType ( string ? mediaType ) =>
102+ ! string . IsNullOrWhiteSpace ( mediaType )
103+ && ( mediaType . EndsWith ( "/json" , StringComparison . OrdinalIgnoreCase )
104+ || mediaType . EndsWith ( "+json" , StringComparison . OrdinalIgnoreCase ) ) ;
105+
106+ private static object RedactElement ( JsonElement el ) =>
107+ el . ValueKind switch
108+ {
109+ JsonValueKind . Object => el . EnumerateObject ( )
110+ . ToDictionary (
111+ p => p . Name ,
112+ p => SensitiveKeywords . Any ( k =>
113+ p . Name . Contains ( k , StringComparison . OrdinalIgnoreCase ) )
114+ ? "[REDACTED]"
115+ : RedactElement ( p . Value ) ) ,
116+ JsonValueKind . Array => el . EnumerateArray ( )
117+ . Select ( RedactElement )
118+ . ToArray ( ) ,
119+ JsonValueKind . String => RedactString ( el . GetString ( ) ! ) ,
120+ _ => el . GetRawText ( )
121+ } ;
122+
123+ private static string RedactString ( string value )
124+ {
125+ if ( value . Length <= MaxPropertyLength )
126+ {
127+ return SensitiveKeywords . Any ( s => value . Contains ( s , StringComparison . OrdinalIgnoreCase ) )
128+ ? "[REDACTED]"
129+ : value ;
130+ }
131+
132+ const int maxKb = MaxPropertyLength / 1024 ;
133+ var actualKb = value . Length / 1024 ;
134+ return $ "[OMITTED: max { maxKb } KB, actual { actualKb } KB]";
135+ }
136+ }
0 commit comments