mcpField
D
diff-profile-archives

diff-profile-archives

github.com/kernel测试与质量1375 次安装2 周前更新
代码仓库

关于 diff-profile-archives

用于比较两个 Kernel 浏览器配置档案的故障排查技能。它根据问题重点检查浏览器状态,帮助分析正常档案与异常档案的行为差异。

主要功能

  1. 01下载并解压 Kernel 配置档案
  2. 02比较与问题相关的浏览器状态
  3. 03检查 Cookie 和 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确认已加载。

详细介绍

对比 Kernel 配置文件存档

对比两个 Kernel 浏览器配置文件的实际内容,找出可能导致所报告问题的状态差异。此技能通过 Kernel CLI 下载并解压两个配置文件,然后生成结构化差异报告,重点关注最可能造成行为不同的部分:Cookie、本地/会话存储、偏好设置、扩展程序状态、登录数据和历史记录。

输入

用户必须提供:

  1. 配置文件 A —“基准”或“正常运行”的配置文件(ID 或名称)
  2. 配置文件 B —“问题对象”或“出现故障”的配置文件(ID 或名称)
  3. 问题描述 — B 上发生而 A 上没有发生的情况(例如:“配置文件 B 上的 chase.com 登录流程失败,但 A 上可以正常运行”“B 上总是触发 datadome 验证,但 A 上从不触发”“缺少已保存的凭据”)

如果缺少任何一项,请先向用户询问,再继续操作。问题描述至关重要——它决定了差异对比中应重点关注配置文件的哪些部分。

先决条件

  • 已安装并完成身份验证的 Kernel CLI(kernel auth)
  • 已设置 KERNEL_API_KEY,或用户已通过 kernel login 登录
  • 可使用 sqlite3 和 jq(如有需要,可通过 apt-get install -y sqlite3 jq 安装)
  • 每个配置文件约有 500MB 可用磁盘空间(配置文件可能很大)

工作流

步骤 1:设置工作区
WORKDIR=$(mktemp -d -t profile-diff-XXXXXX)
cd "$WORKDIR"
mkdir -p a b diff
echo "Workspace: $WORKDIR"
步骤 2:解析并下载两个配置文件
# 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 指定的目录下。它不会保留存档文件。较小的传输量建议使用默认格式。仅在调试格式或解压问题时使用 --format tar,以跳过 zstd 解压;CLI 仍会将响应提取到 --to 指定的目录:

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

如果 CLI 提示某个配置文件尚无已保存的数据,请停止操作:必须先在浏览器会话中使用该配置文件,以便 Kernel 捕获状态。

步骤 3:检查提取后的目录结构

每一侧通常都包含 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 通常将身份验证状态保存在 sessionStorage 中,而不是 cookies
captcha、datadome、cloudflare、akamai、challenge、blockedCookies(供应商专属名称)、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、配额Local 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:标签页内的身份验证状态通常保存在那里,仅查看 cookies 无法了解全貌。

步骤 5:运行有针对性的差异比较
Cookies(SQLite)

现代 Chrome(v80 之后)会将 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 中搜索嵌入的分析队列——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)

对于 SPA(通过 URL 哈希路由的网站,例如 app.example.com/#/home),标签页内的身份验证状态通常存储在 sessionStorage 中,而非 Cookie 中——键包括 authCookie、expirationDateTime、sessionGUID、access_token、账户/租户 GUID 等。如果保存的配置文件缺少身份验证源对应的这些键,应用启动时会发现 sessionStorage 中没有会话,于是跳转到登录页,即使 Cookie 完好无损也是如此。在登录后的 SPA 初始化完成之前就创建配置文件快照,是导致这种常见故障的原因。

Chrome 会将 sessionStorage 命名空间序列化为 map-N-<key> 条目,并通过每个标签页的命名空间 UUID(namespace-<uuid>-<origin>)进行关联。具体的 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 一致,但 B 上缺少 sessionStorage 身份验证键(例如 authCookie、expirationDateTime、sessionGUID、*Guid*、access_token) → 快照是在 SPA 完成登录后的引导流程之前拍摄的。这是典型的保存时竞态:URL 已切换到登录后的路由,配置文件也已保存,但 SPA 还没来得及填充 sessionStorage。下次会话启动时,SPA 会看到 Cookie,却发现当前标签页中没有会话状态,于是跳回登录页面。
  • localStorage 中的分析事件尾部不同(例如,异常配置文件的最后一条 Mixpanel 事件是身份验证流程中的步骤,如双重身份验证提示;正常配置文件的最后一条事件是 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 测试分组)不会生效。
  • 身份验证来源(Firebase、Clerk 等)的 IndexedDB 数据库在 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 锁:配置文件是以快照形式下载的,因此数据库不会被锁定;但如果查询报错 "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 会在加载配置文件时恢复命名空间到 map 的绑定关系。
  • 顶级域名与子域名的 Cookie/存储:网站可能会在 .example.com 上设置 Cookie(覆盖所有子域名),但只在 app.example.com 上写入 sessionStorage。请始终检查 find $side -name Cookies 的结果,以及顶级域名和相关子域名各自的每来源 Local/Session Storage 条目。
  • 隐私:切勿在报告中回显 Cookie 值、密码字段,或 Login Data 中的用户/密码列。只使用长度、是否存在的布尔值以及主机名/名称。

评论

支持 Markdown