Skip to content

bug(sdk/ts): forward().close() never revokes SSH session tokens of open connections #4516

Closed

Description

@CloneOfAlex

User Story

I use OpenShell and build automation against its SDKs. I found this while reviewing sdk/ on main; it is identified from the code paths cited below and has not yet been reproduced against a live gateway.

Problem Statement

On close() (or caller abort), teardown aborts each connection's AbortController. forwardConnection's finally then calls revokeSshSession({ token, allowMissing: true }, { signal }) with that same, already-aborted signal, so the revoke RPC is never sent. The Go file client revokes with a fresh background context.

Impact / Why This Matters

Every SSH session token minted for connections still open at close() stays valid until it expires instead of being revoked, contrary to the method's documented revoke-on-close behavior.

Acceptance Criteria

  • Closing a forward handle with open connections issues RevokeSshSession for each minted token (bounded by its own timeout, independent of the aborted connection signal).

Reproduction Steps

  1. const h = await client.sandbox.forward(name, { targetPort }).
  2. Open a local connection so a session token is minted.
  3. await h.close().
  4. Observe no RevokeSshSession RPC reaches the gateway for that token (the call rejects immediately on the aborted signal).

Suggested UX (if applicable)

No response

Environment

  • OpenShell: main @ 82977e2 (2026-10-13)
  • SDK: TypeScript (sdk/typescript/src/client.ts, forward/forwardConnection)
  • Toolchain: Go 1.25.4 linux/amd64, Node 22

Logs

Activity

  1. added theissue type on Oct 13, 2026
  2. rhuss commented on Oct 16, 2026

    @rhuss
    Contributor

    📋 triage-agent

    Thanks for the report.

    Confirmed by code inspection on main. teardown in sdk/typescript/src/client.ts aborts each connection's AbortController, and forwardConnection's finally block then calls revokeSshSession({ token, allowMissing: true }, { signal }) with that same aborted signal, so the request is rejected before it is sent. The surrounding catch swallows the error. Connections that end normally are revoked correctly, since their signal was never aborted. Only connections still open at close() or caller abort keep a live token until it expires.

    The Go SDK handles this correctly in both its SSH tunnel (revokeSessionForCleanup) and its file client, by revoking with a fresh background context and a short timeout. The TypeScript fix can do the same, for example with AbortSignal.timeout(...) instead of the connection signal. A unit test that closes a forward handle with an open connection and asserts that RevokeSshSession reaches the mock server would lock this in.

    The acceptance criterion is clear. Next step: maintainer acceptance decision.

  3. added
    state:acceptedA maintainer decided OpenShell should pursue this issue
    and removed
    state:triage-neededOpened without agent diagnostics and needs triage
    on Nov 10, 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

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions