Skip to content

feat(agent-vault): accept SOCKS5 over TLS on the proxy - #471

Merged
saifsmailbox98 merged 19 commits into
mainfrom
socks5-av
Nov 3, 2026
Merged

saifsmailbox98 merged 19 commits into
mainfrom
socks5-av

Conversation

@saifsmailbox98

@saifsmailbox98 saifsmailbox98 commented Oct 29, 2026 •

Copy link
Copy Markdown
Member

Description 📣

Adds an opt-in SOCKS5 door to infisical agent-vault proxy, so sandbox providers that tunnel a sandbox's traffic from outside the sandbox (E2B's bring your own proxy) can use Agent Vault. The session token goes in the SOCKS5 password, set on E2B's side, so it never enters the sandbox and the agent sets no proxy env vars.

SOCKS5 door

  • --socks5-port turns it on (off by default), always over TLS, listens on all interfaces
  • --socks5-tls-name: the proxy mints the port's certificate from its own CA and renews it. Or --socks5-tls-cert/--socks5-tls-key for your own (read once at start, expired cert refused)
  • Login is RFC 1929, username ignored, password = session token. Invalid/revoked/expired sessions, a rejected proxy token or a failed resolve are refused at login (01 01). Passwords that aren't shaped like agv_... are refused without asking Infisical
  • CONNECT only, everything else gets 0x07. After the reply the connection takes the same path as CONNECT (handleConnect's peek + MITM block moved into serveIntercepted), so policy, injection, session logs and the mid-tunnel 403 are unchanged
  • 10s for the client's steps, paused while asking Infisical (its own 15s cap); 512 connection cap; per-connection recover; accept loop backs off on temporary errors like net/http
  • Plain SOCKS5 sent to the TLS port logs a rate-limited warning
  • Log lines from this door carry door=socks5, login refusals log the remote address and never the username

Private destinations

On the SOCKS5 door only, under the any-host policy, a host no bundle service names can't resolve to a private, loopback, link-local or similar address (cloud metadata, the proxy's own network). It's checked at dial time on the resolved IP (net.Dialer.Control), through a separate transport so it never reuses a connection the unchecked one opened. Same ranges as the backend's ipRange.ts, with zones stripped and IPv4-in-IPv6 unwrapped. --socks5-allow-private-destinations turns it off. The HTTP door is unchanged.

Other

  • --listen-host to bind the HTTP port to one interface (host only, the port stays --port)
  • every new flag has an INFISICAL_AGENT_VAULT_* env var, telemetry gets socks5, socks5OwnCert, socks5AllowPrivate, customListenHost
  • CI now runs the Agent Vault tests in packages/cmd (they never ran before)

Companion PR (CA download API, UI and the E2B guide): Infisical/infisical#9035

Type ✨

  • Bug fix
  • New feature
  • Improvement
  • Breaking change
  • Documentation

Tests 🛠️

# Here's some code block to paste some code snippets

@infisical-review-police

Copy link
Copy Markdown

💬 Discussion in Slack: #pr-review-cli-471-feat-agent-vault-accept-socks5-over-tls-on-the-proxy

Posted by Review Police — reviews, comments, new commits, and CI failures will stream into this channel.

@greptile-apps

greptile-apps Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 4/5 Tier: plus

[High impact] The PR adds an optional network-facing route into Agent Vault's existing web proxy flow.

The connection cleanup changes are in place, but the previous finding remains partly unresolved because failed small replies can still leave only a success log.

Summary

Adds an optional SOCKS5-over-TLS port to Agent Vault and shares the existing request checks and forwarding path.

  • Sandbox traffic can now enter Agent Vault through SOCKS5 over TLS.
  • Unbundled SOCKS5 requests stop before reaching private network addresses.
  • Operators can choose how the proxy listens and presents its SOCKS5 certificate.
  • SOCKS5 tunnels can finish replies after clients stop sending.

Reviews (12) · Last reviewed commit: "fix(agent-vault): tolerate a half-close ..." · Reviewed by Greptile

Comment thread packages/agentvault/proxy.go Resolved
Comment thread packages/agentvault/socks5.go Resolved

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Beyond the inline findings, I also checked whether the SOCKS5 private-destination guard in packages/agentvault/dial_guard.go could be bypassed. The upstream transport sets Proxy: nil, so the net.Dialer.Control hook sees the real resolved destination, not an intermediate proxy. Zone stripping and Unmap() stop IPv4-mapped and zoned IPv6 forms from slipping past the ranges.

Extended reasoning...

The change adds an internet-facing SOCKS5-over-TLS listener whose login is the session token, a dial-time block on private destinations, and new CLI flags, about 1.8k lines across packages/agentvault and packages/cmd. The dial guard is the main defense against SSRF on this listener, so I checked that no environment proxy sits between it and the real destination, and none does.

Findings marked 🟡 are optional suggestions and need no follow-up push.

Comment thread packages/agentvault/socks5.go Resolved
Comment thread packages/agentvault/socks5.go Resolved
Comment thread packages/agentvault/proxy.go Resolved
@linear

linear Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

AGE2-176

AGE2-279

Comment thread packages/cmd/agent_vault.go Resolved
Comment thread packages/agentvault/dial_guard.go Resolved
Comment thread packages/agentvault/listener_cert.go Resolved
Comment thread packages/cmd/agent_vault.go Resolved
…y, and drop the zone from a scoped IPv6 TLS name
@saifsmailbox98

Copy link
Copy Markdown
Member Author

@greptile review

@scott-ray-wilson scott-ray-wilson left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Tested this with Infisical/infisical#9035, on a local stack with the CLI built from this head and against a real E2B sandbox (bring your own proxy, through a public TCP tunnel to the SOCKS5 port).

The guide's flow works end to end: E2B verified the SOCKS5 TLS against the CA from the new endpoint, credentials were injected for curl, Python and Node, the sandbox environment held no token, private destinations were refused, and a revoke mid-run failed closed. Login and protocol edge cases (non-token passwords, unknown, revoked and expired tokens, BIND and UDP ASSOCIATE, bad hosts, port 0, plain SOCKS5 on the TLS port, silent clients) all behaved as described.

Inline: one High, two Mediums, and three optional Lows. I also replied on the existing pre-login slots thread with a repro.

Comment thread packages/agentvault/socks5.go Resolved
Comment thread packages/agentvault/dial_guard.go Resolved
Comment thread packages/agentvault/dial_guard.go Resolved
Comment thread packages/agentvault/socks5.go Resolved
Comment thread packages/cmd/agent_vault.go Resolved
Comment thread packages/agentvault/socks5.go Resolved
Comment thread packages/agentvault/socks5.go Resolved
…t no service names, whatever the traffic policy
…g when the host's own addresses can't be read
…the connection limit is reached, and refuse an empty --listen-host
…il only a gone session at login, and rate limit the refusal warnings
Comment thread packages/agentvault/cache.go Resolved
Comment thread packages/agentvault/proxy.go Resolved
Comment thread packages/agentvault/dial_guard.go Resolved
@veria-ai

veria-ai Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

PR overview

All previously flagged issues have been addressed. No open security concerns remain on this pull request.

Security review

No open security issues remain on this pull request.

Fixed/addressed: 2 · PR risk: 0/10

Comment thread packages/agentvault/dial_guard.go Resolved

@scott-ray-wilson scott-ray-wilson left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Re-tested at 274450f (with infisical#9035 at 455b11a): rebuilt the CLI, re-ran the live checks behind each comment from the last round, ran the backend changes on an isolated API, and re-ran the earlier test matrix. Every item from the last round is fixed or reasonably deferred, CI is green, and the new refusal handling holds: a token that was never verified never gets a working tunnel. Thanks for turning these around quickly.

Blocking (needs to be addressed before I approve)

  1. The half-close handling keeps abandoned requests running on both doors. Details and a suggested fix in my reply on the proxy.go:457 thread.
  2. Veria's NAT64 thread on dial_guard.go:104 is still open. Applying its suggestion or replying is enough.

Optional, fine to defer to a follow-up

  • Pre-login slots: the 3s login window helps a lot (a request behind 520 idle connections went from 9.5s to 2.5s), but about 170 new connections a second can still keep the port full. A per-IP or separate pre-login cap would close it.
  • Startup escape hatch (dial_guard.go:114): if the machine's addresses can't be read, the only way to start is --socks5-allow-private-destinations, which turns off the whole private-address check, while the message says it skips only that check. The fixed ranges don't need the address list, so they could stay on.
  • IPv6 rotation (socks5_budget.go:55): the per-IP throttle is keyed by full address, so an attacker rotating through an IPv6 /64 gets the whole 20/s budget, and new sessions get the general failure for the length of the flood. Grouping IPv6 by /64 would fix it.
  • Warm-up after a restart: every session is unknown again and re-resolves through the 20/s budget, so a busy proxy can return general failures until it catches up.
  • Refusal summary (socks5.go:403): it's keyed by message, and budget refusals share the "could not be resolved" message with an Infisical outage, so a flood can hide a real outage.
  • Own-address list (dial_guard.go:114): still read once at start, so an address added later (a floating IP, a new IPv6 temporary address) isn't refused.
  • Plain http:// on the HTTP door (proxy.go:571): absolute-form requests still return 502 on a half-close, since the fix covers tunnels only. This predates the PR.

Comment thread packages/agentvault/proxy.go Resolved
Comment thread packages/agentvault/dial_guard.go Resolved
… the upstream a minute to start replying, and normalise NAT64 before both destination checks

@scott-ray-wilson scott-ray-wilson left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

lgtm

@saifsmailbox98
saifsmailbox98 merged commit 49634ac into main Nov 3, 2026
34 checks passed
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