秘匿情報を一括マスクしたら、無人ジョブの認証まで消えた話

  • #自動化
  • #セキュリティ
  • #CLI
  • #テスト
秘匿情報を一括マスクしたら、無人ジョブの認証まで消えた話

今回やったこと

外部から取得した本文をCLIへ渡す処理では、子プロセスに不要な環境変数を引き継がない方が安全です。そこで、名前に KEYTOKENPASSWORDSECRETCREDENTIALSWEBHOOK を含む環境変数をまとめて除去するサニタイズ処理を実装しました。

ところが、この方法には落とし穴があります。CLI自身がヘッドレス実行の認証に使う環境変数まで同じ規則に該当し、フォールバック先のCLIが認証できなくなりました。秘匿情報を渡さない設計が、必要な認証情報まで消してしまった形です。

この記事の実装ステータスは実装済みです。環境変数のフィルタとサブプロセス呼び出しの両方を確認できます。

一括除去と例外リストの境界

実装の基本は、環境変数名に危険なマーカーが含まれているかを判定し、該当しないものだけを子プロセスへ渡すことです。個別のキー名をすべて列挙する方式ではないため、後から新しい秘密が増えても除去対象に入りやすい利点があります。

一方で、CLIの認証は「外部本文から受け取った秘密」とは別の分類です。フォールバックの経路が存在するなら、その経路を起動するための認証だけは残さなければなりません。実装では例外を1つの明示的なタプルに切り出し、マーカーに一致してもその変数だけは残す構造にしました。

SENSITIVE_MARKERS = ("KEY", "TOKEN", "PASSWORD", "SECRET", "CREDENTIALS", "WEBHOOK")
AUTH_EXCEPTIONS = ("CLI_HEADLESS_AUTH",)

def sanitized_env(source):
    return {
        key: value for key, value in source.items()
        if key in AUTH_EXCEPTIONS
        or not any(marker in key for marker in SENSITIVE_MARKERS)
    }

実際の実装では、認証用の例外名はコード内で明示されています。重要なのは、例外を暗黙に作らず、なぜ残すのかをコメントとテストで記録することです。例外が増えるたびに安全性が下がるため、追加は「そのCLI自身が必要とするもの」に限定します。

フォールバックを前提にした確認項目

この処理は、第一候補のモデルが常に成功する前提ではありません。実装では複数のエンジンを順番に呼び出す構成になっており、第一候補が利用できないと別のCLIへ処理が移ります。したがって確認すべきなのは、第一候補の成功だけではなく、フォールバック先が同じサニタイズ済み環境で起動できることです。

確認は次の順で行うと切り分けやすくなります。

  1. 秘密のマーカーに一致する一般的な変数が子プロセスへ渡らないこと。
  2. フォールバックCLIの認証に必要な例外だけが残ること。
  3. サブプロセスの終了コードと標準エラーを保存し、認証失敗をモデル失敗と混同しないこと。
  4. 第一候補を使わない単体呼び出しでも疎通すること。

今回の修正で得た教訓は、サニタイズの正しさを「秘密が消えたか」だけで判定できないことです。呼び出し先が必要とする最小限の実行条件を把握し、その条件を壊さない範囲で入力由来の秘密を落とす必要があります。安全対策は削除規則だけで完結せず、実行経路の契約として設計するものです。

更新履歴

  • 2026-08-12: 初稿。実装とテストをもとに整理。

訂正履歴

なし。