AIで下書きする購入者対応、鍵を入力文から外す小さな見本

購入者から「ダウンロード先が見つからない」と連絡が来たとき、AIに返信文の下書きを頼みたくなります。注文状況と問い合わせ文を渡せば、最初の一文はすぐ作れます。
ただし、受付処理のJSONを丸ごと渡すと、APIキーや本人確認前の社内メモまで同じ入力に入ります。返信案に不要な項目を、便利だからという理由で混ぜないことにしました。ここでは架空のデジタル商品を題材に、5項目の入力を一度止め、3項目だけを別ファイルへ渡す見本を試します。
返信文より先に、入力の境界を決めました
モデルへ渡す項目が多いほど、文面に使える材料は増えます。しかし、購入者への返答にAPIキーは要りません。社内メモも、本人確認前には出したくない情報です。
最初は受付JSONをそのまま渡し、プロンプト側で「鍵と社内メモは無視してください」と書く案を考えました。でも、無視するよう頼むことと、入力から外すことは別です。入力に残っていれば、ログ、外部サービス、次の処理へ渡る経路も増えます。
OWASPは、プロンプトインジェクションが意図しない動作や機微情報の開示につながり得ると説明しています。防御をプロンプトだけに寄せず、モデルの前に渡すデータを小さくするほうを先に試しました。
5項目の受付データを、わざと止めます
見本のincoming.jsonには、購入者へ見せてよい3項目のほかに、ダミーのapi_keyとinternal_noteを入れます。実在の鍵、注文、購入者情報は使いません。
まず、次のファイルを作ります。
work="${TMPDIR:-/tmp}/hibanas-agent-secret-boundary-sample"
mkdir -p "$work"
cd "$work"
cat > incoming.json <<'EOF_JSON'
{
"subject": "購入後のダウンロード先が見つかりません",
"message": "注文番号A-1042の案内をもう一度送ってください。",
"order_status": "paid",
"api_key": "demo-key-should-not-leave-server",
"internal_note": "本人確認前は再送しない"
}
EOF_JSON
続けて、禁止した項目があれば終了コード2で止める検査を置きます。検査は削除をしません。どの入力を分けるかを人が決める入口。
cat > scan_input.py <<'EOF_PY'
import json
from pathlib import Path
blocked = {"api_key", "access_token", "authorization", "internal_note"}
payload = json.loads(Path("incoming.json").read_text(encoding="utf-8"))
hits = sorted(blocked & payload.keys())
if hits:
print("HOLD blocked_fields=" + ",".join(hits))
raise SystemExit(2)
print("OK blocked_fields=0")
EOF_PY
set +e
python3 scan_input.py
scan_status=$?
set -e
printf 'scan_exit=%s\n' "$scan_status"
Python 3.13.5で実行した結果です。
HOLD blocked_fields=api_key,internal_note
scan_exit=2
ここで止まれば、返信の生成まで進みません。最初の案では、api_keyだけを検査対象にしていました。けれども社内メモも購入者へ渡す入力ではありません。鍵だけを探す検査は狭すぎました。
許可した3項目だけを、次の入力へ写します
次は拒否リストではなく、許可する項目の一覧を使います。新しい項目が受付JSONへ増えても、この一覧に足さない限りモデル入力には入りません。
cat > make_model_input.py <<'EOF_PY'
import json
from pathlib import Path
allowed = ("subject", "message", "order_status")
payload = json.loads(Path("incoming.json").read_text(encoding="utf-8"))
model_input = {key: payload[key] for key in allowed if key in payload}
Path("model-input.json").write_text(
json.dumps(model_input, ensure_ascii=False, indent=2) + "\n",
encoding="utf-8",
)
print("OK allowed_fields=" + ",".join(model_input))
print("OUT file=model-input.json keys=" + str(len(model_input)))
EOF_PY
python3 make_model_input.py
python3 - <<'EOF_PY'
import json
from pathlib import Path
p = json.loads(Path('model-input.json').read_text(encoding='utf-8'))
print('blocked_key_present=' + str('api_key' in p).lower())
print('output_keys=' + ','.join(p))
EOF_PY
OK allowed_fields=subject,message,order_status
OUT file=model-input.json keys=3
blocked_key_present=false
output_keys=subject,message,order_status
model-input.jsonが、実際にAIへ渡す候補です。返信文を作る処理は、このファイルだけを読む形にします。受付処理のJSONを再び読めるままにすると、あとから追加した項目が境界を越えます。
この見本で守れる範囲と、守れない範囲
この検査は、購入者対応の安全性を保証するものではありません。問い合わせ本文そのものに「前の指示を無視して」などの文が入る問題も残ります。注文番号で本人確認を済ませたことにもなりません。
それでも、鍵や社内メモを入力から外す役は明確です。AIの返答品質を上げる作業と、渡してよいデータを決める作業は、同じ場所では済ませません。小さな個人ビジネスほど、最初に分けておくほうが見直しやすいと感じました。
本番で使うなら、次の順番が目安です。
- 受付データを受け取ります。
- モデルに渡せる項目を許可リストで選びます。
- 生成した下書きを、人が本人確認と返答内容の両方で確認します。
- 鍵の露出が疑われたら、サービス側で失効して交換します。
この回で使った言葉
| 言葉 | この見本での意味 |
|---|---|
| APIキー | 外部サービスへ接続するための秘密の値です。購入者対応の文章には不要です。 |
| 許可リスト | 入力へ入れてよい項目だけを列挙する一覧です。 |
| モデル入力 | AIへ渡すために切り出したmodel-input.jsonです。 |
| 社内メモ | 購入者へ見せない運用上の判断や注意書きです。 |
| プロンプトインジェクション | 入力文が、意図しない指示としてモデルの動作に影響する問題です。 |


