mcpField
D
diff-profile-archives

diff-profile-archives

github.com/kernelテスト・品質1375 回インストール2 週間前に更新
リポジトリ

diff-profile-archives について

2つの Kernel ブラウザープロファイルアーカイブを比較するトラブルシューティング用スキルです。問題に応じてブラウザー状態を調べ、正常なプロファイルと不具合のあるプロファイルの動作差を診断します。

主な機能

  1. 01Kernel プロファイルの取得と展開
  2. 02問題に関係するブラウザー状態の比較
  3. 03Cookie と Web ストレージの確認
  4. 04設定と拡張機能の確認
  5. 05ログインデータと履歴の確認
  6. 06認証情報の値を出力しない配慮

活用シーン

  1. 01特定プロファイルでのログイン失敗の調査
  2. 02プロファイル間のボット検知の比較
  3. 03保存済み認証情報や設定の欠落確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/kernel/skills --skill diff-profile-archives

インストール後、Claude Code で/diff-profile-archivesを入力して読み込まれたか確認します。

詳細

Diff Kernel Profile Archives

Kernel ブラウザプロファイル 2 つの実際の内容を比較して、報告された問題の原因となり得る状態の違いを特定します。このスキルでは、Kernel CLI を使って両方のプロファイルをダウンロードして展開し、動作の違いにつながる可能性が特に高い項目(Cookie、ローカル/セッションストレージ、設定、拡張機能の状態、ログインデータ、履歴)に焦点を当てた構造化差分を作成します。

Inputs

ユーザーは次の情報を指定する必要があります。

  1. Profile A — 「ベースライン」または「正常に動作する」プロファイル(ID または名前)
  2. Profile B — 「対象」または「動作しない」プロファイル(ID または名前)
  3. Issue description — B では発生するが A では発生しない事象(例:「chase.com のログインフローはプロファイル B では失敗するが、A では成功する」、「datadome のチャレンジが B では必ず表示されるが、A では表示されない」、「保存済みの認証情報が見つからない」)

不足している情報があれば、作業を進める前にユーザーに確認してください。問題の説明は重要です。差分でどの項目を重視するかを判断するために使用します。

Prerequisites

  • Kernel CLI がインストールされ、認証済みであること(kernel auth)
  • KERNEL_API_KEY が設定されているか、ユーザーが kernel login でログインしていること
  • sqlite3 と jq が使用可能であること(必要に応じて apt-get install -y sqlite3 jq でインストール)
  • プロファイルごとに約 500MB の空きディスク容量(プロファイルは大きくなる場合があります)

Workflow

Step 1: Set up workspace
WORKDIR=$(mktemp -d -t profile-diff-XXXXXX)
cd "$WORKDIR"
mkdir -p a b diff
echo "Workspace: $WORKDIR"
Step 2: Resolve and download both profiles
# Replace these with the profile IDs or exact names from the user.
PROFILE_A='<working-id-or-name>'
PROFILE_B='<broken-id-or-name>'

# Record metadata and confirm the identifiers resolve to two distinct profile IDs.
kernel profiles get "$PROFILE_A" -o json | tee diff/profile-a.json
kernel profiles get "$PROFILE_B" -o json | tee diff/profile-b.json
A_ID=$(jq -r '.id' diff/profile-a.json)
B_ID=$(jq -r '.id' diff/profile-b.json)
[ "$A_ID" != "$B_ID" ] || { echo "Both inputs resolve to $A_ID" >&2; exit 1; }

# --to is an extraction directory, not an archive filename.
kernel profiles download "$PROFILE_A" --to a
kernel profiles download "$PROFILE_B" --to b

プロファイル名と ID は、CLI のアクティブなプロジェクトのスコープ内で解決されます。ユーザーがプロジェクトを指定した場合は、すべての profiles get および profiles download コマンドで同じ --project <id-or-name> を使用してください。

CLI はデフォルトで tar.zst を要求し、応答をストリーミングしながら解凍して、--to の直下に展開します。アーカイブファイルは残りません。転送量を抑えるため、デフォルトのままにしてください。zstd の解凍や形式に関する問題のデバッグで解凍処理を回避する場合に限り、--format tar を使用してください。この場合も CLI は応答を --to に展開します。

kernel profiles download "$PROFILE_A" --format tar --to a

CLI がプロファイルに保存済みデータがないと報告した場合は、そこで中止してください。Kernel が状態を取得できるよう、まずブラウザーセッションでプロファイルを使用する必要があります。

Step 3: Inspect the extracted layout

各側には通常、Chrome のユーザーデータディレクトリ構成(例:Cookies、Local Storage/、Preferences などを含む Default/)があります。続行する前に、構成を把握するため find a b -maxdepth 3 -type d | sort を実行してください。

ステップ 4:問題の説明に基づいて絞り込む

ユーザーが説明した内容に基づいて、比較する対象を選びます。すべてを比較する必要はありません。Chrome のプロファイル全体には何百ものファイルが含まれており、その大半は関係ありません。次の基準で絞り込みます。

問題のキーワードまず比較する対象
ログイン、サインイン済み、セッション、「ログアウトされた」Cookies、Session Storage、Login Data、Local Storage
SPA/シングルページアプリのログイン(#/... 形式のルートを含む URL)まず Session Storage、次に Local Storage、最後に Cookies — SPA では、Cookie ではなく sessionStorage に認証状態を保存することがよくあります
captcha、datadome、cloudflare、akamai、challenge、ブロックされたCookies(ベンダー固有の名前)、Local Storage、Preferences 内のフィンガープリント関連の指標
拡張機能、プラグイン、コンテンツスクリプトExtensions/、Local Extension Settings/、Extension State、Secure Preferences
自動入力、保存済みパスワード、認証情報Login Data、Login Data For Account、Web Data
設定、言語、タイムゾーン、UA、ビューポートPreferences、Secure Preferences
履歴、キャッシュ、「記憶している」History、Top Sites、Visited Links
ストレージ、indexeddb、quotaLocal Storage、Session Storage、IndexedDB、Service Worker
ログイン後の初期化が完了する前に保存されたプロファイル(保存時の競合)Session Storage(センチネルキーがない場合)、初期化のタイミングを追跡する手掛かりとしての Local Storage の分析イベント(例:Mixpanel の User Login、Segment の track)

判断に迷ったら、Cookies + Session Storage + Local Storage + Preferences から始めます。「A では動くが B では失敗する」という問題の大半は、これらで原因を説明できます。URL ハッシュ(/#/...)でルーティングする SPA 形式のサイトでは、Session Storage を最初に確認します。タブ内の認証情報がそこに保存されていることが多く、Cookie だけでは状況を把握できません。

ステップ 5:対象を絞った差分を確認する
Cookies(SQLite)

v80 以降の Chrome では、Cookie の内容は encrypted_value に暗号化され、value は空のままです。必ず length(encrypted_value) を選択してください。length(value) はすべての行で 0 を返すため、実際の差異が見えなくなります。

for side in a b; do
  # Cookies live under Default/Cookies (or Default/Network/Cookies on newer Chrome)
  COOKIE_DB=$(find "$side" -name 'Cookies' -not -name '*-journal' | head -1)
  if [ -z "$COOKIE_DB" ]; then
    echo "(no Cookies DB)" > "diff/cookies-$side.txt"
    continue
  fi
  sqlite3 "$COOKIE_DB" \
    "SELECT host_key, name, length(encrypted_value), is_secure, is_httponly, samesite, expires_utc, source_scheme
     FROM cookies ORDER BY host_key, name;" \
    > "diff/cookies-$side.txt" 2>/dev/null || echo "(cookies query failed)" > "diff/cookies-$side.txt"
done
diff -u diff/cookies-a.txt diff/cookies-b.txt > diff/cookies.diff || true

問題に関係するホストのみを対象にします。

HOST=chase.com  # adapt to the issue
for side in a b; do
  sqlite3 "$(find $side -name Cookies -not -name '*-journal' | head -1)" \
    "SELECT host_key, name, length(encrypted_value), expires_utc, is_secure, is_httponly
     FROM cookies WHERE host_key LIKE '%$HOST%' ORDER BY host_key, name;" \
    > "diff/cookies-$HOST-$side.txt"
done
diff -u "diff/cookies-$HOST-a.txt" "diff/cookies-$HOST-b.txt" || true

存在する場合に、一方にはあってもう一方にはないものとしてフラグを立てる、一般的なボット検出用 Cookie 名:_abck、bm_sz、bm_sv(Akamai);__cf_bm、cf_clearance(Cloudflare);datadome(DataDome);_px*、pxvid(HUMAN/PerimeterX);incap_ses_*、visid_incap_*、reese84(Imperva)。

Preferences(JSON)
for side in a b; do
  PREF=$(find "$side" -name 'Preferences' -not -path '*/Extension*' | head -1)
  if [ -z "$PREF" ]; then
    echo "(no Preferences)" > "diff/preferences-$side.json"
    continue
  fi
  # Normalize key ordering and indentation before diffing.
  jq -S . "$PREF" > "diff/preferences-$side.json"
done
diff -u diff/preferences-a.json diff/preferences-b.json > diff/preferences.diff || true

次の差異を確認します:intl.*(ロケール、accept_languages)、profile.default_content_setting_values、webrtc.*、extensions.settings(インストール済み拡張機能のセット)、dns_over_https.*、download.*、safebrowsing.*。

Local Storage(LevelDB)

Local Storage/leveldb/ に対して ls -la を実行しても、ディレクトリが存在するかどうかしか分かりません。LevelDB リーダーを作成せずにキーと値を実際に調べるには、*.log ファイルと *.ldb ファイルに対して strings を実行します。Chrome はキーとほとんどの文字列値を LevelDB 内にプレーンテキストで保存するため、strings | grep を使えば、手間をかけずに必要な情報の 90% を確認できます。

for side in a b; do
  LS_DIR=$(find "$side" -type d -name 'Local Storage' -not -path '*/Extension*' | head -1)
  if [ -z "$LS_DIR" ]; then
    echo "(no Local Storage)" > "diff/localstorage-files-$side.txt"
    echo "(no Local Storage)" > "diff/localstorage-origins-$side.txt"
    echo "(no Local Storage)" > "diff/localstorage-signals-$side.txt"
    continue
  fi
  # File inventory
  ls -la "$LS_DIR/leveldb" > "diff/localstorage-files-$side.txt" 2>&1
  # All origins that have any data
  strings "$LS_DIR"/leveldb/*.{log,ldb} 2>/dev/null \
    | grep -oE 'META:https?://[^[:space:]]+|^_https?://[^[:space:]^]+' \
    | sort -u > "diff/localstorage-origins-$side.txt"
  # Key inventory per matching origin (adapt grep to the issue host)
  strings "$LS_DIR"/leveldb/*.{log,ldb} 2>/dev/null \
    | grep -aE '"event"|"User Login"|"track"|sessionGUID|authCookie|expirationDateTime|access_token|id_token|refresh_token' \
    | head -100 > "diff/localstorage-signals-$side.txt"
done
diff -u diff/localstorage-files-a.txt diff/localstorage-files-b.txt || true
diff -u diff/localstorage-origins-a.txt diff/localstorage-origins-b.txt || true
diff -u diff/localstorage-signals-a.txt diff/localstorage-signals-b.txt || true

初期化タイミングを追跡する手掛かりとしての分析イベント。 SPA ログインでは、localStorage の *.log ファイルを grep して、埋め込まれた分析キューを確認する価値があります。Mixpanel は JSON イベントデータ(mp_<token>_mixpanel)をインラインで書き込み、Segment は _seg_uid、GA は _ga を書き込みます。JSON 内の User Login、Authenticated、Identify イベントの有無から、プロファイルのスナップショットが取得される前に SPA のログイン後の初期化が実際に完了したかどうかが分かります。キュー内の最後のイベントは、SPA が到達した最後の地点の目安になります。問題のあるプロファイルの最後のイベントが View 2FA で、正常なプロファイルの最後のイベントが User Login なら、問題のあるプロファイルは処理の途中で保存されています。

# Mixpanel example: pull the last few events from each side
for side in a b; do
  LS_DIR=$(find "$side" -type d -name 'Local Storage' -not -path '*/Extension*' | head -1)
  if [ -z "$LS_DIR" ]; then
    echo "(no Local Storage)" > "diff/mp-events-$side.txt"
    continue
  fi
  strings "$LS_DIR"/leveldb/*.{log,ldb} 2>/dev/null \
    | grep -oE '"event":"[^"]+"|"Last Login":"[^"]+"|"Timestamp":"[^"]+"' \
    | tail -20 > "diff/mp-events-$side.txt"
done
diff -u diff/mp-events-a.txt diff/mp-events-b.txt || true

さらに細かく調べるには、実際の LevelDB リーダー(例:npx -y level-ls、または level パッケージを使った小さな Node スクリプト)を使用します。

セッションストレージ(LevelDB)

URL ハッシュ(app.example.com/#/home など)でルーティングする SPA では、タブ内の認証状態は通常 Cookie ではなく sessionStorage に保存されます。キーには authCookie、expirationDateTime、sessionGUID、access_token、アカウント/テナントの GUID などがあります。認証オリジンに対するこれらの情報が保存済みプロファイルにないと、アプリの起動時に sessionStorage にセッションがないと判断され、Cookie が残っていてもログイン画面に戻されます。ログイン後の SPA の初期化が完了する前にプロファイルをスナップショットした場合に、よく起こる失敗です。

Chrome は、タブごとの名前空間 UUID(namespace-<uuid>-<origin>)に紐付く map-N-<key> エントリとして sessionStorage の名前空間をシリアライズします。正確な N はタブのナビゲーション履歴によって異なりますが、Chrome はプロファイルの読み込み時に名前空間とマップの紐付けを復元するため、プロファイル間で map-N の番号が異なっていても問題ありません。重要なのは、対象のオリジンにキーが存在するかどうかです。

for side in a b; do
  SS_DIR=$(find "$side" -type d -name 'Session Storage' -not -path '*/Extension*' | head -1)
  if [ -z "$SS_DIR" ]; then
    echo "(no Session Storage)" > "diff/sessionstorage-keys-$side.txt"
    echo "(no Session Storage)" > "diff/sessionstorage-namespaces-$side.txt"
    continue
  fi
  # Inventory all map-N-* keys (the actual session storage entries)
  strings "$SS_DIR"/*.{log,ldb} 2>/dev/null \
    | grep -oE 'map-[0-9]+-[A-Za-z._0-9-]+' \
    | sort -u > "diff/sessionstorage-keys-$side.txt"
  # Origin → namespace UUID mappings (so you know which map-N belongs to which site)
  strings "$SS_DIR"/*.{log,ldb} 2>/dev/null \
    | grep -oE 'namespace-[a-f0-9-]+-https?://[^/[:space:]]+' \
    | sort -u > "diff/sessionstorage-namespaces-$side.txt"
done
diff -u diff/sessionstorage-keys-a.txt diff/sessionstorage-keys-b.txt || true
diff -u diff/sessionstorage-namespaces-a.txt diff/sessionstorage-namespaces-b.txt || true

壊れている側で、認証関連と考えられるキーが欠けていないか確認します。authCookie、expirationDateTime、sessionGUID、userGuid*、userEmail、*_token、access_token、id_token、アカウント/テナント/組織の GUID と考えられるものなどです。正常に動作する側にこれらがまとまって存在し、壊れている側にはログイン前のキー(たとえば __mplss_* の Mixpanel セッションキーのみで、認証キーはないもの)しかない場合、スナップショットの取得が早すぎたということです。

IndexedDB
for side in a b; do
  IDB_DIR=$(find "$side" -type d -name 'IndexedDB' -not -path '*/Extension*' | head -1)
  if [ -z "$IDB_DIR" ]; then
    echo "(no IndexedDB)" > "diff/idb-$side.txt"
    echo "(no IndexedDB)" > "diff/idb-sizes-$side.txt"
    continue
  fi
  # Per-origin database directories
  ls -la "$IDB_DIR" > "diff/idb-$side.txt" 2>&1
  # Sizes per origin (large delta = significant state delta)
  du -sk "$IDB_DIR"/* 2>/dev/null | sort -rn > "diff/idb-sizes-$side.txt"
  # Cheap content inventory via strings
  for db in "$IDB_DIR"/*.indexeddb.leveldb; do
    [ -d "$db" ] || continue
    origin=$(basename "$db" .indexeddb.leveldb)
    strings "$db"/*.{log,ldb} 2>/dev/null \
      | grep -aE '"name"|"id"|token|user|session|auth' \
      | head -40 > "diff/idb-$origin-$side.txt"
  done
done
diff -u diff/idb-a.txt diff/idb-b.txt || true
diff -u diff/idb-sizes-a.txt diff/idb-sizes-b.txt || true

多くのサイトでは、キャッシュやキューに入ったテレメトリに IndexedDB を使いますが、一部の認証フロー(Firebase Auth、Clerk、一部の SaaS アプリ)ではセッショントークンをここに保存します。sessionStorage/localStorage の差分で説明がつかない場合は、確認してみる価値があります。

拡張機能
for side in a b; do
  EXT_DIR=$(find "$side" -type d -name 'Extensions' -not -path '*/Local Extension*' | head -1)
  if [ -n "$EXT_DIR" ]; then
    ls "$EXT_DIR" > "diff/extensions-$side.txt"
  else
    echo "(no Extensions dir)" > "diff/extensions-$side.txt"
  fi
done
diff -u diff/extensions-a.txt diff/extensions-b.txt || true

拡張機能 ID は 32 文字のハッシュです。ID から名前を特定するには、Extensions/<id>/<version>/manifest.json 内の manifest.json を確認します。

ログインデータ(SQLite)
for side in a b; do
  LOGIN_DB=$(find "$side" -name 'Login Data' -not -name '*-journal' | head -1)
  sqlite3 "$LOGIN_DB" \
    "SELECT origin_url, length(username_value) > 0 AS has_user, length(password_value) AS pw_len, date_created
     FROM logins ORDER BY origin_url;" \
    > "diff/logins-$side.txt" 2>/dev/null || echo "(no Login Data)" > "diff/logins-$side.txt"
done
diff -u diff/logins-a.txt diff/logins-b.txt || true

password_value や username_value は絶対に出力しないでください。出力するのは長さ/真偽値のみです。デバッグ中であっても、保存済みの認証情報は機密情報です。

トップレベルのインベントリ(予期しない差分の検出)
for side in a b; do
  (cd "$side" && find . -type f -printf '%P\n' | sort) > "diff/files-$side.txt"
done
diff -u diff/files-a.txt diff/files-b.txt > diff/files.diff || true

これにより、片方のプロファイルには存在するのに、もう片方にはないファイル(Service Worker/ のデータ、IndexedDB/ のデータベース、Trust Tokens など)といった、見落としがちな違いを検出できます。

ステップ 6:調査結果を統合する

観察された各差異を、問題の説明と関連付けます。生の差分をそのままユーザーに見せるのではなく、解釈してください。次のような項目は、根本原因として疑ってください。

  • A には認証 Cookie/セッショントークンがあり、B にはない、または期限切れになっている → セッションが確立されていないか、破棄されています。
  • A と B の Cookie は一致しているが、sessionStorage の認証キー(例:authCookie、expirationDateTime、sessionGUID、*Guid*、access_token)が B にない → SPA のログイン後のブートストラップが完了する前にスナップショットが取得されています。これは典型的な保存タイミングの競合です。URL はログイン後のルートに切り替わり、プロファイルは保存されたものの、SPA はまだ sessionStorage をハイドレートしていません。次のセッションでは SPA が起動し、Cookie はあるのにタブ内のセッション状態がないことを検出して、ログイン画面に戻します。
  • localStorage の分析イベントの末尾が異なる(例:問題のあるプロファイルでは、最後の Mixpanel イベントが 2FA プロンプトなどのフロー途中のステップで、正常なプロファイルでは最後のイベントが User Login/Authenticated/Identify)→ 認証情報がどのストレージ層にあるかにかかわらず、スナップショットの取得タイミングが問題であることを確認できます。
  • ボット検出ベンダーの Cookie(_abck、datadome、cf_clearance)が片方にしかない → 「正常な」プロファイルではすでにチャレンジがクリアされています。もう一方では再度チャレンジが表示されます。
  • intl.accept_languages または intl.selected_languages が異なる → サーバー側のローカライズや地域判定のヒューリスティックによって、処理が分岐します。
  • 拡張機能が片方にだけインストールされている → 広告ブロッカー、フィンガープリント対策ツール、またはパスワードマネージャーがページの動作を変えている可能性があります。
  • webrtc.ip_handling_policy が異なる → WebRTC の IP リーク対策の設定が変わり、フィンガープリントが変わる可能性があります。
  • safebrowsing または dns_over_https が異なる → リクエスト経路に差異が生じています。
  • A には Login Data の行があり、B にはない → B では自動入力が実行されません。
  • Local Storage のオリジンに A ではキーがあり、B にはない → サイト管理のセッション状態(localStorage 内の JWT、機能フラグ、A/B テストのグループ割り当て)が適用されません。
  • 認証オリジンの IndexedDB データベース(Firebase、Clerk など)が、B では空で A ではデータがある → SDK のトークンキャッシュがなく、クライアントは再認証するか、処理に失敗します。
ステップ 7:報告する

次のテンプレートを使用してください。

## Profile Diff: <A> vs <B>

### Issue
<one-line restatement of the user's issue description>

### Likely root causes (ranked)
1. **<finding>** — <evidence: which file, which keys/rows> — <why this could cause the issue>
2. **<finding>** — ...
3. **<finding>** — ...

### Other notable differences (probably unrelated)
- <finding>
- <finding>

### Surfaces that matched (for completeness)
- Cookies for <host>: identical
- Preferences `intl.*`: identical
- Extensions installed: identical
- ...

### Suggested next steps
- <e.g. "re-run the failing flow on profile A with a fresh CDP session and capture the request that sets `_abck` — that cookie is the differentiator">
- <e.g. "delete profile B's `Default/Network/Cookies` rows for chase.com and retry — if the flow now works, the stale cookie was the issue">
ステップ 8:クリーンアップ

抽出したプロファイルデータには認証情報が含まれる可能性があります。作業が終わったら、必ずワークスペースを削除してください。

rm -rf "$WORKDIR"

ユーザーがさらに調査するために成果物を残したい場合は、パスを伝え、自分で削除してもらってください。

メモと注意点

  • ダウンロード先:--to には展開先ディレクトリを指定します。.zip ファイル名を指定したり、unzip、zstd、tar を実行したりしないでください。CLI が要求された tar.zst(デフォルト)または tar 形式のレスポンスを自動で展開します。
  • JSON はローカルで正規化する:profiles download には --pretty フラグがありません。JSON ファイルを差分比較する前に jq -S . を使い、書式やキーの順序がノイズにならないようにしてください。
  • Chrome のプロファイルパスは一定ではありません:データの大半は Default/ の下にありますが、新しい Chrome では Cookie の保存先が Default/Network/Cookies に変更されています。パスをハードコードせず、find を使ってください。
  • SQLite のロック:プロファイルはスナップショットとしてダウンロードされるため、DB はロックされません。ただし、クエリで "database is locked" エラーが発生した場合は、開いているアーカイブ内のパスではなく、展開したコピーに対してクエリを実行していることを確認してください。
  • Cache/、Code Cache/、GPUCache/、Service Worker/CacheStorage/ は比較しないでください — これらはノイズであり、膨大で意味のない差分が発生します。
  • encrypted_value と value:Chrome は v80 以降、Cookie の本文を encrypted_value に暗号化します。必ず cookies テーブルから length(encrypted_value) を選択してください。length(value) は新しいプロファイルではすべての行で 0 を返します。
  • Session Storage の map-N の番号はプロファイル間で非決定的です — タブごとの名前空間 ID です。「プロファイル A では認証キーが map-1- にあり、プロファイル B では map-5- にある」といった差異を問題として指摘しないでください。重要なのは、対象の namespace-<uuid>-<origin> にキーが存在するかどうかです。Chrome はプロファイルの読み込み時に、名前空間とマップの対応関係を復元します。
  • エイペックスドメインとサブドメインの Cookie/ストレージ:サイトによっては .example.com(すべてのサブドメインを対象)に Cookie を設定する一方で、app.example.com のみに sessionStorage を書き込む場合があります。必ず find $side -name Cookies と、エイペックスドメインおよび該当するサブドメイン両方のオリジンごとの Local/Session Storage エントリーを確認してください。
  • プライバシー:Cookie の値、パスワード欄、または Login Data のユーザー名/パスワード列をレポートに出力しないでください。長さ、存在を示すブール値、ホスト名、名前のみを使用してください。

コメント