Repository navigation
Auth2 SSO Callback Server Is a Global Singleton, Race Condition & Port Hijack #312
Description
Describe the bug
The OAuth2 SSO Callback Server in the CLI (used during login --sso) acts as a global singleton due to its reliance on http.DefaultServeMux. This leads to race conditions and potential port hijacking vulnerabilities.
In cmd/login.go:268-270 (approximate location):
srv := &http.Server{Addr: "localhost:" + strconv.Itoa(port)}
http.HandleFunc("/auth/callback", callbackHandler)This logic instantiates an http.Server but does not supply a dedicated Handler, meaning it falls back to the process-global http.DefaultServeMux. http.HandleFunc modifies this shared router, and this route is never cleaned up.
The critical impacts include:
- Port Hijacking / Token Exposure: If a malicious process running locally on a multi-tenant machine binds to port 58085 first, the CLI's temporary HTTP server will fail to start (
log.Fatalf), but the callback/authorization details from the user's browser may be successfully routed to the attacker. While PKCE prevents the attacker from directly exchanging the authorization code (as thecode_verifieris in the memory of the CLI process), theid_token(passed asr.FormValue("id_token")in the fallback implicit flow) might be extracted directly by the attacking server. - Global Mux Pollution & Race Conditions: The URL handler
/auth/callbackpersists permanently inside the process's default HTTP mux. Ifloginis called twice (e.g., in a CI script, or if the first login fails midway), the subsequent calls overwrite the first handler.http.HandleFuncis not completely goroutine-safe when dynamically modifying the mux while a server handles requests. It can incorrectly interleave or fail across consecutive executions in the same process footprint.
Expected behavior
- A locally isolated
http.ServeMuxshould be used per server instance instead ofhttp.DefaultServeMux. For example:mux := http.NewServeMux() mux.HandleFunc("/auth/callback", callbackHandler) srv := &http.Server{ Addr: "localhost:" + strconv.Itoa(port), Handler: mux, }
- The port binding error logic should be explicit: if the expected local port is already bound by another process,
login()should abort safely and alert the user immediately, rather than quietly failing inside a goroutine or leaving the user vulnerable to authorization route hijacking.
Actual behavior
The http.HandleFunc manipulates the global http.DefaultServeMux. The CLI process could leak handlers or have subsequent authentication operations overwrite global states over one another.
How to Reproduce?
- Open a separate terminal and mock an attacker process by listening on port 58085:
nc -l -p 58085
- Run the
microcks-cli login --ssocommand. - Observe that the OAuth authentication flow continues locally.
- When the browser redirects to
http://localhost:58085/auth/callback..., the attackerncinstance intercepts the HTTP request, collecting the query parameters (which may containcodeorid_tokendepending on the SSO provider mapping configuration).
Alternatively, inspecting the source code validates the reliance on http.DefaultServeMux:
- Find
srv := &http.Server{Addr: "localhost:" + strconv.Itoa(port)}insidecmd/login.go. - Notice that
http.HandleFuncis being used, making/auth/callbackglobally registered onhttp.DefaultServeMux.
Microcks version or git rev
microcks-cli2 (Current main/issue-branch code base as of reported date)
Install method (docker-compose, helm chart, operator, docker-desktop extension,...)
Compiled from source (go build)
Additional information
This issue affects multi-tenant environments where CI/CD setups or shared developer workstations might have concurrent or conflicting listeners. Fixing the HTTP handler scoping provides a resilient boundary.
Activity
[-]nsj[/-][+]Auth2 SSO Callback Server Is a Global Singleton, Race Condition & Port Hijack[/+]yada commentedon Oct 15, 2026
Checked on current master (f9550f5).
Still valid: the global router.
cmd/login.go:312still callshttp.HandleFunc("/auth/callback", callbackHandler), which registers on the process-globalhttp.DefaultServeMuxand is never removed. It is the same root cause as #549. Runningoauth2logintwice in one process panics withpattern "/auth/callback" conflicts with pattern "/auth/callback". I reproduced it with the test from #549. Using a privatehttp.NewServeMux()per login and setting it assrv.Handlerwould fix both issues, so I suggest tracking the fix in #549.Outdated: the
log.Fatalfclaim. A bind failure no longer callslog.Fatalf. The goroutine now reportstemporary HTTP server failed: ...through the completion channel andoauth2loginreturns aKindGenericerror (exit 20). The server also setsReadHeaderTimeoutnow.Not verified:
login.go:276), and PKCE protects the code exchange.id_tokenform-value fallback (login.go:281).Leaving this open until the mux fix lands.
Disclosure: this comment was drafted with AI assistance, and the results were run locally.
yada commentedon Oct 17, 2026
Update: there are PRs for this. I built the PR heads and ran the two-logins-in-one-process test from #549 (the one that panics on master):
ServeMuxper SSO login, reopened and rebased): no morepattern "/auth/callback" conflictspanic. The second login just waits for the browser callback, so the test only ends on its timeout. Thecmdtests pass.cmdtests pass. I did not test the occupied-port behavior it also targets.Both PRs touch the same code, so only one of them should be merged.
Disclosure: this comment was drafted with AI assistance, and the results were run locally.