1605 文字
8 分
ターミナル出力のシークレットをパイプで隠す CLI「mask-pipe」を作った

画面共有で env を叩いてヒヤッとしたことがある#

ペアプロ中に環境変数を確認しようと env を叩いた瞬間、相手の画面に AWS のアクセスキーが流れた経験はないでしょうか。docker logs に平文のトークンが混ざるのも、CI のジョブログに DB 接続 URL が出るのも、身に覚えのある人は多いはずです。

この「ターミナル出力経由でシークレットが漏れる瞬間」を塞ぐために、mask-pipe という Go 製の CLI を作りました。TruffleHog がリポジトリを守り、secretlint がコミットを守るのに対して、mask-pipe は ターミナル画面 を守る役割を担います。v0.1.1 をリリースしたので、設計と使い方をまとめておきます。

デモ#

使い方はシンプルで、隠したい出力の先にパイプで繋ぐだけです。

env と docker logs の出力がパイプ経由でマスクされる様子
env | mask-pipe の実行デモ
Terminal window
$ env | mask-pipe
AWS_ACCESS_KEY_ID=AKIA************MPLE
AWS_SECRET_ACCESS_KEY=wJal********************************EKEY
DATABASE_URL=postgres://admin:****@db.example.com:5432/mydb
GITHUB_TOKEN=ghp_********************************ef01
STRIPE_SK=sk_live_********************************p7dc

Docker や Kubernetes のログもそのまま流せます。

Terminal window
$ docker logs -f my-app 2>&1 | mask-pipe
$ kubectl logs pod-name | mask-pipe --show-tail 0

本番運用のコマンドに噛ませる前に、何が置換されるかを確認したいときは --dry-run を使います。元の出力はそのまま、マスク対象があった行だけが報告される挙動です。

Terminal window
$ terraform plan | mask-pipe --dry-run

インストール#

静的ビルドのシングルバイナリとして配布しています。Homebrew・go install・バイナリ直ダウンロードのいずれでも入ります。

Terminal window
$ brew install c12o-dev/tap/mask-pipe
$ go install github.com/c12o-dev/mask-pipe/cmd/mask-pipe@latest

macOS(Intel / Apple Silicon)、Linux(amd64 / arm64)、Windows(amd64)を公式ビルド対象にしています。依存ライブラリは一切なく、ランタイムも不要です。

組み込みパターンは 8 つだけ#

v1 の時点では以下の 8 パターンだけを既定で有効にしています。

ID検出対象マスク例
aws_access_keyAKIA + 16 文字の AWS アクセスキー IDAKIA************MPLE
aws_secret_keyaws_secret_access_key=... の値部分wJal****EKEY
github_tokenghp_ / gho_ / ghu_ / ghs_ / ghr_ で始まるトークンghp_****1234
github_patgithub_pat_ で始まる fine-grained PATgith****BBcc
stripe_keysk_live_ / sk_test_ / pk_live_ / pk_test_sk_l****p7dc
jwtドット区切り 3 セグメントの base64urleyJh****sR8U
db_url_passwordscheme://user:pass@host のパスワード部分://user:****@host
pem_private_key-----BEGIN ... PRIVATE KEY----- ブロック[REDACTED PRIVATE KEY]

全パターンの仕様は docs/specs/002-pattern-library.md に書きました。

設計上のいちばん大事なポリシーは 「再現率より精度を優先する」 ことです。通常の出力を壊す誤検知 1 件は、検出漏れ 10 件よりユーザーの信頼を損ないます。TruffleHog の 800 以上あるパターンをそのまま持ち込まなかったのは、誤検知率をコントロールしきれないと判断したためで、自分たちで精度 99% を保証できる 8 パターンから始めて少しずつ足していく方針にしています。

細かい話だと、Stripe キーの正規表現に \b アンカーを付けるかで悩みました。アンカーがないと disk_test_xxx のようなカラム名が誤検出されることをパターンレビュー用のエージェントが見つけたので、全パターンに単語境界を入れてあります。PEM 秘密鍵は単一行では一致できないので、ADR 0004 に書いた BEGIN/END マーカー方式のマルチラインバッファリング で実装しています(バッファ上限 100 行または 64KB を超えたら fail-open でそのまま流します)。

設定でカスタマイズ#

~/.mask-pipe.toml を置くだけでパターンの切り替え、カスタムパターンの追加、allowlist、表示スタイルの調整ができます。

[builtin]
jwt = false # JWT 検出をオフに
[[custom]]
name = "internal-api-key"
pattern = 'mycompany-key-[a-zA-Z0-9]{32}'
[[allowlist]]
name = "aws-doc-example"
pattern = 'AKIAIOSFODNN7EXAMPLE'
[display]
mask_char = "*"
show_tail = 4

allowlist は「公式ドキュメントのサンプルキー」など、既知の安全な値をマスクしないためのエスケープハッチです。--mask-char '#'--show-tail 0 のような CLI フラグでも同じ調整ができます。

設定の検証には mask-pipe doctor、有効パターンの確認には mask-pipe list-patterns を用意しました。トラブル時はまずこの 2 つを叩いてもらう想定です。

設計で悩んだところ#

なぜ「パイプ専用」にしたのか#

最初は 1Password の op run のように、シェルに仕込んでおけば全ての子プロセスの stdout を透過的にマスクするモードも検討しました。ただ、op run には TTY が潰れて TUI アプリが壊れる という有名な問題があります。同じ穴に落ちるのを避けるため、mask-pipe は 明示的にパイプで繋いだときだけ効く 設計にしました(ADR 0002)。色付き出力も対話プロンプトもそのまま動きます。PTY プロキシ方式の mask-pipe shell は v2 で再検討する予定です。

なぜ Go なのか#

単一バイナリ配布が絶対条件で、OpenSSL の動的リンク事故を避けたかったので Go を選びました(ADR 0001)。標準の regexp パッケージは RE2 を採用しており、計算量が入力長に対して線形 であることが保証されます。ストリーミングで遅延を予測できるのは、リアルタイムに流れるログをフィルタする CLI にとってかなり重要でした。goreleaser で Homebrew・Scoop・APT を一気に配布できるのも楽です。

TruffleHog と競合にしない#

比較表は README に詳しく書きましたが、要点だけ書くとこうなります。

mask-pipeTruffleHogsecretlintGitHub add-mask
守る対象ターミナル出力Git リポジトリコミット前のファイルCI ログ
実行タイミングリアルタイム(pipe)事後スキャンpre-commitCI 実行時
ローカルで動く×(CI 専用)

いずれも 補完関係 にあり、全部組み合わせるのが現実的な防御ラインだと考えています。

既知の限界#

  • TUI アプリはラップできません。 これはパイプで動かすことを選んだ時点での意図的な制約です。vimdocker run -it を巻き込んだ瞬間に操作不能になります。
  • エントロピーベースの検出は v1 では採用していません。 Gitleaks 系の誤検知が許容しきれない領域だったので、パターンマッチ一本で割り切りました。
  • PTY プロキシ(mask-pipe shell)は v2 で検討中です。 「いちいち | mask-pipe と書くのが面倒」という声への答えですが、TTY 問題を再発させずに実装する道筋が見えてから着手します。

おわりに#

公開場所は github.com/c12o-dev/mask-pipe です。brew install c12o-dev/tap/mask-pipe 一発で入るので、画面共有で一度でもヒヤッとしたことがあるなら試してみてください。フィードバックは GitHub Discussions で歓迎します。Star も貰えたら嬉しいです。

ターミナル出力のシークレットをパイプで隠す CLI「mask-pipe」を作った
https://blog.c12o.net/posts/mask-pipe-terminal-secret-masking/
作者
Seu (c12o)
公開日
2026-04-17
ライセンス
CC BY-NC-SA 4.0