Skip to content

feat(orch): add per-sandbox SOCKS5 egress proxy support#2361

Closed
ValentaTomas wants to merge 8 commits intomainfrom
feat/socks5-egress-proxy
Closed

feat(orch): add per-sandbox SOCKS5 egress proxy support#2361
ValentaTomas wants to merge 8 commits intomainfrom
feat/socks5-egress-proxy

Conversation

@ValentaTomas
Copy link
Copy Markdown
Member

Allows routing all sandbox TCP egress traffic through an external SOCKS5 proxy. Config is passed via the sandbox create API request (network.egressProxy) and stored in the proto + DB types for pause/resume persistence.

The tcpfirewall intercepts allowed connections and dials through the SOCKS5 proxy instead of connecting directly when configured.

Allows routing all sandbox TCP egress traffic through an external
SOCKS5 proxy. Config is passed via the sandbox create API request
(network.egressProxy) and stored in the proto + DB types for
pause/resume persistence.

The tcpfirewall intercepts allowed connections and dials through
the SOCKS5 proxy instead of connecting directly when configured.
@cursor
Copy link
Copy Markdown

cursor bot commented Apr 10, 2026

PR Summary

Medium Risk
Adds new configurable egress path that can route all sandbox TCP traffic through a SOCKS5 proxy, which can affect network isolation guarantees and introduces new failure modes (proxy auth/dial errors block connections). Also expands persisted/configured network fields across API, DB, and gRPC boundaries, so mismatches or partial rollouts could break sandbox networking or pause/resume behavior.

Overview
Adds an optional network.egressProxy config to route allowed sandbox TCP egress through a SOCKS5 proxy (with optional per-sandbox {{sandboxID}} username/password substitution), propagating the new fields through the API/OpenAPI + generated clients, DB types for persistence, and orchestrator gRPC/proto. The tcpfirewall now dials upstream connections via the proxy when configured (recording a new SOCKS5 dial error metric), while existing allow/deny egress rules continue to gate whether a connection is permitted. This also bumps several golang.org/x/* dependencies across Go modules.

Reviewed by Cursor Bugbot for commit 543c45a. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread packages/api/internal/orchestrator/update_network.go
Comment thread packages/orchestrator/pkg/tcpfirewall/handlers.go Outdated
github-actions bot and others added 2 commits April 10, 2026 23:41
Allows routing all sandbox TCP egress traffic through an external
SOCKS5 proxy. Config is passed via the sandbox create API request
(network.egressProxy) and stored in the proto + DB types for
pause/resume persistence.

The tcpfirewall intercepts allowed connections and dials through
the SOCKS5 proxy instead of connecting directly when configured.
zap.Error(err))
metrics.RecordError(ctx, ErrorTypeSOCKS5Dial, protocol)

return nil
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When newSOCKS5DialContext returns an error, this returns nil — but both domainHandler and cidrOnlyHandler treat a nil dialFn as "no proxy configured" and fall through to proxyDirect. The log says "blocking connection" but the connection is not blocked; it silently uses a direct connection instead.

In practice xproxy.SOCKS5 never errors today (it just builds a struct), but the code intent is clearly fail-closed. The fix: explicitly close the connection in the error branch, or have callers check whether a proxy address was configured and treat a nil dial function as an error in that case.

DeniedAddresses []string `json:"deniedAddresses,omitempty"`
EgressProxyAddress string `json:"egressProxyAddress,omitempty"`
EgressProxyUsername string `json:"egressProxyUsername,omitempty"`
EgressProxyPassword string `json:"egressProxyPassword,omitempty"`
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EgressProxyPassword is serialized as plaintext in the PostgreSQL JSON column alongside the rest of SandboxNetworkEgressConfig. Anyone with database read access (backups, replicas, admin queries) can recover these credentials. If column-level or application-level encryption is not already applied to this field, credentials will accumulate in plaintext.

if egress.EgressProxyUsername != "" {
result.EgressProxy.Username = &egress.EgressProxyUsername
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing password field in GET egress proxy response

Medium Severity

The dbNetworkConfigToAPI function populates EgressProxy.Username when non-empty but never populates EgressProxy.Password. The Password field is declared in the anonymous struct but is always nil in the response, even when a password was configured. This is inconsistent with buildSandboxEgressConfig which stores the password, and with the username handling in this same block.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 343fa05. Configure here.

github-actions bot and others added 2 commits April 10, 2026 23:54
- egressDialContext now returns (nil, true) on failure so callers
  block the connection instead of silently falling through to direct
- API validates egress proxy address is valid host:port format
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit ae930a2. Configure here.

User: strings.ReplaceAll(user, sandboxIDPlaceholder, sandboxID),
Password: strings.ReplaceAll(pass, sandboxIDPlaceholder, sandboxID),
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Password-only SOCKS5 auth is silently discarded

Low Severity

socks5AuthFromEgress returns nil when username is empty, silently discarding a provided password. Combined with buildSandboxEgressConfig which independently stores username and password, a user who supplies only a password (no username) will have it stored in the DB but never used for SOCKS5 authentication. No validation prevents this misconfiguration.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ae930a2. Configure here.

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.

2 participants