diff --git a/backend/internal/service/claude_code_validator_test.go b/backend/internal/service/claude_code_validator_test.go index f34348720f..09a31bebe3 100644 --- a/backend/internal/service/claude_code_validator_test.go +++ b/backend/internal/service/claude_code_validator_test.go @@ -185,6 +185,65 @@ func TestClaudeCodeValidator_BillingBlockStillRequiresClaudeCodeUA(t *testing.T) require.False(t, ok) } +// 新版 Claude Code CLI 已取消 cch=... 签名字段,billing block 形如 +// `x-anthropic-billing-header: cc_version=...; cc_entrypoint=cli;`(无 cch)。 +// 检测依赖前缀 + cc_entrypoint=cli,不依赖 cch,故无身份 prose 的子请求仍应被识别。 +// 这同时覆盖了本仓 mimicry 注入的新格式 block(见 buildBillingAttributionText)。 +func TestClaudeCodeValidator_BillingBlockRecognizedWithoutCCH(t *testing.T) { + monitorPrompt, err := os.ReadFile("testdata/security_monitor_system_prompt.txt") + require.NoError(t, err) + + validator := NewClaudeCodeValidator() + require.Less(t, validator.bestSimilarityScore(string(monitorPrompt)), systemPromptThreshold) + + req := httptest.NewRequest(http.MethodPost, "http://example.com/v1/messages", nil) + req.Header.Set("User-Agent", "claude-cli/2.1.162 (external, cli)") + req.Header.Set("X-App", "cli") + req.Header.Set("anthropic-beta", "claude-code-20250219") + req.Header.Set("anthropic-version", "2023-06-01") + + ok := validator.Validate(req, map[string]any{ + "model": "claude-3-5-haiku-20241022", + "system": []any{ + map[string]any{ + "type": "text", + // 注意:无 cch 段,对齐新版 CLI 与本仓新的注入格式。 + "text": "x-anthropic-billing-header: cc_version=2.1.162.884; cc_entrypoint=cli;", + }, + map[string]any{ + "type": "text", + "text": string(monitorPrompt), + }, + }, + "metadata": map[string]any{ + "user_id": claudeCodeMetadataUserIDJSON, + }, + }) + require.True(t, ok, "无 cch 的新版 billing block 仍应被识别为 Claude Code") +} + +// 安全回归:去掉 cch 后检测并未放松——非 claude-cli UA 即便携带无 cch 的 billing block +// 仍在 Step 1 被拒,ClaudeCodeOnly group 不会因此被仿冒绕过。 +func TestClaudeCodeValidator_NoCCHBlockStillRequiresClaudeCodeUA(t *testing.T) { + validator := NewClaudeCodeValidator() + req := httptest.NewRequest(http.MethodPost, "http://example.com/v1/messages", nil) + req.Header.Set("User-Agent", "curl/8.0.0") + req.Header.Set("X-App", "cli") + req.Header.Set("anthropic-beta", "claude-code-20250219") + req.Header.Set("anthropic-version", "2023-06-01") + + ok := validator.Validate(req, map[string]any{ + "model": "claude-3-5-haiku-20241022", + "system": []any{ + map[string]any{ + "type": "text", + "text": "x-anthropic-billing-header: cc_version=2.1.162.884; cc_entrypoint=cli;", + }, + }, + }) + require.False(t, ok) +} + func TestClaudeCodeValidator_MessagesPathRejectsNonClaudeCodeUA(t *testing.T) { validator := NewClaudeCodeValidator() req := httptest.NewRequest(http.MethodPost, "http://example.com/v1/messages", nil)