From 7ebd706a7e964254ae0b28577695fceeea7fd591 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 1 May 2026 09:36:59 +0000 Subject: [PATCH 1/3] Initial plan From 36650a4c274a11ac1af77ec74364c82b6972a010 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 1 May 2026 09:46:00 +0000 Subject: [PATCH 2/3] Add SOCKS5 password protection for local proxy interface (RFC 1929) Agent-Logs-Url: https://github.com/shadowsocks/shadowsocks-android/sessions/d496275b-6490-48b5-8dd9-acbbd50734ae Co-authored-by: madeye <627917+madeye@users.noreply.github.com> --- .../github/shadowsocks/bg/ProxyInstance.kt | 21 +++++++++++++++++++ .../com/github/shadowsocks/bg/VpnService.kt | 7 +++++++ .../shadowsocks/preference/DataStore.kt | 3 +++ .../com/github/shadowsocks/utils/Constants.kt | 1 + core/src/main/res/values/strings.xml | 2 ++ .../GlobalSettingsPreferenceFragment.kt | 2 ++ mobile/src/main/res/xml/pref_global.xml | 6 ++++++ .../shadowsocks/tv/MainPreferenceFragment.kt | 3 +++ tv/src/main/res/xml/pref_main.xml | 5 +++++ 9 files changed, 50 insertions(+) diff --git a/core/src/main/java/com/github/shadowsocks/bg/ProxyInstance.kt b/core/src/main/java/com/github/shadowsocks/bg/ProxyInstance.kt index 4882a3fe58..a482f02326 100644 --- a/core/src/main/java/com/github/shadowsocks/bg/ProxyInstance.kt +++ b/core/src/main/java/com/github/shadowsocks/bg/ProxyInstance.kt @@ -64,6 +64,7 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro } private var configFile: File? = null + private var socks5AuthFile: File? = null var trafficMonitor: TrafficMonitor? = null val plugin by lazy { PluginManager.init(PluginConfiguration(profile.plugin ?: "")) } @@ -86,6 +87,7 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro } config.put("dns", "unix://local_dns_path") config.put("mode", mode) + val socksPassword = DataStore.socksPassword config.put("locals", JSONArray().apply { // local SOCKS5 proxy put(JSONObject().apply { @@ -94,6 +96,7 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro put("local_udp_address", DataStore.listenAddress) put("local_udp_port", DataStore.portProxy) put("mode", mode) + if (socksPassword.isNotEmpty()) put("socks5_auth_config_path", "socks5_auth") }) // local DNS proxy @@ -114,6 +117,22 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro }) configFile.writeText(config.toString()) + // write SOCKS5 auth config file if password protection is enabled + val socksAuthFile = File(configFile.parent, "socks5_auth") + if (socksPassword.isNotEmpty()) { + socks5AuthFile = socksAuthFile + socksAuthFile.writeText(JSONObject().apply { + put("password", JSONObject().apply { + put("users", JSONArray().apply { + put(JSONObject().apply { + put("user_name", "shadowsocks") + put("password", socksPassword) + }) + }) + }) + }.toString()) + } + // build the command line val cmd = arrayListOf( File((service as Context).applicationInfo.nativeLibraryDir, Executable.SS_LOCAL).absolutePath, @@ -143,5 +162,7 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro trafficMonitor = null configFile?.delete() // remove old config possibly in device storage configFile = null + socks5AuthFile?.delete() + socks5AuthFile = null } } diff --git a/core/src/main/java/com/github/shadowsocks/bg/VpnService.kt b/core/src/main/java/com/github/shadowsocks/bg/VpnService.kt index 5716d1fc09..17c58d7020 100644 --- a/core/src/main/java/com/github/shadowsocks/bg/VpnService.kt +++ b/core/src/main/java/com/github/shadowsocks/bg/VpnService.kt @@ -214,6 +214,13 @@ class VpnService : BaseVpnService(), BaseService.Interface { "--sock-path", "sock_path", "--dnsgw", "127.0.0.1:${DataStore.portLocalDns}", "--loglevel", "warning") + val socksPassword = DataStore.socksPassword + if (socksPassword.isNotEmpty()) { + cmd += "--username" + cmd += "shadowsocks" + cmd += "--password" + cmd += socksPassword + } if (profile.ipv6) { cmd += "--netif-ip6addr" cmd += PRIVATE_VLAN6_ROUTER diff --git a/core/src/main/java/com/github/shadowsocks/preference/DataStore.kt b/core/src/main/java/com/github/shadowsocks/preference/DataStore.kt index 5f3a772bf4..44dac24343 100644 --- a/core/src/main/java/com/github/shadowsocks/preference/DataStore.kt +++ b/core/src/main/java/com/github/shadowsocks/preference/DataStore.kt @@ -76,6 +76,9 @@ object DataStore : OnPreferenceDataStoreChangeListener { var portTransproxy: Int get() = getLocalPort(Key.portTransproxy, 8200) set(value) = publicStore.putString(Key.portTransproxy, value.toString()) + var socksPassword: String + get() = publicStore.getString(Key.socksPassword) ?: "" + set(value) = publicStore.putString(Key.socksPassword, value) /** * Initialize settings that have complicated default values. diff --git a/core/src/main/java/com/github/shadowsocks/utils/Constants.kt b/core/src/main/java/com/github/shadowsocks/utils/Constants.kt index 52b6de20ec..0644672ad3 100644 --- a/core/src/main/java/com/github/shadowsocks/utils/Constants.kt +++ b/core/src/main/java/com/github/shadowsocks/utils/Constants.kt @@ -40,6 +40,7 @@ object Key { const val portProxy = "portProxy" const val portLocalDns = "portLocalDns" const val portTransproxy = "portTransproxy" + const val socksPassword = "socksPassword" const val route = "route" diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index 04ed7d9d67..87071c002e 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -14,6 +14,8 @@ SOCKS5 proxy port Local DNS port Transproxy port + SOCKS5 proxy password + Set a password to protect the local SOCKS5 proxy from unauthorized use Remote DNS %1$s↑\t%2$s↓ diff --git a/mobile/src/main/java/com/github/shadowsocks/GlobalSettingsPreferenceFragment.kt b/mobile/src/main/java/com/github/shadowsocks/GlobalSettingsPreferenceFragment.kt index cc7ac91647..2b503a6696 100644 --- a/mobile/src/main/java/com/github/shadowsocks/GlobalSettingsPreferenceFragment.kt +++ b/mobile/src/main/java/com/github/shadowsocks/GlobalSettingsPreferenceFragment.kt @@ -59,6 +59,7 @@ class GlobalSettingsPreferenceFragment : PreferenceFragmentCompat() { portLocalDns.setOnBindEditTextListener(EditTextPreferenceModifiers.Port) val portTransproxy = findPreference(Key.portTransproxy)!! portTransproxy.setOnBindEditTextListener(EditTextPreferenceModifiers.Port) + val socksPassword = findPreference(Key.socksPassword)!! val onServiceModeChange = Preference.OnPreferenceChangeListener { _, newValue -> portTransproxy.isEnabled = newValue as String? == Key.modeTransproxy true @@ -68,6 +69,7 @@ class GlobalSettingsPreferenceFragment : PreferenceFragmentCompat() { serviceMode.isEnabled = stopped portProxy.isEnabled = stopped portLocalDns.isEnabled = stopped + socksPassword.isEnabled = stopped if (stopped) onServiceModeChange.onPreferenceChange(serviceMode, DataStore.serviceMode) else { portTransproxy.isEnabled = false } diff --git a/mobile/src/main/res/xml/pref_global.xml b/mobile/src/main/res/xml/pref_global.xml index 9a4808d8b3..29b3369332 100644 --- a/mobile/src/main/res/xml/pref_global.xml +++ b/mobile/src/main/res/xml/pref_global.xml @@ -34,4 +34,10 @@ app:key="portTransproxy" app:title="@string/port_transproxy" app:useSimpleSummaryProvider="true"/> + diff --git a/tv/src/main/java/com/github/shadowsocks/tv/MainPreferenceFragment.kt b/tv/src/main/java/com/github/shadowsocks/tv/MainPreferenceFragment.kt index 198245f728..46a26c548a 100644 --- a/tv/src/main/java/com/github/shadowsocks/tv/MainPreferenceFragment.kt +++ b/tv/src/main/java/com/github/shadowsocks/tv/MainPreferenceFragment.kt @@ -56,6 +56,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo private lateinit var portProxy: EditTextPreference private lateinit var portLocalDns: EditTextPreference private lateinit var portTransproxy: EditTextPreference + private lateinit var socksPassword: EditTextPreference private val onServiceModeChange = Preference.OnPreferenceChangeListener { _, newValue -> portTransproxy.isEnabled = newValue as String? == Key.modeTransproxy true @@ -101,6 +102,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo shareOverLan.isEnabled = stopped portProxy.isEnabled = stopped portLocalDns.isEnabled = stopped + socksPassword.isEnabled = stopped if (stopped) onServiceModeChange.onPreferenceChange(serviceMode, DataStore.serviceMode) else { portTransproxy.isEnabled = false } @@ -141,6 +143,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo portLocalDns.setOnBindEditTextListener(EditTextPreferenceModifiers.Port) portTransproxy = findPreference(Key.portTransproxy)!! portTransproxy.setOnBindEditTextListener(EditTextPreferenceModifiers.Port) + socksPassword = findPreference(Key.socksPassword)!! serviceMode.onPreferenceChangeListener = onServiceModeChange findPreference(Key.about)!!.summary = getString(R.string.about_title, BuildConfig.VERSION_NAME) diff --git a/tv/src/main/res/xml/pref_main.xml b/tv/src/main/res/xml/pref_main.xml index 1e60152712..67e8dc963d 100644 --- a/tv/src/main/res/xml/pref_main.xml +++ b/tv/src/main/res/xml/pref_main.xml @@ -48,6 +48,11 @@ app:key="portTransproxy" app:title="@string/port_transproxy" app:useSimpleSummaryProvider="true"/> + Date: Fri, 1 May 2026 09:46:52 +0000 Subject: [PATCH 3/3] Fix code review issues: use parentFile and consistent variable naming Agent-Logs-Url: https://github.com/shadowsocks/shadowsocks-android/sessions/d496275b-6490-48b5-8dd9-acbbd50734ae Co-authored-by: madeye <627917+madeye@users.noreply.github.com> --- .../main/java/com/github/shadowsocks/bg/ProxyInstance.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/com/github/shadowsocks/bg/ProxyInstance.kt b/core/src/main/java/com/github/shadowsocks/bg/ProxyInstance.kt index a482f02326..c032df80e2 100644 --- a/core/src/main/java/com/github/shadowsocks/bg/ProxyInstance.kt +++ b/core/src/main/java/com/github/shadowsocks/bg/ProxyInstance.kt @@ -118,10 +118,10 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro configFile.writeText(config.toString()) // write SOCKS5 auth config file if password protection is enabled - val socksAuthFile = File(configFile.parent, "socks5_auth") if (socksPassword.isNotEmpty()) { - socks5AuthFile = socksAuthFile - socksAuthFile.writeText(JSONObject().apply { + val authFile = File(configFile.parentFile ?: configFile.canonicalFile.parentFile, "socks5_auth") + socks5AuthFile = authFile + authFile.writeText(JSONObject().apply { put("password", JSONObject().apply { put("users", JSONArray().apply { put(JSONObject().apply {