Pythonで画面録画の秘密情報を監査する
午前中、管理画面で同じ操作を二度繰り返していた。定型作業なら、画面を一度見せてエージェント用の手順にできると助かる。けれど録画データを見返すと、URL のクエリ、ウィンドウ名、クリップボードの断片が一緒に残っていた。
画面操作からスキルを作る仕組みでは、手順の正しさより先に、解析へ渡す記録を止める場所が要る。Microsoft の Skill Recorder も、解析時にはイベント時系列、ウィンドウや文書のタイトル、URL、クリップボードのプレビュー、画面画像、ナレーションをクラウドへ送ると明記している。ローカル録画だからといって、その後の解析までローカルとは限らない。README の注意書きはかなり具体的だ。
ここでは JSON にしたイベント時系列を解析の直前で弾く、小さな監査を置く。これは秘密を安全に消すコードではない。検出したら送信を中断し、録画を捨てて安全なダミー値で撮り直すためのゲートにする。
解析前に JSON を止める Python
入力は session.json とし、辞書と配列のどこに文字列があっても調べる。値は標準出力に出さない。CI のログにトークンをもう一度残す事故を避けるためだ。
import json
import re
import sys
from pathlib import Path
RULES = {
"URL の認証情報": re.compile(r"://[^/\s:@]+:[^/\s@]+@", re.I),
"URL の秘密パラメータ": re.compile(
r"[?&](?:access_token|api[_-]?key|authorization|password|secret|token)=[^&#\s]+",
re.I,
),
"Bearer トークン": re.compile(r"\bBearer\s+[A-Za-z0-9._~+/-]{16,}", re.I),
"GitHub トークン": re.compile(
r"\b(?:gh[pousr]_[A-Za-z0-9_]{20,}|github_pat_[A-Za-z0-9_]{20,})\b"
),
"秘密値らしい代入": re.compile(
r"\b(?:api[_-]?key|password|secret|token)\s*=\s*[^\s]{8,}", re.I
),
}
def walk(value, path="$"):
if isinstance(value, dict):
for key, child in value.items():
yield from walk(child, f"{path}.{key}")
elif isinstance(value, list):
for index, child in enumerate(value):
yield from walk(child, f"{path}[{index}]")
elif isinstance(value, str):
yield path, value
def audit(session):
findings = []
for path, text in walk(session):
for name, pattern in RULES.items():
if pattern.search(text):
findings.append({"path": path, "rule": name})
break
return findings
findings = audit(json.loads(Path(sys.argv[1]).read_text()))
for item in findings:
print(f"BLOCK {item['rule']}: {item['path']}")
raise SystemExit(1 if findings else 0)
ファイル名を recording_audit.py として、解析処理の前に実行する。
python recording_audit.py session.json
手元で確認した入力と出力
URL の token、クリップボードの Bearer 値、.env を開いたウィンドウ名、URL に埋めた Basic 認証の4件を含む JSON で動かした。検証時の出力は次の通りだった。
BLOCK URL の秘密パラメータ: $.events[0].url
BLOCK Bearer トークン: $.events[1].preview
BLOCK 秘密値らしい代入: $.events[2].title
BLOCK URL の認証情報: $.events[3].url
検出した値そのものは表示しない。ここは地味だが大事で、print(text) をデバッグ用に足すと監査の意味がなくなる。終了コードが 1 なので、ラッパースクリプトや CI からは成功時だけ次の解析 API を呼べる。
録画を安全にするコードではない
次の流れで止める。
画面画像や動画に写ったシークレットは、この JSON 監査では読めない。OCR を足して万能にしようとすると、今度は誤検出時の取り回しが悪くなる。自分は録画前に本番の管理画面を閉じ、ダミーの値だけを使う専用ブラウザープロファイルで操作を取り直す方が早かった。
もう一つ落とし穴がある。パターン一致は漏れを減らす補助でしかない。sid や社内固有のパラメータ名はこのままでは拾えない。運用に入れるなら、社内のトークン接頭辞と禁止ホスト名を RULES に追加し、サンプル JSON をテストとして残す。検出漏れを後から再現できる形にするためだ。
おわりに
画面録画からのスキル化は、UI をそのまま再生する話ではない。記録された事実から、別の入力にも通る手順を組み立てる工程になる。その境界を越えるデータに秘密が混じるなら、手順がどれほど便利でも先へ流せない。
まずは解析の直前に、この程度の失敗する監査を置く。次に録画環境を分ける。この二つを先に決めておけば、スキルを増やしたあとに「過去の記録を全部見直す」作業をかなり減らせる。