PIIマスクをLLM無しで4層・0.1msにした
— 遅くて「壊れて見えた」反省から
Sente(先手)には、送信直前にこのMac上で氏名・住所・電話・鍵をプレースホルダに置き換えてから teai.io へ送る オプトイン機能があります(8月の記事)。当初はローカルLLM(Ollama)で 検出していましたが、9月17日に有効化した本人環境で「先手が動かない」状態になりました。 原因を追い、検出をLLMから完全に切り離して4層に作り直したのが今回の話です。
- LLM検出は毎リクエストの全文(巨大なシステムプロンプト含む)を800字ずつ検査し、1ターン112秒。複数セッションが1台のOllamaを取り合うと事実上の無応答。「壊れた」と体験は同じ。
- 既定をLLM無しの4層(パターン / 辞書 / 日本語ヒューリスティック / Apple固有表現認識)に変更。1リクエスト0.1ms、60KBで10ms。Ollamaは
--llmの実験用に格下げ。 - 限界も書きます: Appleの固有表現認識は日本語の人名をほぼ拾わない。敬称も辞書も無い名前は拾えない。完全な検出の保証はしない。
# 有効化(Ollama不要・swiftc があれば Apple の固有表現認識も自動ビルド) te privacy scrub on # 連絡先と git の名前を辞書に(このMacの外に出ません・1回だけ) te privacy scrub harvest # 送らずに確認(所要msも出ます) te privacy scrub preview --file ./sample.txt
何が起きたか
症状は「TUIは立ち上がるが答えが返らない」。ログを追うと、マスク用プロキシが毎リクエストの全文を
800字ごとに Ollama(qwen3:8b)へ投げ、プロセス内のロックで直列に待っていました。エンジンは毎ターン数十KBの
システムプロンプトとツール定義を送り直すので、それだけで数十回の推論。加えて放置された先手のセッションが
十数個残っていて、長時間の te run がファイルを読むたびに同じ検査を走らせ、1台のOllamaを
全員で取り合っていました。te run "1たす1は?" の実測が112秒。画面に何も出ないので、
使う側には「固まった」としか見えません。
判断は一つでした。検出の遅さは精度より先に「壊れている」体験になる。LLMを使わずに 即時で動くものを既定にし、LLMは重ねたい人だけのオプションにする。
4層の中身
| 層 | 拾うもの | 実装 |
|---|---|---|
| 1. パターン | メール・電話・郵便番号+住所・主要サービスの鍵(GitHub / Stripe / Slack / Google / AWS / JWT / npm / Fly / Tailscale ほか)・PEM秘密鍵・高エントロピー文字列・[secret]…[/secret]・password= の値 | 正規表現+エントロピー計算。hex(コミットSHA)・UUID・パスは除外 |
| 2. 辞書 | 自分の連絡先の名前・組織名、git の名前、手書きの固定語 | te privacy scrub harvest が macOS 連絡先から収集。英数字語は単語境界で照合(raven が ravenous を壊さない)。隠さない語は scrub-allow.txt |
| 3. 日本語ヒューリスティック | 「〜さん / 様 / 氏 / 先生」直前の名前、法人格(株式会社・Inc. など)の前後、都道府県+市区郡町村から始まる住所 | 「お客様」「皆さん」「株式会社の設立方法」「東京都内」は残す。分かち書きは使わない |
| 4. Apple固有表現認識 | 人名・組織名 | macOS 内蔵の NaturalLanguage。Swift ソースを同梱し、swiftc があれば on 時に1秒でビルド。常駐プロセスに1行JSONで問い合わせ。失敗したらこの層だけ外す |
4層の検出結果は「原文の部分文字列」として集め、出現範囲の和集合を1つのプレースホルダに置きます。
応答が返ったら対応表で復元。対応表はリクエストごとにメモリ上だけ。Ollamaを重ねる --llm は残していますが、
ラベルもヘルプも「実験用・遅い」に変えました。
実測
| 条件 | 所要 |
|---|---|
| 1リクエスト(4層・Apple層を除く) | 0.1ms |
| 60KBのテキスト | 10ms |
| Apple層込みの preview(ヘルパー起動含む) | 40ms |
te run 一往復 | 4〜5秒(Ollama検出時は28〜53秒、障害時112秒) |
上の例がそのまま限界を示しています。「青葉花子」は残ります。敬称も付いておらず、辞書にも無い名前は どの層も拾えません。「田中太郎さん」は敬称で、「星雲工房」は法人格で、John Smith は Apple の層で拾えています。
踏んだバグ: 重なりで後半が漏れる
「090-1234-5678 token=ghp_…」という並びで、郵便番号のルール(3桁-4桁+40文字)が電話番号から先を巻き込み、
鍵のルールと範囲が重なりました。検出結果を長いものから順に replace していたため、先に長い方が
プレースホルダになった時点で短い方の文字列が見つからなくなり、鍵の後半14文字が平文で残る状態に。
マスクを謳う機能で最悪の形です。置換を「全出現位置を集めて範囲の和集合を作り、まとめて1つに置く」方式に変え、
回帰テストを足しました。郵便番号のルールも後続を日本語の住所文字に限定しています。
te privacy scrub harvest で連絡先を辞書に入れるかどうかで実効性が大きく変わります。
どの層もパターンか辞書かモデルの範囲でしか拾えず、完全な検出は保証しません。「送る前に、手元でもう一段壁を作る」
ためのオプトイン機能である点は8月から変わっていません。応答の一括表示(ストリーミング無し)も同じです。
コマンド早見表
| コマンド | 動作 |
|---|---|
te privacy scrub on | 次回起動から有効(内蔵4層・Ollama不要) |
te privacy scrub harvest | 連絡先+git の名前を辞書に(許可ダイアログ・Mac の外に出ない) |
te privacy scrub preview --file F | 送らずに結果と所要msを確認 |
te privacy scrub status | 有効・層・辞書件数 |
te privacy scrub on --llm | 実験用: Ollama の検出を重ねる(数十秒〜数分遅くなる) |
~/.config/teai/scrub-terms.txt / scrub-allow.txt | 必ず隠す語 / 隠さない語(1行1語) |
te privacy scrub off | 無効化(既定) |