mirror of
https://github.com/router-for-me/CLIProxyAPIPlus.git
synced 2026-03-20 16:12:35 +00:00
refactor: consolidate duplicate UA and header scrubbing into shared misc functions
This commit is contained in:
@@ -14,6 +14,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/misc"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -75,36 +76,9 @@ func createReverseProxy(upstreamURL string, secretSource SecretSource) (*httputi
|
|||||||
req.Header.Del("Authorization")
|
req.Header.Del("Authorization")
|
||||||
req.Header.Del("X-Api-Key")
|
req.Header.Del("X-Api-Key")
|
||||||
req.Header.Del("X-Goog-Api-Key")
|
req.Header.Del("X-Goog-Api-Key")
|
||||||
|
|
||||||
// Remove proxy tracing headers to avoid upstream detection
|
|
||||||
req.Header.Del("X-Forwarded-For")
|
|
||||||
req.Header.Del("X-Forwarded-Host")
|
|
||||||
req.Header.Del("X-Forwarded-Proto")
|
|
||||||
req.Header.Del("X-Forwarded-Port")
|
|
||||||
req.Header.Del("X-Real-IP")
|
|
||||||
req.Header.Del("Forwarded")
|
|
||||||
req.Header.Del("Via")
|
|
||||||
|
|
||||||
// Remove client identity headers that reveal third-party clients
|
// Remove proxy, client identity, and browser fingerprint headers
|
||||||
req.Header.Del("X-Title")
|
misc.ScrubProxyAndFingerprintHeaders(req)
|
||||||
req.Header.Del("X-Stainless-Lang")
|
|
||||||
req.Header.Del("X-Stainless-Package-Version")
|
|
||||||
req.Header.Del("X-Stainless-Os")
|
|
||||||
req.Header.Del("X-Stainless-Arch")
|
|
||||||
req.Header.Del("X-Stainless-Runtime")
|
|
||||||
req.Header.Del("X-Stainless-Runtime-Version")
|
|
||||||
req.Header.Del("Http-Referer")
|
|
||||||
req.Header.Del("Referer")
|
|
||||||
|
|
||||||
// Remove browser / Chromium fingerprint headers
|
|
||||||
req.Header.Del("Sec-Ch-Ua")
|
|
||||||
req.Header.Del("Sec-Ch-Ua-Mobile")
|
|
||||||
req.Header.Del("Sec-Ch-Ua-Platform")
|
|
||||||
req.Header.Del("Sec-Fetch-Mode")
|
|
||||||
req.Header.Del("Sec-Fetch-Site")
|
|
||||||
req.Header.Del("Sec-Fetch-Dest")
|
|
||||||
req.Header.Del("Priority")
|
|
||||||
req.Header.Del("Accept-Encoding")
|
|
||||||
|
|
||||||
// Remove query-based credentials if they match the authenticated client API key.
|
// Remove query-based credentials if they match the authenticated client API key.
|
||||||
// This prevents leaking client auth material to the Amp upstream while avoiding
|
// This prevents leaking client auth material to the Amp upstream while avoiding
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -21,6 +20,7 @@ import (
|
|||||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/auth/gemini"
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/auth/gemini"
|
||||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
||||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces"
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces"
|
||||||
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/misc"
|
||||||
sdkAuth "github.com/router-for-me/CLIProxyAPI/v6/sdk/auth"
|
sdkAuth "github.com/router-for-me/CLIProxyAPI/v6/sdk/auth"
|
||||||
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
|
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
@@ -33,7 +33,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func getGeminiCLIUserAgent() string {
|
func getGeminiCLIUserAgent() string {
|
||||||
return fmt.Sprintf("GeminiCLI/1.0.0/unknown (%s; %s)", runtime.GOOS, runtime.GOARCH)
|
return misc.GeminiCLIUserAgent("")
|
||||||
}
|
}
|
||||||
|
|
||||||
type projectSelectionRequiredError struct{}
|
type projectSelectionRequiredError struct{}
|
||||||
|
|||||||
@@ -4,10 +4,68 @@
|
|||||||
package misc
|
package misc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// GeminiCLIUserAgent returns a User-Agent string that matches the Gemini CLI format.
|
||||||
|
// The model parameter is included in the UA; pass "" or "unknown" when the model is not applicable.
|
||||||
|
func GeminiCLIUserAgent(model string) string {
|
||||||
|
if model == "" {
|
||||||
|
model = "unknown"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("GeminiCLI/1.0.0/%s (%s; %s)", model, runtime.GOOS, runtime.GOARCH)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScrubProxyAndFingerprintHeaders removes all headers that could reveal
|
||||||
|
// proxy infrastructure, client identity, or browser fingerprints from an
|
||||||
|
// outgoing request. This ensures requests to upstream services look like they
|
||||||
|
// originate directly from a native client rather than a third-party client
|
||||||
|
// behind a reverse proxy.
|
||||||
|
func ScrubProxyAndFingerprintHeaders(req *http.Request) {
|
||||||
|
if req == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Proxy tracing headers ---
|
||||||
|
req.Header.Del("X-Forwarded-For")
|
||||||
|
req.Header.Del("X-Forwarded-Host")
|
||||||
|
req.Header.Del("X-Forwarded-Proto")
|
||||||
|
req.Header.Del("X-Forwarded-Port")
|
||||||
|
req.Header.Del("X-Real-IP")
|
||||||
|
req.Header.Del("Forwarded")
|
||||||
|
req.Header.Del("Via")
|
||||||
|
|
||||||
|
// --- Client identity headers ---
|
||||||
|
req.Header.Del("X-Title")
|
||||||
|
req.Header.Del("X-Stainless-Lang")
|
||||||
|
req.Header.Del("X-Stainless-Package-Version")
|
||||||
|
req.Header.Del("X-Stainless-Os")
|
||||||
|
req.Header.Del("X-Stainless-Arch")
|
||||||
|
req.Header.Del("X-Stainless-Runtime")
|
||||||
|
req.Header.Del("X-Stainless-Runtime-Version")
|
||||||
|
req.Header.Del("Http-Referer")
|
||||||
|
req.Header.Del("Referer")
|
||||||
|
|
||||||
|
// --- Browser / Chromium fingerprint headers ---
|
||||||
|
// These are sent by Electron-based clients (e.g. CherryStudio) using the
|
||||||
|
// Fetch API, but NOT by Node.js https module (which Antigravity uses).
|
||||||
|
req.Header.Del("Sec-Ch-Ua")
|
||||||
|
req.Header.Del("Sec-Ch-Ua-Mobile")
|
||||||
|
req.Header.Del("Sec-Ch-Ua-Platform")
|
||||||
|
req.Header.Del("Sec-Fetch-Mode")
|
||||||
|
req.Header.Del("Sec-Fetch-Site")
|
||||||
|
req.Header.Del("Sec-Fetch-Dest")
|
||||||
|
req.Header.Del("Priority")
|
||||||
|
|
||||||
|
// --- Encoding negotiation ---
|
||||||
|
// Antigravity (Node.js) sends "gzip, deflate, br" by default;
|
||||||
|
// Electron-based clients may add "zstd" which is a fingerprint mismatch.
|
||||||
|
req.Header.Del("Accept-Encoding")
|
||||||
|
}
|
||||||
|
|
||||||
// EnsureHeader ensures that a header exists in the target header map by checking
|
// EnsureHeader ensures that a header exists in the target header map by checking
|
||||||
// multiple sources in order of priority: source headers, existing target headers,
|
// multiple sources in order of priority: source headers, existing target headers,
|
||||||
// and finally the default value. It only sets the header if it's not already present
|
// and finally the default value. It only sets the header if it's not already present
|
||||||
@@ -35,3 +93,4 @@ func EnsureHeader(target http.Header, source http.Header, key, defaultValue stri
|
|||||||
target.Set(key, val)
|
target.Set(key, val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -745,12 +744,7 @@ func applyGeminiCLIHeaders(r *http.Request, model string) {
|
|||||||
ginHeaders = ginCtx.Request.Header
|
ginHeaders = ginCtx.Request.Header
|
||||||
}
|
}
|
||||||
|
|
||||||
if model == "" {
|
misc.EnsureHeader(r.Header, ginHeaders, "User-Agent", misc.GeminiCLIUserAgent(model))
|
||||||
model = "unknown"
|
|
||||||
}
|
|
||||||
|
|
||||||
userAgent := fmt.Sprintf("GeminiCLI/1.0.0/%s (%s; %s)", model, runtime.GOOS, runtime.GOARCH)
|
|
||||||
misc.EnsureHeader(r.Header, ginHeaders, "User-Agent", userAgent)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// cliPreviewFallbackOrder returns preview model candidates for a base model.
|
// cliPreviewFallbackOrder returns preview model candidates for a base model.
|
||||||
|
|||||||
@@ -1,50 +1,12 @@
|
|||||||
package executor
|
package executor
|
||||||
|
|
||||||
import "net/http"
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
// scrubProxyAndFingerprintHeaders removes all headers that could reveal
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/misc"
|
||||||
// proxy infrastructure, client identity, or browser fingerprints from an
|
)
|
||||||
// outgoing request. This ensures requests to Google look like they
|
|
||||||
// originate directly from the Antigravity IDE (Node.js) rather than
|
// scrubProxyAndFingerprintHeaders delegates to the shared utility in internal/misc.
|
||||||
// a third-party client behind a reverse proxy.
|
|
||||||
func scrubProxyAndFingerprintHeaders(req *http.Request) {
|
func scrubProxyAndFingerprintHeaders(req *http.Request) {
|
||||||
if req == nil {
|
misc.ScrubProxyAndFingerprintHeaders(req)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Proxy tracing headers ---
|
|
||||||
req.Header.Del("X-Forwarded-For")
|
|
||||||
req.Header.Del("X-Forwarded-Host")
|
|
||||||
req.Header.Del("X-Forwarded-Proto")
|
|
||||||
req.Header.Del("X-Forwarded-Port")
|
|
||||||
req.Header.Del("X-Real-IP")
|
|
||||||
req.Header.Del("Forwarded")
|
|
||||||
req.Header.Del("Via")
|
|
||||||
|
|
||||||
// --- Client identity headers ---
|
|
||||||
req.Header.Del("X-Title")
|
|
||||||
req.Header.Del("X-Stainless-Lang")
|
|
||||||
req.Header.Del("X-Stainless-Package-Version")
|
|
||||||
req.Header.Del("X-Stainless-Os")
|
|
||||||
req.Header.Del("X-Stainless-Arch")
|
|
||||||
req.Header.Del("X-Stainless-Runtime")
|
|
||||||
req.Header.Del("X-Stainless-Runtime-Version")
|
|
||||||
req.Header.Del("Http-Referer")
|
|
||||||
req.Header.Del("Referer")
|
|
||||||
|
|
||||||
// --- Browser / Chromium fingerprint headers ---
|
|
||||||
// These are sent by Electron-based clients (e.g. CherryStudio) using the
|
|
||||||
// Fetch API, but NOT by Node.js https module (which Antigravity uses).
|
|
||||||
req.Header.Del("Sec-Ch-Ua")
|
|
||||||
req.Header.Del("Sec-Ch-Ua-Mobile")
|
|
||||||
req.Header.Del("Sec-Ch-Ua-Platform")
|
|
||||||
req.Header.Del("Sec-Fetch-Mode")
|
|
||||||
req.Header.Del("Sec-Fetch-Site")
|
|
||||||
req.Header.Del("Sec-Fetch-Dest")
|
|
||||||
req.Header.Del("Priority")
|
|
||||||
|
|
||||||
// --- Encoding negotiation ---
|
|
||||||
// Antigravity (Node.js) sends "gzip, deflate, br" by default;
|
|
||||||
// Electron-based clients may add "zstd" which is a fingerprint mismatch.
|
|
||||||
req.Header.Del("Accept-Encoding")
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user