Go でログに機密情報を出力していないか静的解析するツールを作った
はじめに
アプリケーション開発において、デバッグやモニタリングのためにログを出力することは一般的です。しかし、うっかりパスワードやAPIキーなどの機密情報をログに含めてしまうと、重大なセキュリティインシデントにつながる可能性があります。
本記事では、Go で書かれたコードを静的解析し、機密情報がログに出力されていないかチェックするツール leakhound を紹介します。
leakhound とは
leakhoundは、Go の静的解析を利用して、sensitive:"true" タグが付けられた構造体フィールドが log/slog で誤ってログ出力されていないかを検出するツールです。
名前の由来は、リークを嗅ぎつける猟犬(bloodhound)のように、コード内の潜在的なデータ漏洩リスクを追跡することから来ています。
主な特徴
- 静的解析によるゼロランタイムコスト: 実行時のパフォーマンス影響なし
- 予防的なアプローチ: コードレビュー段階で問題を検出
対応環境
- 対象言語: Goで実装されたアプリケーション
-
対応ロギングパッケージ:
log/slog,fmt,log
なぜ作ったのか
実際の開発現場では、以下のような問題がしばしば発生します。
type User struct {
ID int
Name string
Password string
}
// デバッグ中にうっかり...
slog.Info("user login", "user", user) // Passwordも一緒に出力されてしまう!
セキュリティは多層的な防御が重要です。ランタイムでのマスキング処理やログの暗号化など、さまざまな対策がありますが、開発の早い段階で問題を検出することで、より安全性を高められます。
インストール
Go 1.24以降では、tool ディレクティブで管理が可能です。
# プロジェクトに追加(Go 1.24+)
go get -tool github.com/nilpoona/leakhound
# 実行
go tool leakhound ./...
Go 1.23以前の場合や、グローバルにインストールしたい場合は以下の方法も利用できます。
go install github.com/nilpoona/leakhound@latest
leakhound ./...
使い方
1. 機密情報に sensitive タグを付ける
まず、機密情報を含むフィールドに sensitive:"true" タグを付けます。
type User struct {
ID int
Name string
Password string `sensitive:"true" json:"-"`
APIKey string `sensitive:"true" json:"-"`
Email string `sensitive:"true" json:"email"`
}
type Config struct {
Host string
Port int
Token string `sensitive:"true"`
Database string
}
2. leakhound を実行
# カレントディレクトリ配下を検査
go tool leakhound ./...
# 特定のパッケージを検査
go tool leakhound ./internal/...
3. 検出例
以下のようなコードは検出されます。
// 直接フィールドアクセス
slog.Info("msg", "pass", user.Password)
// slog.String でラップしても検出
slog.Info("msg", slog.String("pass", user.Password))
// ポインタ経由でも検出
userPtr := &user
slog.Info("msg", "pass", userPtr.Password)
// sensitive フィールドを含む構造体全体
slog.Info("user data", user)
slog.Info("user data", slog.Any("data", user))
// 同一 package 内の変数代入
password := user.Password
slog.Info("msg", "pass", password)
// 関数の引数(同一 package)
func logValue(val string) {
slog.Info("msg", val)
}
password := user.Password
logValue(password)
検出できないケース
以下のケースは検出ができません。
// リフレクション経由
val := reflect.ValueOf(user).FieldByName("Password")
slog.Info("msg", "pass", val.Interface())
// interface{} 経由
var data interface{} = user.Password
slog.Info("msg", "pass", data)
これらのケースでは、型情報が失われるため、静的解析では追跡できません。機密情報を直接ログ関数に渡していない場合は検出できない点にご注意ください。
実装のポイント
leakhound は Go の golang.org/x/tools/go/analysis パッケージを使用して実装されています。
主な処理フローは以下の通りです。
-
構造体フィールドの解析:
sensitive:"true"タグが付いたフィールドを特定 -
ログ関数呼び出しの検出:
log/slog,fmt,logパッケージの関数呼び出しを探索 - 引数の追跡: 呼び出し時の引数が機密フィールドを参照していないかチェック
- 診断情報の生成: 問題が見つかった場合、該当箇所を報告
特に工夫した点は、以下のようなケースでも検出できるようにしたことです。
- ポインタを経由したフィールドアクセス
-
slog.String(),slog.Any()などのヘルパー関数でラップされたケース - 構造体全体をログ出力するケース(sensitiveフィールドを含む場合)
サードパーティロギングライブラリへの対応
v0.2.0 より、zap、zerolog、logrus などのサードパーティロギングライブラリにも対応しました。
サードパーティのロギングライブラリは設定ファイルを用意することで検知が可能となっています。
設定ファイルの利用
主要なロギングライブラリ向けの設定ファイルを用意しています。以下のコマンドでダウンロードして使用できます。
注意: 提供している設定ファイルは、各ライブラリのよく使われるメソッドのみをカバーしています。すべてのメソッドを網羅しているわけではないため、必要に応じてカスタマイズしてください。
# zap の場合
curl -o .leakhound.yaml https://raw.githubusercontent.com/nilpoona/leakhound/main/examples/zap.yaml
# zerolog の場合
curl -o .leakhound.yaml https://raw.githubusercontent.com/nilpoona/leakhound/main/examples/zerolog.yaml
# logrus の場合
curl -o .leakhound.yaml https://raw.githubusercontent.com/nilpoona/leakhound/main/examples/logrus.yaml
設定ファイルをダウンロードしたら、通常通り実行するだけです。
leakhound ./...
leakhound はカレントディレクトリの .leakhound.yaml を自動的に読み込みます。
カスタム設定
独自のロギングライブラリや、上記以外のライブラリに対応したい場合、あるいは提供されている設定を拡張したい場合は、.leakhound.yaml を作成・編集してカスタマイズできます。
targets:
- package: "go.uber.org/zap"
methods:
- receiver: "*Logger"
names:
- "Info"
- "Debug"
- "Error"
- receiver: "*SugaredLogger"
names:
- "Infow"
- "Debugw"
カスタム設定ファイルのパスを指定することも可能です。
leakhound --config path/to/config.yaml ./...
設定ファイルのフォーマット
targets:
- package: "go.uber.org/zap" # パッケージのインポートパス
functions: # パッケージレベルの関数(省略可)
- "Info"
- "Debug"
methods: # 特定の型のメソッド(省略可)
- receiver: "*Logger" # レシーバの型(ポインタの場合は*)
names: # メソッド名
- "Info"
- "Debug"
要件:
-
functionsまたはmethodsのいずれか一つ以上の指定が必要 - パッケージパスは小文字、数字、
.、-、/のみ使用可能 - 関数名とメソッド名は有効な Go 識別子である必要がある
- レシーバ型はポインタ(
*Logger)または値(Logger)で指定
制限事項(設定ファイルの悪用防止のため):
- 最大 20 ターゲット
- ターゲットごとに最大 50 関数
- ターゲットごとに最大 10 メソッド設定
- メソッド設定ごとに最大 50 メソッド名
より詳しい設定例は、examples/ ディレクトリを参照してください。
今後の展開
今後は以下の拡張を検討しています。
- カスタムタグ名の指定
- AI Readable な結果出力
- 誤検知の除外設定
まとめ
leakhound は、Goでのセキュアなログ出力を支援する静的解析ツールです。
静的解析のメリット:
- ✅ コードレビュー段階で問題を発見
- ✅ ランタイムのパフォーマンス影響ゼロ
機密情報の漏洩は、一度発生すると取り返しのつかない問題になります。開発の早い段階でこうした問題を防ぐために、ぜひ leakhound を活用してみてください。
コントリビューションも歓迎しています!バグ報告や機能追加の提案がありましたら、GitHub の Issues でお知らせください。
Discussion
ライブラリのアップデートに伴い、検出例と検出できない例を更新しました。
サードパーティのロギングライブラリに対応した旨を追記しました。