From 5dc1848466eddc8f9b2f34dcb45eb31cecc342fb Mon Sep 17 00:00:00 2001 From: maplelove Date: Sun, 22 Feb 2026 20:51:00 +0800 Subject: [PATCH] feat(scrub): add comprehensive browser fingerprint and client identity header scrubbing --- internal/api/modules/amp/proxy.go | 21 ++++++++ .../runtime/executor/antigravity_executor.go | 16 +----- internal/runtime/executor/header_scrub.go | 50 +++++++++++++++++++ 3 files changed, 73 insertions(+), 14 deletions(-) create mode 100644 internal/runtime/executor/header_scrub.go diff --git a/internal/api/modules/amp/proxy.go b/internal/api/modules/amp/proxy.go index 21ed9e57..163c408c 100644 --- a/internal/api/modules/amp/proxy.go +++ b/internal/api/modules/amp/proxy.go @@ -83,6 +83,27 @@ func createReverseProxy(upstreamURL string, secretSource SecretSource) (*httputi req.Header.Del("Forwarded") req.Header.Del("Via") + // Remove client identity headers that reveal third-party clients + 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") + + // 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. // This prevents leaking client auth material to the Amp upstream while avoiding // breaking unrelated upstream query parameters. diff --git a/internal/runtime/executor/antigravity_executor.go b/internal/runtime/executor/antigravity_executor.go index 9de6cb08..fdd2f1b7 100644 --- a/internal/runtime/executor/antigravity_executor.go +++ b/internal/runtime/executor/antigravity_executor.go @@ -149,13 +149,7 @@ func (e *AntigravityExecutor) HttpRequest(ctx context.Context, auth *cliproxyaut } httpReq.Close = true httpReq.Header.Del("Accept") - httpReq.Header.Del("X-Forwarded-For") - httpReq.Header.Del("X-Forwarded-Host") - httpReq.Header.Del("X-Forwarded-Proto") - httpReq.Header.Del("X-Forwarded-Port") - httpReq.Header.Del("X-Real-IP") - httpReq.Header.Del("Forwarded") - httpReq.Header.Del("Via") + scrubProxyAndFingerprintHeaders(httpReq) httpClient := newAntigravityHTTPClient(ctx, e.cfg, auth, 0) return httpClient.Do(httpReq) } @@ -1405,13 +1399,7 @@ func (e *AntigravityExecutor) buildRequest(ctx context.Context, auth *cliproxyau httpReq.Header.Set("Content-Type", "application/json") httpReq.Header.Set("Authorization", "Bearer "+token) httpReq.Header.Set("User-Agent", resolveUserAgent(auth)) - httpReq.Header.Del("X-Forwarded-For") - httpReq.Header.Del("X-Forwarded-Host") - httpReq.Header.Del("X-Forwarded-Proto") - httpReq.Header.Del("X-Forwarded-Port") - httpReq.Header.Del("X-Real-IP") - httpReq.Header.Del("Forwarded") - httpReq.Header.Del("Via") + scrubProxyAndFingerprintHeaders(httpReq) if host := resolveHost(base); host != "" { httpReq.Host = host } diff --git a/internal/runtime/executor/header_scrub.go b/internal/runtime/executor/header_scrub.go new file mode 100644 index 00000000..f20558e2 --- /dev/null +++ b/internal/runtime/executor/header_scrub.go @@ -0,0 +1,50 @@ +package executor + +import "net/http" + +// scrubProxyAndFingerprintHeaders removes all headers that could reveal +// 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 +// 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") +}