Skip to content

Forward SSH gateway channels to sandbox loopback - #57

Merged
czpython merged 1 commit into
mainfrom
codex/gateway-loopback
Sep 28, 2026
Merged

czpython merged 1 commit into
mainfrom
codex/gateway-loopback

Conversation

@czpython

@czpython czpython commented Sep 28, 2026 •

Copy link
Copy Markdown
Owner

The SSH gateway refused every direct-tcpip channel. Thus a client could not reach the Chat bridge or VNC on the sandbox loopback.

The gateway now accepts a channel to 127.0.0.1 or localhost and refuses all other destinations. It forwards the channel through an SSH tunnel into the sandbox. The provider opens this tunnel with open_gateway_tunnel. For docker-sbx, the tunnel goes through sbx ssh proxy to sandboxd. sandboxd authenticates the OS user on its local socket, so the gateway uses no key. All channels of one caller connection share one tunnel, and it closes when the caller disconnects.

The sandbox image does not change. The gateway runs no process in the sandbox for forwarding.

sandboxd has two limits:

  • sandboxd accepts a channel to a closed port and then closes it immediately. Thus the client gets an immediate end of data, not ChannelOpenError. A client that starts a missing service must treat an immediate end of data as "not running".
  • sandboxd does not forward to ::1. The channel stays open with no data. Thus the gateway refuses ::1.

Validation:

  • uv run pytest: 734 passed on SQLite. Ruff, format checks, and Pyright passed.
  • New gateway tests use a local SSH server in place of sandboxd. They cover a 1 MiB binary transfer with half-close, refused destinations, one shared tunnel that closes with the caller, and a tunnel that fails to open.
  • On sbx v0.45.1, the gateway and DockerSbxProvider.open_gateway_tunnel forwarded to a real sandbox. A 4 MiB transfer with half-close passed. localhost passed. ::1 and 10.0.0.1 were refused. The sbx ssh proxy process stopped after the caller disconnected.
  • A proxy connection woke a stopped sandbox in 0.2 seconds.

Not tested: the Chat and VNC workflows, the images/sbx image, and Postgres.

Fixes #56.

The change also applies the review checklist to gateway/server.py. GatewayConnection.host has no None default, cast replaces the assert isinstance checks, the single-use host-key and SFTP-extension helpers are inlined, and sftp_backend() is now get_sftp_backend().

@czpython
czpython force-pushed the codex/gateway-loopback branch from f547fec to 050184b Compare September 28, 2026 07:54
@czpython czpython changed the title Forward SSH gateway connections to sandbox loopback Forward SSH gateway channels to sandbox loopback Sep 28, 2026
@czpython
czpython force-pushed the codex/gateway-loopback branch from 050184b to 1cfa0bb Compare September 28, 2026 08:08
The gateway accepts a direct-tcpip channel to 127.0.0.1 or localhost
and refuses all other destinations. It forwards the channel through a
tunnel into the sandbox, which the provider opens with open_gateway_tunnel. For
docker-sbx, the tunnel goes through `sbx ssh proxy` to sandboxd.

All channels of one caller connection share one tunnel. It closes when
the caller disconnects.
@czpython
czpython force-pushed the codex/gateway-loopback branch from 1cfa0bb to 096a12e Compare September 28, 2026 08:14
@czpython
czpython merged commit 3d457fa into main Sep 28, 2026
6 checks passed
@czpython
czpython deleted the codex/gateway-loopback branch September 28, 2026 08:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Gateway: forward to loopback inside the sandbox

1 participant