secret-serialization について
自動シリアライズと、オブジェクト全体を扱うログやテレメトリーが組み合わさって起きる認証情報の間接漏えいを調べるコードレビュー用スキルです。変更箇所の外も含め、リポジトリ全体で両側の経路を追跡します。
主な機能
- 01認証情報フィールドとシリアライザーの確認
- 02オブジェクト全体を扱うログとテレメトリーの追跡
- 03生成経路と生の属性アクセスの区別
- 04報告条件と深刻度の定義
- 05具体的な修正指針
活用シーン
- 01認証情報フィールドを追加する変更のレビュー
- 02ログやテレメトリーのオブジェクト直列化経路の追跡
- 03フィールド除外が生成シリアライザー全体を防ぐかの判定
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ getsentry/ skills --skill secret-serializationインストール後、Claude Code で/secret-serializationを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
SOURCES.mdSPEC.mdreferences/javascript-typescript.mdpython.md詳細
シークレットのシリアライズレビュー
型が自身をシリアライズし、その上流でオブジェクト全体がシリアライズされることによって、漏えいする可能性のある認証情報を探します。
漏えいには通常、単独では問題がないように見える次の2つの変更が必要です。
- 保持側:認証情報が、生成された
repr、str、asdict、model_dump、toJSON、またはプロパティ列挙を備えた型のフィールドに保存されています。 - 出力側:ログ記録、トレース、エラー報告、キャッシュ、またはレスポンスで、オブジェクト全体やすべてのキーワード引数がシリアライズされます(
str(value)、span.set_data(key, obj)、logger.info("%s", obj)、JSON.stringify(args))。
この2つの側は、別々の作者によって何か月も離れて実装されることがよくあります。どちらか一方だけでも報告してください。もう一方がデフォルトブランチにすでに存在し、差分には含まれていないことがよくあるため、必ず既存ツリー内を検索してください。
対象範囲
- このスキルの対象は、生成されたシリアライズ機能やオブジェクト全体を処理する出力処理を介した間接的な漏えいです。
- トークンをログメッセージに埋め込むなどの直接的な漏えいは、
security-reviewの対象です。変更された、オブジェクト全体を処理する出力処理に起因する場合に限り、ここで報告してください。 - ハードコードされたシークレットのリテラルと、コミットされた
.envファイルは対象外です。
参考資料
| 参考資料 | 読むタイミング |
|---|---|
references/python.md | Python のデータクラス、attrs、pydantic、NamedTuple、msgspec、ログ記録、Sentry SDK、または OpenTelemetry のコードをレビューするとき |
references/javascript-typescript.md | JavaScript または TypeScript のクラス、設定オブジェクト、JSON.stringify、util.inspect、pino または winston、あるいは Sentry と OpenTelemetry のスパンをレビューするとき |
認証情報フィールド
フィールド名、型、または値の取得元から認証情報を含むと判断できる場合、そのフィールドを認証情報を含むものとして扱います。
- 名前に
secret、token、password、passwd、pwd、api_key、apikey、access_key、private_key、signing_key、client_secret、bearer、credential、authorization、auth_header、cookie、session_key、dsn、connection_string、webhook_secret、hmac、またはrefresh_tokenが含まれるもの。 SecretStr、SecretBytesなどの型、またはこれらをラップする型。- 環境変数、シークレットマネージャー、または上記のような名前のコンストラクター引数から代入される値。
明示的な除外
次の2種類のパスを区別してください。
- 生成されるパスは、型自体によって生成されます。
__repr__と__str__、asdict、model_dump、toJSON、および独自の列挙可能プロパティの走査(JSON.stringify、util.inspect、スプレッド)が該当します。これらは保持側の責任です。 - 生の属性アクセスは、保存されている値を直接読み取ります。
vars(obj)、obj.__dict__、pickle、json.dumps(obj, default=vars)が該当します。この方法では、フィールドのフラグにかかわらず、保存されているすべての属性が公開されます。これらはシンクとしてのみ扱ってください。リポジトリ内のシンクが、認証情報を含むインスタンスに対して適用されている場合に報告します。
その型にあるすべての生成パスが、単独または複数の仕組みによってブロックされている場合、そのフィールドは完全に除外されています。少なくとも1つの生成パスにフィールドが含まれる場合、そのフィールドは部分的に除外されています。フィールド単位の仕組みでは、生の属性アクセスを防げません。これを防げるのは値を保存しないことだけなので、生の属性アクセスに関する指摘はシンク側で修正してください。
| 仕組み | ブロックするもの | フィールドを引き続き含むもの |
|---|---|---|
dataclasses.field(repr=False)、attrs.field(repr=False) | __repr__、__str__ | asdict、astuple、生のアクセス |
Pydantic Field(repr=False) | __repr__、__str__ | model_dump、model_dump_json、レスポンスのシリアライズ、生のアクセス |
Pydantic Field(exclude=True) | model_dump、model_dump_json、レスポンスのシリアライズ | __repr__、__str__、生のアクセス |
Pydantic Field(repr=False, exclude=True) | すべての生成パス | 生のアクセス |
JavaScript #private フィールド | すべての生成パス | クラス本体の外部からは何もできません |
Object.defineProperty(..., { enumerable: false }) | すべての生成パス | 明示的なプロパティ読み取り、Object.getOwnPropertyNames |
フィールドを省略する手書きの __repr__、__str__、toJSON、または [util.inspect.custom] | 上書きするパスのみ(__str__ がない場合、__repr__ は str() にも使われます) | その他すべての生成パス、生のアクセス |
マスキングラッパー:SecretStr、SecretBytes、プロジェクトの Redacted[T] | すべての生成パス | pickle および default=vars など、ラッパー内に保存された値まで到達する再帰的な __dict__ の走査 |
| オブジェクトに保存しない:メソッド内で読み取る、またはクロージャーで保持する | すべて | なし |
アンダースコアによる命名、TypeScript の private、__slots__、型アノテーション、コメント、dataclass(init=False)、またはフィールドを引き続き代入するカスタム __init__ は、いずれのパスもブロックするものとして扱わないでください。
調査プロセス
- 変更されたハンクを読みます。自動シリアライズする型に追加または変更された認証情報フィールドと、オブジェクトや kwargs をログ、スパン、エラーコンテキスト、キャッシュ、キュー、レスポンスにシリアライズする追加または変更コードを探します。
- 各認証情報フィールドについて、クラス全体、デコレーター、基底クラス、モデル設定を読みます。すべての生成パスを列挙し、上の表を使って、フィールドに適用されている仕組みがどのパスをブロックするか確認します。
- インスタンスがどこで構築され、渡されるかを grep します。デコレーター、
**kwargs、ツール呼び出し引数、タスクペイロード、ミドルウェアに渡されたインスタンスは、汎用シンクに到達します。 - 差分だけでなく、リポジトリ全体を grep して、これらのインスタンスを参照し得るシンクを探します。有用なパターン:
str(value)、repr(、asdict(、model_dump(、set_data(、set_attribute(、set_context(、set_extra(、JSON.stringify(、util.inspect(、オブジェクトを引数に取るロガー、生の属性アクセス(vars(、__dict__、pickle.dumps(、default=vars)。 - 変更されたシンクごとに、キーの許可リスト、認証情報名に基づくマスキング、またはオブジェクトを型名に置き換えるフィルターがあるか確認します。その後、認証情報を含むオブジェクトをそこに渡す呼び出し元を grep します。
- テストは、シリアライズ後の形式に認証情報が含まれないことをアサートしているかどうかの確認にのみ使います。テストがない場合は修正の指針であり、指摘事項ではありません。
- コードが実際に出荷されることを確認します。フィクスチャ、サンプル、生成コード、テスト専用ヘルパーは除外します。
報告対象
| カテゴリ | 報告する場合 |
|---|---|
| 除外されていない認証情報フィールド | 認証情報フィールドが追加された、または生成されたシリアライズ機能を持つ型に移され、生成されるいずれの経路もブロックする仕組みがない場合。 |
| 部分的な除外 | 生成される経路の少なくとも1つにフィールドが含まれ、リポジトリ内のシンクがその型のインスタンスに対してその経路を使用している場合。 |
| 生の属性シンク | フィールドが完全に除外されている、またはラップされている場合でも、認証情報を保持するインスタンスに対してシンクが vars、__dict__、pickle、または default=vars を適用する場合。 |
| 保持用オブジェクトへの認証情報の移動 | リファクタリングによって、遅延読み込みまたはクロージャ内の認証情報がオブジェクトのフィールドに移される場合。 |
| 全項目シリアライズのシンク | 新規または変更されたテレメトリ、ログ、エラーコンテキスト、キャッシュ、またはレスポンスのコードが、許可リストや編集処理なしに、すべてのキーワード引数、すべての属性、またはオブジェクト全体をシリアライズする場合。 |
| シンクのフィルターの喪失 | 既存のシンクで、編集処理、許可リスト、またはオブジェクトを型名に置き換える処理が削除または弱体化される変更。 |
| テレメトリへのオブジェクトの受け渡し | クライアント、設定、または設定項目のオブジェクトが、文字列化するスパン、ログ、またはエラーコンテキストの呼び出しに渡される場合。 |
重大度
| レベル | 該当する場合 |
|---|---|
| high | 認証情報がリポジトリ内のいずれかのシンクに到達する場合:ログ行、スパン属性、エラーイベント、キャッシュエントリ、永続化された行、キューペイロード、または API レスポンス。もう一方のコードは差分より前から存在していても構いません。また、既存の呼び出し元が認証情報を含むオブジェクトを渡す、新しい全項目シンクも該当します。 |
| medium | インスタンスが生成元のモジュール外へ出る、除外されていない認証情報フィールド。ただし、リポジトリ内を検索してシンクが見つからなかった場合。また、許可リストがなく、認証情報を含む呼び出し元を追跡できていない全項目シンク。 |
| low | インスタンスが生成元のスコープ外へ出ることのない、除外されていない認証情報フィールド。 |
- シンクまたは保持用オブジェクトが差分の対象外であることを理由に、重大度を下げないでください。このバグでは、それが通常の発生形態です。
- 到達可能性が、証明されていない前提条件に依存する場合は、低い方の重大度を選んでください。
報告対象外
- 完全に除外されたフィールド。ただし、生の属性シンクがインスタンスを受け取る場合を除きます。
- ブロックされていない経路をリポジトリ内のシンクが使用していない、部分的な除外。
- メソッド内で読み込まれ、インスタンスに保存されない認証情報。
- テスト、フィクスチャ、または例にあるプレースホルダー値や偽の値。
- 生成されたシリアライズ機能がなく、
__dict__または列挙によるシンクもない型。 - すでにスカラーキーを許可リストに含めている、認証情報名に基づいて編集している、またはオブジェクトを型名に置き換えているシンク。
- 差分が接続している場合を除き、差分が変更していない既存のフィールドやシンク。
- スタイル、命名、またはテスト不足のみの指摘。
修正の指針
各指摘には、具体的な修正を1つ含めてください。
- 保持用オブジェクト:生成されるすべての経路をブロックする、値を編集処理可能な型でラップする、またはオブジェクトへの保存をやめる。
- 生の属性シンク:インスタンスではなく、明示的に許可した辞書をシリアライズする。または、認証情報をそのオブジェクトに保存しない。フィールドフラグやラッパーでは修正できません。
- シンク:スカラーキーを許可リストに含め、認証情報名に一致するキーを編集し、スカラー以外の値には
str(value)ではなくtype(value).__name__を記録する。 - 回帰テスト:シンクが使用するものと同じシリアライザー(例:
sentry_sdk.serializer.serialize、json.dumps(asdict(obj))、JSON.stringify(obj)、util.inspect(obj))でインスタンスをシリアライズし、認証情報の文字列が含まれないことを検証する。
指摘の形式
- タイトル:フィールドまたはシンクと漏えい経路を示します。例:"
_shared_secretがRpcClientの dataclass repr に含まれる"。 - 説明:認証情報を露出させる生成経路、インスタンスの移動先、および修正を示す1〜2文。
- 根拠:クラス、デコレーター、確認した生成経路、調査した除外設定、差分の対象外も含めて追跡したすべてのシンクまたは呼び出し元を示す2〜5個の箇条書き。