grok修复: x-statsig-id 改用 goja 跑原版签名器, 自愈跨 reship

手抄的字节算法每次 grok 换 web build 就失效(403 anti-bot), 天天死。
改为内嵌 goja 执行 grok 自己的签名器 chunk + 合成 DOM/WAAPI 垫片, 由 grok
自己的代码做所有随 build 轮换的字节索引/曲线选择, 我们只提供稳定的浏览器原语。

- statsig_engine.go: goja 引擎池 + 构建无关地自动定位当前签名器 chunk
- statsig_shim.js(//go:embed): document/getComputedStyle(WAAPI 采样)/
  crypto.subtle/TextEncoder/base64 垫片 + Turbopack bootstrap
- client.go: statsigID 优先走引擎, 任何失败回退旧静态算法; ensureChallenge
  捕获 seed+curves 并按 reship(chunk 列表变化)重建引擎池
- 旧静态 computeStatsigTail 降级为兜底; 其离线回归测试改 Skip(ground truth
  无法离线重生), 新增 40 并发签名测试

已验证: 动态定位当前 build → conversations/new 返回 200 + conversationId。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-11 05:41:38 +01:00
co-authored by Claude Opus 4.8
parent 4c060729b9
commit cd3f98bd61
6 changed files with 633 additions and 64 deletions
+6 -5
View File
@@ -7,12 +7,14 @@ replace github.com/quic-go/qpack => github.com/quic-go/qpack v0.5.1
require ( require (
github.com/bogdanfinn/fhttp v0.6.8 github.com/bogdanfinn/fhttp v0.6.8
github.com/bogdanfinn/tls-client v1.11.2 github.com/bogdanfinn/tls-client v1.11.2
github.com/dop251/goja v0.0.0-20260701091749-b07b74453ea9
github.com/gin-contrib/cors v1.7.6 github.com/gin-contrib/cors v1.7.6
github.com/gin-gonic/gin v1.11.0 github.com/gin-gonic/gin v1.11.0
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/matoous/go-nanoid/v2 v2.1.0 github.com/matoous/go-nanoid/v2 v2.1.0
github.com/redis/go-redis/v9 v9.16.0 github.com/redis/go-redis/v9 v9.16.0
golang.org/x/crypto v0.51.0 golang.org/x/crypto v0.51.0
golang.org/x/image v0.43.0
gorm.io/datatypes v1.2.7 gorm.io/datatypes v1.2.7
gorm.io/driver/postgres v1.6.0 gorm.io/driver/postgres v1.6.0
gorm.io/gorm v1.31.0 gorm.io/gorm v1.31.0
@@ -21,24 +23,24 @@ require (
require ( require (
filippo.io/edwards25519 v1.1.0 // indirect filippo.io/edwards25519 v1.1.0 // indirect
github.com/andybalholm/brotli v1.2.0 // indirect github.com/andybalholm/brotli v1.2.0 // indirect
github.com/bdandy/go-errors v1.2.2 // indirect
github.com/bdandy/go-socks4 v1.2.3 // indirect
github.com/bogdanfinn/quic-go-utls v1.0.4-utls // indirect github.com/bogdanfinn/quic-go-utls v1.0.4-utls // indirect
github.com/bogdanfinn/utls v1.7.7-barnius // indirect github.com/bogdanfinn/utls v1.7.7-barnius // indirect
github.com/bogdanfinn/websocket v1.5.5-barnius // indirect
github.com/bytedance/sonic v1.14.0 // indirect github.com/bytedance/sonic v1.14.0 // indirect
github.com/bytedance/sonic/loader v0.3.0 // indirect github.com/bytedance/sonic/loader v0.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect github.com/cloudwego/base64x v0.1.6 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dlclark/regexp2/v2 v2.2.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.9 // indirect github.com/gabriel-vasile/mimetype v1.4.9 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect github.com/gin-contrib/sse v1.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.27.0 // indirect github.com/go-playground/validator/v10 v10.27.0 // indirect
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
github.com/go-sql-driver/mysql v1.8.1 // indirect github.com/go-sql-driver/mysql v1.8.1 // indirect
github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-json v0.10.5 // indirect
github.com/goccy/go-yaml v1.18.0 // indirect github.com/goccy/go-yaml v1.19.2 // indirect
github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.6.0 // indirect github.com/jackc/pgx/v5 v5.6.0 // indirect
@@ -60,7 +62,6 @@ require (
github.com/ugorji/go/codec v1.3.0 // indirect github.com/ugorji/go/codec v1.3.0 // indirect
go.uber.org/mock v0.5.2 // indirect go.uber.org/mock v0.5.2 // indirect
golang.org/x/arch v0.20.0 // indirect golang.org/x/arch v0.20.0 // indirect
golang.org/x/image v0.43.0 // indirect
golang.org/x/mod v0.36.0 // indirect golang.org/x/mod v0.36.0 // indirect
golang.org/x/net v0.54.0 // indirect golang.org/x/net v0.54.0 // indirect
golang.org/x/sync v0.21.0 // indirect golang.org/x/sync v0.21.0 // indirect
+12 -34
View File
@@ -1,25 +1,17 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE=
github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/bdandy/go-errors v1.2.2 h1:WdFv/oukjTJCLa79UfkGmwX7ZxONAihKu4V0mLIs11Q=
github.com/bdandy/go-errors v1.2.2/go.mod h1:NkYHl4Fey9oRRdbB1CoC6e84tuqQHiqrOcZpqFEkBxM=
github.com/bdandy/go-socks4 v1.2.3 h1:Q6Y2heY1GRjCtHbmlKfnwrKVU/k81LS8mRGLRlmDlic=
github.com/bdandy/go-socks4 v1.2.3/go.mod h1:98kiVFgpdogR8aIGLWLvjDVZ8XcKPsSI/ypGrO+bqHI=
github.com/bogdanfinn/fhttp v0.6.8 h1:LiQyHOY3i0QoxxNB7nq27/nGNNbtPj0fuBPozhR7Ws4= github.com/bogdanfinn/fhttp v0.6.8 h1:LiQyHOY3i0QoxxNB7nq27/nGNNbtPj0fuBPozhR7Ws4=
github.com/bogdanfinn/fhttp v0.6.8/go.mod h1:A+EKDzMx2hb4IUbMx4TlkoHnaJEiLl8r/1Ss1Y+5e5M= github.com/bogdanfinn/fhttp v0.6.8/go.mod h1:A+EKDzMx2hb4IUbMx4TlkoHnaJEiLl8r/1Ss1Y+5e5M=
github.com/bogdanfinn/quic-go-utls v1.0.4-utls h1:zPjusVVNeJFA2ORMAP0rjnrZrBkV4Dnia4e6ToOfUDA= github.com/bogdanfinn/quic-go-utls v1.0.4-utls h1:zPjusVVNeJFA2ORMAP0rjnrZrBkV4Dnia4e6ToOfUDA=
github.com/bogdanfinn/quic-go-utls v1.0.4-utls/go.mod h1:UONJOaHGWho08kZtkkgH7GjktEPjMemGxjTcNpVPZVA= github.com/bogdanfinn/quic-go-utls v1.0.4-utls/go.mod h1:UONJOaHGWho08kZtkkgH7GjktEPjMemGxjTcNpVPZVA=
github.com/bogdanfinn/quic-go-utls v1.0.9-utls h1:tV6eDEiRbRCcepALSzxR94JUVD3N3ACIiRLgyc2Ep8s=
github.com/bogdanfinn/quic-go-utls v1.0.9-utls/go.mod h1:aHph9B9H9yPOt5xnhWKSOum27DJAqpiHzwX+gjvaXcg=
github.com/bogdanfinn/tls-client v1.11.2 h1:o6qX0L1cEi+4MaBqujxqOeK254VZM20t3QR+A34/V6I= github.com/bogdanfinn/tls-client v1.11.2 h1:o6qX0L1cEi+4MaBqujxqOeK254VZM20t3QR+A34/V6I=
github.com/bogdanfinn/tls-client v1.11.2/go.mod h1:qQIsVGe35NdxYEozNh9JuDZ+aOaOEq2tKAsu2iYEGZg= github.com/bogdanfinn/tls-client v1.11.2/go.mod h1:qQIsVGe35NdxYEozNh9JuDZ+aOaOEq2tKAsu2iYEGZg=
github.com/bogdanfinn/tls-client v1.15.1 h1:KiFAlED55DJ8Fcocn+/1nX6PrDFcttIHAf/GDkV6KN8=
github.com/bogdanfinn/tls-client v1.15.1/go.mod h1:LsU6mXVn8MOFDwTkyRfI7V1BZM1p0wf2ZfZsICW/1fM=
github.com/bogdanfinn/utls v1.7.7-barnius h1:OuJ497cc7F3yKNVHRsYPQdGggmk5x6+V5ZlrCR7fOLU= github.com/bogdanfinn/utls v1.7.7-barnius h1:OuJ497cc7F3yKNVHRsYPQdGggmk5x6+V5ZlrCR7fOLU=
github.com/bogdanfinn/utls v1.7.7-barnius/go.mod h1:aAK1VZQlpKZClF1WEQeq6kyclbkPq4hz6xTbB5xSlmg= github.com/bogdanfinn/utls v1.7.7-barnius/go.mod h1:aAK1VZQlpKZClF1WEQeq6kyclbkPq4hz6xTbB5xSlmg=
github.com/bogdanfinn/websocket v1.5.5-barnius h1:bY+qnxpai1qe7Jmjx+Sds/cmOSpuuLoR8x61rWltjOI=
github.com/bogdanfinn/websocket v1.5.5-barnius/go.mod h1:gvvEw6pTKHb7yOiFvIfAFTStQWyrm25BMVCTj5wRSsI=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
@@ -37,6 +29,10 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dlclark/regexp2/v2 v2.2.1 h1:mf4KkFUj0gJuarK8P+LgiS+Lit7m9N1yAwEfPbee7R0=
github.com/dlclark/regexp2/v2 v2.2.1/go.mod h1:avUrQvPaLz2DrFNHJF0taWAFFX2C1GMSSoeiqFjcBmU=
github.com/dop251/goja v0.0.0-20260701091749-b07b74453ea9 h1:q33zakIx+wEp1Ko5NpDyDBICuXL4JeHUaHbhPowcMEk=
github.com/dop251/goja v0.0.0-20260701091749-b07b74453ea9/go.mod h1:Sc+QOu1WruvaaeT/cxFez/pXHpI9ZDjg/E8QNfSVveI=
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
github.com/gin-contrib/cors v1.7.6 h1:3gQ8GMzs1Ylpf70y8bMw4fVpycXIeX1ZemuSQIsnQQY= github.com/gin-contrib/cors v1.7.6 h1:3gQ8GMzs1Ylpf70y8bMw4fVpycXIeX1ZemuSQIsnQQY=
@@ -53,13 +49,15 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
@@ -67,6 +65,8 @@ github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EO
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20230207041349-798e818bf904 h1:4/hN5RUoecvl+RmJRE2YxKWtnnQls6rQjjW5oV7qg2U=
github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
@@ -108,8 +108,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
github.com/redis/go-redis/v9 v9.16.0 h1:OotgqgLSRCmzfqChbQyG1PHC3tLNR89DG4jdOERSEP4= github.com/redis/go-redis/v9 v9.16.0 h1:OotgqgLSRCmzfqChbQyG1PHC3tLNR89DG4jdOERSEP4=
@@ -136,41 +134,21 @@ go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
golang.org/x/image v0.43.0 h1:FLxcP4ec2350nTfOC8ysKtqYSIFbk/QGjw1ZHNP4tsY= golang.org/x/image v0.43.0 h1:FLxcP4ec2350nTfOC8ysKtqYSIFbk/QGjw1ZHNP4tsY=
golang.org/x/image v0.43.0/go.mod h1:rrpelvGFt+kLPAjPM4HeWPgrl0FtafueU//e5N0qk/Q= golang.org/x/image v0.43.0/go.mod h1:rrpelvGFt+kLPAjPM4HeWPgrl0FtafueU//e5N0qk/Q=
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
golang.org/x/net v0.0.0-20211104170005-ce137452f963/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w= golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=
golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ= golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
+56 -23
View File
@@ -19,6 +19,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"log"
"math" "math"
"math/rand/v2" "math/rand/v2"
"os" "os"
@@ -296,13 +297,15 @@ func (c *Client) FetchSession(ctx context.Context, token string) (email, userID
return strings.TrimSpace(body.Session.Email), strings.TrimSpace(body.Session.UserID), nil return strings.TrimSpace(body.Session.Email), strings.TrimSpace(body.Session.UserID), nil
} }
// grok's x-statsig-id is validated per-session: the 49-byte header is 0x00 plus // grok's x-statsig-id is a per-session anti-bot token. Its 3-byte "F" tail is a
// a 48-byte "seed" published in the homepage <meta name="grok-site-verification">, // browser fingerprint the server recomputes from the homepage seed + curve set,
// and the salt embeds a 3-byte "F" the server recomputes from that seed and the // and the byte-indexing that derives it ROTATES on every grok web reship — so any
// page's curve set. Both rotate whenever grok ships a new web build, so hardcoded // hand-ported algorithm goes stale within a day (403 anti-bot). The durable path
// constants go stale (403 anti-bot). We self-heal: fetch the homepage per session // therefore runs grok's OWN signer in goja (statsig_engine.go): we fetch the seed
// (browser-free, tls-client), derive seed + F, and cache. The static defaults // + curves from the homepage and let grok's code do all the (rotating) indexing.
// below (env-overridable) are a last-resort fallback if the fetch fails. // statsigID prefers that engine; the hand-ported computeStatsigTail below and the
// static env-overridable defaults are only a last-resort fallback. See the package
// doc and statsig_engine.go for the full picture.
// statsigEpoch is the challenge epoch (2023-05-01 00:00 UTC). // statsigEpoch is the challenge epoch (2023-05-01 00:00 UTC).
const ( const (
statsigEpoch = 1682924400 statsigEpoch = 1682924400
@@ -332,6 +335,11 @@ type statsigChallenge struct {
suffix string suffix string
trailer byte trailer byte
fetchedAt time.Time fetchedAt time.Time
// Inputs for the durable goja signer (statsig_js.go): the raw <meta> seed
// content and the curves JSON. Empty when parsing failed (engine then skipped).
seedB64 string
curvesJSON string
} }
// statsigCurve is one entry of the per-load curve set injected via the Next.js // statsigCurve is one entry of the per-load curve set injected via the Next.js
@@ -386,8 +394,14 @@ func (c *Client) ensureChallenge(ctx context.Context, client tlsclient.HttpClien
} }
ch, err := fetchStatsigChallenge(ctx, client, token) ch, err := fetchStatsigChallenge(ctx, client, token)
if err != nil { if err != nil {
// Silent fallback to static defaults is the #1 cause of a recurring
// "403 anti-bot": the homepage structure changed and we never notice.
// Surface it so the failure mode (fetch/parse broke vs. offsets rotated)
// is diagnosable from logs instead of guessing.
log.Printf("grok statsig: self-heal failed, using stale static defaults (403 likely): %v", err)
return return
} }
log.Printf("grok statsig: self-heal ok header[:6]=%x suffix=%s", ch.header[:6], ch.suffix)
statsigMu.Lock() statsigMu.Lock()
statsigCache[token] = ch statsigCache[token] = ch
statsigMu.Unlock() statsigMu.Unlock()
@@ -423,27 +437,36 @@ func fetchStatsigChallenge(ctx context.Context, client tlsclient.HttpClient, tok
if mm == nil { if mm == nil {
return statsigChallenge{}, errors.New("statsig: seed meta not found") return statsigChallenge{}, errors.New("statsig: seed meta not found")
} }
seed, err := decodeStatsigSeed(mm[1])
if err != nil {
return statsigChallenge{}, err
}
curves, err := parseStatsigCurves(html) curves, err := parseStatsigCurves(html)
if err != nil { if err != nil {
return statsigChallenge{}, err return statsigChallenge{}, err
} }
tail, err := computeStatsigTail(seed, curves) curvesJSON, err := json.Marshal(curves)
if err != nil { if err != nil {
return statsigChallenge{}, err return statsigChallenge{}, err
} }
header := make([]byte, 0, 49)
header = append(header, 0x00) // Primary path is the goja signer (statsig_js.go); set it up / refresh it for
header = append(header, seed...) // this build. Static (header, salt) below is only a last-resort fallback.
return statsigChallenge{ ensureEngine(ctx, client, html)
header: header,
suffix: statsigSaltPrefix + tail, ch := statsigChallenge{
trailer: defaultStatsigTrailer, header: statsigHeader,
suffix: statsigSuffix,
trailer: statsigTrailer,
fetchedAt: time.Now(), fetchedAt: time.Now(),
}, nil seedB64: mm[1],
curvesJSON: string(curvesJSON),
}
// Best-effort static derivation (the old hand-ported algorithm) as fallback.
if seed, err := decodeStatsigSeed(mm[1]); err == nil {
if tail, err := computeStatsigTail(seed, curves); err == nil {
ch.header = append([]byte{0x00}, seed...)
ch.suffix = statsigSaltPrefix + tail
ch.trailer = defaultStatsigTrailer
}
}
return ch, nil
} }
func decodeStatsigSeed(s string) ([]byte, error) { func decodeStatsigSeed(s string) ([]byte, error) {
@@ -635,10 +658,20 @@ func cubicBezierEase(x1, y1, x2, y2, p float64) float64 {
func statsigID(path, method, token string) string { func statsigID(path, method, token string) string {
header, suffix, trailer := statsigHeader, statsigSuffix, statsigTrailer header, suffix, trailer := statsigHeader, statsigSuffix, statsigTrailer
statsigMu.Lock() statsigMu.Lock()
if ch, ok := statsigCache[token]; ok { ch, ok := statsigCache[token]
header, suffix, trailer = ch.header, ch.suffix, ch.trailer
}
statsigMu.Unlock() statsigMu.Unlock()
if ok {
header, suffix, trailer = ch.header, ch.suffix, ch.trailer
// Primary: run grok's own signer in goja (durable across reships). Falls
// through to the static computation below on any failure.
if ch.seedB64 != "" && ch.curvesJSON != "" {
if id, err := signWithEngine(ch.seedB64, ch.curvesJSON, path, method); err == nil {
return id
} else if !errors.Is(err, errEngineNotReady) {
log.Printf("grok statsig: js signer failed, using static fallback: %v", err)
}
}
}
counter := uint32(time.Now().Unix() - statsigEpoch) counter := uint32(time.Now().Unix() - statsigEpoch)
sig := fmt.Sprintf("%s!%s!%d%s", method, path, counter, suffix) sig := fmt.Sprintf("%s!%s!%d%s", method, path, counter, suffix)
@@ -0,0 +1,287 @@
package grok
// This file makes x-statsig-id durable across grok web reships by executing grok's
// OWN obfuscated signer (a Turbopack chunk) inside an embedded JS engine (goja),
// under a synthesized DOM + Web-Animations getComputedStyle shim (statsig_shim.js).
// grok's code does all the per-build byte-indexing / curve-selection; we only supply
// the stable browser primitives. This replaces the brittle hand-ported byte-offset
// algorithm in computeStatsigTail (kept as a last-resort fallback). See the package
// doc and the grok-statsig-signer memory for the reverse-engineering details.
import (
"context"
"crypto/sha256"
"crypto/sha512"
_ "embed"
"errors"
"fmt"
"io"
"log"
"regexp"
"strconv"
"strings"
"sync"
http "github.com/bogdanfinn/fhttp"
tlsclient "github.com/bogdanfinn/tls-client"
"github.com/dop251/goja"
)
//go:embed statsig_shim.js
var statsigShimJS string
const sigPoolSize = 4
// errEngineNotReady means the durable signer engine has not been built yet (no
// homepage fetched, or chunk location failed). Callers fall back to the static path.
var errEngineNotReady = errors.New("statsig engine not ready")
var (
// signer-chunk location patterns (Turbopack). The caller chunk contains the
// literal "x-statsig-id" and a lazy import `.A(<moduleId>).then(e=>t(e.default()))`.
statsigCallerRe = regexp.MustCompile(`\.A\((\d+)\)\.then\(`)
chunkPathRe = regexp.MustCompile(`/_next/static/chunks/[a-zA-Z0-9_.\-/]+\.js`)
// goja's parser tries to fetch //# sourceMappingURL=... from disk and errors.
sourceMapRe = regexp.MustCompile(`(?m)//[#@]\s*sourceMappingURL=\S*`)
sigMgrMu sync.Mutex
sigBuildKey string // hash of the homepage chunk list; changes on reship
sigChunkSrc string // current signer chunk source
sigPool chan *sigEngine // pool of ready engines for sigChunkSrc
)
// sigEngine wraps one goja runtime with grok's signer chunk loaded. A goja runtime
// is not safe for concurrent use; the pool hands each engine to one goroutine at a
// time so no per-engine locking is needed.
type sigEngine struct {
rt *goja.Runtime
fire goja.Callable // __grokSignInto
}
func newSigEngine(chunkSrc string) (*sigEngine, error) {
rt := goja.New()
// SHA-256 bridge for crypto.subtle.digest.
if err := rt.Set("__goSha256", func(call goja.FunctionCall) goja.Value {
data := jsBytes(rt, call.Argument(0))
sum := sha256.Sum256(data)
return rt.ToValue(rt.NewArrayBuffer(sum[:]))
}); err != nil {
return nil, err
}
if _, err := rt.RunString(statsigShimJS); err != nil {
return nil, fmt.Errorf("shim: %w", err)
}
if _, err := rt.RunString(sourceMapRe.ReplaceAllString(chunkSrc, "")); err != nil {
return nil, fmt.Errorf("chunk eval: %w", err)
}
if _, err := rt.RunString("__grokBootstrap()"); err != nil {
return nil, fmt.Errorf("bootstrap: %w", err)
}
fire, ok := goja.AssertFunction(rt.Get("__grokSignInto"))
if !ok {
return nil, errors.New("statsig js: __grokSignInto missing")
}
return &sigEngine{rt: rt, fire: fire}, nil
}
// statsigID runs grok's signer for one request. seedB64 is the raw <meta> content;
// curvesJSON is [[{color,deg,bezier}...]...].
func (e *sigEngine) statsigID(seedB64, curvesJSON, path, method string) (string, error) {
_ = e.rt.Set("__SEED", seedB64)
_ = e.rt.Set("__CURVES", curvesJSON)
_ = e.rt.Set("__PATH", path)
_ = e.rt.Set("__METHOD", method)
// RunString drains goja's microtask queue, settling the async signer's promise.
if _, err := e.fire(goja.Undefined()); err != nil {
return "", err
}
if errv := e.rt.Get("__grokErr"); errv != nil && !goja.IsNull(errv) && !goja.IsUndefined(errv) {
return "", fmt.Errorf("statsig js: %s", errv.String())
}
res := e.rt.Get("__grokResult")
if res == nil || goja.IsNull(res) || goja.IsUndefined(res) {
return "", errors.New("statsig js: promise did not settle")
}
id := res.String()
if id == "" {
return "", errors.New("statsig js: empty id")
}
return id, nil
}
// jsBytes extracts the byte contents of a JS Uint8Array / ArrayBuffer value.
func jsBytes(rt *goja.Runtime, v goja.Value) []byte {
if ab, ok := v.Export().(goja.ArrayBuffer); ok {
return ab.Bytes()
}
obj := v.ToObject(rt)
if buf := obj.Get("buffer"); buf != nil {
if ab, ok := buf.Export().(goja.ArrayBuffer); ok {
return ab.Bytes()
}
}
n := int(obj.Get("length").ToInteger())
out := make([]byte, n)
for i := 0; i < n; i++ {
out[i] = byte(obj.Get(strconv.Itoa(i)).ToInteger())
}
return out
}
// signWithEngine borrows an engine from the pool (building one on demand), signs,
// and returns it. Returns an error if the engine subsystem is not ready.
func signWithEngine(seedB64, curvesJSON, path, method string) (string, error) {
sigMgrMu.Lock()
src, pool := sigChunkSrc, sigPool
sigMgrMu.Unlock()
if src == "" || pool == nil {
return "", errEngineNotReady
}
var eng *sigEngine
select {
case eng = <-pool:
default:
var err error
if eng, err = newSigEngine(src); err != nil {
return "", err
}
}
id, err := eng.statsigID(seedB64, curvesJSON, path, method)
select {
case pool <- eng:
default:
}
return id, err
}
// ensureEngine refreshes the global engine pool when the homepage's chunk set
// changes (i.e. grok reshipped). It locates the signer chunk build-agnostically and
// rebuilds the pool. Cheap no-op when the build is unchanged.
func ensureEngine(ctx context.Context, client tlsclient.HttpClient, homeHTML string) {
paths := chunkPathRe.FindAllString(homeHTML, -1)
if len(paths) == 0 {
return
}
key := hashStrings(paths)
sigMgrMu.Lock()
unchanged := key == sigBuildKey && sigPool != nil
sigMgrMu.Unlock()
if unchanged {
return
}
src, err := locateSignerChunk(ctx, client, dedupe(paths))
if err != nil {
log.Printf("grok statsig: locate signer chunk failed (will use static fallback): %v", err)
return
}
// smoke-test: a build must produce a loadable engine before we commit to it.
eng, err := newSigEngine(src)
if err != nil {
log.Printf("grok statsig: signer chunk did not load in goja (static fallback): %v", err)
return
}
pool := make(chan *sigEngine, sigPoolSize)
pool <- eng // reuse the smoke-test engine instead of discarding it
sigMgrMu.Lock()
sigBuildKey = key
sigChunkSrc = src
sigPool = pool
sigMgrMu.Unlock()
log.Printf("grok statsig: self-heal engine ready (build %s..)", key[:8])
}
// locateSignerChunk finds grok's signer chunk from the homepage chunk list:
// the caller chunk holds "x-statsig-id" + `.A(<id>)`; a loader chunk registers that
// <id> with `Promise.all(["static/chunks/XXX.js"]...)` — XXX is the signer.
func locateSignerChunk(ctx context.Context, client tlsclient.HttpClient, paths []string) (string, error) {
var callerID string
loaderRe := (*regexp.Regexp)(nil)
var signerPath string
// pass 1: find the caller chunk + its lazy module id.
for _, p := range paths {
body, err := fetchChunk(ctx, client, p)
if err != nil || !strings.Contains(body, "x-statsig-id") {
continue
}
if m := statsigCallerRe.FindStringSubmatch(body); m != nil {
callerID = m[1]
}
break
}
if callerID == "" {
return "", errors.New("statsig caller module id not found")
}
// loader registers: ,<callerID>,<param>=>{ ... Promise.all(["static/chunks/XXX.js"] ...
loaderRe = regexp.MustCompile(`,` + callerID + `,\w+=>\{[^}]*?Promise\.all\(\["(static/chunks/[^"]+\.js)"`)
// pass 2: find the loader chunk that maps callerID -> signer chunk path.
for _, p := range paths {
body, err := fetchChunk(ctx, client, p)
if err != nil {
continue
}
if m := loaderRe.FindStringSubmatch(body); m != nil {
signerPath = m[1]
break
}
}
if signerPath == "" {
return "", fmt.Errorf("signer chunk path for module %s not found", callerID)
}
src, err := fetchChunk(ctx, client, "/_next/"+signerPath)
if err != nil {
return "", fmt.Errorf("fetch signer chunk: %w", err)
}
return src, nil
}
func fetchChunk(ctx context.Context, client tlsclient.HttpClient, path string) (string, error) {
if !strings.HasPrefix(path, "http") {
path = apiBase + path
}
req, err := http.NewRequest(http.MethodGet, path, nil)
if err != nil {
return "", err
}
req = req.WithContext(ctx)
req.Header = http.Header{
"accept": {"*/*"},
"user-agent": {userAgent},
http.HeaderOrderKey: {"accept", "user-agent"},
}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return "", fmt.Errorf("chunk http %d", resp.StatusCode)
}
b, err := io.ReadAll(resp.Body)
return string(b), err
}
func hashStrings(ss []string) string {
uniq := dedupe(ss)
h := sha512.New()
for _, s := range uniq {
_, _ = io.WriteString(h, s)
_, _ = io.WriteString(h, "\n")
}
return fmt.Sprintf("%x", h.Sum(nil))
}
func dedupe(ss []string) []string {
seen := map[string]bool{}
out := ss[:0:0]
for _, s := range ss {
if !seen[s] {
seen[s] = true
out = append(out, s)
}
}
return out
}
@@ -4,8 +4,10 @@ import (
"context" "context"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"errors"
"os" "os"
"strings" "strings"
"sync"
"testing" "testing"
"time" "time"
) )
@@ -34,8 +36,15 @@ var statsigTriples = []struct {
const statsigTestCurves = `[[{"color":[48,44,6,37,198,15],"deg":192,"bezier":[118,76,158,16]},{"color":[224,216,196,111,43,97],"deg":119,"bezier":[67,167,95,219]},{"color":[90,235,250,5,223,64],"deg":104,"bezier":[100,57,106,204]},{"color":[6,109,253,44,29,224],"deg":151,"bezier":[204,60,142,122]},{"color":[81,0,121,208,228,133],"deg":181,"bezier":[182,89,105,123]},{"color":[31,3,160,181,226,184],"deg":98,"bezier":[207,150,215,136]},{"color":[231,243,81,28,109,131],"deg":175,"bezier":[17,103,7,81]},{"color":[222,250,130,169,55,247],"deg":141,"bezier":[21,238,12,84]},{"color":[74,62,116,145,209,185],"deg":109,"bezier":[0,74,58,233]},{"color":[204,168,203,138,107,125],"deg":214,"bezier":[41,13,239,45]},{"color":[246,116,162,162,84,234],"deg":127,"bezier":[160,202,52,76]},{"color":[138,202,210,36,61,195],"deg":234,"bezier":[31,50,177,229]},{"color":[47,46,196,176,79,255],"deg":221,"bezier":[39,14,9,76]},{"color":[245,164,227,71,201,110],"deg":190,"bezier":[193,208,125,9]},{"color":[199,253,44,33,130,240],"deg":191,"bezier":[168,247,61,37]},{"color":[19,91,5,180,202,243],"deg":216,"bezier":[96,152,42,123]}],[{"color":[42,154,230,170,197,128],"deg":108,"bezier":[160,52,34,184]},{"color":[224,132,83,0,231,162],"deg":238,"bezier":[227,37,122,102]},{"color":[24,82,237,199,100,74],"deg":130,"bezier":[186,106,188,209]},{"color":[47,50,169,65,124,44],"deg":228,"bezier":[198,22,146,225]},{"color":[111,131,95,195,131,73],"deg":207,"bezier":[134,146,202,172]},{"color":[192,10,166,28,236,3],"deg":219,"bezier":[85,77,157,235]},{"color":[236,139,199,72,233,250],"deg":197,"bezier":[180,9,79,147]},{"color":[146,195,78,16,231,211],"deg":93,"bezier":[123,18,87,27]},{"color":[168,211,49,42,124,18],"deg":172,"bezier":[232,171,120,118]},{"color":[43,146,96,11,203,53],"deg":146,"bezier":[11,4,83,198]},{"color":[183,97,101,38,115,41],"deg":159,"bezier":[69,223,197,236]},{"color":[78,73,207,132,109,134],"deg":49,"bezier":[59,18,133,168]},{"color":[239,153,225,207,127,157],"deg":194,"bezier":[13,206,154,182]},{"color":[19,146,127,191,68,199],"deg":165,"bezier":[148,212,7,16]},{"color":[93,225,249,144,88,255],"deg":215,"bezier":[62,30,77,69]},{"color":[203,79,164,145,3,20],"deg":81,"bezier":[228,28,93,130]}],[{"color":[171,53,4,125,232,43],"deg":122,"bezier":[136,9,128,97]},{"color":[143,97,205,57,63,69],"deg":192,"bezier":[232,20,219,189]},{"color":[28,173,18,107,158,44],"deg":255,"bezier":[93,187,184,198]},{"color":[170,17,62,142,200,32],"deg":29,"bezier":[14,31,12,97]},{"color":[19,117,122,173,239,66],"deg":74,"bezier":[117,176,139,212]},{"color":[213,151,230,112,224,255],"deg":25,"bezier":[1,223,72,233]},{"color":[153,131,51,105,69,47],"deg":108,"bezier":[123,177,126,140]},{"color":[120,114,44,151,88,83],"deg":165,"bezier":[16,104,134,75]},{"color":[245,145,194,75,120,26],"deg":142,"bezier":[79,235,38,43]},{"color":[147,63,50,255,239,106],"deg":190,"bezier":[122,143,160,150]},{"color":[233,78,184,130,25,123],"deg":54,"bezier":[87,18,184,226]},{"color":[162,180,233,70,57,249],"deg":87,"bezier":[123,238,61,124]},{"color":[146,95,56,171,38,240],"deg":239,"bezier":[241,134,228,44]},{"color":[111,11,149,62,208,177],"deg":70,"bezier":[103,149,4,37]},{"color":[159,128,118,21,197,153],"deg":175,"bezier":[246,215,172,236]},{"color":[194,131,68,247,215,108],"deg":30,"bezier":[23,91,151,231]}],[{"color":[239,129,141,243,85,208],"deg":38,"bezier":[252,248,245,195]},{"color":[205,56,138,49,126,99],"deg":107,"bezier":[72,85,228,91]},{"color":[185,54,148,122,170,158],"deg":192,"bezier":[33,88,51,136]},{"color":[7,14,75,26,23,41],"deg":93,"bezier":[153,21,55,147]},{"color":[245,211,213,64,5,253],"deg":49,"bezier":[253,19,106,155]},{"color":[144,232,165,21,114,130],"deg":200,"bezier":[193,179,133,226]},{"color":[34,196,100,42,114,0],"deg":52,"bezier":[4,4,71,65]},{"color":[195,8,130,102,201,141],"deg":210,"bezier":[8,173,23,33]},{"color":[86,136,44,95,223,62],"deg":249,"bezier":[220,98,68,113]},{"color":[205,48,9,247,236,71],"deg":75,"bezier":[163,240,28,25]},{"color":[43,190,29,239,55,135],"deg":146,"bezier":[109,245,34,188]},{"color":[146,91,92,2,3,251],"deg":97,"bezier":[183,188,95,157]},{"color":[40,213,196,70,81,174],"deg":120,"bezier":[153,197,61,201]},{"color":[131,92,180,68,131,214],"deg":251,"bezier":[94,191,198,89]},{"color":[47,113,219,96,115,228],"deg":238,"bezier":[22,35,60,63]},{"color":[246,244,203,196,78,136],"deg":44,"bezier":[88,23,205,184]}]]` const statsigTestCurves = `[[{"color":[48,44,6,37,198,15],"deg":192,"bezier":[118,76,158,16]},{"color":[224,216,196,111,43,97],"deg":119,"bezier":[67,167,95,219]},{"color":[90,235,250,5,223,64],"deg":104,"bezier":[100,57,106,204]},{"color":[6,109,253,44,29,224],"deg":151,"bezier":[204,60,142,122]},{"color":[81,0,121,208,228,133],"deg":181,"bezier":[182,89,105,123]},{"color":[31,3,160,181,226,184],"deg":98,"bezier":[207,150,215,136]},{"color":[231,243,81,28,109,131],"deg":175,"bezier":[17,103,7,81]},{"color":[222,250,130,169,55,247],"deg":141,"bezier":[21,238,12,84]},{"color":[74,62,116,145,209,185],"deg":109,"bezier":[0,74,58,233]},{"color":[204,168,203,138,107,125],"deg":214,"bezier":[41,13,239,45]},{"color":[246,116,162,162,84,234],"deg":127,"bezier":[160,202,52,76]},{"color":[138,202,210,36,61,195],"deg":234,"bezier":[31,50,177,229]},{"color":[47,46,196,176,79,255],"deg":221,"bezier":[39,14,9,76]},{"color":[245,164,227,71,201,110],"deg":190,"bezier":[193,208,125,9]},{"color":[199,253,44,33,130,240],"deg":191,"bezier":[168,247,61,37]},{"color":[19,91,5,180,202,243],"deg":216,"bezier":[96,152,42,123]}],[{"color":[42,154,230,170,197,128],"deg":108,"bezier":[160,52,34,184]},{"color":[224,132,83,0,231,162],"deg":238,"bezier":[227,37,122,102]},{"color":[24,82,237,199,100,74],"deg":130,"bezier":[186,106,188,209]},{"color":[47,50,169,65,124,44],"deg":228,"bezier":[198,22,146,225]},{"color":[111,131,95,195,131,73],"deg":207,"bezier":[134,146,202,172]},{"color":[192,10,166,28,236,3],"deg":219,"bezier":[85,77,157,235]},{"color":[236,139,199,72,233,250],"deg":197,"bezier":[180,9,79,147]},{"color":[146,195,78,16,231,211],"deg":93,"bezier":[123,18,87,27]},{"color":[168,211,49,42,124,18],"deg":172,"bezier":[232,171,120,118]},{"color":[43,146,96,11,203,53],"deg":146,"bezier":[11,4,83,198]},{"color":[183,97,101,38,115,41],"deg":159,"bezier":[69,223,197,236]},{"color":[78,73,207,132,109,134],"deg":49,"bezier":[59,18,133,168]},{"color":[239,153,225,207,127,157],"deg":194,"bezier":[13,206,154,182]},{"color":[19,146,127,191,68,199],"deg":165,"bezier":[148,212,7,16]},{"color":[93,225,249,144,88,255],"deg":215,"bezier":[62,30,77,69]},{"color":[203,79,164,145,3,20],"deg":81,"bezier":[228,28,93,130]}],[{"color":[171,53,4,125,232,43],"deg":122,"bezier":[136,9,128,97]},{"color":[143,97,205,57,63,69],"deg":192,"bezier":[232,20,219,189]},{"color":[28,173,18,107,158,44],"deg":255,"bezier":[93,187,184,198]},{"color":[170,17,62,142,200,32],"deg":29,"bezier":[14,31,12,97]},{"color":[19,117,122,173,239,66],"deg":74,"bezier":[117,176,139,212]},{"color":[213,151,230,112,224,255],"deg":25,"bezier":[1,223,72,233]},{"color":[153,131,51,105,69,47],"deg":108,"bezier":[123,177,126,140]},{"color":[120,114,44,151,88,83],"deg":165,"bezier":[16,104,134,75]},{"color":[245,145,194,75,120,26],"deg":142,"bezier":[79,235,38,43]},{"color":[147,63,50,255,239,106],"deg":190,"bezier":[122,143,160,150]},{"color":[233,78,184,130,25,123],"deg":54,"bezier":[87,18,184,226]},{"color":[162,180,233,70,57,249],"deg":87,"bezier":[123,238,61,124]},{"color":[146,95,56,171,38,240],"deg":239,"bezier":[241,134,228,44]},{"color":[111,11,149,62,208,177],"deg":70,"bezier":[103,149,4,37]},{"color":[159,128,118,21,197,153],"deg":175,"bezier":[246,215,172,236]},{"color":[194,131,68,247,215,108],"deg":30,"bezier":[23,91,151,231]}],[{"color":[239,129,141,243,85,208],"deg":38,"bezier":[252,248,245,195]},{"color":[205,56,138,49,126,99],"deg":107,"bezier":[72,85,228,91]},{"color":[185,54,148,122,170,158],"deg":192,"bezier":[33,88,51,136]},{"color":[7,14,75,26,23,41],"deg":93,"bezier":[153,21,55,147]},{"color":[245,211,213,64,5,253],"deg":49,"bezier":[253,19,106,155]},{"color":[144,232,165,21,114,130],"deg":200,"bezier":[193,179,133,226]},{"color":[34,196,100,42,114,0],"deg":52,"bezier":[4,4,71,65]},{"color":[195,8,130,102,201,141],"deg":210,"bezier":[8,173,23,33]},{"color":[86,136,44,95,223,62],"deg":249,"bezier":[220,98,68,113]},{"color":[205,48,9,247,236,71],"deg":75,"bezier":[163,240,28,25]},{"color":[43,190,29,239,55,135],"deg":146,"bezier":[109,245,34,188]},{"color":[146,91,92,2,3,251],"deg":97,"bezier":[183,188,95,157]},{"color":[40,213,196,70,81,174],"deg":120,"bezier":[153,197,61,201]},{"color":[131,92,180,68,131,214],"deg":251,"bezier":[94,191,198,89]},{"color":[47,113,219,96,115,228],"deg":238,"bezier":[22,35,60,63]},{"color":[246,244,203,196,78,136],"deg":44,"bezier":[88,23,205,184]}]]`
// TestComputeStatsigTail is the offline regression test for the F derivation. // TestComputeStatsigTail was the offline regression for the hand-ported F
// derivation (computeStatsigTail). That algorithm is now only a last-resort
// fallback: the durable path runs grok's own signer in goja (statsig_engine.go),
// which self-heals across web reships. grok rotates the F algorithm on every
// reship, so these browser-captured (seed,F) triples go stale and CANNOT be
// regenerated offline. TestSelfHealStatsigE2E (live, via goja) is the real
// regression now. Kept skipped so the reference vectors aren't lost.
func TestComputeStatsigTail(t *testing.T) { func TestComputeStatsigTail(t *testing.T) {
t.Skip("static F derivation is a deprecated fallback; see TestSelfHealStatsigE2E (goja)")
var curves [][]statsigCurve var curves [][]statsigCurve
if err := json.Unmarshal([]byte(statsigTestCurves), &curves); err != nil { if err := json.Unmarshal([]byte(statsigTestCurves), &curves); err != nil {
t.Fatalf("curves: %v", err) t.Fatalf("curves: %v", err)
@@ -92,6 +101,55 @@ func TestSelfHealStatsigE2E(t *testing.T) {
t.Logf("OK bytes=%d head=%.80s", len(body), strings.ReplaceAll(body, "\n", " ")) t.Logf("OK bytes=%d head=%.80s", len(body), strings.ReplaceAll(body, "\n", " "))
} }
// TestStatsigEngineConcurrent fires many concurrent goja signs through the pool to
// catch data races / engine cross-talk (run with -race). Requires a live GROK_TOK
// so the engine is built and the challenge cached.
func TestStatsigEngineConcurrent(t *testing.T) {
token := strings.TrimSpace(os.Getenv("GROK_TOK"))
if token == "" {
t.Skip("no GROK_TOK")
}
c := NewClient("")
client, err := c.newTLSClient()
if err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second)
defer cancel()
c.ensureChallenge(ctx, client, token)
statsigMu.Lock()
ch, ok := statsigCache[token]
statsigMu.Unlock()
if !ok || ch.seedB64 == "" || ch.curvesJSON == "" {
t.Fatal("engine inputs not cached")
}
const n = 40
var wg sync.WaitGroup
errs := make(chan error, n)
for i := 0; i < n; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
id, err := signWithEngine(ch.seedB64, ch.curvesJSON, "/rest/app-chat/conversations/new", "POST")
if err != nil {
errs <- err
return
}
if len(id) < 40 {
errs <- errTooShort
}
}(i)
}
wg.Wait()
close(errs)
for e := range errs {
t.Fatalf("concurrent sign: %v", e)
}
}
var errTooShort = errors.New("statsig id too short")
// TestGenerateVideoE2E generates a real grok video using only the dynamic // TestGenerateVideoE2E generates a real grok video using only the dynamic
// self-healed statsig (no env overrides). Requires a live GROK_TOK. // self-healed statsig (no env overrides). Requires a live GROK_TOK.
func TestGenerateVideoE2E(t *testing.T) { func TestGenerateVideoE2E(t *testing.T) {
@@ -0,0 +1,212 @@
// statsig_shim.js — browser-environment shim that lets grok.com's own obfuscated
// x-statsig-id signer run inside goja. grok's code does all the (per-build,
// rotating) byte-indexing/curve-selection; we only provide the STABLE browser
// primitives it reads from: the seed <meta>, the .r-aufz1o SVG curve DOM, and a
// standard Web-Animations getComputedStyle sampler. Inputs arrive via globals set
// by Go before each sign: __SEED (base64 str), __CURVES (JSON [[{color,deg,bezier}]]),
// __PATH, __METHOD. Go also injects __goSha256(Uint8Array)->ArrayBuffer.
(function () {
'use strict';
var g = globalThis;
// ---- base64 (goja has no atob/btoa) ----
var B64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
g.atob = function (s) {
s = String(s).replace(/=+$/, '');
var out = '', bits = 0, val = 0;
for (var i = 0; i < s.length; i++) {
var c = B64.indexOf(s.charAt(i));
if (c < 0) continue;
val = (val << 6) | c; bits += 6;
if (bits >= 8) { bits -= 8; out += String.fromCharCode((val >> bits) & 0xff); }
}
return out;
};
g.btoa = function (s) {
s = String(s); var out = '';
for (var i = 0; i < s.length; i += 3) {
var b0 = s.charCodeAt(i), b1 = s.charCodeAt(i + 1), b2 = s.charCodeAt(i + 2);
var h0 = b0 >> 2, h1 = ((b0 & 3) << 4) | (b1 >> 4);
var h2 = ((b1 & 15) << 2) | (b2 >> 6), h3 = b2 & 63;
out += B64[h0] + B64[h1];
out += isNaN(b1) ? '=' : B64[h2];
out += isNaN(b2) ? '=' : B64[h3];
}
return out;
};
// ---- TextEncoder (goja has no TextEncoder) ----
if (typeof g.TextEncoder === 'undefined') {
g.TextEncoder = function () {};
g.TextEncoder.prototype.encode = function (str) {
str = String(str);
var bytes = [];
for (var i = 0; i < str.length; i++) {
var c = str.charCodeAt(i);
if (c < 0x80) bytes.push(c);
else if (c < 0x800) { bytes.push(0xc0 | (c >> 6), 0x80 | (c & 0x3f)); }
else if (c >= 0xd800 && c <= 0xdbff) { // surrogate pair
var c2 = str.charCodeAt(++i);
var cp = 0x10000 + ((c & 0x3ff) << 10) + (c2 & 0x3ff);
bytes.push(0xf0 | (cp >> 18), 0x80 | ((cp >> 12) & 0x3f), 0x80 | ((cp >> 6) & 0x3f), 0x80 | (cp & 0x3f));
} else { bytes.push(0xe0 | (c >> 12), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f)); }
}
return Uint8Array.from(bytes);
};
}
// ---- crypto.subtle.digest, backed by Go SHA-256 ----
g.crypto = g.crypto || {};
g.crypto.subtle = g.crypto.subtle || {};
g.crypto.subtle.digest = function (algo, data) {
// grok only ever asks for sha-256; g.__goSha256 returns an ArrayBuffer
var bytes = data instanceof Uint8Array ? data : new Uint8Array(data);
return Promise.resolve(g.__goSha256(bytes));
};
// ---- Web Animations getComputedStyle sampler (the only real math we own) ----
var K = 4096;
function cubicBezier(x1, y1, x2, y2, p) {
if (p <= 0) return 0; if (p >= 1) return 1;
function bez(t, a, b) { var mt = 1 - t; return 3 * a * mt * mt * t + 3 * b * mt * t * t + t * t * t; }
var lo = 0, hi = 1;
for (var i = 0; i < 100; i++) { var m = (lo + hi) / 2; if (bez(m, x1, x2) < p) lo = m; else hi = m; }
return bez((lo + hi) / 2, y1, y2);
}
function hexToRgb(h) { h = h.replace('#', ''); return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)]; }
function sample(anim) {
var kf = anim.keyframes, dur = anim.duration || K;
var frac = anim.currentTime / dur; if (frac < 0) frac = 0; if (frac > 1) frac = 1;
var bm = /cubic-bezier\(([^)]+)\)/.exec(kf.easing || '');
var eased = frac;
if (bm) { var p = bm[1].split(',').map(Number); eased = cubicBezier(p[0], p[1], p[2], p[3], frac); }
var c0 = hexToRgb(kf.color[0]), c1 = hexToRgb(kf.color[1]);
var col = [0, 1, 2].map(function (i) { return Math.round(c0[i] + (c1[i] - c0[i]) * eased); });
var d0 = parseFloat(/rotate\(([-\d.]+)deg\)/.exec(kf.transform[0])[1]);
var d1 = parseFloat(/rotate\(([-\d.]+)deg\)/.exec(kf.transform[1])[1]);
var ang = (d0 + (d1 - d0) * eased) * Math.PI / 180;
var cos = Math.cos(ang), sin = Math.sin(ang);
return { color: 'rgb(' + col[0] + ', ' + col[1] + ', ' + col[2] + ')',
transform: 'matrix(' + cos + ', ' + sin + ', ' + (-sin) + ', ' + cos + ', 0, 0)' };
}
// ---- minimal DOM ----
function makeEl(props) {
var e = {
nodeName: 'DIV', style: {}, childNodes: [], attrs: {}, _anim: null, _parent: null,
setAttribute: function (k, v) { e.attrs[k] = v; },
getAttribute: function (k) { return (k in e.attrs) ? e.attrs[k] : (props && props.attrs && k in props.attrs ? props.attrs[k] : null); },
appendChild: function (c) { e.childNodes.push(c); return c; },
append: function (c) { e.childNodes.push(c); return c; },
removeChild: function (c) { return c; },
remove: function () {},
animate: function (keyframes, opts) {
var anim = {
keyframes: keyframes,
duration: (opts && opts.duration) || (typeof opts === 'number' ? opts : K),
currentTime: 0, pause: function () {}, play: function () {}, cancel: function () {},
effect: { getKeyframes: function () { return Array.isArray(keyframes) ? keyframes : [keyframes]; } },
};
e._anim = anim; return anim;
},
getAnimations: function () { return e._anim ? [e._anim] : []; },
};
if (props) for (var k in props) if (k !== 'attrs') e[k] = props[k];
Object.defineProperty(e, 'parentElement', { get: function () { return e._parent; } });
Object.defineProperty(e, 'innerHTML', { set: function (v) { e._html = v; }, get: function () { return e._html; } });
Object.defineProperty(e, 'textContent', { set: function (v) { e._text = v; }, get: function () { return e._text; } });
return e;
}
// .r-aufz1o group: g.childNodes[0].childNodes[1].getAttribute('d') = svg path whose
// numbers (after substring(9), split('C')) decode back to each curve [c0..c5,deg,b0..b3].
function groupEl(flatCurves) {
var d = '_________' + flatCurves.map(function (c) { return c.join(' '); }).join('C');
var path = makeEl({ attrs: { d: d } });
var inner = makeEl(); inner.childNodes = [makeEl(), path];
var outer = makeEl(); outer.childNodes = [inner]; outer._parent = makeEl();
return outer;
}
var docBody = makeEl();
g.document = {
currentScript: null, body: docBody, head: makeEl(),
createElement: function (tag) { return makeEl({ nodeName: String(tag || 'div').toUpperCase() }); },
querySelectorAll: function (sel) {
sel = String(sel);
if (/aufz1o/.test(sel)) {
var curves = JSON.parse(g.__CURVES);
return curves.map(function (grp) {
return groupEl(grp.map(function (cv) { return cv.color.concat([cv.deg], cv.bezier); }));
});
}
if (/verification|name/i.test(sel)) {
var seed = g.__SEED;
return [{ nodeName: 'META', getAttribute: function (a) { return a === 'content' ? seed : null; },
get content() { return seed; } }];
}
return [];
},
querySelector: function (sel) { var r = this.querySelectorAll(sel); return r[0] || null; },
};
g.window = g;
g.self = g;
g.getComputedStyle = function (el) { return el && el._anim ? sample(el._anim) : { color: 'rgb(0, 0, 0)', transform: 'none' }; };
g.navigator = g.navigator || { userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36' };
g.location = g.location || { href: 'https://grok.com/', origin: 'https://grok.com', pathname: '/' };
// ---- Turbopack capture + bootstrap ----
var TP = []; TP.push = function (entry) { TP._entry = entry; return 0; };
g.TURBOPACK = TP;
// Called by Go AFTER the signer chunk is eval'd: run the module factory, grab default.
g.__grokBootstrap = function () {
var entry = TP._entry;
if (!entry) throw new Error('turbopack entry not registered');
var factory = entry[2];
var exports = {};
var ctx = {
s: function () {
var flat = Array.prototype.slice.call(arguments).flat(Infinity);
var name = null;
for (var i = 0; i < flat.length; i++) {
var x = flat[i];
if (typeof x === 'string') name = x;
else if (typeof x === 'function' && name != null) {
(function (nm, getter) { Object.defineProperty(exports, nm, { get: getter, configurable: true, enumerable: true }); })(name, x);
name = null;
}
}
},
};
factory(ctx);
// exports.default is a GETTER that invokes the module factory on every read,
// returning a FRESH async signer (path,method)=>Promise<id> with a fresh internal
// curve cache. Keep the exports object; read .default fresh per sign so different
// sessions' curves never leak through the closure cache.
g.__grokExports = exports;
if (typeof exports.default !== 'function') throw new Error('no default export');
};
// Called by Go per sign. Fresh signer each time via the re-invoking getter.
g.__grokSign = function () {
var signer = g.__grokExports.default; // fresh async signer
return signer(g.__PATH, g.__METHOD); // returns Promise<string>
};
// Synchronous bridge: Go calls this via RunString (which drains goja's job queue),
// then reads __grokResult / __grokErr. Works because crypto.subtle.digest resolves
// synchronously (Promise.resolve over a Go SHA-256), so the whole await chain settles
// within the microtask drain.
g.__grokResult = null;
g.__grokErr = null;
g.__grokSignInto = function () {
g.__grokResult = null; g.__grokErr = null;
try {
g.__grokSign().then(
function (r) { g.__grokResult = r; },
function (e) { g.__grokErr = (e && e.stack) ? String(e.stack) : String(e); }
);
} catch (e) { g.__grokErr = (e && e.stack) ? String(e.stack) : String(e); }
};
})();