Sente に PIIスクラビングを追加した
— 送る前に、このMacで隠す
Sente(先手)は teai.io の CLI コーディングエージェントです。今回追加したのは、 送信直前にこのMac上のローカルLLM(Ollama)+正規表現で氏名・住所・電話番号・APIキーなどの 機微な情報を検出し、プレースホルダに置き換えてから teai.io へ送るオプトイン機能です。 応答が返ってきたら、こちら側で元の値に復元してから表示します。既定はオフで、有効化しない限り これまでと挙動は変わりません。
te privacy scrub onで有効化。氏名・住所・電話・メール・APIキーを検出してから送信、応答は復元。- クライアント側だけで完結(このMac上のローカルプロキシがbaseURLを差し替えるだけ)。teai.io側のサーバは変更していません。
- Ollamaが使えない時は平文のまま送らずエラーで止まるフェイルクローズ設計。完全な検出を保証するものではありません。
# 有効化(ollama + qwen3.5:4b が必要) ollama pull qwen3.5:4b te privacy scrub on # 使い方はこれまでと同じ te run "この設計、機密情報込みで壁打ちしたい" # 無効化 te privacy scrub off
なぜ作ったか
Senteは日常のコーディング相談だけでなく、社内の込み入った話や、実在の氏名・住所が混ざる文脈で 使われることがあります。teai.io はリクエスト本文を保存しませんが、それでも そもそも機微な情報をクラウドへ一切送らずに済むなら、それに越したことはない—— その発想から、送信の手前にもう一段、ローカルだけで完結する壁を作ることにしました。
仕組み
te privacy scrub on にすると、Senteの起動時にこのMac上へ小さなHTTPプロキシ
(127.0.0.1のみでlisten)が立ち上がり、OpenCode の設定(baseURL)がそのプロキシへ
向け替わります。teai.io のAPIキーはこれまで通りOpenCode側がヘッダに載せるだけで、
プロキシ自身はキーの中身を知りません。
- 正規表現パス — メール・電話番号・郵便番号付き住所・APIキーらしき文字列を即座に検出し、
[EMAIL_1]のようなプレースホルダへ置換 - ローカルLLMパス — 残りの地の文を Ollama(
qwen3.5:4b)に渡し、人名・住所・組織名など機微になりうるスパンを検出だけさせる(書き換えはさせない — LLMにリライトさせると幻覚で内容が変わりうるため、置換は必ずコード側で決定的に行う) - 置換したプレースホルダの対応表はこのMac上のプロキシ内に保持し、teai.io からの応答が返ってきたら元の値に復元してから画面に表示
フェイルクローズ — 「送れないなら止める」
この手の機能で一番怖いのは、ローカルLLMが落ちている時に気づかず平文のまま送ってしまう ことです。Senteでは Ollama が起動していない・モデル未取得・プロキシ自体が異常な場合、 その場でエラーを出して停止し、上流には一切送信しません。実際に Ollama を止めた状態で試すと、 次のように止まります(フォールバックで黙って素通しする、ということはしません):
開発ログから2つ
日本語の地の文がメールアドレスに巻き込まれた
Pythonの正規表現で \w はデフォルトでUnicode文字クラスも含みます。うっかり
[\w.+-]+@[\w-]+ と書いたところ、「メールはyamada@example.comです」の
「メールは」まで丸ごとメールアドレスの一部として飲み込まれました。英数字に明示的に絞って解決しています。
プレースホルダをもう一段プレースホルダ化してしまう
正規表現パスで [PHONE_1] に変換したあとのテキストをローカルLLMに渡すと、
LLMが「これも機微情報っぽい」と [PHONE_1] という文字列自体を拾ってきて
[PERSON_2] のように二重にマスクしてしまう入れ子が起きました。
LLMが返したスパンが既にプレースホルダの形をしていたら再マスクしない、というガードを一行足して解決しています。
コマンド早見表
| コマンド | 動作 |
|---|---|
te privacy scrub on | 次回起動からPIIスクラビングを有効化 |
te privacy scrub off | 無効化(既定) |
te privacy | スクラブを含む、データの扱い全体を表示 |
TE_PII_SCRUB=1 te run "..." | その場一回だけ有効化 |