パスポートデータとカード番号をログから自動的にスクラブする方法
最近、本番環境でサービスがクラッシュし、ユーザーのリクエストボディを含む完全なJSONがELKにダンプされるインシデントを調査していました。そこにはクレジットカード番号、住所、電話番号が含まれていました。デバッグ 목적으로すべてをログに記録することは一般的な慣行ですが、このようなダンプがストレージに保存されると、セキュリティチームから非常に厄介な質問を受けることになります。あらゆる小さなものに正規表現パターンを書くのはすぐに面倒になります。複雑なフォーマットでは壊れ、名前を逃がし、ランダムな数字の羅列を実際の識別子と混同してしまいます。
この問題を解決するには、さまざまな方法があります。利用可能な最も成熟したオープンソースツールの1つが、data-privacy-stack organizationの下でコミュニティによって開発されているPresidioです。
内部構造と必要性
Presidioは、テキスト、スプレッドシート、さらには画像内の個人を特定できる情報(PII)を検出してマスクするのを支援します。このプロジェクトはMicrosoftのエンジニアによってオープンライブラリとして作成され、その後別の organizationに移管され、GitHubで10,000スター以上を獲得しています。
ここでのコアアイデアはシンプルです:プライバシー保護タスクを2つの独立したステップ—分析と匿名化—に分割します。
1つのモノリシックなスクリプトの代わりに、2つの別々のモジュールが得られます:
presidio-analyzer— 受信テキストを検査し、パスポート番号、電話番号、メールアドレス、名前などのエンティティを見つけ、見つかった座標と信頼度スコアを返します。presidio-anonymizer— アナライザーのマークアップを受け取り、変換ルールを適用します:プレースホルダーに置換、暗号化、アスタリスクでマスク、または単に削除します。
この分離は、テキスト自体を変更せずにデータ検出の事実のみをログに記録する必要がある場合、または異なるフィールドタイプに対してマスク方法を柔軟に設定したい場合に便利です。
エンティティ検出の仕組み
正規表現だけでテキストを解析しようとしたことがあるなら、その主な欠点を知っているでしょう:正規表現は文脈を理解しません。16桁のシーケンスを見つけますが、それがカード番号なのか、倉庫内の製品SKUなのかわかりません。
Presidioは複数のアプローチを同時に組み合わせます:
- 正規表現とチェックサム(たとえば、银行カード番号のLuhnアルゴリズムやIBANバリデーター)。
- spaCyやHugging Face Transformersベースの事前学習済みNLPモデル(NER)— 人名、場所、組織。
- 文脈的な単語分析。数字のシーケンスの前に「phone」「tel」「call」などの単語がある場合、検出器の信頼度スコアが上昇します。
- 辞書とストップワードリスト。
Pythonの例
基本的なPythonパイプラインがどのようなものか見てみましょう。まず、パッケージをインストールします:
pip install presidio-analyzer presidio-anonymizer
python -m spacy download en_core_web_lg
まず、アナライザーを実行して、文字列内の脆弱な箇所を見つけます:
from presidio_analyzer import AnalyzerEngine
from presidio_anonymizer import AnonymizerEngine
text = "Привет, меня зовут John Doe. Мой рабочий email [email protected], а телефон +1-555-0199."
analyzer = AnalyzerEngine()
results = analyzer.analyze(text=text, entities=["EMAIL_ADDRESS", "PHONE_NUMBER", "PERSON"], language='en')
print("Найденные сущности:")
for res in results:
print(f"Тип: {res.entity_type}, позиция: {res.start}:{res.end}, уверенность: {res.score:.2f}")
アナライザーがオフセットとエンティティタイプを返した後、匿名化モジュールを接続します:
anonymizer = AnonymizerEngine()
anonymized_result = anonymizer.anonymize(
text=text,
analyzer_results=results
)
print("\nРезультат обработки:")
print(anonymized_result.text)
出力は、名前が<PERSON>に置き換えられ、連絡先が<EMAIL_ADDRESS>と<PHONE_NUMBER>に置き換えられた文字列です。必要に応じて、ハッシュ化、部分的な文字マスク、Fakerによるフェイクデータへの置換を設定できます。
カスタムルールの追加
デフォルトでは、国際的なフォーマット(米国の社会保障番号、VisaカードとMastercard、国際電話番号、メール)に最適化されています。ロシアのパスポート、INN、SNILSなどのローカルデータには、独自の認識器を書く必要があります。
組み込みクラスを使用して、非常に簡単に行えます:
from presidio_analyzer import Pattern, PatternRecognizer
# Паттерн для поиска СНИЛС (формат XXX-XXX-XXX YY)
snils_pattern = Pattern(
name="snils_pattern",
regex=r"\b\d{3}-\d{3}-\d{3}\s\d{2}\b",
score=0.85
)
snils_recognizer = PatternRecognizer(
supported_entity="RU_SNILS",
patterns=[snils_pattern],
context=["снилс", "страховой", "пенсионный"]
)
analyzer.registry.add_recognizer(snils_recognizer)
アナライザーが正規表現との一致を見つけ、contextリストの単語が近くにある場合、信頼度スコアが最大値に上昇します。
ライブラリのその他の機能
プレーンテキストでの作業に加えて、リポジトリには関連するタスクのためのヘルパーモジュールが含まれています:
presidio-image-redactor— OCR(Tesseract)経由で画像やPDFスキャン内のテキストを見つけ、検出された個人データをピクセル内で黒いボックスで塗りつぶします。外部サービスに送信する前にパスポートスキャンを処理するのに便利です。- LLMパイプライン統合。Presidioは、OpenAIやAnthropicにプロンプトを送信する前の中間ウェアとしてよく使用されます:リクエストからユーザーの個人データをスクラブし、匿名化されたテキストをモデルに送信し、必要に応じて戻りでデータを元の場所に復元します。
- FastAPI上のREST API、Dockerコンテナにパッケージ済み。全体をPythonで記述する必要はなく、アナライザーをマイクロサービスとしてデプロイし、Go、Java、Node.jsのバックエンドから呼び出すことができます。
潜在的な課題
最初のニュアンスはリソース消費です。RoBERTaやBERTなどの重いtransformerを接続してより正確なエンティティ認識を行う場合、推論にはメモリが必要になり、データストリーム処理が遅くなります。NLPパイプライン全体を同期的に何十万ものRPSで実行するのは重くなります—非同期のKafkaやCeleryワーカーにマスクをオフロードする必要があります。
2番目の点はロシア語モデルです。基本的なspaCyのen_core_web_lgは英語得很好ですが、ロシア語にはru_core_news_lgモデルを接続するか、テキストの特性に合わせて独自のNER認識器をファインチューニングする必要があります。
実践的な応用例
Presidioは、企業が連邦法152号、GDPR、HIPAAに準拠する必要があるが、既存のインフラをゼロから書き直すのはコストがかかりすぎる場合に便利です。
このライブラリは3つのシナリオに最適です:
- 監視システム(Sentry、OpenTelemetry、ELK)に送信する前に、ログとトレースをスクラブする。
- 実際のユーザーデータでのMLモデル訓練や分析用のデータセットを準備する。
- 大規模言語モデルに基づいたチャットボットやRAGシステムで、受信メッセージと送信メッセージをフィルタリングする。
パーサーをゼロから書く必要なく、データマスキング готовый ツールキットを探しているなら、このリポジトリはブックマークして、自分の例でローカルでテストする価値があります。
関連プロジェクト