Skip to content

feat(cli): fixed callback port for browser login inside containers #5066

Closed

Description

@jhjaggars

User Story

As a user running the OpenShell CLI inside a container (for example through the hypershell installer's wrapper),
I want the CLI's browser-login callback listener to bind a fixed, published port,
so that I can complete browser-based gateway login from the host browser while the CLI runs in the container.

Problem Statement

Both CLI browser login flows — the OIDC Authorization Code + PKCE flow (oidc_browser_auth_flow) and the edge/mTLS browser flow (browser_auth_flow) — bind their local callback listener to a random ephemeral port on 127.0.0.1. A container wrapper cannot know a randomly chosen port before the CLI starts, so it cannot publish the port to the host. The host browser is then redirected to a callback URL that nothing forwards, and login hangs until timeout.

Impact / Why This Matters

Without this, browser login is effectively unusable when the CLI runs in a container. Today users must either run the CLI on the host, or use --network=host, which works on Linux but not with a podman machine VM on macOS, or fall back to non-browser flows. That blocks containerized CLI distributions (such as the hypershell installer) from supporting OIDC or edge gateways that require browser login, and forces the wrapper to choose platform-specific networking hacks instead of a portable port publish.

Proposed Design

Expose the callback listener controls through environment variables, used by both browser login flows:

  • OPENSHELL_CALLBACK_PORT: when set, the listener binds this fixed port instead of an ephemeral one, so the wrapper can publish -p 127.0.0.1:<port>:<port> before login starts. The value is validated as an integer from 1 to 65535.
  • OPENSHELL_CALLBACK_BIND_ADDRESS: when set, the listener binds this address instead of 127.0.0.1. Container runtimes with user-mode networking (pasta, slirp4netns) can only reach listeners bound to 0.0.0.0, so a wrapper sets this inside the container while publishing the port to host loopback only.

Observable behavior:

  • With both variables unset, the listener binds 127.0.0.1:0 exactly as today; no default changes.
  • The bind address never widens to 0.0.0.0 implicitly — only when explicitly set — since a wildcard bind on a host machine would expose the short-lived callback server to the local network.
  • The browser-facing redirect URL stays http://127.0.0.1:<port>/callback (and the edge flow's callback_port parameter uses the bound port), so identity-provider allowlists such as Keycloak's http://127.0.0.1:* keep matching without provider changes.
  • An invalid port or address value, or a fixed port already in use, fails the login command with a clear error instead of falling back silently.

Acceptance Criteria

  • With both environment variables unset, both browser login flows bind 127.0.0.1 on an ephemeral port, unchanged from current behavior.
  • With OPENSHELL_CALLBACK_PORT set, both flows bind exactly that port; the redirect URL / callback_port reflect it.
  • With OPENSHELL_CALLBACK_BIND_ADDRESS=0.0.0.0 set, the listener binds all interfaces while the browser-facing URL remains http://127.0.0.1:<port>/callback.
  • Invalid values (non-integer, 0, out-of-range port; non-IP address) and a busy fixed port produce clear errors naming the offending variable.
  • Environment variables are documented in the CLI auth reference.

Alternatives Considered

  • --network=host on the container: works on Linux, but not with a podman machine VM on macOS, so it is not portable.
  • Deriving the bind address from a heuristic (e.g. detecting a container environment and auto-binding 0.0.0.0): rejected because silently widening the bind address on a host machine would be a security regression; the wildcard bind must stay an explicit operator choice.
  • A single variable combining port and address: rejected as less composable and harder to document; separate variables keep the default-safe behavior of each control independent.

Agent Investigation

  • Both flows bind via TcpListener::bind("127.0.0.1:0") in crates/openshell-cli/src/oidc_auth.rs and crates/openshell-cli/src/auth.rs; the port used in the redirect URL is derived from the bound listener, so a shared bind helper covers both call sites.
  • Keycloak's redirect allowlist already covers http://127.0.0.1:*, so no identity-provider change is needed.
  • Implementation exists in feat(cli): support fixed callback port for browser login in containers #5065 for review.

Activity

  1. TaylorMutch commented on Oct 30, 2026

    @TaylorMutch
    Collaborator

    📋 triage-agent

    Independent assessment: the reported limitation is confirmed by code inspection, and I recommend maintainer acceptance of the feature in principle, subject to the usage clarification below.

    Both browser_auth_flow (crates/openshell-cli/src/auth.rs) and oidc_browser_auth_flow (crates/openshell-cli/src/oidc_auth.rs) bind 127.0.0.1:0. The OIDC redirect URI and the edge callback destination use IPv4 loopback and the listener's allocated port. A container's loopback listener is not reached by ordinary port publication, and the ephemeral port cannot be pinned in an advance mapping. Explicit fixed-port and bind-address controls address both constraints without changing defaults. Gateway middleware, interceptors, and credential providers cannot configure this CLI-owned listener.

    An existing alternative deserves mention: OPENSHELL_NO_BROWSER=1 selects OIDC device authorization when the IdP supports/enables it (see #2793). That does not cover the edge browser flow or IdPs without device authorization, so it does not eliminate this use case. I found no equivalent existing issue in the related-issue search; #5065 already implements this request.

    Before completing evidence triage, please confirm that you personally run OpenShell in the described container workflow, and give the OpenShell version, container runtime/host OS, and affected login mode. The current User Story describes a specific workflow but does not explicitly establish first-hand use. A sanitized invocation and observed failure would make this concrete. (Human note: seems irrelevant; @TaylorMutch )

    Implementation/validation considerations for #5065:

    • Keep wildcard binding explicit, publish the container port to host loopback, and retain existing PKCE/state and edge origin/confirmation checks.
    • Resolve the address-family contract: accepting ::1 while always advertising 127.0.0.1 does not work directly without an explicit IPv4-to-IPv6 forwarding arrangement. Restrict supported addresses or document/test the required mapping.
    • Document equal host/container port numbers for this design, fixed-port contention between concurrent logins, and a complete port-publication example. Keeping the redirect hostname unchanged preserves existing compatible IdP allowlists; it does not guarantee arbitrary IdPs already allow the selected fixed port.
    • Demonstrate host-browser-to-container login on the target runtime, covering both flows before claiming both work. I inspected the code and linked diff; I did not run a container/IdP end-to-end reproduction or independently rerun the PR's reported tests.

    Setting state:needs-info for the narrow first-hand usage evidence above. Technical feasibility is established; the acceptance/roadmap decision remains with maintainers.

  2. added
    state:needs-infoAssessment needs specific evidence or reproduction details
    state:acceptedA maintainer decided OpenShell should pursue this issue
    and removed
    state:triage-neededOpened without agent diagnostics and needs triage
    state:needs-infoAssessment needs specific evidence or reproduction details
    on Oct 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    state:acceptedA maintainer decided OpenShell should pursue this issue

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions