Skip to content

bug: SSH forwarding drops HTTPS behind TLS termination #3674

Description

@danehans

User Story

As an OpenShell user connecting through a TLS-terminating gateway, I want SSH-backed connect and forwarding operations to use the registered HTTPS endpoint so that sandbox sessions work through the gateway.

Problem Statement

When the gateway reports an internal loopback or unspecified HTTP endpoint, the client replaces its host and port with the registered external gateway authority but retains the internal http scheme. It therefore sends plaintext HTTP/2 to the external TLS listener. The operation fails with a misleading GOAWAY FRAME_SIZE_ERROR.

This affects shared client logic used by the CLI and TUI and is not specific to agentgateway.

Impact / Why This Matters

Sandbox creation can succeed while connect, exec, and port-forwarding operations fail as soon as they establish the SSH relay. Users must avoid frontend TLS termination or use a topology whose backend scheme happens to match the frontend scheme. Neither workaround supports a general TLS-terminating ingress deployment.

Acceptance Criteria

  • When a loopback or unspecified server authority is replaced with the registered external authority, its external http or https scheme is used too.
  • A reachable non-loopback server-provided endpoint retains its reported scheme and authority.
  • CLI and TUI SSH connect, exec, and forwarding paths share the corrected behavior.
  • Unit coverage includes an internal http://0.0.0.0:8080 endpoint resolved through an external https://localhost:<port> endpoint.
  • Kubernetes E2E coverage exercises an SSH-backed operation through frontend TLS termination with a plaintext OpenShell backend.

Reproduction Steps

  1. Deploy OpenShell with backend TLS disabled.
  2. Expose its gRPC service through a TLS-terminating Gateway API implementation.
  3. Register the externally reachable https:// endpoint with the OpenShell CLI.
  4. Create a sandbox and run an SSH-backed connect or port-forward operation.
  5. Observe GOAWAY FRAME_SIZE_ERROR on the client and InvalidContentType on the TLS proxy.

Environment

  • OpenShell: 590ab2abd with the in-progress feat(helm): support agentgateway ingress #2469 agentgateway E2E changes
  • OS: macOS 26.6.2
  • Runtime: k3d/Kubernetes
  • Integration: reproduced with agentgateway v1.5.0 and v0.0.0-alpha.3528a428
  • Topologies: shared ListenerSet and dedicated Gateway HTTPS listeners; both kubectl port-forward and direct k3d load-balancer access

Logs

h2 protocol error: connection error detected: frame with invalid size
GoAway(b"", FRAME_SIZE_ERROR, Library)

failed to terminate TLS ... error="received corrupt message of type InvalidContentType"

Investigation

CreateSshSession reports the backend bind scheme, which is http when TLS terminates at the ingress. Client endpoint reconstruction replaces only the internal host and port with the external endpoint authority. For example:

internal: http://0.0.0.0:8080
external: https://localhost:18443
actual:   http://localhost:18443
expected: https://localhost:18443

Activity

  1. danehans commented on Sep 24, 2026

    @danehans
    ContributorAuthor

    /assign

  2. danehans commented on Sep 24, 2026

    @danehans
    ContributorAuthor

    🏗️ build-plan

    Implementation Plan

    Issue type: fix
    Complexity: Low
    Confidence: High — root cause reproduced and isolated

    Summary

    Make the shared SSH gateway resolver return a complete, scheme-aware endpoint. When OpenShell replaces an internal bind authority with the registered external authority, it will carry the external http or https scheme with it for both CLI and TUI paths.

    Scope

    • crates/openshell-core/src/forward.rs: resolve scheme, host, and port as one endpoint decision and expand its unit-test matrix.
    • crates/openshell-cli/src/ssh.rs: use the resolved scheme in the non-bearer SSH path; retain the existing bearer-auth behavior.
    • crates/openshell-tui/src/lib.rs: use the shared resolved scheme for connect, exec, and automatic forwarding.
    • tasks/test.toml: add the focused Rust port_forward regression to the shared and dedicated frontend-TLS tasks, since conformance alone passed during reproduction and did not exercise the failing SSH relay path.

    Implementation Steps

    1. Extend the shared resolver to return the effective scheme with its host and port. Adopt the external scheme whenever the registered endpoint supplies the reachable authority; otherwise preserve the server-reported endpoint and invalid-URL fallback.
    2. Update the CLI and all three TUI call sites without changing bearer-auth routing.
    3. Add unit tests for HTTP-to-HTTPS and HTTPS-to-HTTP replacement, retained non-loopback endpoints, loopback and unspecified addresses, default ports, invalid URLs, and IPv6.
    4. Configure both frontend-TLS agentgateway tasks to run the focused Rust port-forward test after conformance.
    5. Run focused unit/component tests, shared and dedicated frontend-TLS E2E, pre-commit, and normal CI checks.

    Test Plan

    • Unit tests: openshell-core resolver matrix, including http://0.0.0.0:8080 through https://localhost:<port>.
    • Integration tests: CLI and TUI package test suites verify all consumers compile and retain existing behavior.
    • E2E tests: shared ListenerSet TLS and dedicated Gateway TLS each run conformance plus the Rust port_forward test over a plaintext OpenShell backend.

    Risks & Open Questions

    • Preserve the current nuanced IPv4/IPv6 and loopback authority selection while coupling the selected authority to its scheme.
    • Restrict external scheme adoption to valid HTTP(S) endpoints and retain the current fallback for malformed external URLs.
    • Do not change Cloudflare/bearer routing, which already uses the complete external endpoint.

    Documentation Impact

    None expected. This restores intended transport behavior without changing configuration, public APIs, architecture boundaries, or LSM-sensitive behavior.


    Revision 1 — initial plan

  3. removed
    state:triage-neededOpened without agent diagnostics and needs triage
    on Sep 24, 2026
  4. added
    state:acceptedA maintainer decided OpenShell should pursue this issue
    on Sep 24, 2026
  5. added this to the OpenShell 0.1.1 milestone on Sep 24, 2026
  6. purp commented on Sep 24, 2026

    @purp
    Collaborator

    Hi, @danehans. Thanks for finding this and working to fix. We're not going to hold the 0.1.0 release for it, but we'll welcome it any time after that. Tagged it for the next weekly after for now.

  7. danehans commented on Oct 1, 2026

    @danehans
    ContributorAuthor

    @purp thanks for the feedback. The fix for this issue is included in a larger PR that adds support for agentgateway as a k8s ingress. Let me know if you need me to split the fix out of this PR but I'm hoping both can land.

  8. purp commented on Oct 1, 2026

    @purp
    Collaborator

    No worries, and no need to split it just for this.

  9. danehans commented on Oct 7, 2026

    @danehans
    ContributorAuthor

    @purp just wanted to follow-up on this issue now that 0.1.0 was cut. I see the PR that fixes this issue is also referenced by a few other Issues/PRs. Is the plan to include this PR in 0.1.5 since it includes a fix for #3674?

  10. added a commit that references this issue on Oct 7, 2026
    a2cc41b
  11. danehans commented on Oct 8, 2026

    @danehans
    ContributorAuthor

    @purp since #3714 was closed, I pulled the fix for this issue into a dedicated PR- #4344

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

Metadata

Metadata

Assignees

Labels

state:acceptedA maintainer decided OpenShell should pursue this issue

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions