AWS GlueとSnowflake AI_REDACTで、文中の機密性のある文字列のマスキングを試してみた

AWS GlueとSnowflake AI_REDACTで、文中の機密性のある文字列のマスキングを試してみた

AWS GlueとSnowflake AI_REDACTを使い、テキストデータに含まれる氏名や電話番号などの機密情報をマスキングする方法を試してみました。ルールベースとAIベースの2つのアプローチの精度や使い勝手の違いをご紹介します。
2026.07.02

データ事業本部の鈴木です。

会話履歴などの非構造化データには、氏名・電話番号・メールアドレス・住所などの個人情報が含まれることがあります。
LLMへの入力や社内共有の前にこれらの情報をマスクしたい場面があると思いますが、日本語に対応している方法は英語よりも選択肢が少なくなりがちです。

今回は同じ検証データを使い、AWS Glue Jobの Detect PII transform と、Snowflake Cortex AI関数の AI_REDACT でマスク処理を試してみました。

https://docs.aws.amazon.com/glue/latest/dg/detect-PII.html

https://docs.snowflake.com/ja/user-guide/snowflake-cortex/redact-pii

はじめに

非構造化テキスト中には、個人情報をはじめとしたリスクのある文字列が含まれる場合があります。Snowflake の Dynamic Data Masking や AWS Lake Formation の列・行・セルレベル制御は、主に列や行、セル単位のアクセス制御/マスキングに向いています。一方、会話履歴のような非構造化テキストでは、文全体ではなく、氏名・電話番号・住所などの該当箇所だけを伏せて、残りの文脈を分析に使いたいケースがあります。

Snowflake on AWSでS3と組み合わせてマスクをするような場合、以下の2つの機能がこの処理を行う候補にあがります。

  • ルール/パターンベースの検出
    • 正規表現、サービス側で定義済みの PII エンティティなどに基づいて、電話番号・メールアドレス・住所などを検出する
      • 例: AWS Glue Detect PII transform
  • LLM/AI ベースの検出
    • 文脈を考慮しながら、非構造化テキスト内の個人情報や機微情報を検出する
      • 例: Snowflake Cortex AI_REDACT

AWS Glue の Detect PII transform は、Glue Studio のビジュアルジョブやスクリプトから、行単位・列単位でPIIを検出し、編集・ハッシュ化などのアクションを選べます。

https://docs.aws.amazon.com/ja_jp/glue/latest/dg/detect-PII.html

Snowflake の AI_REDACT は Cortex AI 関数の一つで、redact モードでプレースホルダーへの置換、detect モードで検出位置の特定ができます。検出カテゴリを指定した選択的な編集も可能です。

https://docs.snowflake.com/ja/sql-reference/functions/ai_redact

検証データの準備

サンプルCSV

今回の検証では、問い合わせ・オペレーターメモ風のテキストを text 列に持つCSVを用意しました。氏名、電話番号、メールアドレス、生年月日、住所、IPアドレスなど、日本語の自由記述に混在する個人情報(AIで生成した架空のもの)を含めています。

record_id,text
1,"問い合わせ内容:こんにちは、私の名前は山田太郎です。電話番号は090-0123-4567、メールアドレスはtaro.yamada@example.comです。クラスメソッドに所属しています。生年月日は1988年4月15日、住所は東京都架空区Example町1-2-3 Exampleマンション101号室です。最終アクセス元IPは203.0.113.45でした。"
2,"通販の再配達日変更。氏名:佐藤花子 / 生年月日:1985/03/17 / 携帯:070-0123-4567 / 固定電話:03-3000-1234 / 配送先:神奈川県Example市中央区本町2-4-6 / 登録メール:hanako.sato@example.co.jp / 受付端末IP:198.51.100.22。不在票が投函されたため、明日以降の再配達希望。オペレーター対応メモをそのまま社内チャットに貼りたい。"
3,"旅行予約の宿泊者名義確認。氏名:鈴木一郎、生年月日:1979/11/30、連絡先 090-0123-4568、メール ichiro.suzuki@example.jp、宿泊先 大阪府Example市北区Example町3-3-3 Exampleホテル、折返し先 03-3000-1234。予約確認メールの内容を海外LLMで要約してよいか、ゲストから確認あり。IVR通過時の端末IP 192.0.2.10。"

含まれる個人情報の整理

検証データに含まれる主な個人情報は以下のとおりです。

カテゴリ
氏名(日本語) 山田太郎、佐藤花子、鈴木一郎
携帯電話番号 090-0123-4567、070-0123-4567
固定電話番号 03-3000-1234
メールアドレス taro.yamada@example.com
生年月日 1988年4月15日、1985/03/17
住所(日本) 東京都架空区Example町1-2-3 …
IPアドレス 203.0.113.45
社名 クラスメソッド

社名は個人情報ではないですが、会話履歴に残っていると嫌なケースを想定して、ここではマスク対象とします。

1. AWS Glue Detect PII でのマスク

Glue Job の Detect PII トランスフォームを使い、S3 上の CSV を読み込んで text 列のPIIをマスクする流れで検証しました。

1. データの配置

検証用 CSV を S3 バケットにアップロードしました。

アップロードしたファイル

2. 組み込みのパターンを使って処理をする場合

ジョブの作成

Glue Studio からジョブを作成し、ソース(S3 の CSV)→ Detect PII トランスフォーム → ターゲット(S3 等)の構成にしました。

今回は Find sensitive data in each row を選び、全てのパターンで検出をしました。global detection sensitivityはPIIなのでHighとし、Select global actionでPARTIAL_REDACT. Partially redact detected text.を設定することで、一部分を置換するようにしました。

PII検出の設定1

PII検出の設定2

ジョブの実行と結果

ジョブを実行し、出力先のデータを確認しました。
電話番号などはマスクできているものの、氏名など結構漏れていることが分かります。
当然ではありますが、1つ目のレコードにある社名もマスクされていません。

{
    "record_id": "1",
    "text": "問い合わせ内容:こんにちは、私の名前は山田太郎です。電話番号は*************、メールアドレスはtaro.yamada@*******.comです。クラスメソッドに所属しています。生年月日は1988年4月15日、住所は東京都架空区Example町1-2-3 Exampleマンション101号室です。最終アクセス元IPは203.0.113.45でした。",
    "DetectedEntities": {
        "text": [
            {
                "entityType": "UK_PHONE_NUMBER",
                "actionUsed": "PARTIAL_REDACT",
                "start": 31,
                "end": 44
            },
            {
                "entityType": "LUXEMBOURG_PASSPORT_NUMBER",
                "actionUsed": "PARTIAL_REDACT",
                "start": 65,
                "end": 72
            }
        ]
    }
}
{
    "record_id": "2",
    "text": "通販の再配達日変更。氏名:佐藤花子 / 生年月日:1985/03/17 / 携帯:************* / 固定電話:************ / 配送先:神奈川県Example市中央区本町2-4-6 / 登録メール:************************* / 受付端末IP:*************。不在票が投函されたため、明日以降の再配達希望。オペレーター対応メモをそのまま社内チャットに貼りたい。",
    "DetectedEntities": {
        "text": [
            {
                "entityType": "LUXEMBOURG_PASSPORT_NUMBER",
                "actionUsed": "PARTIAL_REDACT",
                "start": 124,
                "end": 131
            },
            {
                "entityType": "IP_ADDRESS",
                "actionUsed": "PARTIAL_REDACT",
                "start": 147,
                "end": 160
            },
            {
                "entityType": "UK_PHONE_NUMBER",
                "actionUsed": "PARTIAL_REDACT",
                "start": 62,
                "end": 74
            },
            {
                "entityType": "UK_PHONE_NUMBER",
                "actionUsed": "PARTIAL_REDACT",
                "start": 41,
                "end": 54
            },
            {
                "entityType": "EMAIL",
                "actionUsed": "PARTIAL_REDACT",
                "start": 112,
                "end": 137
            }
        ]
    }
}
{
    "record_id": "3",
    "text": "旅行予約の宿泊者名義確認。氏名:鈴木一郎、生年月日:1979/11/30、連絡先 *************、メール ************************、宿泊先 大阪府Example市北区Example町3-3-3 Exampleホテル、折返し先 ************。予約確認メールの内容を海外LLMで要約してよいか、ゲストから確認あり。IVR通過時の端末IP **********。",
    "DetectedEntities": {
        "text": [
            {
                "entityType": "IP_ADDRESS",
                "actionUsed": "PARTIAL_REDACT",
                "start": 191,
                "end": 201
            },
            {
                "entityType": "LUXEMBOURG_PASSPORT_NUMBER",
                "actionUsed": "PARTIAL_REDACT",
                "start": 73,
                "end": 80
            },
            {
                "entityType": "UK_PHONE_NUMBER",
                "actionUsed": "PARTIAL_REDACT",
                "start": 41,
                "end": 54
            },
            {
                "entityType": "UK_PHONE_NUMBER",
                "actionUsed": "PARTIAL_REDACT",
                "start": 131,
                "end": 143
            },
            {
                "entityType": "EMAIL",
                "actionUsed": "PARTIAL_REDACT",
                "start": 59,
                "end": 83
            }
        ]
    }
}

全くできていないわけではないので、できる場合もあるけど漏れてしまうものも多いという印象です。

3. 独自の検出パターンを使って処理をする場合

検出パターンの作成

正規表現で独自の検出パターンを指定し、PII検出で使用することができます。

GlueコンソールのCreate detection patternから作成することができました。

Create detection pattern

以下のようにパターンを作成しました。

作成したパターン

ジョブの修正

先のジョブで、作成した独自パターンも含めて全ての検出パターンを設定・保存しました。

ジョブの修正

追加のカスタム検出パターン

ジョブの実行と結果

ジョブを実行し、結果を確認しました。
以下のようにかなりマスクできたものが増えました。名前はどうしてもパターンが拾いづらく一部漏れています。
用途によってどこまでやるかはありますが、入力のパターンが増えるとより意図しない漏れや過剰なマスクが発生すると想像されます。

{
    "record_id": "1",
    "text": "問い合わせ内容:こんにちは、私の名前は山田太郎です。電話番号は*************、メールアドレスはtaro.yamada@*******.comです。*******に所属しています。生年月日は**********、住所は***************************************。最終アクセス元IPは************でした。",
    "DetectedEntities": {
        "text": [
            {
                "entityType": "ip-adress",
                "actionUsed": "PARTIAL_REDACT",
                "start": 164,
                "end": 176
            },
            {
                "entityType": "phone",
                "actionUsed": "PARTIAL_REDACT",
                "start": 31,
                "end": 44
            },
            {
                "entityType": "address",
                "actionUsed": "PARTIAL_REDACT",
                "start": 114,
                "end": 153
            },
            {
                "entityType": "company_name",
                "actionUsed": "PARTIAL_REDACT",
                "start": 79,
                "end": 86
            },
            {
                "entityType": "birthdate",
                "actionUsed": "PARTIAL_REDACT",
                "start": 100,
                "end": 110
            },
            {
                "entityType": "UK_PHONE_NUMBER",
                "actionUsed": "PARTIAL_REDACT",
                "start": 31,
                "end": 44
            },
            {
                "entityType": "LUXEMBOURG_PASSPORT_NUMBER",
                "actionUsed": "PARTIAL_REDACT",
                "start": 65,
                "end": 72
            }
        ]
    }
}
{
    "record_id": "2",
    "text": "通販の再配達日変更。********/ 生年月日:********** / 携帯:************* / 固定電話:************ / 配送先:****************************************************************P:*************。不在票が投函されたため、明日以降の再配達希望。オペレーター対応メモをそのまま社内チャットに貼りたい。",
    "DetectedEntities": {
        "text": [
            {
                "entityType": "ip-adress",
                "actionUsed": "PARTIAL_REDACT",
                "start": 147,
                "end": 160
            },
            {
                "entityType": "phone",
                "actionUsed": "PARTIAL_REDACT",
                "start": 41,
                "end": 54
            },
            {
                "entityType": "birthdate",
                "actionUsed": "PARTIAL_REDACT",
                "start": 25,
                "end": 35
            },
            {
                "entityType": "name",
                "actionUsed": "PARTIAL_REDACT",
                "start": 10,
                "end": 18
            },
            {
                "entityType": "address",
                "actionUsed": "PARTIAL_REDACT",
                "start": 81,
                "end": 145
            },
            {
                "entityType": "LUXEMBOURG_PASSPORT_NUMBER",
                "actionUsed": "PARTIAL_REDACT",
                "start": 124,
                "end": 131
            },
            {
                "entityType": "IP_ADDRESS",
                "actionUsed": "PARTIAL_REDACT",
                "start": 147,
                "end": 160
            },
            {
                "entityType": "UK_PHONE_NUMBER",
                "actionUsed": "PARTIAL_REDACT",
                "start": 62,
                "end": 74
            },
            {
                "entityType": "UK_PHONE_NUMBER",
                "actionUsed": "PARTIAL_REDACT",
                "start": 41,
                "end": 54
            },
            {
                "entityType": "EMAIL",
                "actionUsed": "PARTIAL_REDACT",
                "start": 112,
                "end": 137
            }
        ]
    }
}
{
    "record_id": "3",
    "text": "旅行予約の宿泊者名義確認。*******、生年月日:**********、連絡先 *************、メール ************************、宿泊先 *************************************、折返し先 ************。予約確認メールの内容を海外LLMで要約してよいか、ゲストから確認あり。IVR通過時の端末IP **********。",
    "DetectedEntities": {
        "text": [
            {
                "entityType": "address",
                "actionUsed": "PARTIAL_REDACT",
                "start": 88,
                "end": 125
            },
            {
                "entityType": "phone",
                "actionUsed": "PARTIAL_REDACT",
                "start": 41,
                "end": 54
            },
            {
                "entityType": "birthdate",
                "actionUsed": "PARTIAL_REDACT",
                "start": 26,
                "end": 36
            },
            {
                "entityType": "name",
                "actionUsed": "PARTIAL_REDACT",
                "start": 13,
                "end": 20
            },
            {
                "entityType": "ip-adress",
                "actionUsed": "PARTIAL_REDACT",
                "start": 191,
                "end": 201
            },
            {
                "entityType": "IP_ADDRESS",
                "actionUsed": "PARTIAL_REDACT",
                "start": 191,
                "end": 201
            },
            {
                "entityType": "LUXEMBOURG_PASSPORT_NUMBER",
                "actionUsed": "PARTIAL_REDACT",
                "start": 73,
                "end": 80
            },
            {
                "entityType": "UK_PHONE_NUMBER",
                "actionUsed": "PARTIAL_REDACT",
                "start": 41,
                "end": 54
            },
            {
                "entityType": "UK_PHONE_NUMBER",
                "actionUsed": "PARTIAL_REDACT",
                "start": 131,
                "end": 143
            },
            {
                "entityType": "EMAIL",
                "actionUsed": "PARTIAL_REDACT",
                "start": 59,
                "end": 83
            }
        ]
    }
}

Snowflake AI_REDACT でのマスク

Snowflake に取り込み後、 Cortex AI 関数の AI_REDACT を使い、同じ検証データをテーブルにロードしてマスクするケースも試しました。
AI_REDACTはLLMベースのベストエフォート検出のため、こちらも検出漏れ・誤検出があり得ます。また、文法的に正しい英語テキストで最も性能が高く、日本語や誤字・句読点の乱れがあるテキストでは結果が異なる場合があります。

https://docs.snowflake.com/ja/sql-reference/functions/ai_redact

https://docs.snowflake.com/ja/user-guide/snowflake-cortex/redact-pii

1. データのロード

先ほどAWS Glueで処理したファイルをSnowsightからアップロードし、テーブルを作成しました。

作成したテーブル

2. AI_REDACT でマスク

まずはデフォルトの redact モードで text 列をマスクしました。

SELECT 
    AI_REDACT(text) AS redacted_text
FROM PII_INCLUDE_DATA;

デフォルトで実行

完璧にマスクできていました。通話の書き起こしなどの場合は漏れが出る可能性はあるかもしれないですが、今回くらいの長さであれば問題なさそうです。
AI/LLMに送信してしまってよいデータであればこれでよさそうですね。
ただしクラスメソッドはマスクできていないのでこういった機密性が低いが隠したい文字列はマスク後に自分で削除した方がよさそうです。

カテゴリを絞る場合は categories 引数を指定できました。

カテゴリを指定して実行

最後に検出位置だけ確認したい場合は detect モードでできました。

detectモードで実行

最後に

問い合わせテキスト風の検証データを使い、AWS Glue の Detect PII transform と Snowflake の AI_REDACT で個人情報のマスクを試す手順を整理しました。

Glue は ETL パイプラインに組み込みやすく、パターンを明示的に制御できる点が強みと考えられます。AI_REDACT は SQL から手軽に試せ、文脈に依存する表現の扱いに期待できる一方、日本語テキストやサポートカテゴリの制限は公式Docの注意点どおり確認が必要です。
同様のユースケースを検討されている方の参考になれば幸いです。


Snowflakeの導入支援はクラスメソッドに!

クラスメソッドでは Snowflake の導入を支援しております。
製品の詳細や支援の内容についてお気軽にお問い合わせください。

Snowflakeの詳細を見る

この記事をシェアする

AWSのお困り事はクラスメソッドへ

関連記事