From 2aac6b46eeb8c69c78e4699fb4498bcef812186e Mon Sep 17 00:00:00 2001 From: Junjun Zhang Date: Fri, 21 Aug 2026 09:16:06 +0800 Subject: [PATCH 1/5] ci(windows-proxy): create probe dirs before Out-File; serve PAC on :8099 Out-File does not create parent directories, so the probe build died with 'Could not find a part of the path ...probe/main.go' before any layer ran. Layer 3 also pointed AutoConfigURL at 127.0.0.1:8099 with nothing listening; fetchPACScript then degrades to DIRECT by design and the assertion cannot pass. Serve probe/www with python -m http.server and wait for readiness before probing. Co-Authored-By: ggcode --- .github/workflows/windows-proxy.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/windows-proxy.yml b/.github/workflows/windows-proxy.yml index 9ccb30c8..df1b1c4e 100644 --- a/.github/workflows/windows-proxy.yml +++ b/.github/workflows/windows-proxy.yml @@ -42,6 +42,8 @@ jobs: - name: Build probe helper shell: pwsh run: | + # Out-File does not create parent directories. + New-Item -ItemType Directory -Force -Path probe, probe/fetch, probe/bin | Out-Null @' package main @@ -141,14 +143,24 @@ jobs: New-Item -ItemType Directory -Force -Path probe/www | Out-Null $pac | Out-File -Encoding ascii probe/www/proxy.pac + # Serve the PAC script on 127.0.0.1:8099 -- AutoConfigURL points + # here and fetchPACScript GETs it, so something must listen. + $py = if (Get-Command python -ErrorAction SilentlyContinue) { 'python' } else { 'py' } + $server = Start-Process -FilePath $py -ArgumentList '-m','http.server','8099','--directory','probe/www' -PassThru -WindowStyle Hidden $reg = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings" Set-ItemProperty $reg AutoConfigURL "http://127.0.0.1:8099/proxy.pac" try { + $up = $false + foreach ($i in 1..20) { + try { Invoke-WebRequest -NoProxy -UseBasicParsing http://127.0.0.1:8099/proxy.pac -TimeoutSec 2 | Out-Null; $up = $true; break } catch { Start-Sleep -Milliseconds 500 } + } + if (-not $up) { Write-Error "PAC server on :8099 did not come up"; exit 1 } $out = & probe/bin/probe.exe if ($out -notmatch '^PROXY http://127\.0\.0\.1:8888') { Write-Error "expected PAC-directed proxy, got: $out"; exit 1 } Write-Host "layer3 OK: $out" } finally { Remove-ItemProperty $reg AutoConfigURL + if ($server -and -not $server.HasExited) { Stop-Process -Id $server.Id -Force } } - name: Table tests (all platforms) From f41df9873fc197e184c0a65ecbf56e1b54ef9be8 Mon Sep 17 00:00:00 2001 From: Junjun Zhang Date: Fri, 21 Aug 2026 09:19:09 +0800 Subject: [PATCH 2/5] ci(windows-proxy): fix swapped time imports in probe programs probe/main.go imported time without using it; probe/fetch/main.go used time.Second without importing time -- the go build failed on the runner before any layer test ran. Co-Authored-By: ggcode --- .github/workflows/windows-proxy.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/windows-proxy.yml b/.github/workflows/windows-proxy.yml index df1b1c4e..93a842a6 100644 --- a/.github/workflows/windows-proxy.yml +++ b/.github/workflows/windows-proxy.yml @@ -51,7 +51,6 @@ jobs: "fmt" "net/http" "os" - "time" "github.com/topcheer/ggcode/internal/util" ) @@ -76,9 +75,10 @@ jobs: import ( "fmt" + "io" "net/http" "os" - "io" + "time" "github.com/topcheer/ggcode/internal/util" ) From 31b834330bcaa802fac2c29439f69478b7345ed6 Mon Sep 17 00:00:00 2001 From: Junjun Zhang Date: Fri, 21 Aug 2026 09:30:59 +0800 Subject: [PATCH 3/5] fix(util): ieproxy PAC short-circuit made layer-3 engine dead code on Windows ieproxy.GetProxyFunc() runs its own WinHTTP PAC resolution when AutoConfig is active and returns scheme-less URLs (&url.URL{Host: ...}), so SmartProxyFunc's forwarder/pac engine was unreachable on real Windows and SOCKS PAC directives were misused as HTTP proxies. Build the static layer from httpproxy.Config ourselves (mirroring ieproxy's staticProxy) and leave AutoConfig entirely to layer 3. Found on a real windows-latest runner: probe printed 'PROXY //127.0.0.1:8888' instead of 'http://127.0.0.1:8888'. Co-Authored-By: ggcode --- internal/util/sysproxy_windows.go | 38 +++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/internal/util/sysproxy_windows.go b/internal/util/sysproxy_windows.go index 599f205e..edcffe79 100644 --- a/internal/util/sysproxy_windows.go +++ b/internal/util/sysproxy_windows.go @@ -15,6 +15,7 @@ import ( "github.com/mattn/go-ieproxy" "github.com/saucelabs/forwarder/pac" + "golang.org/x/net/http/httpproxy" ) // Windows system-proxy + PAC support (#761). @@ -69,6 +70,32 @@ type pacState struct { resultCache sync.Map } +// staticSystemProxy resolves the registry static proxy (ProxyEnable / +// ProxyServer / ProxyOverride) for a single request, mirroring +// ieproxy's staticProxy but never consulting the auto-config entry. +// Returns (nil, nil) when the static proxy is disabled. +func staticSystemProxy(req *http.Request) (*url.URL, error) { + conf := ieproxy.GetConf() + if conf.Automatic.Active || !conf.Static.Active || req.URL == nil { + return nil, nil + } + cfg := httpproxy.Config{ + HTTPSProxy: protocolFallback(conf.Static.Protocols, "https"), + HTTPProxy: protocolFallback(conf.Static.Protocols, "http"), + NoProxy: conf.Static.NoProxy, + } + return cfg.ProxyFunc()(req.URL) +} + +// protocolFallback returns the per-protocol static proxy, falling back to +// the protocol-less default entry ("http=...;https=..." vs "host:port"). +func protocolFallback(m map[string]string, proto string) string { + if v, ok := m[proto]; ok { + return v + } + return m[""] +} + var thePAC pacState func buildWindowsProxyFunc() func(*http.Request) (*url.URL, error) { @@ -78,10 +105,13 @@ func buildWindowsProxyFunc() func(*http.Request) (*url.URL, error) { return u, err } - // 2. Static system proxy. ieproxy.GetProxyFunc reads the registry - // (ProxyEnable/ProxyServer/ProxyOverride) and handles per-protocol - // formats; it returns (nil, nil) for direct when disabled. - if u, err := ieproxy.GetProxyFunc()(req); err == nil && u != nil { + // 2. Static system proxy. Read the registry conf ourselves and + // build an httpproxy.Config only from the static entry. We must NOT + // use ieproxy.GetProxyFunc() here: when AutoConfig is active it + // resolves PAC itself via WinHTTP and returns scheme-less URLs + // (&url.URL{Host: ...}), which both short-circuits our own PAC + // engine below (dead code) and mis-handles SOCKS directives. + if u, err := staticSystemProxy(req); err == nil && u != nil { return u, nil } From 71a920130c4ee9a9103af174e65cc429fab6579a Mon Sep 17 00:00:00 2001 From: Junjun Zhang Date: Fri, 21 Aug 2026 09:34:53 +0800 Subject: [PATCH 4/5] fix(util): fAutoDetect (WPAD flag) must not suppress the static proxy layer GitHub windows runners boot with fAutoDetect=true, which makes ieproxy's Automatic.Active true with no AutoConfigURL; gating the static layer on !Automatic.Active turned layer 2 into DIRECT on such machines. staticSystemProxy now consults only Static.Active -- WPAD-only autoDetect has no PreConfiguredURL and already degrades to DIRECT in the PAC layer. Co-Authored-By: ggcode --- internal/util/sysproxy_windows.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/internal/util/sysproxy_windows.go b/internal/util/sysproxy_windows.go index edcffe79..5f9b62ff 100644 --- a/internal/util/sysproxy_windows.go +++ b/internal/util/sysproxy_windows.go @@ -73,10 +73,13 @@ type pacState struct { // staticSystemProxy resolves the registry static proxy (ProxyEnable / // ProxyServer / ProxyOverride) for a single request, mirroring // ieproxy's staticProxy but never consulting the auto-config entry. -// Returns (nil, nil) when the static proxy is disabled. +// Returns (nil, nil) when the static proxy is disabled. Note: an +// autoDetect flag alone (WPAD, no AutoConfigURL) must NOT suppress the +// static proxy -- github runners boot with fAutoDetect=true, and the +// guard used to turn layer 2 into DIRECT there. func staticSystemProxy(req *http.Request) (*url.URL, error) { conf := ieproxy.GetConf() - if conf.Automatic.Active || !conf.Static.Active || req.URL == nil { + if !conf.Static.Active || req.URL == nil { return nil, nil } cfg := httpproxy.Config{ From 5dbe1c04db6b844383288e140af1c92a416ebaf2 Mon Sep 17 00:00:00 2001 From: Junjun Zhang Date: Fri, 21 Aug 2026 09:40:02 +0800 Subject: [PATCH 5/5] ci(windows-proxy): match TestProxyURLFromDirective in the -run filter The filter 'PAC|SmartProxy' does not match TestProxyURLFromDirective, so the directive table tests never ran in CI -- which is how the SOCKS slice off-by-one (fixed in 62984ee1) slipped through after passing the committed tests locally. Co-Authored-By: ggcode --- .github/workflows/windows-proxy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/windows-proxy.yml b/.github/workflows/windows-proxy.yml index 93a842a6..0874c7e3 100644 --- a/.github/workflows/windows-proxy.yml +++ b/.github/workflows/windows-proxy.yml @@ -165,4 +165,4 @@ jobs: - name: Table tests (all platforms) shell: pwsh - run: go test -tags goolm -count=1 -run "PAC|SmartProxy" ./internal/util/ + run: go test -tags goolm -count=1 -run "PAC|SmartProxy|Directive" ./internal/util/