Problem
SOCKS5 raw-TCP egress events (introduced in #337 / PR #355) carry host:port + decision + mode=socks5 but no task_id / correlation_id. HTTP CONNECT audits carry those fields (recovered from Proxy-Authorization per #338) — SOCKS5 doesn't, because SOCKS5v5 no-auth has no channel for per-request credentials.
For the enterprise audit story that #337 sells ("which task reached the database?"), an unattributed raw-TCP event is a real observability gap. The signal is currently "something inside the agent reached db.internal:5432 at T" — usable, but weaker than the HTTP path.
Design sketch
SOCKS5 supports authentication as part of the protocol (RFC 1929 username/password sub-negotiation). The pattern would mirror #338's HTTP approach:
- Change the injected env from
socks5h://127.0.0.1:PORT to socks5h://<b64u(task_id)>:<b64u(correlation_id)>@127.0.0.1:PORT. Go's x/net/proxy.SOCKS5 accepts a Auth{User, Password} object; a proxy URL with userinfo drives that.
- Handshake path in
socks5.go — currently offers only methodNoAuth (0x00). Extend to offer 0x02 (username/password) when the client requests it, fall back to no-auth otherwise. On 0x02, read the sub-negotiation payload per RFC 1929, base64url-decode both halves, stash on the connection.
- Thread through
ValidateAndDial — currently p.ValidateAndDial(context.Background(), host, port). Change to validateAndDialWithIdentity(ctx, host, port, auditDomain, egressIdentity{...}) and pass the decoded task/correlation.
- Backward compat — no-auth stays the default when the client doesn't ask for auth (curl, one-off tools). Only Forge's own subprocess env-injection triggers the auth path.
Client support caveats
- Go clients (
x/net/proxy.SOCKS5) — native support, just needs the URL userinfo.
curl --socks5-hostname — passes user:pass when the URL carries it.
psql / redis-cli etc. via proxychains-ng — proxychains has its own [ProxyList] config for user/pass; the LD_PRELOAD path would need updating. Docs.
- Language SDKs (PySocks, socks-proxy-agent) — support user/pass; docs example needs the auth form.
Scope / non-goals
- Not doing GSSAPI (SOCKS5 method 0x01) — overkill for internal task attribution.
- Not doing UDP (
UDP ASSOCIATE) — separate issue when a UDP use case appears.
- Rejecting a malformed sub-negotiation should fail closed (deny the CONNECT).
Acceptance
Related
Problem
SOCKS5 raw-TCP egress events (introduced in #337 / PR #355) carry
host:port+ decision +mode=socks5but notask_id/correlation_id. HTTP CONNECT audits carry those fields (recovered fromProxy-Authorizationper #338) — SOCKS5 doesn't, because SOCKS5v5 no-auth has no channel for per-request credentials.For the enterprise audit story that #337 sells ("which task reached the database?"), an unattributed raw-TCP event is a real observability gap. The signal is currently "something inside the agent reached db.internal:5432 at T" — usable, but weaker than the HTTP path.
Design sketch
SOCKS5 supports authentication as part of the protocol (RFC 1929 username/password sub-negotiation). The pattern would mirror #338's HTTP approach:
socks5h://127.0.0.1:PORTtosocks5h://<b64u(task_id)>:<b64u(correlation_id)>@127.0.0.1:PORT. Go'sx/net/proxy.SOCKS5accepts aAuth{User, Password}object; a proxy URL with userinfo drives that.socks5.go— currently offers onlymethodNoAuth(0x00). Extend to offer0x02(username/password) when the client requests it, fall back to no-auth otherwise. On0x02, read the sub-negotiation payload per RFC 1929, base64url-decode both halves, stash on the connection.ValidateAndDial— currentlyp.ValidateAndDial(context.Background(), host, port). Change tovalidateAndDialWithIdentity(ctx, host, port, auditDomain, egressIdentity{...})and pass the decoded task/correlation.Client support caveats
x/net/proxy.SOCKS5) — native support, just needs the URL userinfo.curl --socks5-hostname— passesuser:passwhen the URL carries it.psql/redis-clietc. viaproxychains-ng— proxychains has its own[ProxyList]config for user/pass; the LD_PRELOAD path would need updating. Docs.Scope / non-goals
UDP ASSOCIATE) — separate issue when a UDP use case appears.Acceptance
SkillCommandExecutorinjects task/correlation intoALL_PROXYURL userinfo, matching the HTTP-side Egress: subprocess proxy audit events missing task_id / correlation_id #338 pattern.0x00(no-auth) and0x02(RFC 1929), base64url-decodes both halves.handleSOCKS5threads the recovered identity intovalidateAndDialWithIdentitysoOnAttemptevents carryTaskID+CorrelationIDfor SOCKS5 dials.docs/security/egress-control.md(raw-TCP task-attribution section + client support matrix updated).Related
forge-core/security/socks5.go:87)