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 {
|
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 {
|
func isClaudeOAuthToken(apiKey string) bool {
|
||||||
@@ -1263,7 +1263,7 @@ func generateBillingHeader(payload []byte, experimentalCCHSigning bool, version,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func checkSystemInstructionsWithMode(payload []byte, strictMode bool) []byte {
|
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:
|
// 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[3]: system instructions (no cache_control)
|
||||||
// system[4]: doing tasks (no cache_control)
|
// system[4]: doing tasks (no cache_control)
|
||||||
// system[5]: user system messages moved to first user message
|
// 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")
|
system := gjson.GetBytes(payload, "system")
|
||||||
|
|
||||||
// Extract original message text for fingerprint computation (before billing injection).
|
// 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 {
|
if len(userSystemParts) > 0 {
|
||||||
combined := strings.Join(userSystemParts, "\n\n")
|
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
|
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.
|
// buildTextBlock constructs a JSON text block object with proper escaping.
|
||||||
// Uses sjson.SetBytes to handle multi-line text, quotes, and control characters.
|
// Uses sjson.SetBytes to handle multi-line text, quotes, and control characters.
|
||||||
// cacheControl is optional; pass nil to omit cache_control.
|
// 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)
|
billingVersion := helps.DefaultClaudeVersion(cfg)
|
||||||
entrypoint := parseEntrypointFromUA(clientUserAgent)
|
entrypoint := parseEntrypointFromUA(clientUserAgent)
|
||||||
workload := getWorkloadFromContext(ctx)
|
workload := getWorkloadFromContext(ctx)
|
||||||
payload = checkSystemInstructionsWithSigningMode(payload, strictMode, useCCHSigning, billingVersion, entrypoint, workload)
|
payload = checkSystemInstructionsWithSigningMode(payload, strictMode, useCCHSigning, oauthToken, billingVersion, entrypoint, workload)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inject fake user ID
|
// Inject fake user ID
|
||||||
|
|||||||
Reference in New Issue
Block a user