修复grok 403

This commit is contained in:
2026-07-02 11:04:47 +08:00
parent 1de4fe99ca
commit 41bd120404
+75 -15
View File
@@ -1,23 +1,30 @@
// Package grok implements the Grok (grok.com / xAI) provider client. Auth is the
// website "sso" session cookie (a JWT whose only claim is a session_id — no exp,
// no refresh: when the session dies upstream the account is simply dead, never
// renewed). grok.com gates requests with an x-statsig-id header; the web app's
// value is just a base64-encoded fake JS TypeError string, which the upstream
// accepts — so we spoof it the same way (no Cloudflare clearance needed). Uses
// tls-client so the JA3/JA4 fingerprint matches Chrome.
// renewed). grok.com gates requests with an x-statsig-id header; its value
// is a 70-byte anti-bot record — header[49] | counter_le32 |
// sha256("METHOD!path!counter"+suffix)[:16] | trailer — XOR-masked with one
// random byte and base64-encoded; we reproduce it exactly in statsigID (the
// build-specific header/suffix/trailer are env-overridable when grok rotates
// them). Uses tls-client so the JA3/JA4 fingerprint matches Chrome.
package grok
import (
"context"
"crypto/sha256"
"encoding/base64"
"encoding/binary"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"math"
"math/rand/v2"
"os"
"strconv"
"strings"
"time"
http "github.com/bogdanfinn/fhttp"
tlsclient "github.com/bogdanfinn/tls-client"
@@ -197,16 +204,67 @@ func (c *Client) FetchSession(ctx context.Context, token string) (email, userID
return strings.TrimSpace(body.Session.Email), strings.TrimSpace(body.Session.UserID), nil
}
// statsigID mirrors grok2api's _statsig_id: base64 of a fake JS TypeError string.
// The upstream's anti-bot check accepts this spoofed value.
func statsigID() string {
const charset = "abcdefghijklmnopqrstuvwxyz0123456789"
b := make([]byte, 5)
for i := range b {
b[i] = charset[rand.IntN(len(charset))]
// statsig challenge constants for the current grok.com web build. They rotate
// when grok ships a new build; override at runtime via env vars
// (GROK_STATSIG_HEADER_HEX / GROK_STATSIG_SUFFIX / GROK_STATSIG_TRAILER).
// statsigEpoch is the challenge epoch (2023-05-01 00:00 UTC).
const (
statsigEpoch = 1682924400
defaultStatsigHeader = "00e1ebcb2cac08f42039de1eb4d8534da581482fd09ccc95e06e3f03a3e9ddde02eb50b70c2efeaec6401f5d9b5ed329d4"
defaultStatsigSuffix = "obfiowerehiring4fa399100100"
defaultStatsigTrailer = 3
)
var (
statsigHeader = resolveStatsigHeader()
statsigSuffix = envOr("GROK_STATSIG_SUFFIX", defaultStatsigSuffix)
statsigTrailer = resolveStatsigTrailer()
)
func resolveStatsigHeader() []byte {
h := envOr("GROK_STATSIG_HEADER_HEX", defaultStatsigHeader)
b, err := hex.DecodeString(h)
if err != nil || len(b) != 49 {
b, _ = hex.DecodeString(defaultStatsigHeader)
}
msg := fmt.Sprintf("x1:TypeError: Cannot read properties of null (reading 'children['%s']')", string(b))
return base64.StdEncoding.EncodeToString([]byte(msg))
return b
}
func resolveStatsigTrailer() byte {
if v := strings.TrimSpace(os.Getenv("GROK_STATSIG_TRAILER")); v != "" {
if n, err := strconv.Atoi(v); err == nil && n >= 0 && n <= 255 {
return byte(n)
}
}
return defaultStatsigTrailer
}
func envOr(key, def string) string {
if v := strings.TrimSpace(os.Getenv(key)); v != "" {
return v
}
return def
}
// statsigID reproduces grok.com's x-statsig-id anti-bot token for a request. The
// token binds to the request METHOD and URL path and to a coarse timestamp, so
// it must be regenerated per request. See the package doc for the layout.
func statsigID(path, method string) string {
counter := uint32(time.Now().Unix() - statsigEpoch)
sig := fmt.Sprintf("%s!%s!%d%s", method, path, counter, statsigSuffix)
hash := sha256.Sum256([]byte(sig))
raw := make([]byte, 0, 70)
raw = append(raw, statsigHeader...)
raw = binary.LittleEndian.AppendUint32(raw, counter)
raw = append(raw, hash[:16]...)
raw = append(raw, statsigTrailer)
key := byte(rand.IntN(256))
for i := range raw {
raw[i] ^= key
}
return base64.RawStdEncoding.EncodeToString(raw)
}
// applyHeaders sets the browser-like header set + sso cookie + spoofed statsig id.
@@ -219,7 +277,7 @@ func (c *Client) applyHeaders(req *http.Request, token string, extra map[string]
"origin": {origin},
"referer": {origin + "/"},
"user-agent": {userAgent},
"x-statsig-id": {statsigID()},
"x-statsig-id": {statsigID(req.URL.Path, req.Method)},
"x-xai-request-id": {uuid.NewString()},
"sec-ch-ua": {`"Chromium";v="133", "Not(A:Brand";v="99"`},
"sec-ch-ua-mobile": {"?0"},
@@ -243,7 +301,9 @@ func (c *Client) applyHeaders(req *http.Request, token string, extra map[string]
func (c *Client) newTLSClient() (tlsclient.HttpClient, error) {
options := []tlsclient.HttpClientOption{
tlsclient.WithTimeoutSeconds(120),
// Video generation streams inline until progress=100; a 15s clip can take
// several minutes, so allow up to 10m (caller's genCtx caps at 12m).
tlsclient.WithTimeoutSeconds(600),
tlsclient.WithClientProfile(profiles.Chrome_133),
tlsclient.WithRandomTLSExtensionOrder(),
}