mirror of
https://github.com/router-for-me/CLIProxyAPIPlus.git
synced 2026-04-12 09:14:15 +00:00
fix(claude): sanitize forwarded third-party prompts for OAuth cloaking
Only for Claude OAuth requests, sanitize forwarded system-prompt context before it is prepended into the first user message. This preserves neutral task/tool instructions while removing OpenCode branding, docs links, environment banners, and product-specific workflow sections that still triggered Anthropic extra-usage classification after top-level system[] cloaking.
This commit is contained in:
@@ -944,7 +944,7 @@ func claudeCreds(a *cliproxyauth.Auth) (apiKey, baseURL string) {
|
||||
}
|
||||
|
||||
func checkSystemInstructions(payload []byte) []byte {
|
||||
return checkSystemInstructionsWithSigningMode(payload, false, false, "2.1.63", "", "")
|
||||
return checkSystemInstructionsWithSigningMode(payload, false, false, false, "2.1.63", "", "")
|
||||
}
|
||||
|
||||
func isClaudeOAuthToken(apiKey string) bool {
|
||||
@@ -1263,7 +1263,7 @@ func generateBillingHeader(payload []byte, experimentalCCHSigning bool, version,
|
||||
}
|
||||
|
||||
func checkSystemInstructionsWithMode(payload []byte, strictMode bool) []byte {
|
||||
return checkSystemInstructionsWithSigningMode(payload, strictMode, false, "2.1.63", "", "")
|
||||
return checkSystemInstructionsWithSigningMode(payload, strictMode, false, false, "2.1.63", "", "")
|
||||
}
|
||||
|
||||
// checkSystemInstructionsWithSigningMode injects Claude Code-style system blocks:
|
||||
@@ -1274,7 +1274,7 @@ func checkSystemInstructionsWithMode(payload []byte, strictMode bool) []byte {
|
||||
// system[3]: system instructions (no cache_control)
|
||||
// system[4]: doing tasks (no cache_control)
|
||||
// system[5]: user system messages moved to first user message
|
||||
func checkSystemInstructionsWithSigningMode(payload []byte, strictMode bool, experimentalCCHSigning bool, version, entrypoint, workload string) []byte {
|
||||
func checkSystemInstructionsWithSigningMode(payload []byte, strictMode bool, experimentalCCHSigning bool, oauthMode bool, version, entrypoint, workload string) []byte {
|
||||
system := gjson.GetBytes(payload, "system")
|
||||
|
||||
// Extract original message text for fingerprint computation (before billing injection).
|
||||
@@ -1337,13 +1337,111 @@ func checkSystemInstructionsWithSigningMode(payload []byte, strictMode bool, exp
|
||||
|
||||
if len(userSystemParts) > 0 {
|
||||
combined := strings.Join(userSystemParts, "\n\n")
|
||||
payload = prependToFirstUserMessage(payload, combined)
|
||||
if oauthMode {
|
||||
combined = sanitizeForwardedSystemPrompt(combined)
|
||||
}
|
||||
if strings.TrimSpace(combined) != "" {
|
||||
payload = prependToFirstUserMessage(payload, combined)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return payload
|
||||
}
|
||||
|
||||
// sanitizeForwardedSystemPrompt removes third-party branding and high-signal
|
||||
// product-specific prompt sections before forwarding context into the first user
|
||||
// message for Claude OAuth cloaking. The goal is to preserve neutral task/tool
|
||||
// guidance while stripping fingerprints like OpenCode branding, product docs,
|
||||
// and workflow sections that are unique to the third-party client.
|
||||
func sanitizeForwardedSystemPrompt(text string) string {
|
||||
if strings.TrimSpace(text) == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
lines := strings.Split(text, "\n")
|
||||
var kept []string
|
||||
skipUntilNextHeading := false
|
||||
|
||||
shouldDropLine := func(line string) bool {
|
||||
trimmed := strings.TrimSpace(line)
|
||||
if trimmed == "" {
|
||||
return false
|
||||
}
|
||||
lower := strings.ToLower(trimmed)
|
||||
|
||||
dropSubstrings := []string{
|
||||
"you are opencode",
|
||||
"best coding agent on the planet",
|
||||
"opencode.ai/docs",
|
||||
"github.com/anomalyco/opencode",
|
||||
"anomalyco/opencode",
|
||||
"ctrl+p to list available actions",
|
||||
"to give feedback, users should report the issue at",
|
||||
"you are powered by the model named",
|
||||
"the exact model id is",
|
||||
"here is some useful information about the environment",
|
||||
"skills provide specialized instructions and workflows",
|
||||
"use the skill tool to load a skill",
|
||||
"no skills are currently available",
|
||||
"instructions from:",
|
||||
}
|
||||
for _, sub := range dropSubstrings {
|
||||
if strings.Contains(lower, sub) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
switch lower {
|
||||
case "<env>", "</env>", "<directories>", "</directories>", "<example>", "</example>":
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
shouldDropHeading := func(line string) bool {
|
||||
switch strings.ToLower(strings.TrimSpace(line)) {
|
||||
case "# professional objectivity", "# task management", "# tool usage policy", "# code references":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for _, line := range lines {
|
||||
trimmed := strings.TrimSpace(line)
|
||||
|
||||
if skipUntilNextHeading {
|
||||
if strings.HasPrefix(trimmed, "# ") {
|
||||
skipUntilNextHeading = false
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if shouldDropHeading(line) {
|
||||
skipUntilNextHeading = true
|
||||
continue
|
||||
}
|
||||
|
||||
if shouldDropLine(line) {
|
||||
continue
|
||||
}
|
||||
|
||||
line = strings.ReplaceAll(line, "OpenCode", "the coding assistant")
|
||||
line = strings.ReplaceAll(line, "opencode", "coding assistant")
|
||||
kept = append(kept, line)
|
||||
}
|
||||
|
||||
result := strings.Join(kept, "\n")
|
||||
// Collapse excessive blank lines after removing sections.
|
||||
for strings.Contains(result, "\n\n\n") {
|
||||
result = strings.ReplaceAll(result, "\n\n\n", "\n\n")
|
||||
}
|
||||
return strings.TrimSpace(result)
|
||||
}
|
||||
|
||||
// buildTextBlock constructs a JSON text block object with proper escaping.
|
||||
// Uses sjson.SetBytes to handle multi-line text, quotes, and control characters.
|
||||
// cacheControl is optional; pass nil to omit cache_control.
|
||||
@@ -1456,7 +1554,7 @@ func applyCloaking(ctx context.Context, cfg *config.Config, auth *cliproxyauth.A
|
||||
billingVersion := helps.DefaultClaudeVersion(cfg)
|
||||
entrypoint := parseEntrypointFromUA(clientUserAgent)
|
||||
workload := getWorkloadFromContext(ctx)
|
||||
payload = checkSystemInstructionsWithSigningMode(payload, strictMode, useCCHSigning, billingVersion, entrypoint, workload)
|
||||
payload = checkSystemInstructionsWithSigningMode(payload, strictMode, useCCHSigning, oauthToken, billingVersion, entrypoint, workload)
|
||||
}
|
||||
|
||||
// Inject fake user ID
|
||||
|
||||
Reference in New Issue
Block a user