-
Notifications
You must be signed in to change notification settings - Fork 52
feat: Add SOCKS5 Proxy Support with Bootstrap Only Mode #1752
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
feat: Add SOCKS5 Proxy Support with Bootstrap Only Mode #1752
Conversation
Add "Bootstrap Only" toggle in proxy settings to use proxy only for initial connection phase (handshake). When enabled, only SEED_NODE requests go through proxy, all other traffic is direct. Changes: - Add proxyBootstrapOnly setting key - Add UI toggle in ProxySettingsPage - Implement shouldUseProxyForDestination() logic - Add localization for ru and en - Update getProxyAgent() to accept destination parameter 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Resolved conflicts: - _locales/en/messages.json and _locales/ru/messages.json were deleted in upstream (migrated to new localization system) - Added SOCKS5 proxy translations to new localization system (ts/localization/generated/english.ts and translations.ts) - Updated package.json with SOCKS5 dependencies (smart-buffer, socks, socks-proxy-agent) - Updated yarn.lock from upstream New localization system includes all proxy-related strings: - proxyEnabled, proxyHost, proxyPort - proxyAuthUsername, proxyAuthPassword - proxyBootstrapOnly, proxyBootstrapOnlyDescription - proxySettings, proxySaved, proxyValidationError All SOCKS5 proxy functionality maintained with Bootstrap Only mode. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This file was auto-generated by the localization code generator. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Added sessionProxy and all related proxy tokens to generated localization files: - sessionProxy, proxyEnabled, proxyDescription - proxyBootstrapOnly, proxyBootstrapOnlyDescription - proxyHost, proxyPort, proxyAuthUsername, proxyAuthPassword - proxySaved, proxyValidationError tokens This fixes the missing "Proxy" menu item in Settings UI. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Added all proxy-related translations to Russian locale: - sessionProxy, proxyEnabled, proxyDescription - proxyBootstrapOnly, proxyBootstrapOnlyDescription - proxyHost, proxyPort, proxyAuthUsername, proxyAuthPassword - proxySaved, proxyValidationError and all related tokens This completes the proxy UI localization. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
9300e8d to
fbb63e2
Compare
SOCKS_PROXY_PATCH.md
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this file can be removed
package.json
Outdated
| "redux-promise-middleware": "6.2.0", | ||
| "reselect": "5.1.1", | ||
| "rimraf": "6.1.2", | ||
| "smart-buffer": "^4.2.0", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
smart-buffer is a dependency of socks and socks-proxy-agent.
We don't seem to import it directly, so I think it can be removed from our explicit list of dependencies.
| password: string; | ||
| }; | ||
|
|
||
| async function loadProxySettings(): Promise<ProxySettings> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this async is not needed
| const backAction = useUserSettingsBackAction(modalState); | ||
| const closeAction = useUserSettingsCloseAction(modalState); | ||
| const title = useUserSettingsTitle(modalState); | ||
| const forceUpdate = useUpdate(); | ||
|
|
||
| const [settings, setSettings] = useState<ProxySettings>({ | ||
| enabled: false, | ||
| bootstrapOnly: false, | ||
| host: '', | ||
| port: '1080', | ||
| username: '', | ||
| password: '', | ||
| }); | ||
|
|
||
| const [isLoading, setIsLoading] = useState(true); | ||
|
|
||
| useEffect(() => { | ||
| void (async () => { | ||
| const loadedSettings = await loadProxySettings(); | ||
| setSettings(loadedSettings); | ||
| setIsLoading(false); | ||
| })(); | ||
| }, []); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Once loadProxySettings is not async this can become this, I think:
| const backAction = useUserSettingsBackAction(modalState); | |
| const closeAction = useUserSettingsCloseAction(modalState); | |
| const title = useUserSettingsTitle(modalState); | |
| const forceUpdate = useUpdate(); | |
| const [settings, setSettings] = useState<ProxySettings>({ | |
| enabled: false, | |
| bootstrapOnly: false, | |
| host: '', | |
| port: '1080', | |
| username: '', | |
| password: '', | |
| }); | |
| const [isLoading, setIsLoading] = useState(true); | |
| useEffect(() => { | |
| void (async () => { | |
| const loadedSettings = await loadProxySettings(); | |
| setSettings(loadedSettings); | |
| setIsLoading(false); | |
| })(); | |
| }, []); | |
| const backAction = useUserSettingsBackAction(modalState); | |
| const closeAction = useUserSettingsCloseAction(modalState); | |
| const title = useUserSettingsTitle(modalState); | |
| const forceUpdate = useUpdate(); | |
| const [settings, setSettings] = useState<ProxySettings>(loadProxySettings()); |
ts/data/settings-key.ts
Outdated
|
|
||
| // Proxy settings | ||
| const proxyEnabled = 'proxy-enabled'; | ||
| const proxyType = 'proxy-type'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think proxyType is used anywhere, to remove
ts/session/onions/onionPath.ts
Outdated
|
|
||
| // CRITICAL: Use longer timeout when proxy is enabled | ||
| // SOCKS handshake + proxy routing requires more time than direct connection | ||
| const requestTimeout = isProxyEnabled() ? 30000 : 10000; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same comment
| // Enhanced error logging for debugging proxy issues | ||
| const errorDetails = { | ||
| message: (e as Error).message, | ||
| name: (e as Error).name, | ||
| code: (e as any).code, | ||
| errno: (e as any).errno, | ||
| syscall: (e as any).syscall, | ||
| type: (e as any).type, | ||
| stack: (e as Error).stack?.split('\n').slice(0, 3).join('\n'), | ||
| }; | ||
| window?.log?.error('insecureNodeFetch error', errorDetails); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is going to be very verbose.
Maybe split it with two debug levels:
one Window?.log?.error with just the insecureNodeFetch error: ${e.message} and one with window?.log?.debug with what you have currently
ts/updater/updater.ts
Outdated
| '[updater] SOCKS proxy is enabled, skipping auto-update check to prevent traffic leaks. Please update manually.' | ||
| ); | ||
| return false; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be great to have that logic work with proxy.
| "socks": "^2.8.3", | ||
| "socks-proxy-agent": "^8.0.4", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You've added some packages but there is no corresponding changes in the yarn.lock. Why?
| // Extract TLS options from the original agent (if present) to preserve security settings | ||
| // This ensures certificate pinning and other TLS configurations work through the proxy | ||
| const tlsOptions = getTlsOptionsFromAgent(params.fetchOptions?.agent); | ||
| const proxyAgent = getProxyAgent(tlsOptions, params.destination); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not too sure about this way of grabbing some options from the original agent and copying them.
I'd much prefer have a way to build an agent ready to be used from the top, and that agent have the proxy options already provided.
Can you do that? Otherwise I can have a look once you've fixed the other issues.
…oves unnecessary async toggles, and simplifies the initial useEffect per Bilb’s suggestion; proxyType removed from settings-key.ts. Proxy plumbing: insecureNodeFetch now builds a final agent from caller-provided TLS options (call sites pass explicit TLS opts for seed-node and snode agents) and keeps detailed errors at debug with a short error message. Seed/onion/session RPC calls supply TLS options explicitly to avoid introspection. Proxy metadata file SOCKS_PROXY_PATCH.md removed. Proxy + updater: main process applyProxySettings sets proxy env vars (with auth) and default session proxy, clears them when disabled/bootstrap-only, and keeps credentials out of logs. Auto-updater uses session.defaultSession and no longer short-circuits when proxy is on, so updates can flow via the proxy.
…po, and regenerated yarn.lock based on the current package.json. Ran yarn lint (Prettier adjusted the formatting in InsecureNodeFetch.ts; I reverted the formatting for locales.ts).
|
Further testing is required, DPI with bootstrap only enabled behaves inconsistently. WARN 2026-01-12T19:37:30.975Z requestSnodesForPubkeyWithTargetNode attempt #3 failed. 1 retries left...
|


Summary
This change adds SOCKS5 proxy support to Session Desktop, including a Bootstrap-only mode (proxy only for seed-node bootstrap traffic) and auto-updater proxy support for environments where direct Internet access is blocked.
The implementation supports both per-request SOCKS routing for
node-fetchtraffic and (optionally) global Electron proxying for the full application, with immediate application from the Settings UI.User-Facing Features
1) SOCKS5 proxy (optional authentication)
socks5h://…for DNS-through-proxy semantics when using the per-request agent.2) Bootstrap-only mode
When Bootstrap-only is enabled:
FetchDestination.SEED_NODE).3) Proxy settings UI + immediate apply
apply-proxy-settings).4) Auto-updater via proxy
session.fromPartition('persist:auto-updater').session.defaultSession.electron-updaterwherenetSessionis read-only by setting the backing_netSessionfield.Technical Notes (Implementation Details)
Request-level proxying for Session network fetches
ts/session/utils/InsecureNodeFetch.tsSocksProxyAgentWithTls(extendssocks-proxy-agent) to preserve TLS options for secure endpoints.FetchDestination.SEED_NODE.Seed-node bootstrap integration
ts/session/apis/seed_node_api/SeedNodeAPI.tsFetchDestination.SEED_NODE.Global Electron proxy integration (full proxy mode)
ts/mains/main_node.tssession.defaultSession.setProxy({ proxyRules: 'socks5://host:port' })when proxy is enabled and not bootstrap-only.HTTP_PROXY/HTTPS_PROXY/NO_PROXYenv vars for components that rely on standard proxy env vars.app.on('login', …)whenauthInfo.isProxy.Auto-updater integration
ts/updater/updater.tspersist:auto-updater)._netSessionto avoid runtime errors with read-onlynetSession.Security / Behavior Considerations
'<local>'to avoid proxying local traffic.Test Builds
Fork release (recommended download link)
https://github.com/scrense-hash/session-desktop/releases/tag/v1.17.6-socks5-proxy
Testing
Tested scenarios:
localhost:9050)SEED_NODEvia proxy; other destinations direct)SocksProxyAgentWithTls)netSessionTypeError; uses dedicated session when bootstrap-only)