Created
August 30, 2026 13:22
-
-
Save viktor-shcherb/bd681189f04a84d079a110e2b44765b2 to your computer and use it in GitHub Desktop.
Lightpanda/Chromium authenticated-proxy CDP compatibility matrix (Jobseek recon 2026-08-30)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| module example.com/lightpanda-proxy-auth-recon | |
| go 1.24.0 | |
| require ( | |
| github.com/chromedp/cdproto v0.0.0-20250724212937-08a3db8b4327 | |
| github.com/chromedp/chromedp v0.14.2 | |
| ) | |
| require ( | |
| github.com/chromedp/sysutil v1.1.0 // indirect | |
| github.com/go-json-experiment/json v0.0.0-20250725192818-e39067aee2d2 // indirect | |
| github.com/gobwas/httphead v0.1.0 // indirect | |
| github.com/gobwas/pool v0.2.1 // indirect | |
| github.com/gobwas/ws v1.4.0 // indirect | |
| golang.org/x/sys v0.34.0 // indirect | |
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| github.com/chromedp/cdproto v0.0.0-20250724212937-08a3db8b4327 h1:UQ4AU+BGti3Sy/aLU8KVseYKNALcX9UXY6DfpwQ6J8E= | |
| github.com/chromedp/cdproto v0.0.0-20250724212937-08a3db8b4327/go.mod h1:NItd7aLkcfOA/dcMXvl8p1u+lQqioRMq/SqDp71Pb/k= | |
| github.com/chromedp/chromedp v0.14.2 h1:r3b/WtwM50RsBZHMUm9fsNhhzRStTHrKdr2zmwbZSzM= | |
| github.com/chromedp/chromedp v0.14.2/go.mod h1:rHzAv60xDE7VNy/MYtTUrYreSc0ujt2O1/C3bzctYBo= | |
| github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM= | |
| github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8= | |
| github.com/go-json-experiment/json v0.0.0-20250725192818-e39067aee2d2 h1:iizUGZ9pEquQS5jTGkh4AqeeHCMbfbjeb0zMt0aEFzs= | |
| github.com/go-json-experiment/json v0.0.0-20250725192818-e39067aee2d2/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M= | |
| github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= | |
| github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= | |
| github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= | |
| github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= | |
| github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs= | |
| github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc= | |
| github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo= | |
| github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= | |
| github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw= | |
| github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= | |
| golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | |
| golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= | |
| golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package main | |
| import ( | |
| "context" | |
| "encoding/base64" | |
| "encoding/json" | |
| "flag" | |
| "fmt" | |
| "io" | |
| "log" | |
| "net/http" | |
| "strings" | |
| "sync" | |
| "time" | |
| "github.com/chromedp/cdproto/cdp" | |
| "github.com/chromedp/cdproto/fetch" | |
| "github.com/chromedp/cdproto/target" | |
| "github.com/chromedp/chromedp" | |
| ) | |
| const username = "crawler-user" | |
| const password = "crawler-pass" | |
| type output struct { | |
| Backend string `json:"backend"` | |
| PageText string `json:"page_text,omitempty"` | |
| AuthRequiredEvents int `json:"auth_required_events"` | |
| RequestPausedEvents int `json:"request_paused_events"` | |
| ProxyRequests int `json:"proxy_requests"` | |
| AuthenticatedRequest bool `json:"authenticated_request"` | |
| Errors []string `json:"errors"` | |
| } | |
| type state struct { | |
| mu sync.Mutex | |
| authRequiredEvents int | |
| requestPausedEvents int | |
| proxyRequests int | |
| authenticatedRequests int | |
| errors []string | |
| } | |
| func main() { | |
| backend := flag.String("backend", "unknown", "name printed in the result") | |
| cdpURL := flag.String("cdp", "", "complete CDP websocket URL") | |
| proxyListen := flag.String("proxy-listen", "0.0.0.0:18088", "proxy fixture listen address") | |
| originListen := flag.String("origin-listen", "0.0.0.0:18089", "origin fixture listen address") | |
| proxyServer := flag.String("proxy-server", "http://127.0.0.1:18088", "proxy URL visible to the browser") | |
| pageURL := flag.String("page", "http://127.0.0.1:18089/", "origin URL visible to the browser") | |
| contextProxy := flag.Bool("context-proxy", true, "create a browser context configured with proxy-server") | |
| flag.Parse() | |
| if *cdpURL == "" { | |
| log.Fatal("-cdp is required") | |
| } | |
| st := &state{} | |
| origin := originServer(*originListen) | |
| defer func() { _ = origin.Shutdown(context.Background()) }() | |
| proxy := proxyServerFixture(*proxyListen, st) | |
| defer func() { _ = proxy.Shutdown(context.Background()) }() | |
| rootCtx, cancel := context.WithTimeout(context.Background(), 20*time.Second) | |
| defer cancel() | |
| allocatorCtx, allocatorCancel := chromedp.NewRemoteAllocator(rootCtx, *cdpURL, chromedp.NoModifyURL) | |
| defer allocatorCancel() | |
| var tabCtx context.Context | |
| var tabCancel context.CancelFunc | |
| if *contextProxy { | |
| tabCtx, tabCancel = chromedp.NewContext( | |
| allocatorCtx, | |
| chromedp.WithNewBrowserContext(func(params *target.CreateBrowserContextParams) *target.CreateBrowserContextParams { | |
| return params.WithProxyServer(*proxyServer).WithProxyBypassList("<-loopback>") | |
| }), | |
| ) | |
| } else { | |
| tabCtx, tabCancel = chromedp.NewContext(allocatorCtx) | |
| } | |
| defer tabCancel() | |
| chromedp.ListenTarget(tabCtx, func(event any) { | |
| switch e := event.(type) { | |
| case *fetch.EventRequestPaused: | |
| st.mu.Lock() | |
| st.requestPausedEvents++ | |
| st.mu.Unlock() | |
| go runFetchCommand(tabCtx, st, fetch.ContinueRequest(e.RequestID)) | |
| case *fetch.EventAuthRequired: | |
| st.mu.Lock() | |
| st.authRequiredEvents++ | |
| st.mu.Unlock() | |
| go runFetchCommand(tabCtx, st, fetch.ContinueWithAuth(e.RequestID, &fetch.AuthChallengeResponse{ | |
| Response: fetch.AuthChallengeResponseResponseProvideCredentials, | |
| Username: username, | |
| Password: password, | |
| })) | |
| } | |
| }) | |
| out := output{Backend: *backend, Errors: []string{}} | |
| err := chromedp.Run(tabCtx, | |
| fetch.Enable().WithHandleAuthRequests(true), | |
| chromedp.Navigate(*pageURL), | |
| chromedp.Text("body", &out.PageText, chromedp.ByQuery), | |
| ) | |
| if err != nil { | |
| out.Errors = append(out.Errors, err.Error()) | |
| } | |
| st.mu.Lock() | |
| out.AuthRequiredEvents = st.authRequiredEvents | |
| out.RequestPausedEvents = st.requestPausedEvents | |
| out.ProxyRequests = st.proxyRequests | |
| out.AuthenticatedRequest = st.authenticatedRequests > 0 | |
| out.Errors = append(out.Errors, st.errors...) | |
| st.mu.Unlock() | |
| encoded, err := json.MarshalIndent(out, "", " ") | |
| if err != nil { | |
| log.Fatal(err) | |
| } | |
| fmt.Println(string(encoded)) | |
| } | |
| type fetchCommand interface { | |
| Do(context.Context) error | |
| } | |
| func runFetchCommand(ctx context.Context, st *state, command fetchCommand) { | |
| chromedpContext := chromedp.FromContext(ctx) | |
| if chromedpContext == nil || chromedpContext.Target == nil { | |
| recordError(st, fmt.Errorf("target executor unavailable")) | |
| return | |
| } | |
| executorCtx := cdp.WithExecutor(ctx, chromedpContext.Target) | |
| if err := command.Do(executorCtx); err != nil { | |
| recordError(st, err) | |
| } | |
| } | |
| func recordError(st *state, err error) { | |
| st.mu.Lock() | |
| st.errors = append(st.errors, err.Error()) | |
| st.mu.Unlock() | |
| } | |
| func originServer(address string) *http.Server { | |
| mux := http.NewServeMux() | |
| mux.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) { | |
| body := "proxy-auth-ok" | |
| w.Header().Set("Content-Type", "text/plain; charset=utf-8") | |
| w.Header().Set("Content-Length", fmt.Sprint(len(body))) | |
| _, _ = w.Write([]byte(body)) | |
| }) | |
| server := &http.Server{Addr: address, Handler: mux, ReadHeaderTimeout: 2 * time.Second} | |
| go serve(server) | |
| return server | |
| } | |
| func proxyServerFixture(address string, st *state) *http.Server { | |
| server := &http.Server{ | |
| Addr: address, | |
| Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
| st.mu.Lock() | |
| st.proxyRequests++ | |
| st.mu.Unlock() | |
| gotUser, gotPassword, ok := proxyBasicAuth(r) | |
| if !ok || gotUser != username || gotPassword != password { | |
| w.Header().Set("Proxy-Authenticate", `Basic realm="crawler-proxy"`) | |
| w.WriteHeader(http.StatusProxyAuthRequired) | |
| return | |
| } | |
| st.mu.Lock() | |
| st.authenticatedRequests++ | |
| st.mu.Unlock() | |
| request, err := http.NewRequestWithContext(r.Context(), r.Method, r.URL.String(), r.Body) | |
| if err != nil { | |
| http.Error(w, err.Error(), http.StatusBadGateway) | |
| return | |
| } | |
| request.Header = r.Header.Clone() | |
| request.Header.Del("Proxy-Authorization") | |
| response, err := http.DefaultTransport.RoundTrip(request) | |
| if err != nil { | |
| http.Error(w, err.Error(), http.StatusBadGateway) | |
| return | |
| } | |
| defer response.Body.Close() | |
| for key, values := range response.Header { | |
| for _, value := range values { | |
| w.Header().Add(key, value) | |
| } | |
| } | |
| w.WriteHeader(response.StatusCode) | |
| _, _ = io.Copy(w, response.Body) | |
| }), | |
| ReadHeaderTimeout: 2 * time.Second, | |
| } | |
| go serve(server) | |
| return server | |
| } | |
| func proxyBasicAuth(r *http.Request) (string, string, bool) { | |
| value := r.Header.Get("Proxy-Authorization") | |
| prefix := "Basic " | |
| if !strings.HasPrefix(value, prefix) { | |
| return "", "", false | |
| } | |
| raw, err := base64.StdEncoding.DecodeString(strings.TrimSpace(strings.TrimPrefix(value, prefix))) | |
| if err != nil { | |
| return "", "", false | |
| } | |
| user, pass, ok := strings.Cut(string(raw), ":") | |
| return user, pass, ok | |
| } | |
| func serve(server *http.Server) { | |
| if err := server.ListenAndServe(); err != nil && !strings.Contains(err.Error(), "Server closed") { | |
| log.Fatal(err) | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment