关于 diff-profile-archives
用于比较两个 Kernel 浏览器配置档案的故障排查技能。它根据问题重点检查浏览器状态,帮助分析正常档案与异常档案的行为差异。
主要功能
- 01下载并解压 Kernel 配置档案
- 02比较与问题相关的浏览器状态
- 03检查 Cookie 和 Web 存储
- 04查看偏好设置和扩展
- 05检查登录数据与历史记录
- 06避免输出凭据值
适用场景
- 01排查某个档案中的登录故障
- 02比较不同档案的机器人验证行为
- 03查找缺失的已保存凭据或设置
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ kernel/ skills --skill diff-profile-archives安装后在 Claude Code 中输入/diff-profile-archives确认已加载。
详细介绍
对比 Kernel 配置文件存档
对比两个 Kernel 浏览器配置文件的实际内容,找出可能导致所报告问题的状态差异。此技能通过 Kernel CLI 下载并解压两个配置文件,然后生成结构化差异报告,重点关注最可能造成行为不同的部分:Cookie、本地/会话存储、偏好设置、扩展程序状态、登录数据和历史记录。
输入
用户必须提供:
- 配置文件 A —“基准”或“正常运行”的配置文件(ID 或名称)
- 配置文件 B —“问题对象”或“出现故障”的配置文件(ID 或名称)
- 问题描述 — 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、blocked | 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、配额 | 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中的用户/密码列。只使用长度、是否存在的布尔值以及主机名/名称。