Repository navigation
feat: add proxy command for sandbox port forwarding with review fixes - #15
Merged
Merged
Conversation
kofj
reviewed
Mar 19, 2026
- Replace per-request TokenProvider with a single upfront Token string, removing the tokenAwareTransport/tokenErrorKey machinery (~40 lines). Token and sandbox instance share the same lifecycle so one-time acquisition is sufficient. - Remove redundant outer 5 s shutdown timeout in cmd/proxy.go; p.Stop() already blocks with its own 5 s graceful-shutdown window. - Drop time.Sleep(100ms) in TestProxyStartStop; p.Stop() is synchronous. - Translate two Chinese comments to English in internal/proxy/proxy.go. - Make REPL completer position-aware: show <sandbox-id> hint at arg 1, port-format hints at arg 2, and flags only after both args are present. - Add --verbose to REPL help text. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add docs/ags-proxy.md and docs/ags-proxy-zh.md with full reference for the new proxy command (synopsis, options, port syntax, examples, workflow, and see-also links) - Update docs/ags.md and docs/ags-zh.md to include proxy in the command table and see-also section - Update README.md and README-zh.md: add Port Forwarding feature bullet, add a Port Forwarding section with quick examples, and add proxy row to the command reference table Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…eader, document token lifecycle - Problem 1: Replace vague "configure as accessible" note with concrete steps across all docs (cmd/proxy.go, README.md, README-zh.md, docs/ags-proxy.md, docs/ags-proxy-zh.md): navigate to sandbox instance → Network → Open Port and add the port to the allowlist. - Problem 2: Remove redundant Authorization: Bearer header from both HTTP Director and WebSocket upstream dialer; the sandbox gateway authenticates via X-Access-Token only. - Problem 3: Add explicit comments explaining that the access token lifetime is bound to the sandbox instance lifecycle, so no token refresh or 401-retry logic is needed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
keithfx999
marked this pull request as draft
March 26, 2026 06:43
…uts, and network warning - WebSocket Stop() race: start a watchdog goroutine that calls SetReadDeadline(time.Now()) on both connections when p.ctx is cancelled, so bridgeWebSocket goroutines unblock immediately instead of hanging until the remote peer closes. - upstreamResp.Body leak: always defer-close the HTTP response body returned by websocket.Dialer.DialContext, which is non-nil when the upstream rejects the upgrade with a non-101 response. - Missing ResponseHeaderTimeout (30s) on the reverse-proxy Transport to prevent HTTP requests from hanging indefinitely when the remote sandbox is unresponsive. - Missing ReadHeaderTimeout (10s) on http.Server to mitigate Slowloris attacks when --address is set to a non-loopback interface. - Print a warning to stderr when --address is not 127.0.0.1/localhost, informing the user that the proxy (and the sandbox access token) will be reachable from the network. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add ::1 (IPv6 loopback) to the non-warning allowlist alongside 127.0.0.1 and localhost. - Validate --address format upfront with net.ParseIP; non-loopback hostnames now get a clear error instead of a confusing net.Listen failure. - Restrict error detail in the reverse-proxy ErrorHandler: full error text is only sent to the client in verbose mode to avoid leaking internal hostnames or network topology to network-accessible clients. - Add TestProxyStopWithActiveWebSocket: verifies that p.Stop() returns within 10 s even when a WebSocket bridge goroutine is actively blocked in ReadMessage, exercising the SetReadDeadline shutdown path. - Add TestProxyVerboseLogging: verifies that verbose mode writes the HTTP method and path to the configured logger. - Add parsePortSpec edge-case tests: empty string and multi-colon input (e.g. "8080:80:8080"). - Document two known limitations in ags-proxy.md / ags-proxy-zh.md: (1) WebSocket Ping/Pong control frames are not forwarded; (2) the access token is acquired once at startup — restart the proxy if the sandbox is destroyed and recreated. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…flag - WebSocket upstream dial failure now only exposes error details to the client when verbose mode is enabled, matching the behaviour of the HTTP reverse-proxy ErrorHandler and avoiding leaking internal host names or TLS details to network-accessible clients. - WebSocket connection-established and connection-closed log lines are now guarded by p.options.Verbose, consistent with the HTTP request logging path. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix IPv6 listen address bug: use net.JoinHostPort instead of fmt.Sprintf so IPv6 addresses are correctly bracketed (e.g. "[::1]:8080" instead of the invalid "::1:8080"). - Set explicit ReadBufferSize/WriteBufferSize (65536) on both the WebSocket Upgrader and Dialer so large frames are handled efficiently without fragmentation. - Add explanatory comment for why ReadTimeout is intentionally omitted from http.Server (would also cap long-lived WebSocket connections). - Move "Proxy stopped." log line inside the `if p.server != nil` guard so it is not printed when Stop() is called before Start(). - Remove internal implementation detail "SandPortal" from the public package doc comment; use "AGS gateway" instead. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
keithfx999
marked this pull request as ready for review
March 26, 2026 08:58
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
ags proxy, a new subcommand that forwards a remote sandbox port to localhost — similar tokubectl port-forward. Both HTTP and WebSocket protocols are fully supported, making it suitable for web dev servers (Vite, webpack-dev-server), API servers, and any HTTP-based service running inside a sandbox.How it works
https://<port>-<sandbox-id>.<region-domain>X-Access-Token) into every proxied requestChanges
cmd/proxy.gocmd/proxy_test.goparsePortSpec(18 cases incl. edge cases)internal/proxy/proxy.gointernal/proxy/proxy_test.gointernal/repl/repl.godocs/ags-proxy{,-zh}.mdREADME{,-zh}.md,docs/ags{,-zh}.mdSecurity
127.0.0.1by default; prints an explicit warning when--addressis set to a non-loopback address"Bad Gateway"to the client — internal host names and TLS details are never exposedKnown Limitations
ags proxyto pick up the new tokenTest Plan
go test ./internal/proxy/... ./cmd/...— all passgolangci-lint run ./...— 0 issuesgofmt -d .— no diffags proxy <sandbox-id> 8080against a live sandbox, verified HTTP and WebSocket traffic forwarded correctly