1+ """Security utilities for handling sensitive data"""
2+ import re
3+ from typing import Any , Dict
4+
5+
6+ SENSITIVE_FIELDS = [
7+ "secretKey" ,
8+ "accessKey" ,
9+ "password" ,
10+ "passwd" ,
11+ "pwd" ,
12+ "secret" ,
13+ "token" ,
14+ "apiKey" ,
15+ "api_key" ,
16+ "access_key" ,
17+ "secret_key" ,
18+ ]
19+
20+ MASK_PATTERN = "**********"
21+
22+
23+ def mask_sensitive_value (value : str ) -> str :
24+ """Mask a single sensitive value"""
25+ return MASK_PATTERN
26+
27+
28+ def is_masked_value (value : str ) -> bool :
29+ """Check if a value is already masked"""
30+ return value == MASK_PATTERN
31+
32+
33+ def preserve_sensitive_values (
34+ new_config : Dict [str , Any ],
35+ original_config : Dict [str , Any ]
36+ ) -> Dict [str , Any ]:
37+ """
38+ Preserve original sensitive values if masked pattern is detected in update request.
39+
40+ When frontend receives masked values and sends them back in update request,
41+ this function detects masked values and replaces them with original values
42+ from database.
43+
44+ Args:
45+ new_config: Config from update request (may contain masked values)
46+ original_config: Original config from database (contains real values)
47+
48+ Returns:
49+ Config with original sensitive values preserved
50+ """
51+ if not isinstance (new_config , dict ) or not isinstance (original_config , dict ):
52+ return new_config
53+
54+ preserved_config = {}
55+ for key , new_value in new_config .items ():
56+ # Check if key is a sensitive field
57+ key_lower = key .lower ()
58+ is_sensitive = any (
59+ field .lower () == key_lower or field .lower () in key_lower
60+ for field in SENSITIVE_FIELDS
61+ )
62+
63+ # If value is masked and field is sensitive, use original value
64+ if is_sensitive and isinstance (new_value , str ) and is_masked_value (new_value ):
65+ original_value = original_config .get (key )
66+ preserved_config [key ] = original_value if original_value else new_value
67+ elif isinstance (new_value , dict ):
68+ # Recursively process nested dictionaries
69+ original_nested = original_config .get (key , {})
70+ preserved_config [key ] = preserve_sensitive_values (new_value , original_nested )
71+ elif isinstance (new_value , list ):
72+ original_list = original_config .get (key , [])
73+ preserved_list = []
74+ for i , new_item in enumerate (new_value ):
75+ if i < len (original_list ):
76+ orig_item = original_list [i ]
77+ if isinstance (new_item , dict ) and isinstance (orig_item , dict ):
78+ preserved_list .append (preserve_sensitive_values (new_item , orig_item ))
79+ else :
80+ preserved_list .append (new_item )
81+ else :
82+ preserved_list .append (new_item )
83+ preserved_config [key ] = preserved_list
84+ else :
85+ # Keep non-sensitive/non-masked values
86+ preserved_config [key ] = new_value
87+
88+ return preserved_config
89+
90+
91+ def mask_sensitive_dict (data : Dict [str , Any ]) -> Dict [str , Any ]:
92+ """
93+ Recursively mask sensitive fields in a dictionary.
94+
95+ Args:
96+ data: Dictionary that may contain sensitive fields
97+
98+ Returns:
99+ Dictionary with sensitive values masked
100+ """
101+ if not isinstance (data , dict ):
102+ return data
103+
104+ masked_data = {}
105+ for key , value in data .items ():
106+ # Check if the key is a sensitive field (case-insensitive)
107+ key_lower = key .lower ()
108+ is_sensitive = any (
109+ field .lower () == key_lower or field .lower () in key_lower
110+ for field in SENSITIVE_FIELDS
111+ )
112+
113+ if is_sensitive and isinstance (value , str ):
114+ # Mask the sensitive value
115+ masked_data [key ] = MASK_PATTERN
116+ elif isinstance (value , dict ):
117+ # Recursively mask nested dictionaries
118+ masked_data [key ] = mask_sensitive_dict (value )
119+ elif isinstance (value , list ):
120+ # Process list items
121+ masked_data [key ] = [
122+ mask_sensitive_dict (item ) if isinstance (item , dict ) else item
123+ for item in value
124+ ]
125+ else :
126+ # Keep non-sensitive values as-is
127+ masked_data [key ] = value
128+
129+ return masked_data
130+
131+
132+ def mask_sensitive_info (text : str ) -> str :
133+ """
134+ Mask sensitive information in text by replacing values with **********
135+
136+ Args:
137+ text: Original text that may contain sensitive information
138+
139+ Returns:
140+ Text with sensitive values masked
141+ """
142+ masked_text = text
143+
144+ for field in SENSITIVE_FIELDS :
145+ patterns = [
146+ rf'"{ field } "\s*:\s*"[^"]*"' ,
147+ rf'{ field } \s*=\s*[^\s,\]]+' ,
148+ rf"'{ field } '\s*:\s*'[^']*'" ,
149+ ]
150+
151+ for pattern in patterns :
152+ if '"' in pattern :
153+ masked_text = re .sub (pattern , f'"{ field } ": "{ MASK_PATTERN } "' , masked_text , flags = re .IGNORECASE )
154+ elif "'" in pattern :
155+ masked_text = re .sub (pattern , f"'{ field } ': '{ MASK_PATTERN } '" , masked_text , flags = re .IGNORECASE )
156+ else :
157+ masked_text = re .sub (pattern , f'{ field } ={ MASK_PATTERN } ' , masked_text , flags = re .IGNORECASE )
158+
159+ return masked_text
0 commit comments