fix(app): fail closed on invalid connection mode state #7

Open
Naveax wants to merge 2 commits from Naveax/pr/fix-connection-mode-persistence into main
pull from: Naveax/pr/fix-connection-mode-persistence
2 changed files with 18 additions and 3 deletions
Showing only changes of commit f26c459f6c - Show all commits

No files matched your search

+9 -3
View File
@@ -1,3 +1,4 @@
import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
enum ConnectionMode { tun, proxy }
@@ -19,16 +20,21 @@ abstract final class ConnectionSettings {
final storedMode = prefs.getString(_modeKey);
// Only the explicit current proxy value restores SOCKS5. Unknown and
// obsolete values, including the former 'auto'/'ass' labels, fail closed
// to full-tunnel TUN instead of silently selecting a weaker route.
// obsolete values, including the pre-V3 'auto' label, fail closed to
// full-tunnel TUN instead of silently selecting a weaker route.
_mode = storedMode == ConnectionMode.proxy.name
? ConnectionMode.proxy
: ConnectionMode.tun;
if (storedMode != _mode.name) {
// Best-effort. _mode is already the safe TUN default in memory, so a
// failed write leaves nothing insecure — it only means the migration
// is retried next launch. initialize() runs from main() before
// runApp(), with no zone guard, so throwing here would black-screen
// the app on the fresh-install path for no security gain.
final migrated = await prefs.setString(_modeKey, _mode.name);
if (!migrated) {
throw StateError('Could not persist migrated connection mode.');
debugPrint('[ConnectionSettings] Could not persist migrated mode.');
}
}
@@ -67,6 +67,10 @@ class _ConnectionModeTileState extends State<ConnectionModeTile> {
widget.onChanged?.call(next);
}
/// Recovers from an orphaned proxy: tear the runtime down and start a fresh
/// session so a readable set of credentials exists again. No rewarded ad —
/// the user is repairing a connection they already paid for, not opening a
/// new one.
Future<void> _reconnect() async {
if (_reconnecting) return;
final vpn = context.read<VpnConnection>();
@@ -170,6 +174,11 @@ class _ConnectionModeTileState extends State<ConnectionModeTile> {
builder: (context, vpn, _) {
final session = vpn.activeSocksSession;
if (session == null) {
// The proxy can be running while this session is unknown:
// Android killed the app process, the foreground service kept
// the listener alive, and its credentials existed only in the
// memory that died with it. Saying "Connect" there would be a
// lie — the user is already connected.
final orphaned = vpn.status == VpnStatus.connected &&
vpn.adoptedRunningRuntime;
if (orphaned) {