1use relay_event_schema::processor::{
6    ProcessValue, ProcessingResult, ProcessingState, ValueType, process_value,
7};
8use relay_protocol::Annotated;
9
10use crate::{AttributeMode, PiiConfig, PiiProcessor};
11
12pub 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            .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            .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};
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        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(), "very_sensitive_data".to_owned(),
170            ],
171            ..Default::default()
172        };
173
174        let scrubbing_config = data_scrubbing_config.pii_config().unwrap();
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        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().unwrap();
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(), "sensitive_custom".to_owned(),
527                "another_sensitive".to_owned(),
528            ],
529            ..Default::default()
530        };
531
532        let scrubbing_config = scrubbing_config.pii_config().unwrap();
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()], ..Default::default()
629        };
630
631        let scrubbing_config = scrubbing_config.pii_config().unwrap();
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"], }
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().unwrap();
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().unwrap();
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        };
863    }
864
865    attribute_rule_test!(test_scrub_attributes_pii_string_rules_ip, "@ip", "127.0.0.1", @r###"
867    {
868      "@ip|127.0.0.1": {
869        "type": "string",
870        "value": "[ip]"
871      },
872      "_meta": {
873        "@ip|127.0.0.1": {
874          "value": {
875            "": {
876              "rem": [
877                [
878                  "@ip",
879                  "s",
880                  0,
881                  4
882                ]
883              ],
884              "len": 9
885            }
886          }
887        }
888      }
889    }
890    "###);
891
892    attribute_rule_test!(test_scrub_attributes_pii_string_rules_ip_replace, "@ip:replace", "127.0.0.1", @r###"
893    {
894      "@ip:replace|127.0.0.1": {
895        "type": "string",
896        "value": "[ip]"
897      },
898      "_meta": {
899        "@ip:replace|127.0.0.1": {
900          "value": {
901            "": {
902              "rem": [
903                [
904                  "@ip:replace",
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_remove, "@ip:remove", "127.0.0.1", @r###"
919    {
920      "@ip:remove|127.0.0.1": {
921        "type": "string",
922        "value": ""
923      },
924      "_meta": {
925        "@ip:remove|127.0.0.1": {
926          "value": {
927            "": {
928              "rem": [
929                [
930                  "@ip:remove",
931                  "x",
932                  0,
933                  0
934                ]
935              ],
936              "len": 9
937            }
938          }
939        }
940      }
941    }
942    "###);
943
944    attribute_rule_test!(test_scrub_attributes_pii_string_rules_ip_mask, "@ip:mask", "127.0.0.1", @r###"
945    {
946      "@ip:mask|127.0.0.1": {
947        "type": "string",
948        "value": "*********"
949      },
950      "_meta": {
951        "@ip:mask|127.0.0.1": {
952          "value": {
953            "": {
954              "rem": [
955                [
956                  "@ip:mask",
957                  "m",
958                  0,
959                  9
960                ]
961              ],
962              "len": 9
963            }
964          }
965        }
966      }
967    }
968    "###);
969
970    attribute_rule_test!(test_scrub_attributes_pii_string_rules_email, "@email", "test@example.com", @r###"
972    {
973      "@email|test@example.com": {
974        "type": "string",
975        "value": "[email]"
976      },
977      "_meta": {
978        "@email|test@example.com": {
979          "value": {
980            "": {
981              "rem": [
982                [
983                  "@email",
984                  "s",
985                  0,
986                  7
987                ]
988              ],
989              "len": 16
990            }
991          }
992        }
993      }
994    }
995    "###);
996
997    attribute_rule_test!(test_scrub_attributes_pii_string_rules_email_replace, "@email:replace", "test@example.com", @r###"
998    {
999      "@email:replace|test@example.com": {
1000        "type": "string",
1001        "value": "[email]"
1002      },
1003      "_meta": {
1004        "@email:replace|test@example.com": {
1005          "value": {
1006            "": {
1007              "rem": [
1008                [
1009                  "@email:replace",
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_remove, "@email:remove", "test@example.com", @r###"
1024    {
1025      "@email:remove|test@example.com": {
1026        "type": "string",
1027        "value": ""
1028      },
1029      "_meta": {
1030        "@email:remove|test@example.com": {
1031          "value": {
1032            "": {
1033              "rem": [
1034                [
1035                  "@email:remove",
1036                  "x",
1037                  0,
1038                  0
1039                ]
1040              ],
1041              "len": 16
1042            }
1043          }
1044        }
1045      }
1046    }
1047    "###);
1048
1049    attribute_rule_test!(test_scrub_attributes_pii_string_rules_email_mask, "@email:mask", "test@example.com", @r###"
1050    {
1051      "@email:mask|test@example.com": {
1052        "type": "string",
1053        "value": "****************"
1054      },
1055      "_meta": {
1056        "@email:mask|test@example.com": {
1057          "value": {
1058            "": {
1059              "rem": [
1060                [
1061                  "@email:mask",
1062                  "m",
1063                  0,
1064                  16
1065                ]
1066              ],
1067              "len": 16
1068            }
1069          }
1070        }
1071      }
1072    }
1073    "###);
1074
1075    attribute_rule_test!(test_scrub_attributes_pii_string_rules_creditcard, "@creditcard", "4242424242424242", @r###"
1077    {
1078      "@creditcard|4242424242424242": {
1079        "type": "string",
1080        "value": "[creditcard]"
1081      },
1082      "_meta": {
1083        "@creditcard|4242424242424242": {
1084          "value": {
1085            "": {
1086              "rem": [
1087                [
1088                  "@creditcard",
1089                  "s",
1090                  0,
1091                  12
1092                ]
1093              ],
1094              "len": 16
1095            }
1096          }
1097        }
1098      }
1099    }
1100    "###);
1101
1102    attribute_rule_test!(test_scrub_attributes_pii_string_rules_creditcard_replace, "@creditcard:replace", "4242424242424242", @r###"
1103    {
1104      "@creditcard:replace|4242424242424242": {
1105        "type": "string",
1106        "value": "[creditcard]"
1107      },
1108      "_meta": {
1109        "@creditcard:replace|4242424242424242": {
1110          "value": {
1111            "": {
1112              "rem": [
1113                [
1114                  "@creditcard:replace",
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_remove, "@creditcard:remove", "4242424242424242", @r###"
1129    {
1130      "@creditcard:remove|4242424242424242": {
1131        "type": "string",
1132        "value": ""
1133      },
1134      "_meta": {
1135        "@creditcard:remove|4242424242424242": {
1136          "value": {
1137            "": {
1138              "rem": [
1139                [
1140                  "@creditcard:remove",
1141                  "x",
1142                  0,
1143                  0
1144                ]
1145              ],
1146              "len": 16
1147            }
1148          }
1149        }
1150      }
1151    }
1152    "###);
1153
1154    attribute_rule_test!(test_scrub_attributes_pii_string_rules_creditcard_mask, "@creditcard:mask", "4242424242424242", @r###"
1155    {
1156      "@creditcard:mask|4242424242424242": {
1157        "type": "string",
1158        "value": "****************"
1159      },
1160      "_meta": {
1161        "@creditcard:mask|4242424242424242": {
1162          "value": {
1163            "": {
1164              "rem": [
1165                [
1166                  "@creditcard:mask",
1167                  "m",
1168                  0,
1169                  16
1170                ]
1171              ],
1172              "len": 16
1173            }
1174          }
1175        }
1176      }
1177    }
1178    "###);
1179
1180    attribute_rule_test!(test_scrub_attributes_pii_string_rules_iban, "@iban", "DE89370400440532013000", @r###"
1182    {
1183      "@iban|DE89370400440532013000": {
1184        "type": "string",
1185        "value": "[iban]"
1186      },
1187      "_meta": {
1188        "@iban|DE89370400440532013000": {
1189          "value": {
1190            "": {
1191              "rem": [
1192                [
1193                  "@iban",
1194                  "s",
1195                  0,
1196                  6
1197                ]
1198              ],
1199              "len": 22
1200            }
1201          }
1202        }
1203      }
1204    }
1205    "###);
1206
1207    attribute_rule_test!(test_scrub_attributes_pii_string_rules_iban_replace, "@iban:replace", "DE89370400440532013000", @r###"
1208    {
1209      "@iban:replace|DE89370400440532013000": {
1210        "type": "string",
1211        "value": "[iban]"
1212      },
1213      "_meta": {
1214        "@iban:replace|DE89370400440532013000": {
1215          "value": {
1216            "": {
1217              "rem": [
1218                [
1219                  "@iban:replace",
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_remove, "@iban:remove", "DE89370400440532013000", @r###"
1234    {
1235      "@iban:remove|DE89370400440532013000": {
1236        "type": "string",
1237        "value": ""
1238      },
1239      "_meta": {
1240        "@iban:remove|DE89370400440532013000": {
1241          "value": {
1242            "": {
1243              "rem": [
1244                [
1245                  "@iban:remove",
1246                  "x",
1247                  0,
1248                  0
1249                ]
1250              ],
1251              "len": 22
1252            }
1253          }
1254        }
1255      }
1256    }
1257    "###);
1258
1259    attribute_rule_test!(test_scrub_attributes_pii_string_rules_iban_mask, "@iban:mask", "DE89370400440532013000", @r###"
1260    {
1261      "@iban:mask|DE89370400440532013000": {
1262        "type": "string",
1263        "value": "**********************"
1264      },
1265      "_meta": {
1266        "@iban:mask|DE89370400440532013000": {
1267          "value": {
1268            "": {
1269              "rem": [
1270                [
1271                  "@iban:mask",
1272                  "m",
1273                  0,
1274                  22
1275                ]
1276              ],
1277              "len": 22
1278            }
1279          }
1280        }
1281      }
1282    }
1283    "###);
1284
1285    attribute_rule_test!(test_scrub_attributes_pii_string_rules_mac, "@mac", "4a:00:04:10:9b:50", @r###"
1287    {
1288      "@mac|4a:00:04:10:9b:50": {
1289        "type": "string",
1290        "value": "*****************"
1291      },
1292      "_meta": {
1293        "@mac|4a:00:04:10:9b:50": {
1294          "value": {
1295            "": {
1296              "rem": [
1297                [
1298                  "@mac",
1299                  "m",
1300                  0,
1301                  17
1302                ]
1303              ],
1304              "len": 17
1305            }
1306          }
1307        }
1308      }
1309    }
1310    "###);
1311
1312    attribute_rule_test!(test_scrub_attributes_pii_string_rules_mac_replace, "@mac:replace", "4a:00:04:10:9b:50", @r###"
1313    {
1314      "@mac:replace|4a:00:04:10:9b:50": {
1315        "type": "string",
1316        "value": "[mac]"
1317      },
1318      "_meta": {
1319        "@mac:replace|4a:00:04:10:9b:50": {
1320          "value": {
1321            "": {
1322              "rem": [
1323                [
1324                  "@mac:replace",
1325                  "s",
1326                  0,
1327                  5
1328                ]
1329              ],
1330              "len": 17
1331            }
1332          }
1333        }
1334      }
1335    }
1336    "###);
1337
1338    attribute_rule_test!(test_scrub_attributes_pii_string_rules_mac_remove, "@mac:remove", "4a:00:04:10:9b:50", @r###"
1339    {
1340      "@mac:remove|4a:00:04:10:9b:50": {
1341        "type": "string",
1342        "value": ""
1343      },
1344      "_meta": {
1345        "@mac:remove|4a:00:04:10:9b:50": {
1346          "value": {
1347            "": {
1348              "rem": [
1349                [
1350                  "@mac:remove",
1351                  "x",
1352                  0,
1353                  0
1354                ]
1355              ],
1356              "len": 17
1357            }
1358          }
1359        }
1360      }
1361    }
1362    "###);
1363
1364    attribute_rule_test!(test_scrub_attributes_pii_string_rules_mac_mask, "@mac:mask", "4a:00:04:10:9b:50", @r###"
1365    {
1366      "@mac:mask|4a:00:04:10:9b:50": {
1367        "type": "string",
1368        "value": "*****************"
1369      },
1370      "_meta": {
1371        "@mac:mask|4a:00:04:10:9b:50": {
1372          "value": {
1373            "": {
1374              "rem": [
1375                [
1376                  "@mac:mask",
1377                  "m",
1378                  0,
1379                  17
1380                ]
1381              ],
1382              "len": 17
1383            }
1384          }
1385        }
1386      }
1387    }
1388    "###);
1389
1390    attribute_rule_test!(test_scrub_attributes_pii_string_rules_uuid, "@uuid", "ceee0822-ed8f-4622-b2a3-789e73e75cd1", @r###"
1392    {
1393      "@uuid|ceee0822-ed8f-4622-b2a3-789e73e75cd1": {
1394        "type": "string",
1395        "value": "************************************"
1396      },
1397      "_meta": {
1398        "@uuid|ceee0822-ed8f-4622-b2a3-789e73e75cd1": {
1399          "value": {
1400            "": {
1401              "rem": [
1402                [
1403                  "@uuid",
1404                  "m",
1405                  0,
1406                  36
1407                ]
1408              ],
1409              "len": 36
1410            }
1411          }
1412        }
1413      }
1414    }
1415    "###);
1416
1417    attribute_rule_test!(test_scrub_attributes_pii_string_rules_uuid_replace, "@uuid:replace", "ceee0822-ed8f-4622-b2a3-789e73e75cd1", @r###"
1418    {
1419      "@uuid:replace|ceee0822-ed8f-4622-b2a3-789e73e75cd1": {
1420        "type": "string",
1421        "value": "[uuid]"
1422      },
1423      "_meta": {
1424        "@uuid:replace|ceee0822-ed8f-4622-b2a3-789e73e75cd1": {
1425          "value": {
1426            "": {
1427              "rem": [
1428                [
1429                  "@uuid:replace",
1430                  "s",
1431                  0,
1432                  6
1433                ]
1434              ],
1435              "len": 36
1436            }
1437          }
1438        }
1439      }
1440    }
1441    "###);
1442
1443    attribute_rule_test!(test_scrub_attributes_pii_string_rules_uuid_remove, "@uuid:remove", "ceee0822-ed8f-4622-b2a3-789e73e75cd1", @r###"
1444    {
1445      "@uuid:remove|ceee0822-ed8f-4622-b2a3-789e73e75cd1": {
1446        "type": "string",
1447        "value": ""
1448      },
1449      "_meta": {
1450        "@uuid:remove|ceee0822-ed8f-4622-b2a3-789e73e75cd1": {
1451          "value": {
1452            "": {
1453              "rem": [
1454                [
1455                  "@uuid:remove",
1456                  "x",
1457                  0,
1458                  0
1459                ]
1460              ],
1461              "len": 36
1462            }
1463          }
1464        }
1465      }
1466    }
1467    "###);
1468
1469    attribute_rule_test!(test_scrub_attributes_pii_string_rules_uuid_mask, "@uuid:mask", "ceee0822-ed8f-4622-b2a3-789e73e75cd1", @r###"
1470    {
1471      "@uuid:mask|ceee0822-ed8f-4622-b2a3-789e73e75cd1": {
1472        "type": "string",
1473        "value": "************************************"
1474      },
1475      "_meta": {
1476        "@uuid:mask|ceee0822-ed8f-4622-b2a3-789e73e75cd1": {
1477          "value": {
1478            "": {
1479              "rem": [
1480                [
1481                  "@uuid:mask",
1482                  "m",
1483                  0,
1484                  36
1485                ]
1486              ],
1487              "len": 36
1488            }
1489          }
1490        }
1491      }
1492    }
1493    "###);
1494
1495    attribute_rule_test!(test_scrub_attributes_pii_string_rules_imei, "@imei", "356938035643809", @r###"
1497    {
1498      "@imei|356938035643809": {
1499        "type": "string",
1500        "value": "[imei]"
1501      },
1502      "_meta": {
1503        "@imei|356938035643809": {
1504          "value": {
1505            "": {
1506              "rem": [
1507                [
1508                  "@imei",
1509                  "s",
1510                  0,
1511                  6
1512                ]
1513              ],
1514              "len": 15
1515            }
1516          }
1517        }
1518      }
1519    }
1520    "###);
1521
1522    attribute_rule_test!(test_scrub_attributes_pii_string_rules_imei_replace, "@imei:replace", "356938035643809", @r###"
1523    {
1524      "@imei:replace|356938035643809": {
1525        "type": "string",
1526        "value": "[imei]"
1527      },
1528      "_meta": {
1529        "@imei:replace|356938035643809": {
1530          "value": {
1531            "": {
1532              "rem": [
1533                [
1534                  "@imei:replace",
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_remove, "@imei:remove", "356938035643809", @r###"
1549    {
1550      "@imei:remove|356938035643809": {
1551        "type": "string",
1552        "value": ""
1553      },
1554      "_meta": {
1555        "@imei:remove|356938035643809": {
1556          "value": {
1557            "": {
1558              "rem": [
1559                [
1560                  "@imei:remove",
1561                  "x",
1562                  0,
1563                  0
1564                ]
1565              ],
1566              "len": 15
1567            }
1568          }
1569        }
1570      }
1571    }
1572    "###);
1573
1574    attribute_rule_test!(
1576        test_scrub_attributes_pii_string_rules_pemkey,
1577        "@pemkey",
1578        "-----BEGIN EC PRIVATE KEY-----\nMIHbAgEBBEFbLvIaAaez3q0u6BQYMHZ28B7iSdMPPaODUMGkdorl3ShgTbYmzqGL\n-----END EC PRIVATE KEY-----",
1579    @r###"
1580    {
1581      "@pemkey|-----BEGIN EC PRIVATE KEY-----\nMIHbAgEBBEFbLvIaAaez3q0u6BQYMHZ28B7iSdMPPaODUMGkdorl3ShgTbYmzqGL\n-----END EC PRIVATE KEY-----": {
1582        "type": "string",
1583        "value": "-----BEGIN EC PRIVATE KEY-----\n[pemkey]\n-----END EC PRIVATE KEY-----"
1584      },
1585      "_meta": {
1586        "@pemkey|-----BEGIN EC PRIVATE KEY-----\nMIHbAgEBBEFbLvIaAaez3q0u6BQYMHZ28B7iSdMPPaODUMGkdorl3ShgTbYmzqGL\n-----END EC PRIVATE KEY-----": {
1587          "value": {
1588            "": {
1589              "rem": [
1590                [
1591                  "@pemkey",
1592                  "s",
1593                  31,
1594                  39
1595                ]
1596              ],
1597              "len": 124
1598            }
1599          }
1600        }
1601      }
1602    }
1603    "###);
1604
1605    attribute_rule_test!(
1606        test_scrub_attributes_pii_string_rules_pemkey_replace,
1607        "@pemkey:replace",
1608        "-----BEGIN EC PRIVATE KEY-----\nMIHbAgEBBEFbLvIaAaez3q0u6BQYMHZ28B7iSdMPPaODUMGkdorl3ShgTbYmzqGL\n-----END EC PRIVATE KEY-----",
1609    @r###"
1610    {
1611      "@pemkey:replace|-----BEGIN EC PRIVATE KEY-----\nMIHbAgEBBEFbLvIaAaez3q0u6BQYMHZ28B7iSdMPPaODUMGkdorl3ShgTbYmzqGL\n-----END EC PRIVATE KEY-----": {
1612        "type": "string",
1613        "value": "-----BEGIN EC PRIVATE KEY-----\n[pemkey]\n-----END EC PRIVATE KEY-----"
1614      },
1615      "_meta": {
1616        "@pemkey:replace|-----BEGIN EC PRIVATE KEY-----\nMIHbAgEBBEFbLvIaAaez3q0u6BQYMHZ28B7iSdMPPaODUMGkdorl3ShgTbYmzqGL\n-----END EC PRIVATE KEY-----": {
1617          "value": {
1618            "": {
1619              "rem": [
1620                [
1621                  "@pemkey:replace",
1622                  "s",
1623                  31,
1624                  39
1625                ]
1626              ],
1627              "len": 124
1628            }
1629          }
1630        }
1631      }
1632    }
1633    "###);
1634
1635    attribute_rule_test!(
1636        test_scrub_attributes_pii_string_rules_pemkey_remove,
1637        "@pemkey:remove",
1638        "-----BEGIN EC PRIVATE KEY-----\nMIHbAgEBBEFbLvIaAaez3q0u6BQYMHZ28B7iSdMPPaODUMGkdorl3ShgTbYmzqGL\n-----END EC PRIVATE KEY-----",
1639    @r###"
1640    {
1641      "@pemkey:remove|-----BEGIN EC PRIVATE KEY-----\nMIHbAgEBBEFbLvIaAaez3q0u6BQYMHZ28B7iSdMPPaODUMGkdorl3ShgTbYmzqGL\n-----END EC PRIVATE KEY-----": {
1642        "type": "string",
1643        "value": "-----BEGIN EC PRIVATE KEY-----\n\n-----END EC PRIVATE KEY-----"
1644      },
1645      "_meta": {
1646        "@pemkey:remove|-----BEGIN EC PRIVATE KEY-----\nMIHbAgEBBEFbLvIaAaez3q0u6BQYMHZ28B7iSdMPPaODUMGkdorl3ShgTbYmzqGL\n-----END EC PRIVATE KEY-----": {
1647          "value": {
1648            "": {
1649              "rem": [
1650                [
1651                  "@pemkey:remove",
1652                  "x",
1653                  31,
1654                  31
1655                ]
1656              ],
1657              "len": 124
1658            }
1659          }
1660        }
1661      }
1662    }
1663    "###);
1664
1665    attribute_rule_test!(test_scrub_attributes_pii_string_rules_urlauth, "@urlauth", "https://username:password@example.com/", @r###"
1667    {
1668      "@urlauth|https://username:password@example.com/": {
1669        "type": "string",
1670        "value": "https://[auth]@example.com/"
1671      },
1672      "_meta": {
1673        "@urlauth|https://username:password@example.com/": {
1674          "value": {
1675            "": {
1676              "rem": [
1677                [
1678                  "@urlauth",
1679                  "s",
1680                  8,
1681                  14
1682                ]
1683              ],
1684              "len": 38
1685            }
1686          }
1687        }
1688      }
1689    }
1690    "###);
1691
1692    attribute_rule_test!(test_scrub_attributes_pii_string_rules_urlauth_replace, "@urlauth:replace", "https://username:password@example.com/", @r###"
1693    {
1694      "@urlauth:replace|https://username:password@example.com/": {
1695        "type": "string",
1696        "value": "https://[auth]@example.com/"
1697      },
1698      "_meta": {
1699        "@urlauth:replace|https://username:password@example.com/": {
1700          "value": {
1701            "": {
1702              "rem": [
1703                [
1704                  "@urlauth:replace",
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_remove, "@urlauth:remove", "https://username:password@example.com/", @r###"
1719    {
1720      "@urlauth:remove|https://username:password@example.com/": {
1721        "type": "string",
1722        "value": "https://@example.com/"
1723      },
1724      "_meta": {
1725        "@urlauth:remove|https://username:password@example.com/": {
1726          "value": {
1727            "": {
1728              "rem": [
1729                [
1730                  "@urlauth:remove",
1731                  "x",
1732                  8,
1733                  8
1734                ]
1735              ],
1736              "len": 38
1737            }
1738          }
1739        }
1740      }
1741    }
1742    "###);
1743
1744    attribute_rule_test!(test_scrub_attributes_pii_string_rules_urlauth_mask, "@urlauth:mask", "https://username:password@example.com/", @r###"
1745    {
1746      "@urlauth:mask|https://username:password@example.com/": {
1747        "type": "string",
1748        "value": "https://*****************@example.com/"
1749      },
1750      "_meta": {
1751        "@urlauth:mask|https://username:password@example.com/": {
1752          "value": {
1753            "": {
1754              "rem": [
1755                [
1756                  "@urlauth:mask",
1757                  "m",
1758                  8,
1759                  25
1760                ]
1761              ],
1762              "len": 38
1763            }
1764          }
1765        }
1766      }
1767    }
1768    "###);
1769
1770    attribute_rule_test!(test_scrub_attributes_pii_string_rules_usssn, "@usssn", "078-05-1120", @r###"
1772    {
1773      "@usssn|078-05-1120": {
1774        "type": "string",
1775        "value": "***********"
1776      },
1777      "_meta": {
1778        "@usssn|078-05-1120": {
1779          "value": {
1780            "": {
1781              "rem": [
1782                [
1783                  "@usssn",
1784                  "m",
1785                  0,
1786                  11
1787                ]
1788              ],
1789              "len": 11
1790            }
1791          }
1792        }
1793      }
1794    }
1795    "###);
1796
1797    attribute_rule_test!(test_scrub_attributes_pii_string_rules_usssn_replace, "@usssn:replace", "078-05-1120", @r###"
1798    {
1799      "@usssn:replace|078-05-1120": {
1800        "type": "string",
1801        "value": "[us-ssn]"
1802      },
1803      "_meta": {
1804        "@usssn:replace|078-05-1120": {
1805          "value": {
1806            "": {
1807              "rem": [
1808                [
1809                  "@usssn:replace",
1810                  "s",
1811                  0,
1812                  8
1813                ]
1814              ],
1815              "len": 11
1816            }
1817          }
1818        }
1819      }
1820    }
1821    "###);
1822
1823    attribute_rule_test!(test_scrub_attributes_pii_string_rules_usssn_remove, "@usssn:remove", "078-05-1120", @r###"
1824    {
1825      "@usssn:remove|078-05-1120": {
1826        "type": "string",
1827        "value": ""
1828      },
1829      "_meta": {
1830        "@usssn:remove|078-05-1120": {
1831          "value": {
1832            "": {
1833              "rem": [
1834                [
1835                  "@usssn:remove",
1836                  "x",
1837                  0,
1838                  0
1839                ]
1840              ],
1841              "len": 11
1842            }
1843          }
1844        }
1845      }
1846    }
1847    "###);
1848
1849    attribute_rule_test!(test_scrub_attributes_pii_string_rules_usssn_mask, "@usssn:mask", "078-05-1120", @r###"
1850    {
1851      "@usssn:mask|078-05-1120": {
1852        "type": "string",
1853        "value": "***********"
1854      },
1855      "_meta": {
1856        "@usssn:mask|078-05-1120": {
1857          "value": {
1858            "": {
1859              "rem": [
1860                [
1861                  "@usssn:mask",
1862                  "m",
1863                  0,
1864                  11
1865                ]
1866              ],
1867              "len": 11
1868            }
1869          }
1870        }
1871      }
1872    }
1873    "###);
1874
1875    attribute_rule_test!(test_scrub_attributes_pii_string_rules_userpath, "@userpath", "/Users/john/Documents", @r###"
1877    {
1878      "@userpath|/Users/john/Documents": {
1879        "type": "string",
1880        "value": "/Users/[user]/Documents"
1881      },
1882      "_meta": {
1883        "@userpath|/Users/john/Documents": {
1884          "value": {
1885            "": {
1886              "rem": [
1887                [
1888                  "@userpath",
1889                  "s",
1890                  7,
1891                  13
1892                ]
1893              ],
1894              "len": 21
1895            }
1896          }
1897        }
1898      }
1899    }
1900    "###);
1901
1902    attribute_rule_test!(test_scrub_attributes_pii_string_rules_userpath_replace, "@userpath:replace", "/Users/john/Documents", @r###"
1903    {
1904      "@userpath:replace|/Users/john/Documents": {
1905        "type": "string",
1906        "value": "/Users/[user]/Documents"
1907      },
1908      "_meta": {
1909        "@userpath:replace|/Users/john/Documents": {
1910          "value": {
1911            "": {
1912              "rem": [
1913                [
1914                  "@userpath:replace",
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_remove, "@userpath:remove", "/Users/john/Documents", @r###"
1929    {
1930      "@userpath:remove|/Users/john/Documents": {
1931        "type": "string",
1932        "value": "/Users//Documents"
1933      },
1934      "_meta": {
1935        "@userpath:remove|/Users/john/Documents": {
1936          "value": {
1937            "": {
1938              "rem": [
1939                [
1940                  "@userpath:remove",
1941                  "x",
1942                  7,
1943                  7
1944                ]
1945              ],
1946              "len": 21
1947            }
1948          }
1949        }
1950      }
1951    }
1952    "###);
1953
1954    attribute_rule_test!(test_scrub_attributes_pii_string_rules_userpath_mask, "@userpath:mask", "/Users/john/Documents", @r###"
1955    {
1956      "@userpath:mask|/Users/john/Documents": {
1957        "type": "string",
1958        "value": "/Users/****/Documents"
1959      },
1960      "_meta": {
1961        "@userpath:mask|/Users/john/Documents": {
1962          "value": {
1963            "": {
1964              "rem": [
1965                [
1966                  "@userpath:mask",
1967                  "m",
1968                  7,
1969                  11
1970                ]
1971              ],
1972              "len": 21
1973            }
1974          }
1975        }
1976      }
1977    }
1978    "###);
1979
1980    attribute_rule_test!(test_scrub_attributes_pii_string_rules_password, "@password", "my_password_123", @r###"
1983    {
1984      "@password|my_password_123": {
1985        "type": "string",
1986        "value": ""
1987      },
1988      "_meta": {
1989        "@password|my_password_123": {
1990          "value": {
1991            "": {
1992              "rem": [
1993                [
1994                  "@password",
1995                  "x",
1996                  0,
1997                  0
1998                ]
1999              ],
2000              "len": 15
2001            }
2002          }
2003        }
2004      }
2005    }
2006    "###);
2007
2008    attribute_rule_test!(test_scrub_attributes_pii_string_rules_password_remove, "@password:remove", "my_password_123", @r###"
2009    {
2010      "@password:remove|my_password_123": {
2011        "type": "string",
2012        "value": ""
2013      },
2014      "_meta": {
2015        "@password:remove|my_password_123": {
2016          "value": {
2017            "": {
2018              "rem": [
2019                [
2020                  "@password:remove",
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_replace, "@password:replace", "my_password_123", @r###"
2035    {
2036      "@password:replace|my_password_123": {
2037        "type": "string",
2038        "value": "[password]"
2039      },
2040      "_meta": {
2041        "@password:replace|my_password_123": {
2042          "value": {
2043            "": {
2044              "rem": [
2045                [
2046                  "@password:replace",
2047                  "s",
2048                  0,
2049                  10
2050                ]
2051              ],
2052              "len": 15
2053            }
2054          }
2055        }
2056      }
2057    }
2058    "###);
2059
2060    attribute_rule_test!(test_scrub_attributes_pii_string_rules_password_mask, "@password:mask", "my_password_123", @r###"
2061    {
2062      "@password:mask|my_password_123": {
2063        "type": "string",
2064        "value": "***************"
2065      },
2066      "_meta": {
2067        "@password:mask|my_password_123": {
2068          "value": {
2069            "": {
2070              "rem": [
2071                [
2072                  "@password:mask",
2073                  "m",
2074                  0,
2075                  15
2076                ]
2077              ],
2078              "len": 15
2079            }
2080          }
2081        }
2082      }
2083    }
2084    "###);
2085
2086    attribute_rule_test!(test_scrub_attributes_pii_string_rules_bearer, "@bearer", "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9", @r###"
2088    {
2089      "@bearer|Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9": {
2090        "type": "string",
2091        "value": "Bearer [token]"
2092      },
2093      "_meta": {
2094        "@bearer|Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9": {
2095          "value": {
2096            "": {
2097              "rem": [
2098                [
2099                  "@bearer",
2100                  "s",
2101                  0,
2102                  14
2103                ]
2104              ],
2105              "len": 43
2106            }
2107          }
2108        }
2109      }
2110    }
2111    "###);
2112
2113    attribute_rule_test!(
2114        test_scrub_attributes_pii_string_rules_bearer_replace,
2115        "@bearer:replace",
2116        "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
2117    @r###"
2118    {
2119      "@bearer:replace|Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9": {
2120        "type": "string",
2121        "value": "Bearer [token]"
2122      },
2123      "_meta": {
2124        "@bearer:replace|Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9": {
2125          "value": {
2126            "": {
2127              "rem": [
2128                [
2129                  "@bearer:replace",
2130                  "s",
2131                  0,
2132                  14
2133                ]
2134              ],
2135              "len": 43
2136            }
2137          }
2138        }
2139      }
2140    }
2141    "###);
2142
2143    attribute_rule_test!(
2144        test_scrub_attributes_pii_string_rules_bearer_remove,
2145        "@bearer:remove",
2146        "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
2147    @r###"
2148    {
2149      "@bearer:remove|Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9": {
2150        "type": "string",
2151        "value": ""
2152      },
2153      "_meta": {
2154        "@bearer:remove|Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9": {
2155          "value": {
2156            "": {
2157              "rem": [
2158                [
2159                  "@bearer:remove",
2160                  "x",
2161                  0,
2162                  0
2163                ]
2164              ],
2165              "len": 43
2166            }
2167          }
2168        }
2169      }
2170    }
2171    "###);
2172
2173    attribute_rule_test!(
2174        test_scrub_attributes_pii_string_rules_bearer_mask, 
2175        "@bearer:mask",
2176        "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
2177    @r###"
2178    {
2179      "@bearer:mask|Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9": {
2180        "type": "string",
2181        "value": "*******************************************"
2182      },
2183      "_meta": {
2184        "@bearer:mask|Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9": {
2185          "value": {
2186            "": {
2187              "rem": [
2188                [
2189                  "@bearer:mask",
2190                  "m",
2191                  0,
2192                  43
2193                ]
2194              ],
2195              "len": 43
2196            }
2197          }
2198        }
2199      }
2200    }
2201    "###);
2202}