SahilGoel commited on
Commit
8e34db8
·
verified ·
1 Parent(s): 19cd60d

Upload code/pii_shield.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. code/pii_shield.py +93 -0
code/pii_shield.py ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ pii_shield.py — Lightweight PII Masking
3
+ =======================================
4
+ Masks sensitive data before logging or sending to external services.
5
+ Pure regex, zero dependencies — works in 512MB environments.
6
+
7
+ Detects & masks:
8
+ - PAN numbers (ABCDE1234F pattern)
9
+ - Aadhaar numbers (12 digits)
10
+ - Bank account numbers (9-18 digits near keywords)
11
+ - IFSC codes
12
+ - Mobile numbers
13
+ - Email addresses
14
+ """
15
+
16
+ import re
17
+ import logging
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+ PII_PATTERNS = [
22
+ {
23
+ "name": "PAN",
24
+ "pattern": r'\b([A-Z]{5}\d{4}[A-Z])\b',
25
+ "mask": "XXXXX0000X",
26
+ },
27
+ {
28
+ "name": "Aadhaar",
29
+ "pattern": r'\b(\d{4}[\s\-]?\d{4}[\s\-]?\d{4})\b',
30
+ "mask": "XXXX-XXXX-XXXX",
31
+ },
32
+ {
33
+ "name": "Mobile",
34
+ "pattern": r'(?:\+91[\s\-]?)?\b([6-9]\d{9})\b',
35
+ "mask": "XXXXXXXXXX",
36
+ },
37
+ {
38
+ "name": "Email",
39
+ "pattern": r'\b([a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,})\b',
40
+ "mask": "***@***.***",
41
+ },
42
+ {
43
+ "name": "IFSC",
44
+ "pattern": r'\b([A-Z]{4}0[A-Z0-9]{6})\b',
45
+ "mask": "XXXX0XXXXXX",
46
+ },
47
+ {
48
+ "name": "BankAccount",
49
+ "pattern": r'(?:account[\s\-:]*(?:no|number|num)?[\s\-:]*)\b(\d{9,18})\b',
50
+ "mask": "XXXXXXXXXX",
51
+ "case_insensitive": True,
52
+ },
53
+ ]
54
+
55
+
56
+ def mask_pii(text: str) -> str:
57
+ """Mask all PII patterns in the given text. Returns sanitized text."""
58
+ masked = text
59
+ for p in PII_PATTERNS:
60
+ flags = re.IGNORECASE if p.get("case_insensitive") else 0
61
+ pattern = p["pattern"]
62
+ mask = p["mask"]
63
+ count = 0
64
+
65
+ def replace(m):
66
+ nonlocal count
67
+ count += 1
68
+ return mask
69
+
70
+ masked = re.sub(pattern, replace, masked, flags=flags)
71
+ if count > 0:
72
+ logger.debug(f"PII Shield: masked {count} {p['name']}(s)")
73
+
74
+ return masked
75
+
76
+
77
+ def has_pii(text: str) -> bool:
78
+ """Check if text contains any PII. Returns True if sensitive data detected."""
79
+ for p in PII_PATTERNS:
80
+ flags = re.IGNORECASE if p.get("case_insensitive") else 0
81
+ if re.search(p["pattern"], text, flags):
82
+ return True
83
+ return False
84
+
85
+
86
+ def shield_transaction(description: str) -> str:
87
+ """Shield a single transaction description — masks PAN/Aadhaar but preserves merchant info."""
88
+ # Only mask PAN and Aadhaar — keep bank account numbers visible for classification
89
+ for pattern_name in ["PAN", "Aadhaar"]:
90
+ for p in PII_PATTERNS:
91
+ if p["name"] == pattern_name:
92
+ description = re.sub(p["pattern"], p["mask"], description)
93
+ return description