fix(app): stop probing the control plane while disconnected
Android CI / android (push) Canceled after 0s
Android CI / android (push) Canceled after 0s
VpnConnection._init() started a 30 s /health poll from the constructor with no lifecycle gating, so it ran for the life of the app process - backgrounded, and whether or not the user ever connected. The app is excluded from its own tunnel, so those requests left in the clear. A periodic request that fires while the user is deliberately not connected is an avoidable fingerprint, and rule 60 already ruled probes out: reachability is meant to be passive. All it bought was the status dot before the first connect, and it got that wrong in both directions - "Offline" on a transient resolver failure against a server that was fine. Reachability now comes from a session sync that succeeded. serverHealthy is isConnected && hasSyncedOnce, so a green dot means a working session rather than a check made beforehand; while disconnected the bar reads "Ready" and claims nothing either way. checkHealth() goes with it. Nothing calls it, and leaving a probe helper in place invites its reuse. Pinned in test/session_lifecycle_regression_test.dart so it cannot come back as a tidy-up. flutter analyze clean, 10/10 tests pass. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
1 parent
571c07bca8
commit
62e968f133
5 files changed
+39
-37
No files matched your search
@@ -164,4 +164,24 @@ void main() {
|
||||
reason: 'the nonce check must not be skippable',
|
||||
);
|
||||
});
|
||||
|
||||
test('nothing probes the control plane while disconnected - rule 60', () {
|
||||
final vpn = File('lib/logic/vpn_connection.dart').readAsStringSync();
|
||||
final hivemind = File('lib/logic/hivemind_service.dart').readAsStringSync();
|
||||
final bar = File('lib/components/status_bar.dart').readAsStringSync();
|
||||
|
||||
// The runtime excludes the app from its own tunnel
|
||||
// (XrayVPNService.kt addDisallowedApplication), so a periodic /health probe
|
||||
// was a cleartext-SNI metronome to the API host - which resolves to the
|
||||
// tunnel IP. It fed step 1 of the kill chain in KNOWN_SECURITY_ISSUES #3,
|
||||
// in exchange for a green dot before the user had connected to anything.
|
||||
expect(vpn, isNot(contains('_healthTimer')));
|
||||
expect(vpn, isNot(contains('_checkHealth')));
|
||||
expect(hivemind, isNot(contains('checkHealth')));
|
||||
expect(hivemind, isNot(contains("'/health'")));
|
||||
|
||||
// Reachability is passive now: a session sync that succeeded, nothing else.
|
||||
expect(bar, isNot(contains('serverReachable')));
|
||||
expect(bar, contains('isConnected && timer.hasSyncedOnce'));
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user