@@ -836,6 +836,21 @@ func TestClient_redactSensitiveData_Attributes(t *testing.T) {
836836 regexp .MustCompile (`<secret>.*?</secret>` ),
837837 regexp .MustCompile (`<key>.*?</key>` ),
838838 regexp .MustCompile (`<community>.*?</community>` ),
839+ // CDATA sections
840+ regexp .MustCompile (`<password><!\[CDATA\[.*?\]\]></password>` ),
841+ regexp .MustCompile (`<secret><!\[CDATA\[.*?\]\]></secret>` ),
842+ regexp .MustCompile (`<key><!\[CDATA\[.*?\]\]></key>` ),
843+ regexp .MustCompile (`<community><!\[CDATA\[.*?\]\]></community>` ),
844+ // Namespace-aware elements
845+ regexp .MustCompile (`<[a-zA-Z0-9_-]+:password[^>]*>.*?</[a-zA-Z0-9_-]+:password>` ),
846+ regexp .MustCompile (`<[a-zA-Z0-9_-]+:secret[^>]*>.*?</[a-zA-Z0-9_-]+:secret>` ),
847+ regexp .MustCompile (`<[a-zA-Z0-9_-]+:key[^>]*>.*?</[a-zA-Z0-9_-]+:key>` ),
848+ regexp .MustCompile (`<[a-zA-Z0-9_-]+:community[^>]*>.*?</[a-zA-Z0-9_-]+:community>` ),
849+ // Namespaced CDATA sections
850+ regexp .MustCompile (`<[a-zA-Z0-9_-]+:password[^>]*><!\[CDATA\[.*?\]\]></[a-zA-Z0-9_-]+:password>` ),
851+ regexp .MustCompile (`<[a-zA-Z0-9_-]+:secret[^>]*><!\[CDATA\[.*?\]\]></[a-zA-Z0-9_-]+:secret>` ),
852+ regexp .MustCompile (`<[a-zA-Z0-9_-]+:key[^>]*><!\[CDATA\[.*?\]\]></[a-zA-Z0-9_-]+:key>` ),
853+ regexp .MustCompile (`<[a-zA-Z0-9_-]+:community[^>]*><!\[CDATA\[.*?\]\]></[a-zA-Z0-9_-]+:community>` ),
839854 // Attributes (double quotes)
840855 regexp .MustCompile (`password="[^"]*"` ),
841856 regexp .MustCompile (`secret="[^"]*"` ),
@@ -1026,6 +1041,21 @@ func TestClient_redactSensitiveData_XPathFilters(t *testing.T) {
10261041 regexp .MustCompile (`<secret>.*?</secret>` ),
10271042 regexp .MustCompile (`<key>.*?</key>` ),
10281043 regexp .MustCompile (`<community>.*?</community>` ),
1044+ // CDATA sections
1045+ regexp .MustCompile (`<password><!\[CDATA\[.*?\]\]></password>` ),
1046+ regexp .MustCompile (`<secret><!\[CDATA\[.*?\]\]></secret>` ),
1047+ regexp .MustCompile (`<key><!\[CDATA\[.*?\]\]></key>` ),
1048+ regexp .MustCompile (`<community><!\[CDATA\[.*?\]\]></community>` ),
1049+ // Namespace-aware elements
1050+ regexp .MustCompile (`<[a-zA-Z0-9_-]+:password[^>]*>.*?</[a-zA-Z0-9_-]+:password>` ),
1051+ regexp .MustCompile (`<[a-zA-Z0-9_-]+:secret[^>]*>.*?</[a-zA-Z0-9_-]+:secret>` ),
1052+ regexp .MustCompile (`<[a-zA-Z0-9_-]+:key[^>]*>.*?</[a-zA-Z0-9_-]+:key>` ),
1053+ regexp .MustCompile (`<[a-zA-Z0-9_-]+:community[^>]*>.*?</[a-zA-Z0-9_-]+:community>` ),
1054+ // Namespaced CDATA sections
1055+ regexp .MustCompile (`<[a-zA-Z0-9_-]+:password[^>]*><!\[CDATA\[.*?\]\]></[a-zA-Z0-9_-]+:password>` ),
1056+ regexp .MustCompile (`<[a-zA-Z0-9_-]+:secret[^>]*><!\[CDATA\[.*?\]\]></[a-zA-Z0-9_-]+:secret>` ),
1057+ regexp .MustCompile (`<[a-zA-Z0-9_-]+:key[^>]*><!\[CDATA\[.*?\]\]></[a-zA-Z0-9_-]+:key>` ),
1058+ regexp .MustCompile (`<[a-zA-Z0-9_-]+:community[^>]*><!\[CDATA\[.*?\]\]></[a-zA-Z0-9_-]+:community>` ),
10291059 // Attribute values (double quotes)
10301060 regexp .MustCompile (`password="[^"]*"` ),
10311061 regexp .MustCompile (`secret="[^"]*"` ),
@@ -1099,3 +1129,144 @@ func TestClient_redactSensitiveData_XPathFilters(t *testing.T) {
10991129 })
11001130 }
11011131}
1132+
1133+ // TestClient_redactSensitiveData_NamespaceAndCDATA tests namespace-aware and CDATA redaction patterns
1134+ // This test validates the security enhancement that added 12 new patterns for comprehensive coverage
1135+ func TestClient_redactSensitiveData_NamespaceAndCDATA (t * testing.T ) {
1136+ client := & Client {
1137+ redactionPatterns : defaultRedactionPatterns ,
1138+ }
1139+
1140+ tests := []struct {
1141+ name string
1142+ input string
1143+ shouldContain string // What MUST be in output
1144+ mustNotContain []string // What MUST NOT be in output (sensitive data)
1145+ desc string
1146+ }{
1147+ {
1148+ name : "Namespace-aware password element" ,
1149+ input : `<config><auth:password>secret123</auth:password><hostname>router1</hostname></config>` ,
1150+ shouldContain : "[REDACTED]" ,
1151+ mustNotContain : []string {"secret123" },
1152+ desc : "Should redact <auth:password> with namespace prefix" ,
1153+ },
1154+ {
1155+ name : "Namespace-aware secret element" ,
1156+ input : `<data><cisco:secret>my_secret_value</cisco:secret></data>` ,
1157+ shouldContain : "[REDACTED]" ,
1158+ mustNotContain : []string {"my_secret_value" },
1159+ desc : "Should redact <cisco:secret> with namespace prefix" ,
1160+ },
1161+ {
1162+ name : "Namespace-aware key element" ,
1163+ input : `<config><vpn:key>encryption_key_123</vpn:key></config>` ,
1164+ shouldContain : "[REDACTED]" ,
1165+ mustNotContain : []string {"encryption_key_123" },
1166+ desc : "Should redact <vpn:key> with namespace prefix" ,
1167+ },
1168+ {
1169+ name : "Namespace-aware community element" ,
1170+ input : `<snmp><mgmt:community>public_string</mgmt:community></snmp>` ,
1171+ shouldContain : "[REDACTED]" ,
1172+ mustNotContain : []string {"public_string" },
1173+ desc : "Should redact <mgmt:community> with namespace prefix" ,
1174+ },
1175+ {
1176+ name : "CDATA password element" ,
1177+ input : `<config><password><![CDATA[p@ssw0rd!]]></password></config>` ,
1178+ shouldContain : "[REDACTED]" ,
1179+ mustNotContain : []string {"p@ssw0rd!" },
1180+ desc : "Should redact CDATA password content" ,
1181+ },
1182+ {
1183+ name : "CDATA secret element" ,
1184+ input : `<auth><secret><![CDATA[secret_token_xyz]]></secret></auth>` ,
1185+ shouldContain : "[REDACTED]" ,
1186+ mustNotContain : []string {"secret_token_xyz" },
1187+ desc : "Should redact CDATA secret content" ,
1188+ },
1189+ {
1190+ name : "CDATA key element" ,
1191+ input : `<crypto><key><![CDATA[api_key_12345]]></key></crypto>` ,
1192+ shouldContain : "[REDACTED]" ,
1193+ mustNotContain : []string {"api_key_12345" },
1194+ desc : "Should redact CDATA key content" ,
1195+ },
1196+ {
1197+ name : "CDATA community element" ,
1198+ input : `<snmp><community><![CDATA[community_ro]]></community></snmp>` ,
1199+ shouldContain : "[REDACTED]" ,
1200+ mustNotContain : []string {"community_ro" },
1201+ desc : "Should redact CDATA community content" ,
1202+ },
1203+ {
1204+ name : "Namespace CDATA password" ,
1205+ input : `<config><ns:password><![CDATA[ns_password_value]]></ns:password></config>` ,
1206+ shouldContain : "[REDACTED]" ,
1207+ mustNotContain : []string {"ns_password_value" },
1208+ desc : "Should redact namespace-aware CDATA password" ,
1209+ },
1210+ {
1211+ name : "Namespace CDATA secret" ,
1212+ input : `<data><auth:secret><![CDATA[secret_in_ns]]></auth:secret></data>` ,
1213+ shouldContain : "[REDACTED]" ,
1214+ mustNotContain : []string {"secret_in_ns" },
1215+ desc : "Should redact namespace-aware CDATA secret" ,
1216+ },
1217+ {
1218+ name : "Namespace CDATA key" ,
1219+ input : `<vpn><config:key><![CDATA[key_in_ns]]></config:key></vpn>` ,
1220+ shouldContain : "[REDACTED]" ,
1221+ mustNotContain : []string {"key_in_ns" },
1222+ desc : "Should redact namespace-aware CDATA key" ,
1223+ },
1224+ {
1225+ name : "Namespace CDATA community" ,
1226+ input : `<snmp><cisco:community><![CDATA[community_in_ns]]></cisco:community></snmp>` ,
1227+ shouldContain : "[REDACTED]" ,
1228+ mustNotContain : []string {"community_in_ns" },
1229+ desc : "Should redact namespace-aware CDATA community" ,
1230+ },
1231+ {
1232+ name : "Mixed redaction types" ,
1233+ input : `<config><password>plain_pass</password><auth:password>ns_pass</auth:password><secret><![CDATA[cdata_secret]]></secret><vpn:key><![CDATA[ns_cdata_key]]></vpn:key></config>` ,
1234+ shouldContain : "[REDACTED]" ,
1235+ mustNotContain : []string {"plain_pass" , "ns_pass" , "cdata_secret" , "ns_cdata_key" },
1236+ desc : "Should handle multiple redaction types simultaneously" ,
1237+ },
1238+ {
1239+ name : "Preserve non-sensitive content" ,
1240+ input : `<config><hostname>router1</hostname><auth:password>secret</auth:password><interface>GigE0/0</interface></config>` ,
1241+ shouldContain : "router1" ,
1242+ mustNotContain : []string {"secret" },
1243+ desc : "Should preserve non-sensitive data like hostnames" ,
1244+ },
1245+ }
1246+
1247+ for _ , tt := range tests {
1248+ t .Run (tt .name , func (t * testing.T ) {
1249+ result := client .redactSensitiveData (tt .input )
1250+
1251+ // Verify sensitive data is NOT in output
1252+ for _ , sensitive := range tt .mustNotContain {
1253+ if strings .Contains (result , sensitive ) {
1254+ t .Errorf ("%s FAILED: Sensitive data '%s' was not redacted\n " +
1255+ "Input: %s\n " +
1256+ "Output: %s\n " +
1257+ "Reason: %s" ,
1258+ tt .name , sensitive , tt .input , result , tt .desc )
1259+ }
1260+ }
1261+
1262+ // Verify [REDACTED] IS in output
1263+ if ! strings .Contains (result , tt .shouldContain ) {
1264+ t .Errorf ("%s FAILED: Expected '%s' in output\n " +
1265+ "Input: %s\n " +
1266+ "Output: %s\n " +
1267+ "Reason: %s" ,
1268+ tt .name , tt .shouldContain , tt .input , result , tt .desc )
1269+ }
1270+ })
1271+ }
1272+ }
0 commit comments