QuentinGuironnet commited on
Commit
d889b51
·
verified ·
1 Parent(s): af7dda7

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +162 -0
app.py ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+ import gradio as gr
3
+
4
+ # ---------------------------
5
+ # 1. REGEX PATTERNS
6
+ # ---------------------------
7
+ EMAIL_RE = re.compile(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b")
8
+ URL_RE = re.compile(r"\bhttps?://\S+|\bwww\.\S+\b", re.IGNORECASE)
9
+ IP_RE = re.compile(r"\b\d{1,3}(?:\.\d{1,3}){3}\b")
10
+ DATE_RE = re.compile(r"\b(?:\d{1,2}[\/\-.]\d{1,2}[\/\-.]\d{2,4}|\d{4}-\d{2}-\d{2})\b")
11
+
12
+ # téléphones un peu larges, on filtrera les trop courts
13
+ PHONE_RE = re.compile(r"(?:\+?\d[\d\s().-]{5,}\d)")
14
+
15
+ # cartes bancaires : on détecte 13 à 19 chiffres avec séparateurs
16
+ CARD_RE = re.compile(r"\b(?:\d[ -]*?){13,19}\b")
17
+
18
+ # SSN US basique
19
+ SSN_RE = re.compile(r"\b\d{3}-\d{2}-\d{4}\b")
20
+
21
+
22
+ # ---------------------------
23
+ # 2. UTILITAIRES
24
+ # ---------------------------
25
+ def luhn_valid(number: str) -> bool:
26
+ """Vérifie un numéro de carte avec Luhn (simple)."""
27
+ digits = [int(d) for d in number if d.isdigit()]
28
+ if len(digits) < 13 or len(digits) > 19:
29
+ return False
30
+ checksum = 0
31
+ parity = len(digits) % 2
32
+ for i, d in enumerate(digits):
33
+ if i % 2 == parity:
34
+ d = d * 2
35
+ if d > 9:
36
+ d -= 9
37
+ checksum += d
38
+ return checksum % 10 == 0
39
+
40
+
41
+ def mask_value(value: str, style: str = "tag") -> str:
42
+ """
43
+ style:
44
+ - tag -> [PII]
45
+ - stars -> ********
46
+ - keep_len -> même longueur mais *
47
+ """
48
+ if style == "tag":
49
+ return "[PII]"
50
+ elif style == "stars":
51
+ return "*" * len(value)
52
+ elif style == "keep_len":
53
+ return "".join("*" if not c.isspace() else c for c in value)
54
+ return "[PII]"
55
+
56
+
57
+ # ---------------------------
58
+ # 3. ANONYMISATION
59
+ # ---------------------------
60
+ def anonymize(text: str, style: str):
61
+ if not text.strip():
62
+ return "Paste or type a text to anonymize.", "{}"
63
+
64
+ counts = {
65
+ "email": 0,
66
+ "url": 0,
67
+ "ip": 0,
68
+ "date": 0,
69
+ "phone": 0,
70
+ "card": 0,
71
+ "ssn": 0,
72
+ }
73
+
74
+ # 1) Emails
75
+ def repl_email(m):
76
+ counts["email"] += 1
77
+ return mask_value(m.group(), style)
78
+ text = EMAIL_RE.sub(repl_email, text)
79
+
80
+ # 2) URLs
81
+ def repl_url(m):
82
+ counts["url"] += 1
83
+ return mask_value(m.group(), style)
84
+ text = URL_RE.sub(repl_url, text)
85
+
86
+ # 3) IPs
87
+ def repl_ip(m):
88
+ counts["ip"] += 1
89
+ return mask_value(m.group(), style)
90
+ text = IP_RE.sub(repl_ip, text)
91
+
92
+ # 4) Dates
93
+ def repl_date(m):
94
+ counts["date"] += 1
95
+ return mask_value(m.group(), style)
96
+ text = DATE_RE.sub(repl_date, text)
97
+
98
+ # 5) SSN
99
+ def repl_ssn(m):
100
+ counts["ssn"] += 1
101
+ return mask_value(m.group(), style)
102
+ text = SSN_RE.sub(repl_ssn, text)
103
+
104
+ # 6) Cartes bancaires (avec Luhn)
105
+ def repl_card(m):
106
+ raw = m.group()
107
+ digits = "".join(ch for ch in raw if ch.isdigit())
108
+ if luhn_valid(digits):
109
+ counts["card"] += 1
110
+ return mask_value(raw, style)
111
+ return raw # pas une vraie carte
112
+ text = CARD_RE.sub(repl_card, text)
113
+
114
+ # 7) Téléphones
115
+ def repl_phone(m):
116
+ raw = m.group()
117
+ # on nettoie
118
+ digits = "".join(ch for ch in raw if ch.isdigit())
119
+ if len(digits) < 6: # trop court pour être un vrai numéro
120
+ return raw
121
+ counts["phone"] += 1
122
+ return mask_value(raw, style)
123
+ text = PHONE_RE.sub(repl_phone, text)
124
+
125
+ # petit résumé JSON
126
+ import json
127
+ stats = json.dumps({k: v for k, v in counts.items() if v > 0}, indent=2, ensure_ascii=False)
128
+ return text, stats or "{}"
129
+
130
+
131
+ # ---------------------------
132
+ # 4. GRADIO UI
133
+ # ---------------------------
134
+ with gr.Blocks(title="PII-Shield — Anonymizer") as demo:
135
+ gr.Markdown("# 🛡️ PII-Shield — Text Anonymizer")
136
+ gr.Markdown("Collez un SMS, un e-mail ou un texte administratif. L’outil masque automatiquement emails, téléphones, URLs, IPs, dates, SSN et cartes bancaires (Luhn).")
137
+
138
+ with gr.Row():
139
+ inp = gr.Textbox(lines=10, label="Texte à anonymiser")
140
+ style = gr.Radio(
141
+ ["tag", "stars", "keep_len"],
142
+ value="tag",
143
+ label="Style de masquage",
144
+ info="tag = [PII], stars = ********, keep_len = même longueur"
145
+ )
146
+
147
+ btn = gr.Button("Analyser / Masquer")
148
+
149
+ out = gr.Textbox(lines=10, label="Texte anonymisé")
150
+ stats = gr.JSON(label="Éléments détectés")
151
+
152
+ examples = [
153
+ "Salut Marie, écris-moi à marie.dupont@example.com ou appelle au +33 6 12 34 56 78. RDV le 12/05/2024.",
154
+ "Payment card: 4111 1111 1111 1111, IP: 192.168.0.1, site: https://example.org",
155
+ "US client SSN: 123-45-6789"
156
+ ]
157
+ gr.Examples(examples=examples, inputs=inp, outputs=[out, stats])
158
+
159
+ btn.click(anonymize, inputs=[inp, style], outputs=[out, stats])
160
+
161
+ if __name__ == "__main__":
162
+ demo.launch()