diff --git a/internal/runtime/executor/claude_executor.go b/internal/runtime/executor/claude_executor.go index ac1dcfce..398d0e3b 100644 --- a/internal/runtime/executor/claude_executor.go +++ b/internal/runtime/executor/claude_executor.go @@ -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 "", "", "", "", "", "": + 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