ログに秘密が混ざっていない?Pythonでトークンを伏せ字にする安全な実験

セキュリティカテゴリを表すパンダのイラスト セキュリティ

この記事について
この記事は、生成AIを活用した自動生成フローで作成しています。Python標準ライブラリの正規表現とログ処理を使い、架空のトークンを伏せ字にする実験を紹介します。

検証ステータス:🧪 ローカルPythonでの実行確認対象・本番ログ未検証

ログに秘密が混ざっていない?Pythonでトークンを伏せ字にする安全な実験

ログが便利だからこそ起きる事故

障害調査のためにHTTPリクエストやアプリケーションのエラーを記録すると、原因を追いやすくなります。一方、調査用のログには、本来ほかの人へ渡してはいけない認証情報が紛れ込むことがあります。例えば、問い合わせに対応するためログをチャットへ貼り付けたとき、Authorizationヘッダーまで一緒に送ってしまうケースです。ログを保存した担当者だけでなく、閲覧権限のある人、外部の調査委託先、バックアップの保管先まで影響範囲が広がります。

重要なのは「漏れてから置換する」より「最初から記録しない」ことです。ただし、既存システムの調査で、手元のログを確認しながら共有用のコピーを作らなければならない場面もあります。ここでは、そのような場面で伏せ字処理の限界を理解するため、架空の文字列だけを使います。実際のパスワードやAPIキーを入力しないでください。

対応環境と安全な準備

項目条件
言語Python 3.10以降を想定
追加ライブラリ不要。reとioのみ
入力記事内で生成する架空のログ
出力画面上の伏せ字済みテキスト
制限未知の秘密形式を自動発見する保証はない

今回のサンプルは、ネットワーク接続もファイル削除も行いません。実際の業務ログを読み込む処理も含めていません。最初に入力を自分で把握できる小さな文字列へ限定すると、期待する伏せ字がどこに入るかを比較できます。

まず試す:認証ヘッダーを隠す

次のコードでは、Bearerの後ろに続く文字列を架空のトークンとして扱います。re.subはパターンに一致した部分だけを置き換える関数です。ここではヘッダーの名前と認証方式は残し、秘密に当たる部分だけを固定文字列へ変換します。

import re

sample = '''GET /api/orders HTTP/1.1
Authorization: Bearer DEMO_ONLY_NOT_A_REAL_TOKEN
X-Request-ID: demo-001
'''
pattern = re.compile(r"(?im)^(Authorization:\s*Bearer\s+)[^\r\n]+$")
redacted = pattern.sub(r"\1[REDACTED]", sample)
print(redacted)
assert "DEMO_ONLY_NOT_A_REAL_TOKEN" not in redacted

期待する表示ではAuthorization: Bearer [REDACTED]となり、X-Request-IDはそのまま残ります。実行結果を読むときは「秘密が消えた」だけでなく、調査に必要なリクエストIDが残っていることも確認してください。全部を削除すれば安全に見えますが、どの障害と結び付くログなのか判断できなくなることがあります。

なぜこの正規表現で置き換わるのか

^は行の先頭、Authorization:は対象ヘッダー名です。\s*は空白がゼロ個以上、Bearerは認証方式の文字列を意味します。最後の[^\r\n]+は改行以外の文字を一文字以上受け取ります。re.MULTILINEによって、文字列全体の先頭だけでなく各行の先頭も対象になります。re.IGNORECASEはヘッダー名の大文字小文字の違いを吸収します。

一方、この正規表現は「AuthorizationヘッダーのBearer形式」という狭い条件にしか対応しません。JSONのaccess_token、URLのクエリーパラメーター、Cookie、例外メッセージ内のキーは別物です。狭いパターンの成功を、すべての秘密を検出できた証拠と考えないことが大切です。

この流れを図にすると、共有用コピーの作成前に確認すべき位置が分かります。図は「伏せ字をしたあとにも検査が必要」という一点を示しています。

flowchart TD
  A[架空ログを用意] --> B[対象形式を決める]
  B --> C[伏せ字処理]
  C --> D{元の秘密が残るか}
  D -->|残る| E[共有しない・規則を修正]
  D -->|残らない| F[人が内容を確認]
  F --> G[共有用コピーとして扱う]

図の右側へ進める条件は、単にプログラムがエラーなく終了したことではありません。入力の中に置いた秘密が出力に残っていないことを確認し、さらに対象外の形式が含まれていないか人が確かめる必要があります。自動処理と目視確認は役割が違います。

一つだけ条件を変えるとどうなるか

試しに、サンプル中のBearerをBasicへ変更してみます。今回のパターンはBearer専用なので、Basicの後ろにある文字列は置換されません。これはコードの不具合というより、意図的に定めた検出範囲の境界です。形式を変えたのに元の文字列が残ったら、そのログを外へ出してはいけません。

反対に、すべてのAuthorization:行を無条件で伏せ字にすれば対象範囲は広がります。ただし、認証方式の情報まで失うため、401エラーや認証方式の取り違えを調べる際に不便になる場合があります。調査の目的と秘匿すべき範囲を決めてから、置換する単位を選びましょう。

もう一つの落とし穴は、置換前の文字列を別の変数やデバッグ出力へ残すことです。画面上の最終結果が伏せ字でも、途中の例外ログや標準出力に元の値が流れていたら保護できません。秘密が最初に発生する場所から、画面表示、ログ収集、転送、保管まで経路全体を確認する必要があります。

失敗したときの切り分け

「置換されない」場合は、まず入力形式と正規表現の想定が一致しているかを確認します。ヘッダー名の前に空白がある、複数行に折り返されている、別のキー名になっている、といった違いで一致しないことがあります。実データを公開の正規表現テスターへ貼り付けるのは避け、同じ構造の架空文字列へ置き換えて再現してください。

「必要な情報まで消える」場合は、置換対象が広すぎます。行全体を削除するのか、秘密の値だけを置換するのかを整理し、調査に必要な識別子を残せるか確認します。ただし、識別子自体が個人情報や追跡可能な値なら、それも別途匿名化が必要です。名前がrequest_idだから安全とは限りません。

「置換後のログが正常に読めない」場合は、改行や文字コードを壊していないか確認します。ログの意味を保つためには、伏せ字以外の部分が変わらないことも重要です。置換前後で行数や主要なイベントIDを比較する方法が役立ちます。大量ログを扱う際は、処理が途中で失敗した場合に元のログを上書きしない設計が必要です。

業務で使うなら:まず記録しない設計へ

実務では、アプリケーション側のログ出力時点で認証ヘッダーやパスワード欄を除外する方が安全です。共有直前の伏せ字は補助的な防御にすぎません。すでに秘密が記録されてしまった場合、ログの伏せ字だけで対応完了にはできません。アクセス可能だった範囲を調査し、必要なら資格情報の失効・再発行、閲覧権限や保存期間の見直しを行います。

本番導入時は、扱うデータ形式を一覧化し、架空のテストケースで「消えるべき値」と「残るべき値」を両方検査してください。対象外の形式が見つかったときは安全側に倒し、共有を止める判断が必要です。伏せ字処理が通ったから外部公開してよい、という自動承認にはしないでください。

まとめ

ログの伏せ字で大切なのは、正規表現を複雑にすることではなく、対象形式と限界を明確にすることです。今回の実験で見えるのは、Bearer形式には効いても、Basic形式にはそのままでは効かないという差です。秘密の種類が変われば検査方法も変わります。まず記録しない、次に必要な範囲だけ保管する、共有前に自動検査と人の確認を重ねる。この順序で考えると、ログを調査に使う便利さと秘密の保護を両立しやすくなります。

参考資料

文書情報

記事タイトル
ログに秘密が混ざっていない?Pythonでトークンを伏せ字にする安全な実験
作成日
更新日
Source URL
https://papanda925.com/?p=18583

ライセンス: 本記事のうち、当サイトが権利を有する本文・自作図表は、特記なき限り CC BY 4.0 で利用できます。生成AIを活用して作成・編集した内容を含みます。コードについて、別途ライセンス表示またはリンク先GitHubリポジトリのライセンスがある場合は、その条件を優先します。引用・第三者資料・画像・商標等は本ライセンスの対象外です。 利用ポリシー

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