feat(orch): add per-sandbox SOCKS5 egress proxy support#2361
feat(orch): add per-sandbox SOCKS5 egress proxy support#2361ValentaTomas wants to merge 8 commits intomainfrom
Conversation
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.
PR SummaryMedium Risk Overview Reviewed by Cursor Bugbot for commit 543c45a. Bugbot is set up for automated code reviews on this repo. Configure here. |
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 |
There was a problem hiding this comment.
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"` |
There was a problem hiding this comment.
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 | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
Reviewed by Cursor Bugbot for commit 343fa05. Configure here.
- 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
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
❌ 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), | ||
| } | ||
| } |
There was a problem hiding this comment.
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)
Reviewed by Cursor Bugbot for commit ae930a2. Configure here.


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.