反编译本地 Claude Code 2.1.145 二进制 (Bun 1.3.14 打包, @anthropic-ai/sdk@0.94.0 嵌入) 提取真实指纹,系统性升级 mimicry。 核心改动: - 新增 ClaudeCodeBundle struct 作为单一事实源,DefaultBundle 描述当前 伪装目标的完整快照 (CLIVersion/SDKVersion/RuntimeVersion/OS/Arch) - DefaultCLIVersion/DefaultStainlessPackageVersion/CLICurrentVersion/ DefaultHeaders 全部派生自 DefaultBundle,消除三处 (2.1.92, 2.1.104, 0.70.0, 0.81.0) 版本分裂 - CLI 版本 2.1.92/2.1.104 -> 2.1.145 - SDK 版本 0.70.0/0.81.0 -> 0.94.0 - 新增 12 个 2.1.145 反编译确认的 anthropic-beta token: advanced-tool-use, tool-search-tool, mcp-servers, mcp-client, mid-conversation-system, afk-mode, cache-diagnosis, context-hint, environments, managed-agents, skills, compact - FullClaudeCodeMimicryBetas() 从 7 个 token 升级到 21 个 ordered list - 修正 BetaTokenEfficientTools 错日期 (2026-03-28 -> 2025-02-19) - 从默认 beta header 移除已 GA 的 BetaFineGrainedToolStreaming / BetaTokenEfficientTools (常量保留供客户端显式 merge) - claudemask.RequiredNodeHeaders 加 X-Claude-Code-Session-Id 强制 新增 ensureClaudeCodeSessionID helper (claude_code_session_id.go): - 真实 CLI 在 SDK 内强制 X-Claude-Code-Session-Id:y_(),缺失被判第三方 - OAuth mimic 路径: metadata.user_id 派生 -> canonical UUID 写入 -> 兜底 uuid.NewString() - API key passthrough 路径: 不从 body 派生,保护客户端原始语义 - 所有路径均对客户端传入的非法 UUID 执行删除 (避免恶意值上游透传) - 所有写入 header 的 session-id 都通过 uuid.Parse 校验 测试: - 新增 14 个 ensureClaudeCodeSessionID 单元测试,含恶意 UUID 注入拒绝 + API key 路径隔离 + canonical 形式校验 - 新增 3 个 bundle 派生一致性测试 - mask_test 加 session-id 缺失校验 case - 老 UA 断言 2.1.104 -> 2.1.145 不在范围: - TLS 指纹 (utls 已处理) - Bun.hash vs xxHash64 算法验证 (需 golden vectors,独立项目) References: - VERSION:2.1.145 BUILD_TIME:2026-05-19T01:36:35Z GIT_SHA:daa4c3755d45ab0cf97bb41db8c03bd2dfd2ff5f
181 lines
7.3 KiB
Go
181 lines
7.3 KiB
Go
package claude
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// TestDefaultBundleVersions verifies the active Claude Code bundle snapshot
|
|
// matches the reverse-engineered 2.1.145 binary. Bump this when DefaultBundle
|
|
// is upgraded to a newer CLI release.
|
|
func TestDefaultBundleVersions(t *testing.T) {
|
|
if got := DefaultBundle.CLIVersion; got != "2.1.145" {
|
|
t.Fatalf("DefaultBundle.CLIVersion = %q, want %q", got, "2.1.145")
|
|
}
|
|
if got := DefaultBundle.SDKVersion; got != "0.94.0" {
|
|
t.Fatalf("DefaultBundle.SDKVersion = %q, want %q", got, "0.94.0")
|
|
}
|
|
}
|
|
|
|
// TestDefaultBundleDrivesLegacyExports ensures all legacy exported vars stay
|
|
// in sync with DefaultBundle (single source of truth).
|
|
func TestDefaultBundleDrivesLegacyExports(t *testing.T) {
|
|
if DefaultCLIVersion != DefaultBundle.CLIVersion {
|
|
t.Fatalf("DefaultCLIVersion = %q, want %q (from DefaultBundle)", DefaultCLIVersion, DefaultBundle.CLIVersion)
|
|
}
|
|
if DefaultStainlessPackageVersion != DefaultBundle.SDKVersion {
|
|
t.Fatalf("DefaultStainlessPackageVersion = %q, want %q (from DefaultBundle)", DefaultStainlessPackageVersion, DefaultBundle.SDKVersion)
|
|
}
|
|
if DefaultStainlessRuntimeVersion != DefaultBundle.RuntimeVersion {
|
|
t.Fatalf("DefaultStainlessRuntimeVersion = %q, want %q (from DefaultBundle)", DefaultStainlessRuntimeVersion, DefaultBundle.RuntimeVersion)
|
|
}
|
|
if DefaultStainlessOS != DefaultBundle.DefaultOS {
|
|
t.Fatalf("DefaultStainlessOS = %q, want %q (from DefaultBundle)", DefaultStainlessOS, DefaultBundle.DefaultOS)
|
|
}
|
|
if DefaultStainlessArch != DefaultBundle.DefaultArch {
|
|
t.Fatalf("DefaultStainlessArch = %q, want %q (from DefaultBundle)", DefaultStainlessArch, DefaultBundle.DefaultArch)
|
|
}
|
|
if CLICurrentVersion != DefaultBundle.CLIVersion {
|
|
t.Fatalf("CLICurrentVersion = %q, want %q (from DefaultBundle)", CLICurrentVersion, DefaultBundle.CLIVersion)
|
|
}
|
|
}
|
|
|
|
// TestDefaultHeadersDrivenByBundle ensures DefaultHeaders map carries the
|
|
// values from DefaultBundle, not stale hard-coded literals.
|
|
func TestDefaultHeadersDrivenByBundle(t *testing.T) {
|
|
if got := DefaultHeaders["X-Stainless-Package-Version"]; got != DefaultBundle.SDKVersion {
|
|
t.Fatalf("DefaultHeaders[X-Stainless-Package-Version] = %q, want %q", got, DefaultBundle.SDKVersion)
|
|
}
|
|
if got := DefaultHeaders["X-Stainless-Runtime-Version"]; got != DefaultBundle.RuntimeVersion {
|
|
t.Fatalf("DefaultHeaders[X-Stainless-Runtime-Version] = %q, want %q", got, DefaultBundle.RuntimeVersion)
|
|
}
|
|
if got := DefaultHeaders["X-Stainless-OS"]; got != DefaultBundle.DefaultOS {
|
|
t.Fatalf("DefaultHeaders[X-Stainless-OS] = %q, want %q", got, DefaultBundle.DefaultOS)
|
|
}
|
|
if got := DefaultHeaders["X-Stainless-Arch"]; got != DefaultBundle.DefaultArch {
|
|
t.Fatalf("DefaultHeaders[X-Stainless-Arch] = %q, want %q", got, DefaultBundle.DefaultArch)
|
|
}
|
|
ua := DefaultHeaders["User-Agent"]
|
|
if !strings.Contains(ua, "claude-cli/"+DefaultBundle.CLIVersion) {
|
|
t.Fatalf("DefaultHeaders[User-Agent] = %q, want substring %q", ua, "claude-cli/"+DefaultBundle.CLIVersion)
|
|
}
|
|
}
|
|
|
|
func TestApplyFingerprintOverridesUpdatesSharedDefaults(t *testing.T) {
|
|
t.Setenv("CLAUDE_CODE_ENTRYPOINT", "")
|
|
t.Setenv("CLAUDE_AGENT_SDK_VERSION", "")
|
|
t.Setenv("CLAUDE_AGENT_SDK_CLIENT_APP", "")
|
|
t.Setenv("CLAUDE_CODE_WORKLOAD", "")
|
|
|
|
originalVersion := DefaultCLIVersion
|
|
originalPackageVersion := DefaultStainlessPackageVersion
|
|
originalRuntimeVersion := DefaultStainlessRuntimeVersion
|
|
originalOS := DefaultStainlessOS
|
|
originalArch := DefaultStainlessArch
|
|
|
|
t.Cleanup(func() {
|
|
ApplyFingerprintOverrides(
|
|
originalVersion,
|
|
originalPackageVersion,
|
|
originalRuntimeVersion,
|
|
originalOS,
|
|
originalArch,
|
|
)
|
|
})
|
|
|
|
ApplyFingerprintOverrides("2.1.999", "0.99.0", "v99.0.0", "Linux", "x64")
|
|
|
|
if got := DefaultCLIVersion; got != "2.1.999" {
|
|
t.Fatalf("DefaultCLIVersion = %q, want %q", got, "2.1.999")
|
|
}
|
|
if got := DefaultUserAgent(); got != "claude-cli/2.1.999 (external, cli)" {
|
|
t.Fatalf("DefaultUserAgent() = %q", got)
|
|
}
|
|
|
|
profile := DefaultDeviceProfile()
|
|
if profile.StainlessPackageVersion != "0.99.0" {
|
|
t.Fatalf("DefaultDeviceProfile().StainlessPackageVersion = %q", profile.StainlessPackageVersion)
|
|
}
|
|
if profile.StainlessRuntimeVersion != "v99.0.0" {
|
|
t.Fatalf("DefaultDeviceProfile().StainlessRuntimeVersion = %q", profile.StainlessRuntimeVersion)
|
|
}
|
|
if profile.StainlessOS != "Linux" {
|
|
t.Fatalf("DefaultDeviceProfile().StainlessOS = %q", profile.StainlessOS)
|
|
}
|
|
if profile.StainlessArch != "x64" {
|
|
t.Fatalf("DefaultDeviceProfile().StainlessArch = %q", profile.StainlessArch)
|
|
}
|
|
|
|
if got := DefaultHeaders["User-Agent"]; got != profile.UserAgent {
|
|
t.Fatalf("DefaultHeaders[User-Agent] = %q, want %q", got, profile.UserAgent)
|
|
}
|
|
if got := DefaultHeaders["X-Stainless-Package-Version"]; got != profile.StainlessPackageVersion {
|
|
t.Fatalf("DefaultHeaders[X-Stainless-Package-Version] = %q, want %q", got, profile.StainlessPackageVersion)
|
|
}
|
|
if got := DefaultHeaders["X-Stainless-Runtime-Version"]; got != profile.StainlessRuntimeVersion {
|
|
t.Fatalf("DefaultHeaders[X-Stainless-Runtime-Version] = %q, want %q", got, profile.StainlessRuntimeVersion)
|
|
}
|
|
if got := DefaultHeaders["X-Stainless-OS"]; got != profile.StainlessOS {
|
|
t.Fatalf("DefaultHeaders[X-Stainless-OS] = %q, want %q", got, profile.StainlessOS)
|
|
}
|
|
if got := DefaultHeaders["X-Stainless-Arch"]; got != profile.StainlessArch {
|
|
t.Fatalf("DefaultHeaders[X-Stainless-Arch] = %q, want %q", got, profile.StainlessArch)
|
|
}
|
|
if got := DefaultHeaders["anthropic-dangerous-direct-browser-access"]; got != "true" {
|
|
t.Fatalf("DefaultHeaders[anthropic-dangerous-direct-browser-access] = %q, want %q", got, "true")
|
|
}
|
|
}
|
|
|
|
func TestDefaultUserAgentReflectsLocalRuntimeDescriptors(t *testing.T) {
|
|
t.Setenv("CLAUDE_CODE_ENTRYPOINT", "sdk-cli")
|
|
t.Setenv("CLAUDE_AGENT_SDK_VERSION", "0.0.77")
|
|
t.Setenv("CLAUDE_AGENT_SDK_CLIENT_APP", "cron-daemon")
|
|
t.Setenv("CLAUDE_CODE_WORKLOAD", "cron")
|
|
|
|
got := DefaultUserAgent()
|
|
want := "claude-cli/2.1.145 (external, sdk-cli, agent-sdk/0.0.77, client-app/cron-daemon, workload/cron)"
|
|
if got != want {
|
|
t.Fatalf("DefaultUserAgent() = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestDetailedCodeUserAgentReflectsEntrypointAndSDK(t *testing.T) {
|
|
t.Setenv("CLAUDE_CODE_ENTRYPOINT", "remote")
|
|
t.Setenv("CLAUDE_AGENT_SDK_VERSION", "0.0.77")
|
|
t.Setenv("CLAUDE_AGENT_SDK_CLIENT_APP", "desktop")
|
|
|
|
got := DetailedCodeUserAgent()
|
|
want := "claude-code/2.1.145 (remote, agent-sdk/0.0.77, client-app/desktop)"
|
|
if got != want {
|
|
t.Fatalf("DetailedCodeUserAgent() = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestOptionalAPIHeaders(t *testing.T) {
|
|
t.Setenv("CLAUDE_CODE_CONTAINER_ID", "ctr-123")
|
|
t.Setenv("CLAUDE_CODE_REMOTE_SESSION_ID", "remote-456")
|
|
t.Setenv("CLAUDE_AGENT_SDK_CLIENT_APP", "desktop")
|
|
t.Setenv("CLAUDE_CODE_ADDITIONAL_PROTECTION", "true")
|
|
|
|
headers := OptionalAPIHeaders()
|
|
if got := headers["x-claude-remote-container-id"]; got != "ctr-123" {
|
|
t.Fatalf("x-claude-remote-container-id = %q", got)
|
|
}
|
|
if got := headers["x-claude-remote-session-id"]; got != "remote-456" {
|
|
t.Fatalf("x-claude-remote-session-id = %q", got)
|
|
}
|
|
if got := headers["x-client-app"]; got != "desktop" {
|
|
t.Fatalf("x-client-app = %q", got)
|
|
}
|
|
if got := headers["x-anthropic-additional-protection"]; got != "true" {
|
|
t.Fatalf("x-anthropic-additional-protection = %q", got)
|
|
}
|
|
}
|
|
|
|
func TestAttributionHeaderDisabled(t *testing.T) {
|
|
t.Setenv("CLAUDE_CODE_ATTRIBUTION_HEADER", "false")
|
|
if !AttributionHeaderDisabled() {
|
|
t.Fatal("expected AttributionHeaderDisabled() to be true when env=false")
|
|
}
|
|
}
|