Authenticate naive local SOCKS loopback (#1166 part 2)#45
Conversation
…1166 part 2) Generate per-port credentials for the naive external-plugin SOCKS listener and dial it from the sing-box socks outbound with the same creds. Android does not isolate 127.0.0.1 per app, so an unauthenticated plugin SOCKS listener could be reached by any local app to leak the egress IP. Verified on-device: the naive SOCKS port now rejects unauthenticated connections (curl: 'No authentication method was acceptable') and accepts with creds. Scoped to naive only; other external plugins (mieru/trojan-go/hysteria v1) need separate per-plugin auth verification before enabling.
📝 WalkthroughWalkthroughThree files are modified to add optional SOCKS authentication for NaiveBean local proxy listeners. ChangesNaiveBean Authenticated Local SOCKS
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
Comment |
…wed params) - ConfigBuilder: gate naive loopback creds on !forExport so the exported sing-box config stays credential-free and matches the credential-free exported naive config (ProxyEntity.buildNaiveConfig), avoiding a broken standalone export. - NaiveFmt: rename buildNaiveConfig params username/password -> listenUsername/ listenPassword to stop shadowing NaiveBean.username/.password receiver properties.
Summary
Follow-up to PR #44. Authenticates the naive external-plugin local SOCKS loopback so
other apps on the device cannot use it as an open relay to learn the egress IP.
Android does not isolate
127.0.0.1per app, so the naive sidecar's SOCKS listener (bound toloopback) was reachable by any local app without credentials — the same class of leak the
authenticated mixed inbound already closes, on a port that auth did not cover.
Changes
ConfigBuilder.kt—ConfigBuildResultgainslocalProxyCredentials: Map<Int, Pair<String,String>>.For a naive external outbound, generate per-port
neko:<random>credentials, set them on thesing-box
Outbound_SocksOptions, and record them.NaiveFmt.kt—buildNaiveConfig(port, username?, password?)emitslisten = socks://user:pass@127.0.0.1:port(URL-encoded) when creds are provided; unchangedotherwise (the config-preview path stays credential-free).
BoxInstance.kt— pass the per-port creds tobuildNaiveConfigat runtime.Scope
Naive only. Other external plugins (mieru, trojan-go, hysteria v1) use the same local socks
loopback but need separate per-plugin auth verification before enabling — deliberately not
touched here.
Testing
curl --socks5 127.0.0.1:<port>without creds →No authentication method was acceptable(rejected);curl --socks5 neko:<secret>@127.0.0.1:<port>→SOCKS5 request granted, traffic flows;listen: socks://neko:…@127.0.0.1:45165.Notes
--listenURI accepts a[user:pass@]userinfo for thesocksproto andenforces it (empirically confirmed above).
buildNaiveConfig(port)overload is retained for the config-preview/exportpath so no live per-session secret is shown.
Greptile Summary
This PR authenticates the naiveproxy external-plugin local SOCKS loopback by generating a per-session random credential pair (
neko:<uuid-hex>) inbuildConfig, threading it throughConfigBuildResult.localProxyCredentials, and passing it to both the sing-box SOCKS outbound options and the naive--listenURI. This closes the open-relay hole on127.0.0.1that other on-device apps could exploit to probe the egress IP via the naive sidecar.ConfigBuilder.kt: Generates per-port credentials forNaiveBeanexternal outbounds when!forExport, stores them inlocalProxyCredentials, and sets matchingusername/passwordon the sing-boxOutbound_SocksOptions. The export guard keeps the export path credential-free so exported profiles remain self-consistent.NaiveFmt.kt: Adds optionallistenUsername/listenPasswordparameters (resolving the previously flagged property-shadowing concern); when provided, emitssocks://user:pass@127.0.0.1:portas the listen URL.BoxInstance.kt: Minimal wiring — looks up per-port credentials fromconfig.localProxyCredentialsand forwards them tobuildNaiveConfig.Confidence Score: 5/5
Safe to merge — the credential wiring is consistent across all three files, the export path is correctly guarded, and both halves of every code path agree on whether credentials are present.
Credentials are generated once in buildConfig, stored in localProxyCredentials keyed by localPort, and consumed symmetrically in the sing-box Outbound_SocksOptions and buildNaiveConfig listen URL. The !forExport guard keeps both halves of the export path credential-free. No port-key mismatch, no race between config build and plugin startup, and the UUID-hex password is URL-safe so urlSafe() round-trips cleanly.
No files require special attention.
Important Files Changed
Sequence Diagram
%%{init: {'theme': 'neutral'}}%% sequenceDiagram participant BC as buildConfig() participant CBR as ConfigBuildResult participant BI as BoxInstance.init() participant NF as buildNaiveConfig() participant SB as sing-box (SOCKS client) participant NP as naiveproxy (SOCKS server) BC->>BC: mkPort() → localPort BC->>BC: UUID.randomUUID() → pass BC->>CBR: "localProxyCredentials[localPort] = (neko, pass)" BC->>CBR: "Outbound_SocksOptions username=neko password=pass" BI->>CBR: "config = buildConfig(profile)" BI->>CBR: "creds = localProxyCredentials[port]" BI->>NF: buildNaiveConfig(port, neko, pass) NF->>NP: "listen = socks://neko:pass@127.0.0.1:port" SB->>NP: "SOCKS5 connect username=neko password=pass" NP-->>SB: Auth accepted → traffic forwarded%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%% sequenceDiagram participant BC as buildConfig() participant CBR as ConfigBuildResult participant BI as BoxInstance.init() participant NF as buildNaiveConfig() participant SB as sing-box (SOCKS client) participant NP as naiveproxy (SOCKS server) BC->>BC: mkPort() → localPort BC->>BC: UUID.randomUUID() → pass BC->>CBR: "localProxyCredentials[localPort] = (neko, pass)" BC->>CBR: "Outbound_SocksOptions username=neko password=pass" BI->>CBR: "config = buildConfig(profile)" BI->>CBR: "creds = localProxyCredentials[port]" BI->>NF: buildNaiveConfig(port, neko, pass) NF->>NP: "listen = socks://neko:pass@127.0.0.1:port" SB->>NP: "SOCKS5 connect username=neko password=pass" NP-->>SB: Auth accepted → traffic forwardedReviews (2): Last reviewed commit: "review: address Greptile feedback (skip ..." | Re-trigger Greptile