3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Amazon Bedrock ガードレールでマイナンバーをどこまで検出できるか試してみた

3
Posted at

地方自治体のガバメントクラウド AWS 環境では Amazon Bedrock を使うことができます。インターネット接続のない閉域ネットワークで、処理が国内リージョンに閉じるなど、必要なセキュリティ要件を満たした上であれば、マイナンバー利用事務系と呼ばれる地方自治体の住民情報などを扱う基幹業務システムのネットワークでも Bedrock を使った生成 AI を活用できます。

しかし、生成 AI にマイナンバーを含むデータを処理させることは、マイナンバー法や個人情報保護委員会の指針などに従い慎重に判断しなければなりません。そのため、実際にマイナンバーを含むデータを生成 AI に処理させている事例はまだ少ないのが現状かと思われます。

ただ、マイナンバー利用事務系のネットワークにおける業務の全てがマイナンバーそのものを扱っているわけではないため、マイナンバー利用事務系のネットワークのクライアントで、マイナンバーを含まないデータの処理を生成 AI で行いたい場面は多いです。こういった場面で、自治体の職員が安心して生成 AI を利用できるよう、マイナンバーを含むデータを誤って生成 AI に処理させないための対策をしておきたいところです。

そこで、この対策として Bedrock ガードレールの機能を使えないか、検証してみました。

Bedrock ガードレールと検証の目的

AWS のドキュメントでは以下のように説明されています。

Amazon Bedrock ガードレールは、安全な生成 AI アプリケーションの構築に役立つ設定可能な保護を提供します。基盤モデル (FMs) 全体の包括的な安全とプライバシーの制御により、Amazon Bedrock ガードレールは一貫したユーザーエクスペリエンスを提供し、望ましくないコンテンツを検出してフィルタリングし、ユーザー入力またはモデル応答 (推論コンテンツブロックを除く) に存在する可能性のある機密情報を保護します。

Amazon Bedrock ガードレールを使用して有害なコンテンツを検出してフィルタリングする

今回は Bedrock を使ったアプリケーションで、生成 AI モデルへデータを渡す手前で Bedrock ガードレールを適用することで、データに含まれるマイナンバーが生成 AI モデルへの入力に含まれないようにできるかについて検証します。

併せて、Bedrock ガードレールに組み込まれている PII(個人を特定できる機密情報)のフィルターで、マイナンバー以外の個人情報をどの程度フィルターすることができるかも検証します。

今回は検証用として、Excel ファイルをアップロードすると、個人情報をマスクしてから生成 AI モデルに分析させる簡易な Web アプリを AgentCore Runtime で作成し、動作を確認することにします。

この検証の留意事項

そもそも Bedrock ガードレールには日本のマイナンバーに特化したフィルターがない

Bedrock ガードレールには、2026/11/01 現在、日本のマイナンバーに特化したフィルターはありません。 そのため、現時点では正規表現フィルターを使うしかマイナンバーを検出する方法はありません。つまり、正規表現の精度がマイナンバーの検出に大きな影響を与えることになります。

機密情報を含むデータが処理される範囲

今回、個人情報のマスク処理は Bedrock の ApplyGuardrail API が行います。そのため、個人情報を含むデータは Bedrock までは渡っています。AWS のサービスである Bedrock までは個人情報などの機密情報が渡ることを許容し、Bedrock の API で呼び出される生成 AI モデルには機密情報を渡さないことの検証となります。

機密情報を含むデータが処理されるリージョン

実運用を見据えて、使用する生成 AI モデルは Claude Haiku 4.5 とし、Bedrock ガードレールと生成 AI モデルは、ガバメントクラウドの要件を満たすよう国内リージョンで完結するものを選びました。

ただし、今回は PoC のため、フロントエンドはグローバルリージョンのサービスである CloudFront で S3 の SPA (Single Page Application) を配信する構成としました。実際に運用する場合は、国内リージョンで完結できるよう、例えば VPC + ALB + ECS 構成などにする必要があるかと思います。

Bedrock ガードレールの対象外としたもの

Excel ファイルのファイル名は Claude に渡していません。シート名はマスクせずにそのまま Claude へ渡しているため、今回の検証の対象外としました。実運用の際はシート名も考慮が要ると思います。

検証用 Web アプリの概要

Web アプリの構成は以下のとおりです。

pii-filter-moc.drawio.png

処理の流れは次のとおりです。

  1. ブラウザで Cognito のマネージドログインにログインし、アクセストークンを取得
  2. Excel ファイルを Base64 にして、CloudFront 経由で AgentCore Runtime に送る(CloudFront の /runtimes/* を AgentCore のエンドポイントに振り分けているので、ブラウザから見ると同じオリジンになり、CORS の設定は不要)
  3. AgentCore Runtime は、JWT 認証で Cognito のアクセストークンを検証
  4. Runtime の中で、Excel の各セルを ApplyGuardrail(INPUT) でマスク
  5. マスク後のデータだけを、Converse API で Claude に渡す
  6. Claude の応答を、もう一度 ApplyGuardrail(OUTPUT) でチェック

CloudFront と WAF を除き、以下のとおり国内リージョンで処理するようにしています。

サービス 設定
Bedrock ガードレール 東京リージョンに作成しクロスリージョン推論はオフ
推論モデル jp.anthropic.claude-haiku-4-5-20251001-v1:0(jp. 推論プロファイル。処理先は東京・大阪リージョン)
AgentCore Runtime 東京リージョン
Cognito 東京リージョン
S3 東京リージョン
CloudWatch Logs 東京リージョン

Bedrock ガードレールの設定

Bedrock のマネジメントコンソールから「ガードレール」→「ガードレールの作成」に進みます。任意の名前を設定し、「Cross-Region inference」のチェックはオフのままとします。

スクリーンショット 2026-11-01 23.53.55.png

「機密情報フィルターを追加」まで進み、フィルターしたい個人情報の項目を設定します。

スクリーンショット 2026-10-31 8.44.34.png

ここでは以下のとおり機密情報フィルターに PII タイプを設定しました。なお、処理を「マスク」としたのは、今回の検証の目的は、機密情報がどの程度ガードレールでフィルターされるかの調査であるためです。実運用では、ブロックを選択する場合もあるかと思います。

PII タイプ 入力 出力
NAME マスク マスク
ADDRESS マスク マスク
AGE マスク マスク
EMAIL マスク マスク
PHONE マスク マスク
CREDIT_DEBIT_CARD_NUMBER マスク マスク

既存の PII タイプに日本のマイナンバーはないため、次のとおり機密情報フィルターに正規表現パターンを追加します。

スクリーンショット 2026-11-02 0.11.17.png

JP_MYNUMBER:半角 12 桁のマイナンバー(区切りなし、空白、ハイフン)

\b\d{4}[- ]?\d{4}[- ]?\d{4}\b

JP_MYNUMBER_FULLWIDTH:全角 12 桁のマイナンバー

[0-9]{4}[-  ]?[0-9]{4}[-  ]?[0-9]{4}

ついでに日本の電話番号と郵便番号に特化したフィルターを検証する正規表現パターンも追加しておきます。

JP_PHONE:日本の電話番号(携帯、固定、フリーダイヤル、+81)

(?:\+81[- ]?|\b0)(?:[5789]0-?\d{4}-?\d{4}|\d{1,4}-\d{1,4}-\d{4}|120-?\d{3}-?\d{3}|\d{9})\b

JP_POSTAL_CODE:郵便番号(〒付き、またはハイフン付き)

〒 ?\d{3}-?\d{4}\b|\b\d{3}-\d{4}\b

なお、正規表現パターンに「前後に数字が続かない 12 桁」を否定先読み・後読みを使って (?<!\d) や (?!\d) で表そうとしましたが、「Regex pattern is invalid」というエラーになりました。

ガードレールの作成後、画面から動作確認できます。以下はプロンプトにマイナンバーっぽい番号を入れたときの動作で、その部分だけマスクされたことが分かります。

スクリーンショット 2026-11-02 0.25.56.png

最後にガードレールの「バージョンを作成」を実行します。

スクリーンショット 2026-10-31 9.30.19.png

Cognito ユーザープールの作成

AgentCore Runtime を JWT 認証で使うため、Cognito ユーザープールを作成します。「アプリケーションタイプ」は「シングルページアプリケーション(SPA)」とします。「リターン URL」は一旦開発環境で使う http://localhost:5173/ としました。

スクリーンショット 2026-11-02 0.39.00.png

「アプリケーションクライアント」からマネージドログインページを編集し、「許可されているサインアウト URL」を設定します。(一旦開発環境で使う http://localhost:5173/ を設定。)

「OpenID Connect のスコープ」は「E メール」「OpenID」とします。

スクリーンショット 2026-10-31 15.15.23.png

「マネージドログイン」にスタイルが設定されていなければ新規作成します。

スクリーンショット 2026-10-31 15.23.47.png

検証用にユーザーを追加します。

スクリーンショット 2026-10-31 9.44.13.png

ユーザープールの ID、アプリケーションクライアントの ID、ドメイン(xxxx.auth.ap-northeast-1.amazoncognito.com)は後で使うので控えておきます。

AgentCore Runtime の作成

個人情報のマスクと Claude の呼び出しを行う処理は、AgentCore Runtime にデプロイします。デプロイには AgentCore CLI を使います。

AgentCore CLI のインストール

AgentCore CLI は npm でインストールします。

$ npm install -g @aws/agentcore
$ agentcore --version

AgentCore Runtime が使う IAM ロールを作成

AgentCore Runtime が必要最小限の生成 AI モデルのみを呼び出せるよう、IAM ロールを別途作成します。

「信頼されたエンティティ」は以下のとおり設定します。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AssumeRolePolicy",
      "Effect": "Allow",
      "Principal": {
        "Service": "bedrock-agentcore.amazonaws.com"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": {
          "aws:SourceAccount": "<ACCOUNT_ID>"
        },
        "ArnLike": {
          "aws:SourceArn": "arn:aws:bedrock-agentcore:ap-northeast-1:<ACCOUNT_ID>:*"
        }
      }
    }
  ]
}

「インラインポリシー」に以下のとおり設定します。<GUARDRAIL_ID> は作成した Bedrock ガードレールの ID に置き換えてください。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "RuntimeLogs",
      "Effect": "Allow",
      "Action": ["logs:DescribeLogStreams", "logs:CreateLogGroup"],
      "Resource": "arn:aws:logs:ap-northeast-1:<ACCOUNT_ID>:log-group:/aws/bedrock-agentcore/runtimes/*"
    },
    {
      "Sid": "RuntimeLogStreams",
      "Effect": "Allow",
      "Action": ["logs:CreateLogStream", "logs:PutLogEvents"],
      "Resource": "arn:aws:logs:ap-northeast-1:<ACCOUNT_ID>:log-group:/aws/bedrock-agentcore/runtimes/*:log-stream:*"
    },
    {
      "Sid": "Tracing",
      "Effect": "Allow",
      "Action": ["xray:PutTraceSegments", "xray:PutTelemetryRecords", "xray:GetSamplingRules", "xray:GetSamplingTargets"],
      "Resource": "*"
    },
    {
      "Sid": "Metrics",
      "Effect": "Allow",
      "Action": "cloudwatch:PutMetricData",
      "Resource": "*",
      "Condition": {
        "StringEquals": {"cloudwatch:namespace": "bedrock-agentcore"}
      }
    },
    {
      "Sid": "WorkloadAccessToken",
      "Effect": "Allow",
      "Action": ["bedrock-agentcore:GetWorkloadAccessToken", "bedrock-agentcore:GetWorkloadAccessTokenForJWT"],
      "Resource": [
        "arn:aws:bedrock-agentcore:ap-northeast-1:<ACCOUNT_ID>:workload-identity-directory/default",
        "arn:aws:bedrock-agentcore:ap-northeast-1:<ACCOUNT_ID>:workload-identity-directory/default/workload-identity/*"
      ]
    },
    {
      "Sid": "ApplyGuardrail",
      "Effect": "Allow",
      "Action": "bedrock:ApplyGuardrail",
      "Resource": "arn:aws:bedrock:ap-northeast-1:<ACCOUNT_ID>:guardrail/<GUARDRAIL_ID>"
    },
    {
      "Sid": "InvokeJpInferenceProfile",
      "Effect": "Allow",
      "Action": "bedrock:InvokeModel",
      "Resource": "arn:aws:bedrock:ap-northeast-1:<ACCOUNT_ID>:inference-profile/jp.anthropic.claude-haiku-4-5-20251001-v1:0"
    },
    {
      "Sid": "InvokeFoundationModelOnlyViaJpProfile",
      "Effect": "Allow",
      "Action": "bedrock:InvokeModel",
      "Resource": [
        "arn:aws:bedrock:ap-northeast-1::foundation-model/anthropic.claude-haiku-4-5-20251001-v1:0",
        "arn:aws:bedrock:ap-northeast-3::foundation-model/anthropic.claude-haiku-4-5-20251001-v1:0"
      ],
      "Condition": {
        "StringLike": {
          "bedrock:InferenceProfileArn": "arn:aws:bedrock:ap-northeast-1:<ACCOUNT_ID>:inference-profile/jp.anthropic.claude-haiku-4-5-20251001-v1:0"
        }
      }
    }
  ]
}

プロジェクトを作成する

AgentCore CLI でプロジェクトのひな形を出力します。

$ agentcore create --name PiiFilterPoc --skip-git --region ap-northeast-1

PiiFilterPoc/ の下に、次のようなファイルができます。

PiiFilterPoc/
├── agentcore/
│   ├── agentcore.json     ← プロジェクトの設定(Runtime などの定義)
│   ├── aws-targets.json   ← デプロイ先のアカウントとリージョン
│   └── cdk/               ← CLI が管理する CDK アプリ
└── app/                   ← Runtime のコードを置く場所

Runtime を追加する

Runtime の設定を agentcore.json に追加します。

JWT 認証には、作成した Cognito のユーザープール ID とアプリケーションクライアント ID を使います。<USER_POOL_ID> と <CLIENT_ID> をそれぞれ置き換えてください。

$ cd PiiFilterPoc
$ agentcore add runtime \
  --name PiiFilterPoc \
  --language python \
  --protocol HTTP \
  --description "Excel PII filter PoC (Bedrock Guardrails + Claude via jp. inference profile)" \
  --environment-variables '{"GUARDRAIL_ID":"<GUARDRAIL_ID>","GUARDRAIL_VERSION":"1","MODEL_ID":"jp.anthropic.claude-haiku-4-5-20251001-v1:0"}' \
  --role-arn arn:aws:iam::<ACCOUNT_ID>:role/<実行ロールの名前> \
  --authorizer-type CUSTOM_JWT \
  --authorizer-configuration '{"customJwtAuthorizer":{"discoveryUrl":"https://cognito-idp.ap-northeast-1.amazonaws.com/<USER_POOL_ID>/.well-known/openid-configuration","allowedClients":["<CLIENT_ID>"]}}'

アプリケーションコードを配置する

app/PiiFilterPoc/ に、main.py(エントリーポイント)と pyproject.toml(依存関係)を置きます。コードの中身は後で説明します。

app/PiiFilterPoc/
├── main.py
└── pyproject.toml

Runtime をデプロイする

プロジェクトのディレクトリ(PiiFilterPoc/)で、デプロイを実行します。リージョンは --region で明示します。

$ AWS_PROFILE=<デプロイに使うプロファイル> agentcore deploy --region ap-northeast-1

デプロイが完了すると、CloudFormation のスタック AgentCore-PiiFilterPoc-default ができます。スタックの出力から、Runtime の ARN を確認します。

$ aws cloudformation describe-stacks \
  --region ap-northeast-1 \
  --stack-name AgentCore-PiiFilterPoc-default \
  --query 'Stacks[0].Outputs'

Runtime ARN for: PiiFilterPoc の値(arn:aws:bedrock-agentcore:ap-northeast-1:<ACCOUNT_ID>:runtime/PiiFilterPoc_default_PiiFilterPoc-xxxxxxxxxx)を控えます。フロントエンドから Runtime を呼び出すときに使います。

検証用のアプリケーションコード

アプリケーションコードの全文は、GitHub のリポジトリで公開しています。

ファイル 内容
PiiFilterPoc/app/PiiFilterPoc/main.py Runtime のコード(マスクと Claude の呼び出し)
PiiFilterPoc/app/PiiFilterPoc/pyproject.toml Runtime の依存関係
PiiFilterPoc/agentcore/agentcore.example.json Runtime の設定の例(アカウント固有の値はプレースホルダー)
frontend/src/api.ts フロントエンドから Runtime を呼び出す部分
samples/generate_test_excel.py テストデータの生成
samples/evaluate.py 結果の集計

リポジトリには、AgentCore CLI のプロジェクトと、フロントエンドのコード一式が入っています。自分の環境で動かすときは、agentcore.example.json を agentcore.json にコピーし、Bedrock ガードレールの ID や Cognito の ID などを書き換えてください。手順は README に書いています。

ここでは、アプリケーションコードの意図を説明します。

Runtime の入出力

フロントエンドからは、次の JSON を送ります。

{
  "filename": "<Excel ファイル名>",
  "content_base64": "<Excel ファイルを Base64 にしたもの>",
  "instruction": "このデータの傾向を要約してください。"
}

Runtime は、シートごとのマスクの結果(セルごとの元の値、マスク後の値、検出された種類)、Claude の応答、応答のチェックの結果、Bedrock ガードレールの消費量(text units)、Claude のトークン数を返します。

セルを 1 つずつ別のコンテンツブロックにして送る

セルを 1 つずつ別のコンテンツブロックにして、まとめて ApplyGuardrail に送るようにしています。隣り合うセルを 1 つのテキストにつなげて送ると、氏名とフリガナが 1 つの {NAME} に置き換えられるなど、セルの区切りが消えてしまい、マスクの結果をセルに対応づけられなくなるためです。

res = bedrock.apply_guardrail(
    guardrailIdentifier=GUARDRAIL_ID,
    guardrailVersion=GUARDRAIL_VERSION,
    source="INPUT",
    content=[{"text": {"text": text}} for text in texts],  # セルごとに 1 ブロック
)

なお、1 回のリクエストに入れるブロックは、送信量を抑えるため合計 12,000 文字までにしています。ただし、これで ApplyGuardrail の秒間のクォータに収まることが保証されるわけではありません。連続した呼び出しや、複数の利用者が同時に使った場合は、クォータを超えることがあります。そのため、PoC としては、ファイルサイズ(1 MB)、展開後のサイズ(20 MB)、セル数(1 万)、1 つのセルの文字数(12,000 文字)、指示の文字数(2,000 文字)に上限を設けています。

マスクできたか確かめられない行は Claude に渡さない

ApplyGuardrail の応答の outputs という項目の数が送ったブロックの数と合わないときは、どのセルがどうマスクされたかを確かめられません。その場合、その行は「対応づけ失敗」とし、Claude には渡さないようにしています。ApplyGuardrail の呼び出しがエラーになったときは、処理全体をエラーにします。どちらの場合も、マスクできたか確かめられないデータがモデルに渡ることはありません。

Claude の呼び出しと応答のチェック

Claude は、Converse API で jp. 推論プロファイルを指定して呼び出します。システムプロンプトでは、{NAME} などがマスクのプレースホルダーであること、プレースホルダーが並んでいるのは 1 つの値が複数のフィルターに検出されたためであること、マスクされた値を推測しないことを伝えています。

Claude の応答は、もう一度 ApplyGuardrail(source="OUTPUT")にかけます。入力のマスクをすり抜けた個人情報を、Claude が応答に含めてしまった場合に検出するためです。

フロントエンドから Runtime を呼び出す

フロントエンド(React)では、Cognito へのログインに AWS Amplify の JavaScript ライブラリ(aws-amplify)を使っています。ログインは signInWithRedirect で Cognito のマネージドログインの画面に移動して行い、ログイン後は fetchAuthSession でアクセストークンを取り出します。このアクセストークンを Authorization: Bearer ヘッダーに付けて、Runtime を呼び出します。

テストデータと集計スクリプト

テストデータ(test_pii.xlsx)は、Python のスクリプトで生成しています。値は全て架空のものです。シートは 3 つあります。

シート 内容
顧客一覧 日本語の顧客データ 20 行(氏名、フリガナ、年齢、メールアドレス、電話番号、郵便番号、住所、マイナンバー、クレジットカード番号、備考)
English 英語の顧客データ 10 行(参考)
マイナンバー マイナンバーの表記ゆれ 8 ケースと、マイナンバーに似た数字 6 ケースを、2 行ずつ

生成したときに、セルごとの正解ラベル(expected.json)も出力します。

テストデータが実在する個人の情報と偶然一致しないように、次の工夫をしています。

テストデータ項目 内容
マイナンバー チェックデジットを、わざと誤った値にしています。発行されるマイナンバーはチェックデジットが必ず正しいので、実在する番号と一致することはありません。Bedrock ガードレールの正規表現はチェックデジットを確認しないので、マスクの結果には影響しません。
電話番号 090-0000-0001 や 0300000003 のように、ひと目で架空とわかる番号にしています。
住所 町名を「架空町」に、英語の住所は「Example St, Anytown」にしています。
メールアドレス 例示用に予約されたドメイン(example.jp、example.com)を使っています。
クレジットカード番号 カード会社が公開しているテスト用の番号を使っています。

アプリの画面から結果を JSON でダウンロードし、集計スクリプト(evaluate.py)で正解ラベルと突き合わせます。マイナンバーのセルは、プレースホルダー以外に数字が残っていないかどうかで、「完全」「一部残存」「未検出」に分けて判定します。

スクリプトの実行例は以下のとおりです。

$ uv run samples/generate_test_excel.py
$ uv run samples/evaluate.py samples/test_pii_result.json

S3 と CloudFront でアプリケーションを公開する

フロントエンドを S3 に置き、CloudFront で配信します。CloudFront の /runtimes/* を AgentCore のエンドポイントに振り分けることで、フロントエンドと Runtime を同じオリジンにします。

ビルドしてアップロードする

東京リージョンに S3 バケットを作成します。「パブリックアクセスをすべてブロック」はオンのままにします。静的ウェブサイトホスティングは使いません。

アプリケーションのフロントエンドに、先の手順で控えた Cognito の値と、Runtime の ARN を、frontend/.env.local ファイルで環境変数として渡します。

frontend/.env.local
VITE_USER_POOL_ID=<ユーザープール ID>
VITE_USER_POOL_CLIENT_ID=<アプリケーションクライアント ID>
VITE_COGNITO_DOMAIN=<ドメイン(https:// は付けない)>
VITE_AGENT_RUNTIME_ARN=<控えた Runtime の ARN>

リポジトリの frontend/ で、フロントエンドの依存関係をインストールして、ビルドします。npm ci は、package-lock.json に記録されたバージョンのとおりにインストールします。

$ cd frontend
$ npm ci
$ npm run build

出力された dist/ の中身を、バケットの直下にアップロードします。

$ aws s3 sync dist/ s3://<バケット名>/ --delete

CloudFront ディストリビューションを作成する

CloudFront ディストリビューションを作成します。オリジンは作成した S3 バケットに設定します。バケットはパブリックアクセスをブロックしているので、オリジンアクセスコントロール(OAC)を使い、表示されるバケットポリシーを S3 バケットに設定します。

スクリーンショット 2026-11-01 9.35.57.png

デフォルトルートオブジェクトは index.html とします。

スクリーンショット 2026-11-01 9.41.58.png

AgentCore のオリジンとビヘイビアを追加する

作成したディストリビューションに、2 つ目のオリジンを追加します。

項目 設定
オリジンドメイン bedrock-agentcore.ap-northeast-1.amazonaws.com
プロトコル HTTPS のみ

スクリーンショット 2026-11-01 9.44.33.png

続いて、次の動作(ビヘイビア)を追加します。

項目 設定
パスパターン /runtimes/*
オリジン AgentCore のオリジン
ビューワープロトコルポリシー HTTPS only
許可する HTTP メソッド GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE
キャッシュポリシー CachingDisabled
オリジンリクエストポリシー AllViewerExceptHostHeader

スクリーンショット 2026-11-01 9.48.22.png

スクリーンショット 2026-11-01 9.49.06.png

Cognito に CloudFront の URL を追加する

Cognito ユーザープールの「アプリケーションクライアント」の「ログインページ」の設定で、次の URL を追加します。末尾の / も必要です。

項目 URL
許可されているコールバック URL https://<ディストリビューションのドメイン>/
許可されているサインアウト URL https://<ディストリビューションのドメイン>/

スクリーンショット 2026-11-01 9.52.44.png

ここまでで、ログインはできるようになります。ただし、Excel ファイルを処理するには、次の 2 つの設定も確認してください。注意点 1 は、ディストリビューションで AWS WAF を有効にした場合の設定です。

注意点 1: WAF がファイルの送信をブロックする

今回の手順で CloudFront のディストリビューションを作ったときは、AWS WAF が有効になっていました。WAF を有効にすると適用される AWS のマネージドルール(Core rule set)には、本文が 8 KB を超えるリクエストをブロックする SizeRestrictions_BODY というルールが含まれています。今回のアプリは Excel ファイルを POST の本文に入れて送るので、このルールだけを、ブロックせずにカウントだけするように変えます。

  1. WAF のコンソールで「保護パック(ウェブ ACL)」を開き、ディストリビューションに付いているウェブ ACL(CreatedByCloudFront-xxxxxxxx)を開きます。
  2. 「ルール」から AWS-AWSManagedRulesCommonRuleSet を選び、ルールの一覧で SizeRestrictions_BODY を探し、アクションを「Count」に変えて保存します。

スクリーンショット 2026-11-01 10.14.44.png

注意点 2: オリジンのレスポンスタイムアウトを延ばす

CloudFront は、オリジンからの応答を既定で 30 秒しか待ちません。ApplyGuardrail と Claude の処理は 30 秒を超えることがあるので、延ばしておきます。

  1. ディストリビューションの「オリジン」から、bedrock-agentcore.ap-northeast-1.amazonaws.com を選び、「編集」を押します。
  2. 「レスポンスタイムアウト」を 60 に変えて保存します。

スクリーンショット 2026-11-01 10.16.46.png

検証結果

CloudFront の URL でアプリを開き、test_pii.xlsx を処理しました。Claude への指示は「このデータの傾向を要約してください。」です。

マイナンバー

マイナンバーのシートの結果です。

スクリーンショット 2026-11-02 2.27.13.png

色の付いたセルが、検出されたセルです。上段がマスク後の値、下段が元の値です。

  • 全ての表記でマスクできました。
  • 全角数字は全角用の正規表現 JP_MYNUMBER_FULLWIDTH でマスクされています。
  • 顧客一覧のシートのマイナンバーの列も全てマスクできました。
  • 組み込みの PHONE も一部のマイナンバーに反応し {PHONE}{JP_MYNUMBER} になりました。ただ、同じ表記でも反応したりしなかったりと規則性はありませんでした。

マイナンバーに似た数字(誤検出)

マイナンバーのシートの後半には、マイナンバーに似た数字のケースを入れています。

  • 11 桁と 13 桁の数字には JP_MYNUMBER は付きませんでした。13 桁の数字の 1 つは、組み込みの PHONE に電話番号として検出されました。
  • 12 桁の数字は、マイナンバーと区別できません。(Bedrock ガードレールの正規表現ではチェックデジットを確認できないため。)
  • 16 桁のカード番号は、その一部(12 桁)が JP_MYNUMBER にも一致しました。カード番号としてもマスクされているので、実害はありません。

ログに個人情報が残っていないか

AgentCore Runtime のロググループ(/aws/bedrock-agentcore/runtimes/...)から、Claude への入力に個人情報が含まれていないか確認します。

Claude への入力は、ロググループの otel-rt-logs というログストリームに、gen_ai.user.message というイベントとして記録されます。マネジメントコンソールで確認する場合の手順は以下のとおりです。

  1. CloudWatch の「ログ管理」を開き、/aws/bedrock-agentcore/runtimes/<Runtime ID>-DEFAULT を選ぶ
  2. 「すべてのログストリームを検索」を開く
  3. 検索欄に "gen_ai.user.message" と入力すると、Claude への入力が記録されたイベントだけが表示され、イベントを開くと、body の中に、Claude に渡したテキストがそのまま入っていることが確認できる

スクリーンショット 2026-11-01 21.06.33.png

ログを確認した結果は次のとおりでした。

  • Claude への入力(Converse API のリクエストの中身)が、gen_ai.user.message というイベントとして記録されていました。AgentCore Runtime の OpenTelemetry(ADOT)による記録です。
  • マイナンバーについては、ログに残っていたのはプレースホルダー({JP_MYNUMBER}、{JP_MYNUMBER_FULLWIDTH})だけでした。テストに使ったマイナンバーを、区切りなし、空白、ハイフン、全角の各表記で検索しても、1 件も見つかりませんでした。
  • 同じ実行のログには、マスクされなかった漢字の氏名(田中結衣)、カタカナの氏名、住所の番地({ADDRESS}4-10-13 の 4-10-13)、13 桁の数字が、元の値のまま残っていました。
  • マスクする前のデータ(ApplyGuardrail への入力)は、ログに記録されていませんでした。

つまり、Bedrock ガードレールをすり抜けた個人情報は、モデルに渡るだけでなく、ログにもそのまま残ることが分かります。

組み込みの PII フィルター(参考)

  • メールアドレス、クレジットカード番号、英語の氏名と住所は、全てのセルで検出されました。
  • フリガナ(カタカナの氏名)は、検出されないセルが多くありました。スズキ ハナコ が スズキ {ADDRESS} になるなど、名前の一部だけが住所として置き換えられたセルもあります。見出しの「フリガナ」というセル自体も {NAME} に置き換えられました。
  • 漢字の氏名にも、検出されないセル(田中結衣)や、{NAME}衣 のように一部だけがマスクされたセル(小林 結衣)がありました。
  • 年齢は、数字だけのセルだと検出されないことが多くありました。
  • 日本語の住所は全てのセルで検出されましたが、一部のセルでは {ADDRESS}4-10-13 のように、番地がマスクされずに残りました。
  • 備考の「田中工業株式会社との取引あり」は、「田中」が氏名として検出され、{NAME}工業株式会社 になりました。

スクリーンショット 2026-11-02 2.48.33.png

スクリーンショット 2026-11-02 2.48.47.png

スクリーンショット 2026-11-02 2.49.12.png

日本の電話番号と郵便番号の正規表現(参考)

こちらもマイナンバーと同様に正規表現パターンですが、概ね期待どおりフィルター出来ていました。

まとめ

Bedrock ガードレールには、機密情報フィルターがありますが、日本のマイナンバーに特化した組み込みのフィルターは現時点で存在しないため、正規表現でフィルターを作る必要があります。

今回想定した表記は全て正規表現で検出できましたが、正規表現ではマイナンバーとほかの 12 桁の数字を区別できないため、注文番号などもマイナンバーとしてマスクされます。また、想定していない特殊な表記までは検出できない可能性があります。

その他の個人情報のフィルターについては、名前や住所の一部のマスクが漏れるなど若干怪しいところがあり、これを全面的に信頼したアプリケーションの処理を作るのはちょっと難しいかもしれません。

日本のマイナンバーに特化した機密情報フィルターが提供されるとありがたいと思いました。

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?