Skip to main content

relay_pii/
eap.rs

1//! Provides the `scrub` function for scrubbing EAP items.
2//!
3//! This module also contains tests for scrubbing attributes.
4
5use relay_event_schema::processor::{
6    ProcessValue, ProcessingResult, ProcessingState, ValueType, process_value,
7};
8use relay_protocol::Annotated;
9
10use crate::{AttributeMode, PiiConfig, PiiProcessor};
11
12/// Scrubs an EAP item (such as an `OurLog` or `SpanV2`) with the given PII configs.
13pub fn scrub<T: ProcessValue>(
14    value_type: ValueType,
15    item: &mut Annotated<T>,
16    advanced_rules: Option<&PiiConfig>,
17    legacy_rule: Option<&PiiConfig>,
18) -> ProcessingResult {
19    let state = ProcessingState::root().enter_borrowed("", None, [value_type]);
20
21    if let Some(config) = advanced_rules {
22        let mut processor = PiiProcessor::new(config.compiled())
23            // For advanced rules we want to treat attributes as objects.
24            .attribute_mode(AttributeMode::Object);
25        process_value(item, &mut processor, &state)?;
26    }
27
28    if let Some(config) = legacy_rule {
29        let mut processor = PiiProcessor::new(config.compiled())
30            // For "legacy" rules we want to identify attributes with their values.
31            .attribute_mode(AttributeMode::ValueOnly);
32        process_value(item, &mut processor, &state)?;
33    }
34
35    Ok(())
36}
37
38#[cfg(test)]
39mod tests {
40    use relay_event_schema::processor::ValueType;
41    use relay_event_schema::protocol::{Attributes, OurLog, SpanV2, TraceMetric};
42    use relay_protocol::{Annotated, SerializableAnnotated};
43
44    use crate::{DataScrubbingConfig, PiiConfig, eap};
45
46    #[test]
47    fn test_scrub_attributes_pii_default_rules() {
48        // `user.name`, `sentry.release`, and `url.path` are marked as follows in `sentry-conventions`:
49        // * `user.name`: `true`
50        // * `sentry.release`: `false`
51        // * `url.path`: `maybe`
52        // Therefore, `user.name` is the only one that should be scrubbed by default rules.
53        let json = r#"{
54              "sentry.description": {
55                  "type": "string",
56                  "value": "secret123"
57              },
58              "user.name": {
59                  "type": "string",
60                  "value": "secret123"
61              },
62              "sentry.release": {
63                  "type": "string",
64                  "value": "secret123"
65              },
66              "url.path": {
67                  "type": "string",
68                  "value": "secret123"
69              },
70              "password": {
71                  "type": "string",
72                  "value": "secret123"
73              },
74              "secret": {
75                  "type": "string",
76                  "value": "topsecret"
77              },
78              "api_key": {
79                  "type": "string",
80                  "value": "sk-1234567890abcdef"
81              },
82              "auth_token": {
83                  "type": "string",
84                  "value": "bearer_token_123"
85              },
86              "credit_card": {
87                  "type": "string",
88                  "value": "4571234567890111"
89              },
90              "visa_card": {
91                  "type": "string",
92                  "value": "4571 2345 6789 0111"
93              },
94              "bank_account": {
95                  "type": "string",
96                  "value": "DE89370400440532013000"
97              },
98              "iban_code": {
99                  "type": "string",
100                  "value": "NO9386011117945"
101              },
102              "authorization": {
103                  "type": "string",
104                  "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"
105              },
106              "private_key": {
107                  "type": "string",
108                  "value": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC7VJTUt9Us8cKB\n-----END PRIVATE KEY-----"
109              },
110              "database_url": {
111                  "type": "string",
112                  "value": "https://username:password@example.com/db"
113              },
114              "file_path": {
115                  "type": "string",
116                  "value": "C:\\Users\\john\\Documents\\secret.txt"
117              },
118              "unix_path": {
119                  "type": "string",
120                  "value": "/home/alice/private/data.json"
121              },
122              "social_security": {
123                  "type": "string",
124                  "value": "078-05-1120"
125              },
126              "user_email": {
127                  "type": "string",
128                  "value": "john.doe@example.com"
129              },
130              "client_ip": {
131                  "type": "string",
132                  "value": "192.168.1.100"
133              },
134              "ipv6_addr": {
135                  "type": "string",
136                  "value": "2001:0db8:85a3:0000:0000:8a2e:0370:7334"
137              },
138              "mac_address": {
139                  "type": "string",
140                  "value": "4a:00:04:10:9b:50"
141              },
142              "session_id": {
143                  "type": "string",
144                  "value": "ceee0822-ed8f-4622-b2a3-789e73e75cd1"
145              },
146              "device_imei": {
147                  "type": "string",
148                  "value": "356938035643809"
149              },
150              "public_data": {
151                  "type": "string",
152                  "value": "public_data"
153              },
154              "very_sensitive_data": {
155                  "type": "string",
156                  "value": "very_sensitive_data"
157              }
158        }"#;
159
160        let mut data = Annotated::<Attributes>::from_json(json).unwrap();
161
162        let data_scrubbing_config = DataScrubbingConfig {
163            scrub_data: true,
164            scrub_defaults: true,
165            scrub_ip_addresses: true,
166            exclude_fields: vec!["public_data".to_owned()],
167            sensitive_fields: vec![
168                "value".to_owned(), // Make sure the inner 'value' of the attribute object isn't scrubbed.
169                "very_sensitive_data".to_owned(),
170            ],
171            ..Default::default()
172        };
173
174        let scrubbing_config = data_scrubbing_config.pii_config();
175
176        eap::scrub(ValueType::Span, &mut data, None, scrubbing_config.as_ref()).unwrap();
177
178        insta::assert_json_snapshot!(SerializableAnnotated(&data));
179    }
180
181    #[test]
182    fn test_scrub_attributes_pii_custom_object_rules() {
183        // `user.name`, `sentry.release`, and `url.path` are marked as follows in `sentry-conventions`:
184        // * `user.name`: `true`
185        // * `sentry.release`: `false`
186        // * `url.path`: `maybe`
187        // Therefore, `sentry.release` is the only one that should not be scrubbed by custom rules.
188        let json = r#"
189        {
190          "sentry.description": {
191              "type": "string",
192              "value": "secret123"
193          },
194          "user.name": {
195              "type": "string",
196              "value": "secret123"
197          },
198          "sentry.release": {
199              "type": "string",
200              "value": "secret123"
201          },
202          "url.path": {
203              "type": "string",
204              "value": "secret123"
205          },
206          "password": {
207              "type": "string",
208              "value": "default_scrubbing_rules_are_off"
209          },
210          "test_field_mac": {
211              "type": "string",
212              "value": "4a:00:04:10:9b:50"
213          },
214          "test_field_imei": {
215              "type": "string",
216              "value": "356938035643809"
217          },
218          "test_field_uuid": {
219              "type": "string",
220              "value": "123e4567-e89b-12d3-a456-426614174000"
221          },
222          "test_field_regex_passes": {
223              "type": "string",
224              "value": "wxyz"
225          },
226          "test_field_regex_fails": {
227              "type": "string",
228              "value": "abc"
229          }
230        }"#;
231
232        let mut data = Annotated::<Attributes>::from_json(json).unwrap();
233
234        let scrubbing_config = DataScrubbingConfig {
235            scrub_data: true,
236            scrub_defaults: false,
237            scrub_ip_addresses: false,
238            ..Default::default()
239        };
240
241        let scrubbing_config = scrubbing_config.pii_config();
242
243        let config = serde_json::from_value::<PiiConfig>(serde_json::json!(
244        {
245            "rules": {
246                "project:0": {
247                    "type": "mac",
248                    "redaction": {
249                        "method": "replace",
250                        "text": "ITS_GONE"
251                    }
252                },
253                "project:1": {
254                    "type": "imei",
255                    "redaction": {
256                        "method": "replace",
257                        "text": "ITS_GONE"
258                    }
259                },
260                "project:2": {
261                    "type": "uuid",
262                    "redaction": {
263                        "method": "replace",
264                        "text": "BYE"
265                    }
266                },
267                "project:3": {
268                    "type": "pattern",
269                    "pattern": "[w-z]+",
270                    "redaction": {
271                        "method": "replace",
272                        "text": "REGEXED"
273                    }
274                },
275                "project:4": {
276                    "type": "anything",
277                    "redaction": {
278                        "method": "replace",
279                        "text": "[USER NAME]"
280                    }
281                },
282                "project:5": {
283                    "type": "anything",
284                    "redaction": {
285                        "method": "replace",
286                        "text": "[RELEASE]"
287                    }
288                },
289                "project:6": {
290                    "type": "anything",
291                    "redaction": {
292                        "method": "replace",
293                        "text": "[URL PATH]"
294                    }
295                },
296                "project:7": {
297                    "type": "anything",
298                    "redaction": {
299                        "method": "replace",
300                        "text": "[DESCRIPTION]"
301                    }
302                }
303            },
304            "applications": {
305                "test_field_mac.value": [
306                    "project:0"
307                ],
308                "test_field_imei.value": [
309                    "project:1"
310                ],
311                "test_field_uuid.value": [
312                    "project:2"
313                ],
314                "test_field_regex_passes.value || test_field_regex_fails.value": [
315                    "project:3"
316                ],
317                "'user.name'.value": [
318                    "project:4"
319                ],
320                "'sentry.release'.value": [
321                    "project:5"
322                ],
323                "'url.path'.value": [
324                    "project:6"
325                ],
326                "'sentry.description'.value": [
327                    "project:7"
328                ]
329            }
330        }
331        ))
332        .unwrap();
333
334        eap::scrub(
335            ValueType::Span,
336            &mut data,
337            Some(&config),
338            scrubbing_config.as_ref(),
339        )
340        .unwrap();
341
342        insta::assert_json_snapshot!(SerializableAnnotated(&data), @r###"
343        {
344          "password": {
345            "type": "string",
346            "value": "default_scrubbing_rules_are_off"
347          },
348          "sentry.description": {
349            "type": "string",
350            "value": "[DESCRIPTION]"
351          },
352          "sentry.release": {
353            "type": "string",
354            "value": "secret123"
355          },
356          "test_field_imei": {
357            "type": "string",
358            "value": "ITS_GONE"
359          },
360          "test_field_mac": {
361            "type": "string",
362            "value": "ITS_GONE"
363          },
364          "test_field_regex_fails": {
365            "type": "string",
366            "value": "abc"
367          },
368          "test_field_regex_passes": {
369            "type": "string",
370            "value": "REGEXED"
371          },
372          "test_field_uuid": {
373            "type": "string",
374            "value": "BYE"
375          },
376          "url.path": {
377            "type": "string",
378            "value": "[URL PATH]"
379          },
380          "user.name": {
381            "type": "string",
382            "value": "[USER NAME]"
383          },
384          "_meta": {
385            "sentry.description": {
386              "value": {
387                "": {
388                  "rem": [
389                    [
390                      "project:7",
391                      "s",
392                      0,
393                      13
394                    ]
395                  ],
396                  "len": 9
397                }
398              }
399            },
400            "test_field_imei": {
401              "value": {
402                "": {
403                  "rem": [
404                    [
405                      "project:1",
406                      "s",
407                      0,
408                      8
409                    ]
410                  ],
411                  "len": 15
412                }
413              }
414            },
415            "test_field_mac": {
416              "value": {
417                "": {
418                  "rem": [
419                    [
420                      "project:0",
421                      "s",
422                      0,
423                      8
424                    ]
425                  ],
426                  "len": 17
427                }
428              }
429            },
430            "test_field_regex_passes": {
431              "value": {
432                "": {
433                  "rem": [
434                    [
435                      "project:3",
436                      "s",
437                      0,
438                      7
439                    ]
440                  ],
441                  "len": 4
442                }
443              }
444            },
445            "test_field_uuid": {
446              "value": {
447                "": {
448                  "rem": [
449                    [
450                      "project:2",
451                      "s",
452                      0,
453                      3
454                    ]
455                  ],
456                  "len": 36
457                }
458              }
459            },
460            "url.path": {
461              "value": {
462                "": {
463                  "rem": [
464                    [
465                      "project:6",
466                      "s",
467                      0,
468                      10
469                    ]
470                  ],
471                  "len": 9
472                }
473              }
474            },
475            "user.name": {
476              "value": {
477                "": {
478                  "rem": [
479                    [
480                      "project:4",
481                      "s",
482                      0,
483                      11
484                    ]
485                  ],
486                  "len": 9
487                }
488              }
489            }
490          }
491        }
492        "###);
493    }
494
495    #[test]
496    fn test_scrub_attributes_sensitive_fields() {
497        let json = r#"
498        {
499            "normal_field": {
500                "type": "string",
501                "value": "normal_data"
502            },
503            "sensitive_custom": {
504                "type": "string",
505                "value": "should_be_removed"
506            },
507            "another_sensitive": {
508                "type": "integer",
509                "value": 42
510            },
511            "my_value": {
512                "type": "string",
513                "value": "this_should_be_removed_as_sensitive"
514            }
515        }
516        "#;
517
518        let mut data = Annotated::<Attributes>::from_json(json).unwrap();
519
520        let scrubbing_config = DataScrubbingConfig {
521            scrub_data: true,
522            scrub_defaults: false,
523            scrub_ip_addresses: false,
524            sensitive_fields: vec![
525                "value".to_owned(), // Make sure the inner 'value' of the attribute object isn't scrubbed.
526                "sensitive_custom".to_owned(),
527                "another_sensitive".to_owned(),
528            ],
529            ..Default::default()
530        };
531
532        let scrubbing_config = scrubbing_config.pii_config();
533
534        eap::scrub(ValueType::Span, &mut data, None, scrubbing_config.as_ref()).unwrap();
535
536        insta::assert_json_snapshot!(SerializableAnnotated(&data), @r###"
537        {
538          "another_sensitive": {
539            "type": "integer",
540            "value": null
541          },
542          "my_value": {
543            "type": "string",
544            "value": "[Filtered]"
545          },
546          "normal_field": {
547            "type": "string",
548            "value": "normal_data"
549          },
550          "sensitive_custom": {
551            "type": "string",
552            "value": "[Filtered]"
553          },
554          "_meta": {
555            "another_sensitive": {
556              "value": {
557                "": {
558                  "rem": [
559                    [
560                      "strip-fields",
561                      "x"
562                    ]
563                  ]
564                }
565              }
566            },
567            "my_value": {
568              "value": {
569                "": {
570                  "rem": [
571                    [
572                      "strip-fields",
573                      "s",
574                      0,
575                      10
576                    ]
577                  ],
578                  "len": 35
579                }
580              }
581            },
582            "sensitive_custom": {
583              "value": {
584                "": {
585                  "rem": [
586                    [
587                      "strip-fields",
588                      "s",
589                      0,
590                      10
591                    ]
592                  ],
593                  "len": 17
594                }
595              }
596            }
597          }
598        }
599        "###);
600    }
601
602    #[test]
603    fn test_scrub_attributes_safe_fields() {
604        let json = r#"
605        {
606            "password": {
607                "type": "string",
608                "value": "secret123"
609            },
610            "credit_card": {
611                "type": "string",
612                "value": "4242424242424242"
613            },
614            "secret": {
615                "type": "string",
616                "value": "this_should_stay"
617            }
618        }
619        "#;
620
621        let mut data = Annotated::<Attributes>::from_json(json).unwrap();
622
623        let scrubbing_config = DataScrubbingConfig {
624            scrub_data: true,
625            scrub_defaults: true,
626            scrub_ip_addresses: false,
627            exclude_fields: vec!["secret".to_owned()], // Only 'secret' is safe
628            ..Default::default()
629        };
630
631        let scrubbing_config = scrubbing_config.pii_config();
632
633        eap::scrub(ValueType::Span, &mut data, None, scrubbing_config.as_ref()).unwrap();
634
635        insta::assert_json_snapshot!(SerializableAnnotated(&data), @r###"
636        {
637          "credit_card": {
638            "type": "string",
639            "value": "[Filtered]"
640          },
641          "password": {
642            "type": "string",
643            "value": "[Filtered]"
644          },
645          "secret": {
646            "type": "string",
647            "value": "this_should_stay"
648          },
649          "_meta": {
650            "credit_card": {
651              "value": {
652                "": {
653                  "rem": [
654                    [
655                      "@creditcard:filter",
656                      "s",
657                      0,
658                      10
659                    ]
660                  ],
661                  "len": 16
662                }
663              }
664            },
665            "password": {
666              "value": {
667                "": {
668                  "rem": [
669                    [
670                      "@password:filter",
671                      "s",
672                      0,
673                      10
674                    ]
675                  ],
676                  "len": 9
677                }
678              }
679            }
680          }
681        }
682        "###);
683    }
684
685    #[test]
686    fn test_scrub_attributes_implicit_attribute_value() {
687        let json = r#"
688        {
689            "remove_this_string_abc123": {
690                "type": "string",
691                "value": "abc123"
692            }
693        }
694        "#;
695
696        let mut data = Annotated::<Attributes>::from_json(json).unwrap();
697
698        let config = serde_json::from_value::<PiiConfig>(serde_json::json!({
699            "rules": {
700                "remove_abc123": {
701                    "type": "pattern",
702                    "pattern": "abc123",
703                    "redaction": {
704                        "method": "replace",
705                        "text": "abc---"
706                    }
707                },
708            },
709            "applications": {
710                "remove_this_string_abc123": ["remove_abc123"], // This selector should NOT match
711            }
712        }))
713        .unwrap();
714
715        let scrubbing_config = DataScrubbingConfig {
716            scrub_data: true,
717            scrub_defaults: true,
718            ..Default::default()
719        };
720
721        let scrubbing_config = scrubbing_config.pii_config();
722
723        eap::scrub(
724            ValueType::OurLog,
725            &mut data,
726            Some(&config),
727            scrubbing_config.as_ref(),
728        )
729        .unwrap();
730
731        insta::assert_json_snapshot!(SerializableAnnotated(&data), @r###"
732        {
733          "remove_this_string_abc123": {
734            "type": "string",
735            "value": "abc123"
736          }
737        }
738        "###);
739
740        let config = serde_json::from_value::<PiiConfig>(serde_json::json!({
741            "rules": {
742                "remove_abc123": {
743                    "type": "pattern",
744                    "pattern": "abc123",
745                    "redaction": {
746                        "method": "replace",
747                        "text": "abc---"
748                    }
749                },
750            },
751            "applications": {
752                "remove_this_string_abc123.value": ["remove_abc123"],
753            }
754        }))
755        .unwrap();
756
757        eap::scrub(
758            ValueType::OurLog,
759            &mut data,
760            Some(&config),
761            scrubbing_config.as_ref(),
762        )
763        .unwrap();
764
765        insta::assert_json_snapshot!(SerializableAnnotated(&data), @r###"
766        {
767          "remove_this_string_abc123": {
768            "type": "string",
769            "value": "abc---"
770          },
771          "_meta": {
772            "remove_this_string_abc123": {
773              "value": {
774                "": {
775                  "rem": [
776                    [
777                      "remove_abc123",
778                      "s",
779                      0,
780                      6
781                    ]
782                  ],
783                  "len": 6
784                }
785              }
786            }
787          }
788        }
789        "###);
790    }
791
792    macro_rules! attribute_rule_test {
793        ($test_name:ident, $rule_type:expr, $test_value:expr, @$snapshot:literal) => {
794            #[test]
795            fn $test_name() {
796                let config = serde_json::from_value::<PiiConfig>(serde_json::json!({
797                    "applications": {
798                        "$string": [$rule_type]
799                    }
800                }))
801                .unwrap();
802
803                let mut scrubbing_config = crate::DataScrubbingConfig::default();
804                scrubbing_config.scrub_data = true;
805                scrubbing_config.scrub_defaults = false;
806                scrubbing_config.scrub_ip_addresses = false;
807
808                let scrubbing_config = scrubbing_config.pii_config();
809
810                let span_json = format!(r#"{{
811                    "start_timestamp": 1544719859.0,
812                    "end_timestamp": 1544719860.0,
813                    "trace_id": "5b8efff798038103d269b633813fc60c",
814                    "span_id": "eee19b7ec3c1b174",
815                    "name": "test",
816                    "attributes": {{
817                        "{rule_type}|{value}": {{
818                            "type": "string",
819                            "value": "{value}"
820                        }}
821                    }}
822                }}"#,
823                rule_type = $rule_type,
824                value = $test_value
825                    .replace('\\', "\\\\")
826                    .replace('"', "\\\"")
827                    .replace('\n', "\\n")
828                );
829
830                let mut span = Annotated::<SpanV2>::from_json(&span_json).unwrap();
831
832                eap::scrub(ValueType::Span, &mut span, Some(&config), scrubbing_config.as_ref()).unwrap();
833
834                insta::allow_duplicates!(insta::assert_json_snapshot!(SerializableAnnotated(&span.value().unwrap().attributes), @$snapshot));
835
836                let log_json = format!(r#"{{
837                    "timestamp": 1544719860.0,
838                    "trace_id": "5b8efff798038103d269b633813fc60c",
839                    "span_id": "eee19b7ec3c1b174",
840                    "level": "info",
841                    "body": "Test log",
842                    "attributes": {{
843                        "{rule_type}|{value}": {{
844                            "type": "string",
845                            "value": "{value}"
846                        }}
847                    }}
848                }}"#,
849                rule_type = $rule_type,
850                value = $test_value
851                    .replace('\\', "\\\\")
852                    .replace('"', "\\\"")
853                    .replace('\n', "\\n")
854                );
855
856                let mut log = Annotated::<OurLog>::from_json(&log_json).unwrap();
857
858                eap::scrub(ValueType::OurLog, &mut log, Some(&config), scrubbing_config.as_ref()).unwrap();
859
860                insta::allow_duplicates!(insta::assert_json_snapshot!(SerializableAnnotated(&log.value().unwrap().attributes), @$snapshot));
861
862                let metric_json = format!(r#"{{
863                    "timestamp": 1544719860.0,
864                    "trace_id": "5b8efff798038103d269b633813fc60c",
865                    "name": "test.metric",
866                    "type": "counter",
867                    "value": 1.0,
868                    "attributes": {{
869                        "{rule_type}|{value}": {{
870                            "type": "string",
871                            "value": "{value}"
872                        }}
873                    }}
874                }}"#,
875                rule_type = $rule_type,
876                value = $test_value
877                    .replace('\\', "\\\\")
878                    .replace('"', "\\\"")
879                    .replace('\n', "\\n")
880                );
881
882                let mut metric = Annotated::<TraceMetric>::from_json(&metric_json).unwrap();
883
884                eap::scrub(ValueType::TraceMetric, &mut metric, Some(&config), scrubbing_config.as_ref()).unwrap();
885
886                insta::allow_duplicates!(insta::assert_json_snapshot!(SerializableAnnotated(&metric.value().unwrap().attributes), @$snapshot));
887            }
888        };
889    }
890
891    // IP rules
892    attribute_rule_test!(test_scrub_attributes_pii_string_rules_ip, "@ip", "127.0.0.1", @r###"
893    {
894      "@ip|127.0.0.1": {
895        "type": "string",
896        "value": "[ip]"
897      },
898      "_meta": {
899        "@ip|127.0.0.1": {
900          "value": {
901            "": {
902              "rem": [
903                [
904                  "@ip",
905                  "s",
906                  0,
907                  4
908                ]
909              ],
910              "len": 9
911            }
912          }
913        }
914      }
915    }
916    "###);
917
918    attribute_rule_test!(test_scrub_attributes_pii_string_rules_ip_replace, "@ip:replace", "127.0.0.1", @r###"
919    {
920      "@ip:replace|127.0.0.1": {
921        "type": "string",
922        "value": "[ip]"
923      },
924      "_meta": {
925        "@ip:replace|127.0.0.1": {
926          "value": {
927            "": {
928              "rem": [
929                [
930                  "@ip:replace",
931                  "s",
932                  0,
933                  4
934                ]
935              ],
936              "len": 9
937            }
938          }
939        }
940      }
941    }
942    "###);
943
944    attribute_rule_test!(test_scrub_attributes_pii_string_rules_ip_remove, "@ip:remove", "127.0.0.1", @r###"
945    {
946      "@ip:remove|127.0.0.1": {
947        "type": "string",
948        "value": ""
949      },
950      "_meta": {
951        "@ip:remove|127.0.0.1": {
952          "value": {
953            "": {
954              "rem": [
955                [
956                  "@ip:remove",
957                  "x",
958                  0,
959                  0
960                ]
961              ],
962              "len": 9
963            }
964          }
965        }
966      }
967    }
968    "###);
969
970    attribute_rule_test!(test_scrub_attributes_pii_string_rules_ip_mask, "@ip:mask", "127.0.0.1", @r###"
971    {
972      "@ip:mask|127.0.0.1": {
973        "type": "string",
974        "value": "*********"
975      },
976      "_meta": {
977        "@ip:mask|127.0.0.1": {
978          "value": {
979            "": {
980              "rem": [
981                [
982                  "@ip:mask",
983                  "m",
984                  0,
985                  9
986                ]
987              ],
988              "len": 9
989            }
990          }
991        }
992      }
993    }
994    "###);
995
996    // Email rules
997    attribute_rule_test!(test_scrub_attributes_pii_string_rules_email, "@email", "test@example.com", @r###"
998    {
999      "@email|test@example.com": {
1000        "type": "string",
1001        "value": "[email]"
1002      },
1003      "_meta": {
1004        "@email|test@example.com": {
1005          "value": {
1006            "": {
1007              "rem": [
1008                [
1009                  "@email",
1010                  "s",
1011                  0,
1012                  7
1013                ]
1014              ],
1015              "len": 16
1016            }
1017          }
1018        }
1019      }
1020    }
1021    "###);
1022
1023    attribute_rule_test!(test_scrub_attributes_pii_string_rules_email_replace, "@email:replace", "test@example.com", @r###"
1024    {
1025      "@email:replace|test@example.com": {
1026        "type": "string",
1027        "value": "[email]"
1028      },
1029      "_meta": {
1030        "@email:replace|test@example.com": {
1031          "value": {
1032            "": {
1033              "rem": [
1034                [
1035                  "@email:replace",
1036                  "s",
1037                  0,
1038                  7
1039                ]
1040              ],
1041              "len": 16
1042            }
1043          }
1044        }
1045      }
1046    }
1047    "###);
1048
1049    attribute_rule_test!(test_scrub_attributes_pii_string_rules_email_remove, "@email:remove", "test@example.com", @r###"
1050    {
1051      "@email:remove|test@example.com": {
1052        "type": "string",
1053        "value": ""
1054      },
1055      "_meta": {
1056        "@email:remove|test@example.com": {
1057          "value": {
1058            "": {
1059              "rem": [
1060                [
1061                  "@email:remove",
1062                  "x",
1063                  0,
1064                  0
1065                ]
1066              ],
1067              "len": 16
1068            }
1069          }
1070        }
1071      }
1072    }
1073    "###);
1074
1075    attribute_rule_test!(test_scrub_attributes_pii_string_rules_email_mask, "@email:mask", "test@example.com", @r###"
1076    {
1077      "@email:mask|test@example.com": {
1078        "type": "string",
1079        "value": "****************"
1080      },
1081      "_meta": {
1082        "@email:mask|test@example.com": {
1083          "value": {
1084            "": {
1085              "rem": [
1086                [
1087                  "@email:mask",
1088                  "m",
1089                  0,
1090                  16
1091                ]
1092              ],
1093              "len": 16
1094            }
1095          }
1096        }
1097      }
1098    }
1099    "###);
1100
1101    // Credit card rules
1102    attribute_rule_test!(test_scrub_attributes_pii_string_rules_creditcard, "@creditcard", "4242424242424242", @r###"
1103    {
1104      "@creditcard|4242424242424242": {
1105        "type": "string",
1106        "value": "[creditcard]"
1107      },
1108      "_meta": {
1109        "@creditcard|4242424242424242": {
1110          "value": {
1111            "": {
1112              "rem": [
1113                [
1114                  "@creditcard",
1115                  "s",
1116                  0,
1117                  12
1118                ]
1119              ],
1120              "len": 16
1121            }
1122          }
1123        }
1124      }
1125    }
1126    "###);
1127
1128    attribute_rule_test!(test_scrub_attributes_pii_string_rules_creditcard_replace, "@creditcard:replace", "4242424242424242", @r###"
1129    {
1130      "@creditcard:replace|4242424242424242": {
1131        "type": "string",
1132        "value": "[creditcard]"
1133      },
1134      "_meta": {
1135        "@creditcard:replace|4242424242424242": {
1136          "value": {
1137            "": {
1138              "rem": [
1139                [
1140                  "@creditcard:replace",
1141                  "s",
1142                  0,
1143                  12
1144                ]
1145              ],
1146              "len": 16
1147            }
1148          }
1149        }
1150      }
1151    }
1152    "###);
1153
1154    attribute_rule_test!(test_scrub_attributes_pii_string_rules_creditcard_remove, "@creditcard:remove", "4242424242424242", @r###"
1155    {
1156      "@creditcard:remove|4242424242424242": {
1157        "type": "string",
1158        "value": ""
1159      },
1160      "_meta": {
1161        "@creditcard:remove|4242424242424242": {
1162          "value": {
1163            "": {
1164              "rem": [
1165                [
1166                  "@creditcard:remove",
1167                  "x",
1168                  0,
1169                  0
1170                ]
1171              ],
1172              "len": 16
1173            }
1174          }
1175        }
1176      }
1177    }
1178    "###);
1179
1180    attribute_rule_test!(test_scrub_attributes_pii_string_rules_creditcard_mask, "@creditcard:mask", "4242424242424242", @r###"
1181    {
1182      "@creditcard:mask|4242424242424242": {
1183        "type": "string",
1184        "value": "****************"
1185      },
1186      "_meta": {
1187        "@creditcard:mask|4242424242424242": {
1188          "value": {
1189            "": {
1190              "rem": [
1191                [
1192                  "@creditcard:mask",
1193                  "m",
1194                  0,
1195                  16
1196                ]
1197              ],
1198              "len": 16
1199            }
1200          }
1201        }
1202      }
1203    }
1204    "###);
1205
1206    // IBAN rules
1207    attribute_rule_test!(test_scrub_attributes_pii_string_rules_iban, "@iban", "DE89370400440532013000", @r###"
1208    {
1209      "@iban|DE89370400440532013000": {
1210        "type": "string",
1211        "value": "[iban]"
1212      },
1213      "_meta": {
1214        "@iban|DE89370400440532013000": {
1215          "value": {
1216            "": {
1217              "rem": [
1218                [
1219                  "@iban",
1220                  "s",
1221                  0,
1222                  6
1223                ]
1224              ],
1225              "len": 22
1226            }
1227          }
1228        }
1229      }
1230    }
1231    "###);
1232
1233    attribute_rule_test!(test_scrub_attributes_pii_string_rules_iban_replace, "@iban:replace", "DE89370400440532013000", @r###"
1234    {
1235      "@iban:replace|DE89370400440532013000": {
1236        "type": "string",
1237        "value": "[iban]"
1238      },
1239      "_meta": {
1240        "@iban:replace|DE89370400440532013000": {
1241          "value": {
1242            "": {
1243              "rem": [
1244                [
1245                  "@iban:replace",
1246                  "s",
1247                  0,
1248                  6
1249                ]
1250              ],
1251              "len": 22
1252            }
1253          }
1254        }
1255      }
1256    }
1257    "###);
1258
1259    attribute_rule_test!(test_scrub_attributes_pii_string_rules_iban_remove, "@iban:remove", "DE89370400440532013000", @r###"
1260    {
1261      "@iban:remove|DE89370400440532013000": {
1262        "type": "string",
1263        "value": ""
1264      },
1265      "_meta": {
1266        "@iban:remove|DE89370400440532013000": {
1267          "value": {
1268            "": {
1269              "rem": [
1270                [
1271                  "@iban:remove",
1272                  "x",
1273                  0,
1274                  0
1275                ]
1276              ],
1277              "len": 22
1278            }
1279          }
1280        }
1281      }
1282    }
1283    "###);
1284
1285    attribute_rule_test!(test_scrub_attributes_pii_string_rules_iban_mask, "@iban:mask", "DE89370400440532013000", @r###"
1286    {
1287      "@iban:mask|DE89370400440532013000": {
1288        "type": "string",
1289        "value": "**********************"
1290      },
1291      "_meta": {
1292        "@iban:mask|DE89370400440532013000": {
1293          "value": {
1294            "": {
1295              "rem": [
1296                [
1297                  "@iban:mask",
1298                  "m",
1299                  0,
1300                  22
1301                ]
1302              ],
1303              "len": 22
1304            }
1305          }
1306        }
1307      }
1308    }
1309    "###);
1310
1311    // MAC address rules
1312    attribute_rule_test!(test_scrub_attributes_pii_string_rules_mac, "@mac", "4a:00:04:10:9b:50", @r###"
1313    {
1314      "@mac|4a:00:04:10:9b:50": {
1315        "type": "string",
1316        "value": "*****************"
1317      },
1318      "_meta": {
1319        "@mac|4a:00:04:10:9b:50": {
1320          "value": {
1321            "": {
1322              "rem": [
1323                [
1324                  "@mac",
1325                  "m",
1326                  0,
1327                  17
1328                ]
1329              ],
1330              "len": 17
1331            }
1332          }
1333        }
1334      }
1335    }
1336    "###);
1337
1338    attribute_rule_test!(test_scrub_attributes_pii_string_rules_mac_replace, "@mac:replace", "4a:00:04:10:9b:50", @r###"
1339    {
1340      "@mac:replace|4a:00:04:10:9b:50": {
1341        "type": "string",
1342        "value": "[mac]"
1343      },
1344      "_meta": {
1345        "@mac:replace|4a:00:04:10:9b:50": {
1346          "value": {
1347            "": {
1348              "rem": [
1349                [
1350                  "@mac:replace",
1351                  "s",
1352                  0,
1353                  5
1354                ]
1355              ],
1356              "len": 17
1357            }
1358          }
1359        }
1360      }
1361    }
1362    "###);
1363
1364    attribute_rule_test!(test_scrub_attributes_pii_string_rules_mac_remove, "@mac:remove", "4a:00:04:10:9b:50", @r###"
1365    {
1366      "@mac:remove|4a:00:04:10:9b:50": {
1367        "type": "string",
1368        "value": ""
1369      },
1370      "_meta": {
1371        "@mac:remove|4a:00:04:10:9b:50": {
1372          "value": {
1373            "": {
1374              "rem": [
1375                [
1376                  "@mac:remove",
1377                  "x",
1378                  0,
1379                  0
1380                ]
1381              ],
1382              "len": 17
1383            }
1384          }
1385        }
1386      }
1387    }
1388    "###);
1389
1390    attribute_rule_test!(test_scrub_attributes_pii_string_rules_mac_mask, "@mac:mask", "4a:00:04:10:9b:50", @r###"
1391    {
1392      "@mac:mask|4a:00:04:10:9b:50": {
1393        "type": "string",
1394        "value": "*****************"
1395      },
1396      "_meta": {
1397        "@mac:mask|4a:00:04:10:9b:50": {
1398          "value": {
1399            "": {
1400              "rem": [
1401                [
1402                  "@mac:mask",
1403                  "m",
1404                  0,
1405                  17
1406                ]
1407              ],
1408              "len": 17
1409            }
1410          }
1411        }
1412      }
1413    }
1414    "###);
1415
1416    // UUID rules
1417    attribute_rule_test!(test_scrub_attributes_pii_string_rules_uuid, "@uuid", "ceee0822-ed8f-4622-b2a3-789e73e75cd1", @r###"
1418    {
1419      "@uuid|ceee0822-ed8f-4622-b2a3-789e73e75cd1": {
1420        "type": "string",
1421        "value": "************************************"
1422      },
1423      "_meta": {
1424        "@uuid|ceee0822-ed8f-4622-b2a3-789e73e75cd1": {
1425          "value": {
1426            "": {
1427              "rem": [
1428                [
1429                  "@uuid",
1430                  "m",
1431                  0,
1432                  36
1433                ]
1434              ],
1435              "len": 36
1436            }
1437          }
1438        }
1439      }
1440    }
1441    "###);
1442
1443    attribute_rule_test!(test_scrub_attributes_pii_string_rules_uuid_replace, "@uuid:replace", "ceee0822-ed8f-4622-b2a3-789e73e75cd1", @r###"
1444    {
1445      "@uuid:replace|ceee0822-ed8f-4622-b2a3-789e73e75cd1": {
1446        "type": "string",
1447        "value": "[uuid]"
1448      },
1449      "_meta": {
1450        "@uuid:replace|ceee0822-ed8f-4622-b2a3-789e73e75cd1": {
1451          "value": {
1452            "": {
1453              "rem": [
1454                [
1455                  "@uuid:replace",
1456                  "s",
1457                  0,
1458                  6
1459                ]
1460              ],
1461              "len": 36
1462            }
1463          }
1464        }
1465      }
1466    }
1467    "###);
1468
1469    attribute_rule_test!(test_scrub_attributes_pii_string_rules_uuid_remove, "@uuid:remove", "ceee0822-ed8f-4622-b2a3-789e73e75cd1", @r###"
1470    {
1471      "@uuid:remove|ceee0822-ed8f-4622-b2a3-789e73e75cd1": {
1472        "type": "string",
1473        "value": ""
1474      },
1475      "_meta": {
1476        "@uuid:remove|ceee0822-ed8f-4622-b2a3-789e73e75cd1": {
1477          "value": {
1478            "": {
1479              "rem": [
1480                [
1481                  "@uuid:remove",
1482                  "x",
1483                  0,
1484                  0
1485                ]
1486              ],
1487              "len": 36
1488            }
1489          }
1490        }
1491      }
1492    }
1493    "###);
1494
1495    attribute_rule_test!(test_scrub_attributes_pii_string_rules_uuid_mask, "@uuid:mask", "ceee0822-ed8f-4622-b2a3-789e73e75cd1", @r###"
1496    {
1497      "@uuid:mask|ceee0822-ed8f-4622-b2a3-789e73e75cd1": {
1498        "type": "string",
1499        "value": "************************************"
1500      },
1501      "_meta": {
1502        "@uuid:mask|ceee0822-ed8f-4622-b2a3-789e73e75cd1": {
1503          "value": {
1504            "": {
1505              "rem": [
1506                [
1507                  "@uuid:mask",
1508                  "m",
1509                  0,
1510                  36
1511                ]
1512              ],
1513              "len": 36
1514            }
1515          }
1516        }
1517      }
1518    }
1519    "###);
1520
1521    // IMEI rules
1522    attribute_rule_test!(test_scrub_attributes_pii_string_rules_imei, "@imei", "356938035643809", @r###"
1523    {
1524      "@imei|356938035643809": {
1525        "type": "string",
1526        "value": "[imei]"
1527      },
1528      "_meta": {
1529        "@imei|356938035643809": {
1530          "value": {
1531            "": {
1532              "rem": [
1533                [
1534                  "@imei",
1535                  "s",
1536                  0,
1537                  6
1538                ]
1539              ],
1540              "len": 15
1541            }
1542          }
1543        }
1544      }
1545    }
1546    "###);
1547
1548    attribute_rule_test!(test_scrub_attributes_pii_string_rules_imei_replace, "@imei:replace", "356938035643809", @r###"
1549    {
1550      "@imei:replace|356938035643809": {
1551        "type": "string",
1552        "value": "[imei]"
1553      },
1554      "_meta": {
1555        "@imei:replace|356938035643809": {
1556          "value": {
1557            "": {
1558              "rem": [
1559                [
1560                  "@imei:replace",
1561                  "s",
1562                  0,
1563                  6
1564                ]
1565              ],
1566              "len": 15
1567            }
1568          }
1569        }
1570      }
1571    }
1572    "###);
1573
1574    attribute_rule_test!(test_scrub_attributes_pii_string_rules_imei_remove, "@imei:remove", "356938035643809", @r###"
1575    {
1576      "@imei:remove|356938035643809": {
1577        "type": "string",
1578        "value": ""
1579      },
1580      "_meta": {
1581        "@imei:remove|356938035643809": {
1582          "value": {
1583            "": {
1584              "rem": [
1585                [
1586                  "@imei:remove",
1587                  "x",
1588                  0,
1589                  0
1590                ]
1591              ],
1592              "len": 15
1593            }
1594          }
1595        }
1596      }
1597    }
1598    "###);
1599
1600    // PEM key rules
1601    attribute_rule_test!(
1602        test_scrub_attributes_pii_string_rules_pemkey,
1603        "@pemkey",
1604        "-----BEGIN EC PRIVATE KEY-----\nMIHbAgEBBEFbLvIaAaez3q0u6BQYMHZ28B7iSdMPPaODUMGkdorl3ShgTbYmzqGL\n-----END EC PRIVATE KEY-----",
1605    @r###"
1606    {
1607      "@pemkey|-----BEGIN EC PRIVATE KEY-----\nMIHbAgEBBEFbLvIaAaez3q0u6BQYMHZ28B7iSdMPPaODUMGkdorl3ShgTbYmzqGL\n-----END EC PRIVATE KEY-----": {
1608        "type": "string",
1609        "value": "-----BEGIN EC PRIVATE KEY-----\n[pemkey]\n-----END EC PRIVATE KEY-----"
1610      },
1611      "_meta": {
1612        "@pemkey|-----BEGIN EC PRIVATE KEY-----\nMIHbAgEBBEFbLvIaAaez3q0u6BQYMHZ28B7iSdMPPaODUMGkdorl3ShgTbYmzqGL\n-----END EC PRIVATE KEY-----": {
1613          "value": {
1614            "": {
1615              "rem": [
1616                [
1617                  "@pemkey",
1618                  "s",
1619                  31,
1620                  39
1621                ]
1622              ],
1623              "len": 124
1624            }
1625          }
1626        }
1627      }
1628    }
1629    "###);
1630
1631    attribute_rule_test!(
1632        test_scrub_attributes_pii_string_rules_pemkey_replace,
1633        "@pemkey:replace",
1634        "-----BEGIN EC PRIVATE KEY-----\nMIHbAgEBBEFbLvIaAaez3q0u6BQYMHZ28B7iSdMPPaODUMGkdorl3ShgTbYmzqGL\n-----END EC PRIVATE KEY-----",
1635    @r###"
1636    {
1637      "@pemkey:replace|-----BEGIN EC PRIVATE KEY-----\nMIHbAgEBBEFbLvIaAaez3q0u6BQYMHZ28B7iSdMPPaODUMGkdorl3ShgTbYmzqGL\n-----END EC PRIVATE KEY-----": {
1638        "type": "string",
1639        "value": "-----BEGIN EC PRIVATE KEY-----\n[pemkey]\n-----END EC PRIVATE KEY-----"
1640      },
1641      "_meta": {
1642        "@pemkey:replace|-----BEGIN EC PRIVATE KEY-----\nMIHbAgEBBEFbLvIaAaez3q0u6BQYMHZ28B7iSdMPPaODUMGkdorl3ShgTbYmzqGL\n-----END EC PRIVATE KEY-----": {
1643          "value": {
1644            "": {
1645              "rem": [
1646                [
1647                  "@pemkey:replace",
1648                  "s",
1649                  31,
1650                  39
1651                ]
1652              ],
1653              "len": 124
1654            }
1655          }
1656        }
1657      }
1658    }
1659    "###);
1660
1661    attribute_rule_test!(
1662        test_scrub_attributes_pii_string_rules_pemkey_remove,
1663        "@pemkey:remove",
1664        "-----BEGIN EC PRIVATE KEY-----\nMIHbAgEBBEFbLvIaAaez3q0u6BQYMHZ28B7iSdMPPaODUMGkdorl3ShgTbYmzqGL\n-----END EC PRIVATE KEY-----",
1665    @r###"
1666    {
1667      "@pemkey:remove|-----BEGIN EC PRIVATE KEY-----\nMIHbAgEBBEFbLvIaAaez3q0u6BQYMHZ28B7iSdMPPaODUMGkdorl3ShgTbYmzqGL\n-----END EC PRIVATE KEY-----": {
1668        "type": "string",
1669        "value": "-----BEGIN EC PRIVATE KEY-----\n\n-----END EC PRIVATE KEY-----"
1670      },
1671      "_meta": {
1672        "@pemkey:remove|-----BEGIN EC PRIVATE KEY-----\nMIHbAgEBBEFbLvIaAaez3q0u6BQYMHZ28B7iSdMPPaODUMGkdorl3ShgTbYmzqGL\n-----END EC PRIVATE KEY-----": {
1673          "value": {
1674            "": {
1675              "rem": [
1676                [
1677                  "@pemkey:remove",
1678                  "x",
1679                  31,
1680                  31
1681                ]
1682              ],
1683              "len": 124
1684            }
1685          }
1686        }
1687      }
1688    }
1689    "###);
1690
1691    // URL auth rules
1692    attribute_rule_test!(test_scrub_attributes_pii_string_rules_urlauth, "@urlauth", "https://username:password@example.com/", @r###"
1693    {
1694      "@urlauth|https://username:password@example.com/": {
1695        "type": "string",
1696        "value": "https://[auth]@example.com/"
1697      },
1698      "_meta": {
1699        "@urlauth|https://username:password@example.com/": {
1700          "value": {
1701            "": {
1702              "rem": [
1703                [
1704                  "@urlauth",
1705                  "s",
1706                  8,
1707                  14
1708                ]
1709              ],
1710              "len": 38
1711            }
1712          }
1713        }
1714      }
1715    }
1716    "###);
1717
1718    attribute_rule_test!(test_scrub_attributes_pii_string_rules_urlauth_replace, "@urlauth:replace", "https://username:password@example.com/", @r###"
1719    {
1720      "@urlauth:replace|https://username:password@example.com/": {
1721        "type": "string",
1722        "value": "https://[auth]@example.com/"
1723      },
1724      "_meta": {
1725        "@urlauth:replace|https://username:password@example.com/": {
1726          "value": {
1727            "": {
1728              "rem": [
1729                [
1730                  "@urlauth:replace",
1731                  "s",
1732                  8,
1733                  14
1734                ]
1735              ],
1736              "len": 38
1737            }
1738          }
1739        }
1740      }
1741    }
1742    "###);
1743
1744    attribute_rule_test!(test_scrub_attributes_pii_string_rules_urlauth_remove, "@urlauth:remove", "https://username:password@example.com/", @r###"
1745    {
1746      "@urlauth:remove|https://username:password@example.com/": {
1747        "type": "string",
1748        "value": "https://@example.com/"
1749      },
1750      "_meta": {
1751        "@urlauth:remove|https://username:password@example.com/": {
1752          "value": {
1753            "": {
1754              "rem": [
1755                [
1756                  "@urlauth:remove",
1757                  "x",
1758                  8,
1759                  8
1760                ]
1761              ],
1762              "len": 38
1763            }
1764          }
1765        }
1766      }
1767    }
1768    "###);
1769
1770    attribute_rule_test!(test_scrub_attributes_pii_string_rules_urlauth_mask, "@urlauth:mask", "https://username:password@example.com/", @r###"
1771    {
1772      "@urlauth:mask|https://username:password@example.com/": {
1773        "type": "string",
1774        "value": "https://*****************@example.com/"
1775      },
1776      "_meta": {
1777        "@urlauth:mask|https://username:password@example.com/": {
1778          "value": {
1779            "": {
1780              "rem": [
1781                [
1782                  "@urlauth:mask",
1783                  "m",
1784                  8,
1785                  25
1786                ]
1787              ],
1788              "len": 38
1789            }
1790          }
1791        }
1792      }
1793    }
1794    "###);
1795
1796    // US SSN rules
1797    attribute_rule_test!(test_scrub_attributes_pii_string_rules_usssn, "@usssn", "078-05-1120", @r###"
1798    {
1799      "@usssn|078-05-1120": {
1800        "type": "string",
1801        "value": "***********"
1802      },
1803      "_meta": {
1804        "@usssn|078-05-1120": {
1805          "value": {
1806            "": {
1807              "rem": [
1808                [
1809                  "@usssn",
1810                  "m",
1811                  0,
1812                  11
1813                ]
1814              ],
1815              "len": 11
1816            }
1817          }
1818        }
1819      }
1820    }
1821    "###);
1822
1823    attribute_rule_test!(test_scrub_attributes_pii_string_rules_usssn_replace, "@usssn:replace", "078-05-1120", @r###"
1824    {
1825      "@usssn:replace|078-05-1120": {
1826        "type": "string",
1827        "value": "[us-ssn]"
1828      },
1829      "_meta": {
1830        "@usssn:replace|078-05-1120": {
1831          "value": {
1832            "": {
1833              "rem": [
1834                [
1835                  "@usssn:replace",
1836                  "s",
1837                  0,
1838                  8
1839                ]
1840              ],
1841              "len": 11
1842            }
1843          }
1844        }
1845      }
1846    }
1847    "###);
1848
1849    attribute_rule_test!(test_scrub_attributes_pii_string_rules_usssn_remove, "@usssn:remove", "078-05-1120", @r###"
1850    {
1851      "@usssn:remove|078-05-1120": {
1852        "type": "string",
1853        "value": ""
1854      },
1855      "_meta": {
1856        "@usssn:remove|078-05-1120": {
1857          "value": {
1858            "": {
1859              "rem": [
1860                [
1861                  "@usssn:remove",
1862                  "x",
1863                  0,
1864                  0
1865                ]
1866              ],
1867              "len": 11
1868            }
1869          }
1870        }
1871      }
1872    }
1873    "###);
1874
1875    attribute_rule_test!(test_scrub_attributes_pii_string_rules_usssn_mask, "@usssn:mask", "078-05-1120", @r###"
1876    {
1877      "@usssn:mask|078-05-1120": {
1878        "type": "string",
1879        "value": "***********"
1880      },
1881      "_meta": {
1882        "@usssn:mask|078-05-1120": {
1883          "value": {
1884            "": {
1885              "rem": [
1886                [
1887                  "@usssn:mask",
1888                  "m",
1889                  0,
1890                  11
1891                ]
1892              ],
1893              "len": 11
1894            }
1895          }
1896        }
1897      }
1898    }
1899    "###);
1900
1901    // User path rules
1902    attribute_rule_test!(test_scrub_attributes_pii_string_rules_userpath, "@userpath", "/Users/john/Documents", @r###"
1903    {
1904      "@userpath|/Users/john/Documents": {
1905        "type": "string",
1906        "value": "/Users/[user]/Documents"
1907      },
1908      "_meta": {
1909        "@userpath|/Users/john/Documents": {
1910          "value": {
1911            "": {
1912              "rem": [
1913                [
1914                  "@userpath",
1915                  "s",
1916                  7,
1917                  13
1918                ]
1919              ],
1920              "len": 21
1921            }
1922          }
1923        }
1924      }
1925    }
1926    "###);
1927
1928    attribute_rule_test!(test_scrub_attributes_pii_string_rules_userpath_replace, "@userpath:replace", "/Users/john/Documents", @r###"
1929    {
1930      "@userpath:replace|/Users/john/Documents": {
1931        "type": "string",
1932        "value": "/Users/[user]/Documents"
1933      },
1934      "_meta": {
1935        "@userpath:replace|/Users/john/Documents": {
1936          "value": {
1937            "": {
1938              "rem": [
1939                [
1940                  "@userpath:replace",
1941                  "s",
1942                  7,
1943                  13
1944                ]
1945              ],
1946              "len": 21
1947            }
1948          }
1949        }
1950      }
1951    }
1952    "###);
1953
1954    attribute_rule_test!(test_scrub_attributes_pii_string_rules_userpath_remove, "@userpath:remove", "/Users/john/Documents", @r###"
1955    {
1956      "@userpath:remove|/Users/john/Documents": {
1957        "type": "string",
1958        "value": "/Users//Documents"
1959      },
1960      "_meta": {
1961        "@userpath:remove|/Users/john/Documents": {
1962          "value": {
1963            "": {
1964              "rem": [
1965                [
1966                  "@userpath:remove",
1967                  "x",
1968                  7,
1969                  7
1970                ]
1971              ],
1972              "len": 21
1973            }
1974          }
1975        }
1976      }
1977    }
1978    "###);
1979
1980    attribute_rule_test!(test_scrub_attributes_pii_string_rules_userpath_mask, "@userpath:mask", "/Users/john/Documents", @r###"
1981    {
1982      "@userpath:mask|/Users/john/Documents": {
1983        "type": "string",
1984        "value": "/Users/****/Documents"
1985      },
1986      "_meta": {
1987        "@userpath:mask|/Users/john/Documents": {
1988          "value": {
1989            "": {
1990              "rem": [
1991                [
1992                  "@userpath:mask",
1993                  "m",
1994                  7,
1995                  11
1996                ]
1997              ],
1998              "len": 21
1999            }
2000          }
2001        }
2002      }
2003    }
2004    "###);
2005
2006    // Password rules
2007    // @password defaults to remove
2008    attribute_rule_test!(test_scrub_attributes_pii_string_rules_password, "@password", "my_password_123", @r###"
2009    {
2010      "@password|my_password_123": {
2011        "type": "string",
2012        "value": ""
2013      },
2014      "_meta": {
2015        "@password|my_password_123": {
2016          "value": {
2017            "": {
2018              "rem": [
2019                [
2020                  "@password",
2021                  "x",
2022                  0,
2023                  0
2024                ]
2025              ],
2026              "len": 15
2027            }
2028          }
2029        }
2030      }
2031    }
2032    "###);
2033
2034    attribute_rule_test!(test_scrub_attributes_pii_string_rules_password_remove, "@password:remove", "my_password_123", @r###"
2035    {
2036      "@password:remove|my_password_123": {
2037        "type": "string",
2038        "value": ""
2039      },
2040      "_meta": {
2041        "@password:remove|my_password_123": {
2042          "value": {
2043            "": {
2044              "rem": [
2045                [
2046                  "@password:remove",
2047                  "x",
2048                  0,
2049                  0
2050                ]
2051              ],
2052              "len": 15
2053            }
2054          }
2055        }
2056      }
2057    }
2058    "###);
2059
2060    attribute_rule_test!(test_scrub_attributes_pii_string_rules_password_replace, "@password:replace", "my_password_123", @r###"
2061    {
2062      "@password:replace|my_password_123": {
2063        "type": "string",
2064        "value": "[password]"
2065      },
2066      "_meta": {
2067        "@password:replace|my_password_123": {
2068          "value": {
2069            "": {
2070              "rem": [
2071                [
2072                  "@password:replace",
2073                  "s",
2074                  0,
2075                  10
2076                ]
2077              ],
2078              "len": 15
2079            }
2080          }
2081        }
2082      }
2083    }
2084    "###);
2085
2086    attribute_rule_test!(test_scrub_attributes_pii_string_rules_password_mask, "@password:mask", "my_password_123", @r###"
2087    {
2088      "@password:mask|my_password_123": {
2089        "type": "string",
2090        "value": "***************"
2091      },
2092      "_meta": {
2093        "@password:mask|my_password_123": {
2094          "value": {
2095            "": {
2096              "rem": [
2097                [
2098                  "@password:mask",
2099                  "m",
2100                  0,
2101                  15
2102                ]
2103              ],
2104              "len": 15
2105            }
2106          }
2107        }
2108      }
2109    }
2110    "###);
2111
2112    // Bearer token rules
2113    attribute_rule_test!(test_scrub_attributes_pii_string_rules_bearer, "@bearer", "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9", @r###"
2114    {
2115      "@bearer|Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9": {
2116        "type": "string",
2117        "value": "Bearer [token]"
2118      },
2119      "_meta": {
2120        "@bearer|Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9": {
2121          "value": {
2122            "": {
2123              "rem": [
2124                [
2125                  "@bearer",
2126                  "s",
2127                  0,
2128                  14
2129                ]
2130              ],
2131              "len": 43
2132            }
2133          }
2134        }
2135      }
2136    }
2137    "###);
2138
2139    attribute_rule_test!(
2140        test_scrub_attributes_pii_string_rules_bearer_replace,
2141        "@bearer:replace",
2142        "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
2143    @r###"
2144    {
2145      "@bearer:replace|Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9": {
2146        "type": "string",
2147        "value": "Bearer [token]"
2148      },
2149      "_meta": {
2150        "@bearer:replace|Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9": {
2151          "value": {
2152            "": {
2153              "rem": [
2154                [
2155                  "@bearer:replace",
2156                  "s",
2157                  0,
2158                  14
2159                ]
2160              ],
2161              "len": 43
2162            }
2163          }
2164        }
2165      }
2166    }
2167    "###);
2168
2169    attribute_rule_test!(
2170        test_scrub_attributes_pii_string_rules_bearer_remove,
2171        "@bearer:remove",
2172        "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
2173    @r###"
2174    {
2175      "@bearer:remove|Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9": {
2176        "type": "string",
2177        "value": ""
2178      },
2179      "_meta": {
2180        "@bearer:remove|Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9": {
2181          "value": {
2182            "": {
2183              "rem": [
2184                [
2185                  "@bearer:remove",
2186                  "x",
2187                  0,
2188                  0
2189                ]
2190              ],
2191              "len": 43
2192            }
2193          }
2194        }
2195      }
2196    }
2197    "###);
2198
2199    attribute_rule_test!(
2200        test_scrub_attributes_pii_string_rules_bearer_mask,
2201        "@bearer:mask",
2202        "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
2203    @r###"
2204    {
2205      "@bearer:mask|Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9": {
2206        "type": "string",
2207        "value": "*******************************************"
2208      },
2209      "_meta": {
2210        "@bearer:mask|Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9": {
2211          "value": {
2212            "": {
2213              "rem": [
2214                [
2215                  "@bearer:mask",
2216                  "m",
2217                  0,
2218                  43
2219                ]
2220              ],
2221              "len": 43
2222            }
2223          }
2224        }
2225      }
2226    }
2227    "###);
2228}