fix(gateway): auto mode recognize Claude Code IDE clients via any cc_entrypoint

This commit is contained in:
feitianbubu
2026-06-18 19:22:10 +08:00
parent 4a5665da5b
commit e3e31bd4c5
2 changed files with 46 additions and 7 deletions
@@ -53,8 +53,10 @@ const (
// 格式固定、不随提示词改版漂移,是比身份 prose 更稳定的客户端标识。
// 生成见 gateway_billing_block.go;同类识别见 pkg/apicompat/anthropic_to_responses.go。
claudeCodeBillingHeaderPrefix = "x-anthropic-billing-header"
// claudeCodeCLIEntrypointMarker 标识请求来自 Claude Code CLI 入口。
claudeCodeCLIEntrypointMarker = "cc_entrypoint=cli"
// claudeCodeEntrypointMarker 标识计费块携带入口归因字段。不绑定具体入口值
// cli / claude-vscode / jetbrains / sdk 等都是真实入口):入口值会随新增 IDE 漂移,
// 且伪造者同样可填任意值、不构成防伪边界,故仅要求该字段存在即可。
claudeCodeEntrypointMarker = "cc_entrypoint="
)
// NewClaudeCodeValidator 创建验证器实例
@@ -180,7 +182,7 @@ func (v *ClaudeCodeValidator) hasClaudeCodeSystemPrompt(body map[string]any) boo
// 计费归因块识别(WHY 见 claudeCodeBillingHeaderPrefix 注释)。先于 Dice 检查,
// 大小写敏感:该块由 gateway_billing_block.go 固定小写生成。
if strings.HasPrefix(text, claudeCodeBillingHeaderPrefix) &&
strings.Contains(text, claudeCodeCLIEntrypointMarker) {
strings.Contains(text, claudeCodeEntrypointMarker) {
return true
}
@@ -135,7 +135,44 @@ func TestClaudeCodeValidator_BillingBlockRecognizedWithoutIdentityPrompt(t *test
require.True(t, ok)
}
func TestClaudeCodeValidator_BillingBlockNonCLIEntrypointFallsThrough(t *testing.T) {
func TestClaudeCodeValidator_BillingBlockVSCodeEntrypointRecognized(t *testing.T) {
// 回归:Claude Code 在 VSCode 扩展内运行时,计费块入口为 cc_entrypoint=claude-vscode
// 而非 cli。其安全监视器子请求同样不携带身份 prose,此前写死 cc_entrypoint=cli 的
// 快速通道无法识别它,导致 claude_code_only 分组误拒。入口值不应作为识别条件。
monitorPrompt, err := os.ReadFile("testdata/security_monitor_system_prompt.txt")
require.NoError(t, err)
validator := NewClaudeCodeValidator()
// 前提:完整监视器正文经 Dice 相似度远低于阈值,放行只可能来自计费归因块识别。
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.181 (external, claude-vscode, agent-sdk/0.3.181)")
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-opus-4-8",
"system": []any{
map[string]any{
"type": "text",
"text": "x-anthropic-billing-header: cc_version=2.1.181.f17; cc_entrypoint=claude-vscode;",
},
map[string]any{
"type": "text",
"text": string(monitorPrompt),
},
},
"metadata": map[string]any{
"user_id": claudeCodeMetadataUserIDJSON,
},
})
require.True(t, ok)
}
func TestClaudeCodeValidator_BillingBlockWithoutEntrypointFallsThrough(t *testing.T) {
validator := NewClaudeCodeValidator()
req := httptest.NewRequest(http.MethodPost, "http://example.com/v1/messages", nil)
req.Header.Set("User-Agent", "claude-cli/2.1.162 (external, cli)")
@@ -143,14 +180,14 @@ func TestClaudeCodeValidator_BillingBlockNonCLIEntrypointFallsThrough(t *testing
req.Header.Set("anthropic-beta", "claude-code-20250219")
req.Header.Set("anthropic-version", "2023-06-01")
// 计费块存在但 entrypoint 不是 cli(如 sdk,且无身份 prose
// 不应凭前缀放行,应落回 Dice 检查并失败。验证 cc_entrypoint=cli 这一条件是必要
// 计费块前缀命中但完全没有 cc_entrypoint= 字段,且无身份 prose
// 不应凭前缀放行,应落回 Dice 检查并失败。验证 cc_entrypoint= 字段的存在仍是必要条件
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=sdk; cch=d8726;",
"text": "x-anthropic-billing-header: cc_version=2.1.162.884; cch=d8726;",
},
map[string]any{
"type": "text",