Skip to content

Auth2 SSO Callback Server Is a Global Singleton, Race Condition & Port Hijack #312

Open

Description

@rootp1

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:

  1. 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 the code_verifier is in the memory of the CLI process), the id_token (passed as r.FormValue("id_token") in the fallback implicit flow) might be extracted directly by the attacking server.
  2. Global Mux Pollution & Race Conditions: The URL handler /auth/callback persists permanently inside the process's default HTTP mux. If login is called twice (e.g., in a CI script, or if the first login fails midway), the subsequent calls overwrite the first handler. http.HandleFunc is 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

  1. A locally isolated http.ServeMux should be used per server instance instead of http.DefaultServeMux. For example:
    mux := http.NewServeMux()
    mux.HandleFunc("/auth/callback", callbackHandler)
    srv := &http.Server{
        Addr: "localhost:" + strconv.Itoa(port),
        Handler: mux,
    }
  2. 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?

  1. Open a separate terminal and mock an attacker process by listening on port 58085:
    nc -l -p 58085
  2. Run the microcks-cli login --sso command.
  3. Observe that the OAuth authentication flow continues locally.
  4. When the browser redirects to http://localhost:58085/auth/callback..., the attacker nc instance intercepts the HTTP request, collecting the query parameters (which may contain code or id_token depending on the SSO provider mapping configuration).

Alternatively, inspecting the source code validates the reliance on http.DefaultServeMux:

  1. Find srv := &http.Server{Addr: "localhost:" + strconv.Itoa(port)} inside cmd/login.go.
  2. Notice that http.HandleFunc is being used, making /auth/callback globally registered on http.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

  1. changed the title [-]nsj[/-] [+]Auth2 SSO Callback Server Is a Global Singleton, Race Condition & Port Hijack[/+] on May 6, 2026
  2. yada commented on Oct 15, 2026

    @yada
    Member

    Checked on current master (f9550f5).

    Still valid: the global router. cmd/login.go:312 still calls http.HandleFunc("/auth/callback", callbackHandler), which registers on the process-global http.DefaultServeMux and is never removed. It is the same root cause as #549. Running oauth2login twice in one process panics with pattern "/auth/callback" conflicts with pattern "/auth/callback". I reproduced it with the test from #549. Using a private http.NewServeMux() per login and setting it as srv.Handler would fix both issues, so I suggest tracking the fix in #549.

    Outdated: the log.Fatalf claim. A bind failure no longer calls log.Fatalf. The goroutine now reports temporary HTTP server failed: ... through the completion channel and oauth2login returns a KindGeneric error (exit 20). The server also sets ReadHeaderTimeout now.

    Not verified:

    • The port-hijack scenario (another process binding 58085 first). The callback does check the state nonce (login.go:276), and PKCE protects the code exchange.
    • The id_token form-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.

  3. yada commented on Oct 17, 2026

    @yada
    Member

    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):

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions