Presidio, wykrywanie i anonimizacja danych PII
Presidio to biblioteka i zestaw usług do znajdowania danych osobowych w tekście, obrazach i tabelach oraz do ich maskowania, szyfrowania lub zastępowania. Działa lokalnie, na licencji MIT, i nie wysyła niczego na zewnątrz.
Typowe zastosowanie wygląda tak: masz zgłoszenia od klientów, transkrypcje rozmów albo logi aplikacji, chcesz je przeanalizować lub wysłać do modelu językowego, ale nie możesz przekazać nazwisk, numerów kart i adresów. Presidio stoi w tym miejscu jako filtr.
Pierwsza rzecz do wiedzenia: projekt zmienił właściciela
Nazwa „Microsoft Presidio" jest już myląca i to najważniejsza informacja przed rozpoczęciem pracy.
Projekt przechodzi spod Microsoftu do niezależnej, zarządzanej przez społeczność organizacji Data Privacy Stack. Repozytorium żyje teraz pod adresem data-privacy-stack/presidio, a komitet techniczny obejmuje osoby spoza Microsoftu.
Co zostaje bez zmian: licencja MIT, dotychczasowa funkcjonalność, interfejsy programistyczne oraz integracje z usługami Azure. Kod, który dziś działa, będzie działał dalej.
Co wymaga Twojej reakcji: obrazy kontenerów. Nowe wydania trafiają do rejestru GitHuba pod ghcr.io/data-privacy-stack/presidio-*. Stare adresy w mcr.microsoft.com pozostają dostępne, ale przestały wskazywać najnowsze wydanie, więc znacznik latest w tamtej lokalizacji zamraża Cię na starej wersji bez żadnego ostrzeżenia.
To jest dokładnie ten rodzaj cichej zmiany, który zauważa się po pół roku, gdy okazuje się, że brakuje rozpoznawaczy dodanych w międzyczasie. Jeśli masz w konfiguracji wdrożenia adres z mcr.microsoft.com, zmień go teraz.
Z czego składa się Presidio
Projekt dzieli się na kilka elementów, które można używać osobno, i to rozdzielenie ma sens praktyczny.
Analizator znajduje w tekście fragmenty wyglądające na dane osobowe i przypisuje im typ oraz ocenę pewności. Zwraca pozycje w tekście, nie zmieniony tekst.
Anonimizator bierze wynik analizy i wykonuje na nim operację: zamienia, maskuje, haszuje, szyfruje albo usuwa. Rozdzielenie tych dwóch kroków pozwala obejrzeć, co zostało znalezione, zanim cokolwiek zmienisz.
Osobne komponenty obsługują obrazy, gdzie tekst najpierw przechodzi przez rozpoznawanie znaków, a wykryte fragmenty są zamalowywane, oraz dane strukturalne, gdzie operacja idzie na całe kolumny zamiast na pojedyncze wystąpienia.
Każdy z tych elementów da się uruchomić jako biblioteka w procesie Pythona albo jako usługę w kontenerze. Wersja biblioteczna jest szybsza, bo nie ma przelotu po sieci, wersja usługowa wygodniejsza, gdy resztę aplikacji piszesz w innym języku.
Instalacja i pierwsze uruchomienie
pip install presidio-analyzer presidio-anonymizer
python -m spacy download pl_core_news_lgModel językowy pobierasz osobno i to jest krok, o którym najczęściej się zapomina. Bez niego rozpoznawanie nazwisk i nazw organizacji nie zadziała, bo opiera się na modelu rozpoznawania jednostek nazwanych, a nie na wyrażeniach regularnych.
from presidio_analyzer import AnalyzerEngine
from presidio_anonymizer import AnonymizerEngine
analyzer = AnalyzerEngine()
anonymizer = AnonymizerEngine()
tekst = "Kontakt do klienta: anna.kowalska@firma.pl, telefon 601 234 567."
wyniki = analyzer.analyze(text=tekst, language="en")
for w in wyniki:
print(w.entity_type, w.start, w.end, w.score)
print(anonymizer.anonymize(text=tekst, analyzer_results=wyniki).text)Wynik wygląda mniej więcej tak:
EMAIL_ADDRESS 22 45 1.0
PHONE_NUMBER 55 66 0.75
Kontakt do klienta: <EMAIL_ADDRESS>, telefon <PHONE_NUMBER>.Zwróć uwagę na oceny pewności. Adres poczty ma jedynkę, bo przeszedł walidację formatu. Numer telefonu ma 0,75, bo ciąg cyfr w tym układzie bywa też numerem faktury albo identyfikatorem zamówienia. Ta liczba jest Twoim głównym pokrętłem: próg odcięcia decyduje o proporcji między przepuszczonymi danymi a zmasakrowanym tekstem.
Języki i domyślna konfiguracja
Domyślna konfiguracja jest angielska i to źródło większości rozczarowań przy pierwszym uruchomieniu na polskim tekście.
Rozpoznawacze dzielą się na dwie grupy. Uniwersalne, oparte na wzorcach i sumach kontrolnych, działają niezależnie od języka: adres poczty, numer karty, adres IP, adres portfela kryptowalut. Językowe i krajowe wymagają odpowiedniego modelu oraz odpowiedniej konfiguracji: nazwiska, adresy, krajowe numery identyfikacyjne.
Zestaw krajowy rośnie z każdym wydaniem, przy czym rozkłada się nierówno. Gotowe rozpoznawacze mają dziś Stany Zjednoczone, Wielka Brytania, Hiszpania, Włochy, Polska, Singapur, Australia, Indie, Finlandia, Korea, Nigeria, Filipiny, Kanada, Szwecja, Republika Południowej Afryki, Tajlandia, Turcja i Niemcy, tylko że liczba typów na kraj waha się od jednego do kilkunastu.
Polska ma dokładnie jeden: PL_PESEL, sprawdzany wzorcem, słowami z otoczenia i sumą kontrolną. Rozpoznawacz jest przypisany do języka polskiego, więc odezwie się dopiero wtedy, gdy uruchomisz analizę z language="pl" i wgranym polskim modelem, a nie w domyślnej konfiguracji angielskiej. Numeru NIP w zestawie nie ma, więc trzeba go dopisać samodzielnie, o czym niżej.
Doszedł też filtr krajowy na rozpoznawaczach predefiniowanych. To praktyczna rzecz, bo ładowanie kilkudziesięciu rozpoznawaczy z całego świata do systemu obsługującego jeden rynek zwiększa liczbę fałszywych trafień i spowalnia analizę bez żadnego zysku.
Własny rozpoznawacz
To jest miejsce, w którym Presidio zarabia na siebie w projekcie firmowym, bo domyślne typy nigdy nie pokrywają Twoich danych.
from presidio_analyzer import Pattern, PatternRecognizer
nip = PatternRecognizer(
supported_entity="PL_NIP",
patterns=[Pattern(name="nip", regex=r"\b\d{10}\b", score=0.4)],
context=["nip", "numer identyfikacji podatkowej"],
)
analyzer.registry.add_recognizer(nip)Dwie rzeczy w tym kodzie są ważniejsze niż samo wyrażenie regularne.
Ocena początkowa 0,4 jest celowo niska, bo dziesięć cyfr to także numer telefonu z prefiksem albo identyfikator w systemie magazynowym. Lista context podnosi tę ocenę, gdy w pobliżu pada słowo „nip". To jest mechanizm wzmocnienia kontekstowego i on odróżnia Presidio od prostego przeszukiwania wyrażeniami regularnymi.
Przy numerach z sumą kontrolną, a NIP taką ma, opłaca się pójść krok dalej i napisać własną klasę z metodą walidującą. Poprawna suma kontrolna podnosi ocenę do jedynki, błędna sprowadza ją do zera, i liczba fałszywych trafień spada praktycznie do zera. Ten sam mechanizm działa dla numerów kart, gdzie sprawdzana jest suma Luhna, i tak samo zbudowany jest wbudowany rozpoznawacz PESEL, więc jego kod to gotowy wzorzec do przepisania.
Operacje anonimizacji i wybór właściwej
| Operacja | Co robi | Odwracalna | Kiedy użyć |
|---|---|---|---|
| Zamiana | Wstawia etykietę typu | Nie | Tekst idzie do modelu językowego |
| Maskowanie | Zostawia kilka ostatnich znaków | Nie | Interfejs dla obsługi klienta |
| Haszowanie | Skrót kryptograficzny | Nie | Zliczanie unikalnych osób |
| Szyfrowanie | Szyfr symetryczny z kluczem | Tak | Trzeba odzyskać oryginał |
| Usunięcie | Wycina fragment | Nie | Publiczne udostępnienie zbioru |
Wybór między haszowaniem a szyfrowaniem sprowadza się do jednego pytania: czy ktoś kiedyś będzie musiał odzyskać oryginalną wartość. Jeśli tak, hasz jest ślepą uliczką i trzeba użyć szyfrowania, pilnując przy tym klucza, bo jego wyciek unieważnia całą ochronę.
Warto też pamiętać, że hasz nie jest anonimizacją w rozumieniu RODO, jeśli zbiór możliwych wartości jest mały. Zahaszowany numer telefonu da się odtworzyć, licząc skróty wszystkich dziewięciocyfrowych numerów, co na zwykłym komputerze zajmuje minuty. Dosolenie skrótu pomaga, o ile sól nie leży obok danych.
Obrazy i dane tabelaryczne
Tekst to najczęstszy przypadek, ale dwa pozostałe rządzą się własnymi prawami i warto je znać, zanim okaże się, że wyciek poszedł drogą, o której nikt nie pomyślał.
Przy obrazach potok wygląda tak: rozpoznawanie znaków wyciąga tekst razem z pozycjami, analizator ocenia ten tekst, a wykryte prostokąty zostają zamalowane. Skuteczność zależy więc głównie od jakości rozpoznawania znaków, a nie od samego Presidio. Zdjęcie dokumentu zrobione pod kątem, skan o niskiej rozdzielczości albo pismo odręczne dają tekst z literówkami, którego rozpoznawacz nie dopasuje.
Praktyczna konsekwencja jest taka, że przy obrazach próg pewności warto obniżyć bardziej niż przy tekście, a wynik i tak przejrzeć. Lepiej zamalować o dwa prostokąty za dużo niż zostawić widoczny numer dowodu.
Dane strukturalne to osobna historia, bo tam decyzja zapada na poziomie kolumny, nie pojedynczej komórki. Kolumna nazwana email prawie na pewno zawiera adresy i nie ma sensu analizować każdego wiersza z osobna. Presidio potrafi wywnioskować typ kolumny z próbki wartości, a potem zastosować jedną operację do całości, co przy milionach wierszy jest różnicą między minutami a godzinami.
Uwaga dotyczy kolumn tekstowych o swobodnej treści, na przykład notatek obsługi klienta. Tam wnioskowanie po nagłówku nie zadziała i trzeba wrócić do analizy komórka po komórce, przyjmując, że to będzie najwolniejszy element całego przetwarzania. Warto go wtedy zrównoleglić i zmierzyć na próbce, zanim uruchomisz go na całym zbiorze.
Presidio kontra alternatywy
| Rozwiązanie | Model działania | Koszt | Kiedy wybrać |
|---|---|---|---|
| Presidio | Lokalnie, biblioteka lub kontener | Darmowe, licencja MIT | Dane nie mogą opuścić infrastruktury |
| Usługi chmurowe dostawców | Wywołanie interfejsu zewnętrznego | Za tysiąc znaków | Mały wolumen, brak zespołu do utrzymania |
| Model językowy do wykrywania | Prompt do modelu | Za tokeny | Nietypowe dane bez jasnego wzorca |
| Własne wyrażenia regularne | Kod w projekcie | Czas programisty | Jeden dobrze zdefiniowany format |
Trzeci wiersz warto rozważyć uczciwie, bo bywa kuszący. Model językowy poradzi sobie z danymi, których nie da się opisać wzorcem, na przykład z opisem stanu zdrowia pozwalającym zidentyfikować osobę. Ceną jest koszt za każdy dokument, opóźnienie oraz brak powtarzalności: ten sam tekst może dać różne wyniki. Do przetwarzania milionów rekordów to się nie skaluje.
Sensowny układ w praktyce to Presidio jako pierwsza warstwa dla wszystkiego, co ma wzorzec, i model językowy wyłącznie dla reszty. Model uruchamiany na jednym procencie dokumentów kosztuje jeden procent tego, co model uruchamiany na wszystkich.
Presidio przed modelem językowym
Najczęstsze dziś zastosowanie to filtr między danymi firmy a zewnętrznym modelem.
Układ jest prosty: przed wysłaniem promptu przepuszczasz go przez analizator i anonimizator, wysyłasz wersję pozbawioną danych osobowych, a w odpowiedzi podstawiasz oryginalne wartości z powrotem. Do tego ostatniego kroku potrzebujesz szyfrowania albo własnej mapy zamian, bo zwykła zamiana na etykietę jest nieodwracalna.
Ta warstwa rozwiązuje inny problem niż mechanizmy typu Guardrails AI czy NeMo Guardrails. Tamte pilnują, co model odpowiada. Presidio pilnuje, co model w ogóle zobaczy. W poważnym wdrożeniu potrzebujesz obu, bo model może przecież ujawnić dane, które sam wcześniej otrzymał w innym kontekście.
Przy budowaniu takiego łańcucha w LangChainie istnieje gotowa integracja, ale własne opakowanie na dwa wywołania funkcji nie jest trudniejsze i daje pełną kontrolę nad tym, co dokładnie leci na zewnątrz.
Jedna pułapka jest tu nieoczywista. Anonimizacja zmienia długość tekstu, więc jeśli w tym samym przepływie liczysz pozycje znaków, na przykład do podświetlania fragmentów w interfejsie, po anonimizacji przestają się zgadzać. Zapisuj przesunięcia razem z wynikami analizy.
Typowe błędy
Pierwszy to poleganie na domyślnym progu pewności. Wartość domyślna jest kompromisem dla tekstu angielskiego i na Twoich danych prawie na pewno wymaga przesunięcia w jedną albo drugą stronę.
Drugi to brak modelu językowego. Bez pobranego modelu rozpoznawania jednostek nazwanych nazwiska i organizacje przechodzą bez wykrycia, a biblioteka nie zgłasza tego jako błąd.
Trzeci to traktowanie wyniku jako pewnego. Presidio jest narzędziem statystycznym i przepuszcza część danych, zwłaszcza zapisanych nietypowo, z literówkami albo w obcym formacie. Zbiór przeznaczony do publikacji wymaga przeglądu przez człowieka.
Czwarty to haszowanie tam, gdzie potrzebna była pseudonimizacja odwracalna. Odkrycie tego po skasowaniu oryginałów oznacza utratę danych.
Piąty to zostawienie starego adresu rejestru kontenerów. Znacznik latest w mcr.microsoft.com nie wskazuje już najnowszego wydania, więc wdrożenie cicho stoi na starej wersji.
Szósty to anonimizacja tekstu z pominięciem metadanych. Nazwisko usunięte z treści dokumentu, ale zostawione w nazwie pliku, we właściwościach pliku albo w ścieżce w logu, nie zostało usunięte.
FAQ
Czy Presidio jest darmowe?
Tak, na licencji MIT, bez opłat i bez limitów wolumenu. Kosztem jest moc obliczeniowa po Twojej stronie oraz czas na dostrojenie rozpoznawaczy, bo domyślna konfiguracja rzadko pasuje do konkretnego zbioru danych.
Czy Presidio obsługuje język polski?
Częściowo. Rozpoznawacze oparte na wzorcach, takie jak adres poczty czy numer karty, działają niezależnie od języka. Wykrywanie nazwisk wymaga polskiego modelu spaCy. Numer PESEL ma gotowy rozpoznawacz PL_PESEL z walidacją sumy kontrolnej, ale ładuje się on tylko przy analizie ustawionej na język polski. Numeru NIP w zestawie predefiniowanym nie ma i trzeba go dodać samodzielnie.
Czy Presidio wysyła dane na zewnątrz?
Nie. Cała analiza dzieje się lokalnie, w Twoim procesie albo w Twoim kontenerze, i to jest główny powód, dla którego wybiera się je zamiast usługi chmurowej. Wyjątkiem jest sytuacja, w której sam skonfigurujesz zewnętrzny model jako silnik rozpoznawania.
Czy Presidio wystarczy do zgodności z RODO?
Nie samo w sobie. Jest narzędziem technicznym, a zgodność zależy od podstawy prawnej przetwarzania, retencji, dostępów i całej reszty. Pomaga natomiast w pseudonimizacji, którą rozporządzenie wymienia jako środek ograniczający ryzyko.
Czym różni się anonimizacja od pseudonimizacji?
Anonimizacja jest nieodwracalna i po jej wykonaniu dane przestają być danymi osobowymi. Pseudonimizacja zamienia wartości na zastępcze, ale pozwala je odtworzyć przy pomocy klucza, więc dane nadal podlegają ochronie. Presidio potrafi jedno i drugie, zależnie od wybranej operacji.
Dokumentacja stoi na stronie projektu, a kod i wydania w repozytorium na GitHubie.