fix(copilot): unify request fingerprint across all Copilot API calls

- Make header constants in copilot_auth.rs public, add COPILOT_INTEGRATION_ID
- Unify User-Agent across 4 internal methods (eliminate "CC-Switch" leakage) and version string
- claude.rs add_auth_headers now references shared constants, adds user-agent / api-version / openai-intent
- forwarder.rs filters all 6 fixed fingerprint headers for Copilot requests to prevent reqwest append duplication
- stream_check.rs health check pipeline aligned with new fingerprint
This commit is contained in:
Jason
2026-03-19 17:29:04 +08:00
parent 8ccfbd36d6
commit 82c75de51c
4 changed files with 41 additions and 16 deletions

View File

@@ -856,9 +856,22 @@ impl RequestForwarder {
// 过滤黑名单 Headers保护隐私并避免冲突
for (key, value) in headers {
let key_str = key.as_str();
if HEADER_BLACKLIST
.iter()
.any(|h| key.as_str().eq_ignore_ascii_case(h))
.any(|h| key_str.eq_ignore_ascii_case(h))
{
continue;
}
// Copilot 请求:过滤会由 add_auth_headers 注入的固定指纹头,
// 防止客户端原始头与注入头重复reqwest header() 是追加语义)
if is_copilot
&& (key_str.eq_ignore_ascii_case("user-agent")
|| key_str.eq_ignore_ascii_case("editor-version")
|| key_str.eq_ignore_ascii_case("editor-plugin-version")
|| key_str.eq_ignore_ascii_case("copilot-integration-id")
|| key_str.eq_ignore_ascii_case("x-github-api-version")
|| key_str.eq_ignore_ascii_case("openai-intent"))
{
continue;
}

View File

@@ -346,12 +346,15 @@ impl ProviderAdapter for ClaudeAdapter {
AuthStrategy::Bearer => {
request.header("Authorization", format!("Bearer {}", auth.api_key))
}
// GitHub Copilot: Bearer + 特定的 Editor headers
// GitHub Copilot: Bearer + 统一指纹头
AuthStrategy::GitHubCopilot => request
.header("Authorization", format!("Bearer {}", auth.api_key))
.header("Editor-Version", "vscode/1.85.0")
.header("Editor-Plugin-Version", "copilot/1.150.0")
.header("Copilot-Integration-Id", "vscode-chat"),
.header("editor-version", super::copilot_auth::COPILOT_EDITOR_VERSION)
.header("editor-plugin-version", super::copilot_auth::COPILOT_PLUGIN_VERSION)
.header("copilot-integration-id", super::copilot_auth::COPILOT_INTEGRATION_ID)
.header("user-agent", super::copilot_auth::COPILOT_USER_AGENT)
.header("x-github-api-version", super::copilot_auth::COPILOT_API_VERSION)
.header("openai-intent", "conversation-panel"),
_ => request,
}
}

View File

@@ -46,10 +46,11 @@ const TOKEN_REFRESH_BUFFER_SECONDS: i64 = 60;
const COPILOT_MODELS_URL: &str = "https://api.githubcopilot.com/models";
/// Copilot API Header 常量
const COPILOT_EDITOR_VERSION: &str = "vscode/1.96.0";
const COPILOT_PLUGIN_VERSION: &str = "copilot-chat/0.26.7";
const COPILOT_USER_AGENT: &str = "GitHubCopilotChat/0.26.7";
const COPILOT_API_VERSION: &str = "2025-04-01";
pub const COPILOT_EDITOR_VERSION: &str = "vscode/1.96.0";
pub const COPILOT_PLUGIN_VERSION: &str = "copilot-chat/0.26.7";
pub const COPILOT_USER_AGENT: &str = "GitHubCopilotChat/0.26.7";
pub const COPILOT_API_VERSION: &str = "2025-04-01";
pub const COPILOT_INTEGRATION_ID: &str = "vscode-chat";
/// Copilot 使用量 API URL
const COPILOT_USAGE_URL: &str = "https://api.github.com/copilot_internal/user";
@@ -465,6 +466,7 @@ impl CopilotAuthManager {
.http_client
.post(GITHUB_DEVICE_CODE_URL)
.header("Accept", "application/json")
.header("User-Agent", COPILOT_USER_AGENT)
.form(&[("client_id", GITHUB_CLIENT_ID), ("scope", "read:user")])
.send()
.await?;
@@ -502,6 +504,7 @@ impl CopilotAuthManager {
.http_client
.post(GITHUB_OAUTH_TOKEN_URL)
.header("Accept", "application/json")
.header("User-Agent", COPILOT_USER_AGENT)
.form(&[
("client_id", GITHUB_CLIENT_ID),
("device_code", device_code),
@@ -948,7 +951,9 @@ impl CopilotAuthManager {
.http_client
.get(GITHUB_USER_URL)
.header("Authorization", format!("token {}", github_token))
.header("User-Agent", "CC-Switch")
.header("User-Agent", COPILOT_USER_AGENT)
.header("Editor-Version", COPILOT_EDITOR_VERSION)
.header("Editor-Plugin-Version", COPILOT_PLUGIN_VERSION)
.send()
.await?;
@@ -978,9 +983,9 @@ impl CopilotAuthManager {
.http_client
.get(COPILOT_TOKEN_URL)
.header("Authorization", format!("token {}", github_token))
.header("User-Agent", "CC-Switch")
.header("Editor-Version", "vscode/1.85.0")
.header("Editor-Plugin-Version", "copilot/1.150.0")
.header("User-Agent", COPILOT_USER_AGENT)
.header("Editor-Version", COPILOT_EDITOR_VERSION)
.header("Editor-Plugin-Version", COPILOT_PLUGIN_VERSION)
.send()
.await?;

View File

@@ -13,6 +13,7 @@ use crate::app_config::AppType;
use crate::error::AppError;
use crate::provider::Provider;
use crate::proxy::providers::transform::anthropic_to_openai;
use crate::proxy::providers::copilot_auth;
use crate::proxy::providers::{get_adapter, AuthInfo, AuthStrategy};
/// 健康状态枚举
@@ -362,9 +363,12 @@ impl StreamCheckService {
.header("content-type", "application/json")
.header("accept", "text/event-stream")
.header("accept-encoding", "identity")
.header("editor-version", "vscode/1.85.0")
.header("editor-plugin-version", "copilot/1.150.0")
.header("copilot-integration-id", "vscode-chat");
.header("user-agent", copilot_auth::COPILOT_USER_AGENT)
.header("editor-version", copilot_auth::COPILOT_EDITOR_VERSION)
.header("editor-plugin-version", copilot_auth::COPILOT_PLUGIN_VERSION)
.header("copilot-integration-id", copilot_auth::COPILOT_INTEGRATION_ID)
.header("x-github-api-version", copilot_auth::COPILOT_API_VERSION)
.header("openai-intent", "conversation-panel");
} else if is_openai_chat {
// OpenAI-compatible: Bearer auth + standard headers only
request_builder = request_builder