mysh – AI時代でも安心してDBを扱えるMySQL接続マネージャを作った

はじめに

エラー通知がissueに起票される。Claude Codeにコードベースを調査してもらい、原因の切り分けに実データが必要になる。SQLを書いてもらって手動で実行し、その結果をClaude Codeに渡す。

ただし、クエリ結果に個人情報が含まれていればそのまま渡すわけにはいきません。毎回SQLを調整するか、結果から個人情報を手で消してから渡していました。この手間を毎回やるのも現実的ではなく、1回忘れたら終わりです。

既存ツールではなぜ解決できなかったのか

出力マスキング機能を持つMySQLクライアントを探しましたが、見つけられませんでした。

ツール マスキング機能 備考
mycli なし シンタックスハイライト・補完が中心
DataGrip なし カラム非表示は可能だが出力マスクではない
TablePlus / DBeaver なし GUIクライアント全般に同様
MySQL Enterprise サーバーサイドのみ mask_inner() 等の関数。Enterprise Edition限定

MySQL Enterprise Editionにはサーバーサイドのマスキング関数がありますが、有料かつDB側の設定が必要です。クライアント側で、しかもAI連携を意識した出力マスキングを提供するツールは見当たりませんでした。

myshを作った

ないなら作る。クエリ結果を自動マスクするMySQL接続マネージャ「mysh」をGoで書きました。Claude Codeと2日で完成しました。

マスキングデモ

production環境ではマスク、development環境では生データ。環境に応じて自動で切り替わる。

仕組み:環境×出力先で自動判定

myshのマスキングは「接続先の環境」と「出力先(ターミナル or パイプ)」の組み合わせで自動判定されます。

env ターミナル(人間) パイプ/キャプチャ(AI)
production 自動マスク 自動マスク
staging そのまま 自動マスク
development そのまま そのまま

production環境では、どんな出力先であってもマスクが適用されます。生データが必要な場合は --raw フラグを使えますが、対話的な確認が必須です。

$ mysh run prod-db -e "SELECT * FROM users" --raw
⚠ Raw output requested for production connection "prod-db".
  Masking will be disabled. Continue? [y/N]:

この確認はTTY(ターミナル)でしか応答できません。AIツールやスクリプトは非TTYで実行されるため、確認に応答できず、マスク解除は不可能です。

マスク対象の設定

接続の追加時に、マスク対象のカラムを設定します。完全一致とワイルドカードの両方に対応しています。

Columns to mask (comma-separated, wildcards OK) [email,phone,*password*,*secret*,*token*,*address*]:

production/staging環境ではデフォルト値が提案されるので、Enterを押すだけで一般的な個人情報カラムをカバーできます。*pass* と設定すればフィールド名に pass を含むカラムがすべてマスクされます。

スキーマをClaude Codeに見せてマスク対象を選定してもらうと、抜け漏れなく設定できるのでおすすめです。

マスク結果

種別 元の値 マスク後
メール alice@example.com a***@example.com
電話番号 090-1234-5678 0***
名前 Alice A***

使い方

接続の追加

接続追加デモ

対話的に設定を進められます。CLIフラグで一部の項目を事前に渡すことも可能です。

# 全対話
mysh add

# フラグで一部指定(パスワードは常に対話入力)
mysh add --name prod --env prod --db-host 127.0.0.1 --db-user app --db-name myapp 
  --ssh-host bastion.example.com --ssh-user deploy

設定完了後に接続テストが実行されます。失敗した場合は、修正箇所を選んでその場で直せます。

クエリ実行

接続が1つだけなら、接続名を省略できます。

mysh run -e "SELECT COUNT(*) FROM users"  # インラインSQL
mysh run query.sql                          # SQLファイル
mysh tables                                 # テーブル一覧

SSHトンネル

踏み台サーバー経由の接続もコマンド1つで完結します。

mysh tunnel production                                  # トンネル起動
mysh run production -e "SHOW PROCESSLIST"               # トンネル自動再利用
mysh tunnel stop production                             # 停止

セキュリティ設計

マスキング以外にも、情報漏洩リスクを減らすための設計をしています。

  • パスワード暗号化 — AES-256-GCMで暗号化し、Argon2idで鍵導出。GPUによる総当たり攻撃に耐性あり
  • Keychain連携 — macOSではマスターパスワードをKeychainに保存。毎回の入力が不要に
  • パーミッション — 設定ファイルは 0600 で作成。他ユーザーからの読み取りを防止
  • パスワード引数禁止 — CLIフラグでパスワードを渡せない設計。シェル履歴やプロセスリストへの漏洩を防止

技術的なポイント

なぜGoで書いたのか

シングルバイナリで配布でき、クロスコンパイルも容易なため、CLIツールに向いています。golang.org/x/term でTTY検出ができるため、AI連携時の挙動切り替えが自然に実装できました。

TTY検出によるマスク判定

term.IsTerminal() でstdoutがターミナルかパイプかを判定し、環境設定と組み合わせてマスクの適用を決定しています。production環境では判定結果にかかわらず常にマスクを適用し、--raw の解除には os.Stdin のTTY判定を追加で要求します。

func (c *Connection) ShouldMask(isTTY bool) bool {
    if c.Env == "production" {
        return true
    }
    if c.Env == "development" {
        return false
    }
    return !isTTY // staging: パイプ時のみ
}

運用Tips

  • READ権限ユーザーで接続する — マスキングと合わせて、意図しないデータ変更も防げる
  • 開発環境でもmyshを使う — マスキングは適用されないのでテストにも支障なし。ふだんからmyshを使っておけば、本番調査のときだけ別のツールに切り替える手間がない
  • マスク対象のAI選定SHOW COLUMNS FROM テーブル名 の結果をClaude Codeに見せて「個人情報を含みそうなカラムを選んで」と頼むと、抜け漏れなく設定できる

制限事項

  • カラム名ベースのマスキングのため、カラム名が不規則な場合(col1, data 等)は手動でマスク対象に追加する必要がある
  • MySQL専用。PostgreSQLやその他のDBには非対応
  • マスクパターンは先頭1文字を残して *** に置換する固定形式。カスタマイズには非対応
  • 大量行(数万行〜)のクエリでは、マスク処理分のオーバーヘッドが発生する

使ってみて

業務で使い始めて、不具合対応のフローが変わりました。これまでは「SQLの結果から個人情報を手で消す → Claude Codeに貼る」としていたのが、「mysh runの結果をそのまま渡す」だけで済むようになりました。

一番大きいのは、手作業のマスキングを忘れるリスクがなくなったことです。うっかり個人情報をそのまま渡す心配がなくなり、実データを使った調査へ集中できるようになりました。

DB接続マネージャツールへの画面切り替えもなくなり、コードの調査からクエリ実行まですべてターミナルで完結しています。

試してみる

Homebrewなら30秒でインストールできます。

brew tap atani/tap && brew install mysh
mysh add  # 対話的に接続を追加

使ってみて良ければ、GitHubにStarをもらえると開発の励みになります。

GitHub: https://github.com/atani/mysh

コメント

タイトルとURLをコピーしました