Skip to content

Commit f707dba

Browse files
fix(oauth): reap browser launcher and keep native callback on loopback
Address code review: - openBrowser: reap the launcher process asynchronously so it does not linger as a zombie for the lifetime of the server. - listenCallback: take an explicit bindAll flag and bind to all interfaces only inside a container (where the published port arrives via eth0). A native run, even with a fixed callback port, now stays on 127.0.0.1 instead of 0.0.0.0. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 92db283 commit f707dba

4 files changed

Lines changed: 31 additions & 11 deletions

File tree

internal/oauth/callback.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,15 @@ type callbackServer struct {
3636

3737
// listenCallback binds the local callback listener.
3838
//
39-
// A random port (port == 0) binds to 127.0.0.1 only: the redirect target is
40-
// loopback and never reachable off-host. A fixed port binds to all interfaces
41-
// because Docker's published-port DNAT delivers traffic to the container's eth0
42-
// rather than to loopback; exposure is still constrained by the host-side
43-
// publish (e.g. -p 127.0.0.1:8085:8085).
44-
func listenCallback(port int) (net.Listener, error) {
39+
// It binds to loopback (127.0.0.1) by default so the callback server is never
40+
// exposed on other interfaces. bindAll is set only inside a container, where
41+
// Docker's published-port DNAT delivers traffic to the container's eth0 rather
42+
// than to loopback; host-side exposure is still constrained by the publish
43+
// (e.g. -p 127.0.0.1:8085:8085). A native run — even with a fixed port — stays
44+
// on loopback.
45+
func listenCallback(port int, bindAll bool) (net.Listener, error) {
4546
host := "127.0.0.1"
46-
if port > 0 {
47+
if bindAll {
4748
host = "0.0.0.0"
4849
}
4950
addr := fmt.Sprintf("%s:%d", host, port)

internal/oauth/callback_test.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,22 @@ func TestCallbackHandlerEscapesError(t *testing.T) {
7171
}
7272

7373
func TestListenCallbackRandomPortIsLoopback(t *testing.T) {
74-
listener, err := listenCallback(0)
74+
listener, err := listenCallback(0, false)
7575
require.NoError(t, err)
7676
defer listener.Close()
7777

7878
addr, ok := listener.Addr().(*net.TCPAddr)
7979
require.True(t, ok)
80-
assert.True(t, addr.IP.IsLoopback(), "random port must bind loopback only, got %s", addr.IP)
80+
assert.True(t, addr.IP.IsLoopback(), "default bind must be loopback only, got %s", addr.IP)
8181
assert.NotZero(t, addr.Port)
8282
}
83+
84+
func TestListenCallbackBindAllForContainer(t *testing.T) {
85+
listener, err := listenCallback(0, true)
86+
require.NoError(t, err)
87+
defer listener.Close()
88+
89+
addr, ok := listener.Addr().(*net.TCPAddr)
90+
require.True(t, ok)
91+
assert.True(t, addr.IP.IsUnspecified(), "bindAll must bind all interfaces, got %s", addr.IP)
92+
}

internal/oauth/env.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,14 @@ func openBrowser(url string) error {
3131

3232
cmd.Stdout = io.Discard
3333
cmd.Stderr = io.Discard
34-
return cmd.Start()
34+
if err := cmd.Start(); err != nil {
35+
return err
36+
}
37+
// The launcher (xdg-open/open/rundll32) exits as soon as it hands off to the
38+
// browser. Reap it asynchronously so it does not linger as a zombie for the
39+
// lifetime of this long-running server.
40+
go func() { _ = cmd.Wait() }()
41+
return nil
3542
}
3643

3744
// isRunningInDocker reports whether the process is running inside a Docker (or

internal/oauth/flow.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,9 @@ func (m *Manager) beginPKCE(prompter Prompter) (*flowPlan, error) {
5454
}
5555
verifier := oauth2.GenerateVerifier()
5656

57-
listener, err := listenCallback(m.config.CallbackPort)
57+
// Bind to all interfaces only inside a container, where the published port
58+
// is delivered via eth0 rather than loopback. Native runs stay on loopback.
59+
listener, err := listenCallback(m.config.CallbackPort, m.inDocker())
5860
if err != nil {
5961
return nil, err
6062
}

0 commit comments

Comments
 (0)