Skip to content
Closed
4 changes: 4 additions & 0 deletions assets/locales/ar.po
Original file line number Diff line number Diff line change
Expand Up @@ -1641,3 +1641,7 @@ msgstr "Couldn't check for updates"

msgid "check_connection_and_retry"
msgstr "Check your connection and try again"

msgid "feature_disabled_for_build"
msgstr "This feature is not available in this build"

4 changes: 4 additions & 0 deletions assets/locales/bn.po
Original file line number Diff line number Diff line change
Expand Up @@ -1677,3 +1677,7 @@ msgstr "Couldn't check for updates"

msgid "check_connection_and_retry"
msgstr "Check your connection and try again"

msgid "feature_disabled_for_build"
msgstr "This feature is not available in this build"

4 changes: 4 additions & 0 deletions assets/locales/en.po
Original file line number Diff line number Diff line change
Expand Up @@ -1634,3 +1634,7 @@ msgstr "Couldn't check for updates"

msgid "check_connection_and_retry"
msgstr "Check your connection and try again"

msgid "feature_disabled_for_build"
msgstr "This feature is not available in this build"

4 changes: 4 additions & 0 deletions assets/locales/es-cu.po
Original file line number Diff line number Diff line change
Expand Up @@ -1702,3 +1702,7 @@ msgstr "Couldn't check for updates"

msgid "check_connection_and_retry"
msgstr "Check your connection and try again"

msgid "feature_disabled_for_build"
msgstr "This feature is not available in this build"

4 changes: 4 additions & 0 deletions assets/locales/es.po
Original file line number Diff line number Diff line change
Expand Up @@ -1698,3 +1698,7 @@ msgstr "Couldn't check for updates"

msgid "check_connection_and_retry"
msgstr "Check your connection and try again"

msgid "feature_disabled_for_build"
msgstr "This feature is not available in this build"

4 changes: 4 additions & 0 deletions assets/locales/fa.po
Original file line number Diff line number Diff line change
Expand Up @@ -1675,3 +1675,7 @@ msgstr "Couldn't check for updates"

msgid "check_connection_and_retry"
msgstr "Check your connection and try again"

msgid "feature_disabled_for_build"
msgstr "This feature is not available in this build"

4 changes: 4 additions & 0 deletions assets/locales/fr-ca.po
Original file line number Diff line number Diff line change
Expand Up @@ -2092,3 +2092,7 @@ msgstr "Couldn't check for updates"

msgid "check_connection_and_retry"
msgstr "Check your connection and try again"

msgid "feature_disabled_for_build"
msgstr "This feature is not available in this build"

4 changes: 4 additions & 0 deletions assets/locales/fr.po
Original file line number Diff line number Diff line change
Expand Up @@ -1561,3 +1561,7 @@ msgstr "Couldn't check for updates"

msgid "check_connection_and_retry"
msgstr "Check your connection and try again"

msgid "feature_disabled_for_build"
msgstr "This feature is not available in this build"

4 changes: 4 additions & 0 deletions assets/locales/hi.po
Original file line number Diff line number Diff line change
Expand Up @@ -1677,3 +1677,7 @@ msgstr "Couldn't check for updates"

msgid "check_connection_and_retry"
msgstr "Check your connection and try again"

msgid "feature_disabled_for_build"
msgstr "This feature is not available in this build"

4 changes: 4 additions & 0 deletions assets/locales/ms.po
Original file line number Diff line number Diff line change
Expand Up @@ -1687,3 +1687,7 @@ msgstr "Couldn't check for updates"

msgid "check_connection_and_retry"
msgstr "Check your connection and try again"

msgid "feature_disabled_for_build"
msgstr "This feature is not available in this build"

4 changes: 4 additions & 0 deletions assets/locales/my.po
Original file line number Diff line number Diff line change
Expand Up @@ -1717,3 +1717,7 @@ msgstr "Couldn't check for updates"

msgid "check_connection_and_retry"
msgstr "Check your connection and try again"

msgid "feature_disabled_for_build"
msgstr "This feature is not available in this build"

4 changes: 4 additions & 0 deletions assets/locales/ps.po
Original file line number Diff line number Diff line change
Expand Up @@ -1704,3 +1704,7 @@ msgstr "Couldn't check for updates"

msgid "check_connection_and_retry"
msgstr "Check your connection and try again"

msgid "feature_disabled_for_build"
msgstr "This feature is not available in this build"

4 changes: 4 additions & 0 deletions assets/locales/ru.po
Original file line number Diff line number Diff line change
Expand Up @@ -1694,3 +1694,7 @@ msgstr "Couldn't check for updates"

msgid "check_connection_and_retry"
msgstr "Check your connection and try again"

msgid "feature_disabled_for_build"
msgstr "This feature is not available in this build"

4 changes: 4 additions & 0 deletions assets/locales/th.po
Original file line number Diff line number Diff line change
Expand Up @@ -1649,3 +1649,7 @@ msgstr "Couldn't check for updates"

msgid "check_connection_and_retry"
msgstr "Check your connection and try again"

msgid "feature_disabled_for_build"
msgstr "This feature is not available in this build"

4 changes: 4 additions & 0 deletions assets/locales/tk.po
Original file line number Diff line number Diff line change
Expand Up @@ -1662,3 +1662,7 @@ msgstr "Couldn't check for updates"

msgid "check_connection_and_retry"
msgstr "Check your connection and try again"

msgid "feature_disabled_for_build"
msgstr "This feature is not available in this build"

4 changes: 4 additions & 0 deletions assets/locales/tr.po
Original file line number Diff line number Diff line change
Expand Up @@ -1675,3 +1675,7 @@ msgstr "Couldn't check for updates"

msgid "check_connection_and_retry"
msgstr "Check your connection and try again"

msgid "feature_disabled_for_build"
msgstr "This feature is not available in this build"

4 changes: 4 additions & 0 deletions assets/locales/ur.po
Original file line number Diff line number Diff line change
Expand Up @@ -2042,3 +2042,7 @@ msgstr "Couldn't check for updates"

msgid "check_connection_and_retry"
msgstr "Check your connection and try again"

msgid "feature_disabled_for_build"
msgstr "This feature is not available in this build"

4 changes: 4 additions & 0 deletions assets/locales/vi.po
Original file line number Diff line number Diff line change
Expand Up @@ -1680,3 +1680,7 @@ msgstr "Couldn't check for updates"

msgid "check_connection_and_retry"
msgstr "Check your connection and try again"

msgid "feature_disabled_for_build"
msgstr "This feature is not available in this build"

4 changes: 4 additions & 0 deletions assets/locales/zh-Hans.po
Original file line number Diff line number Diff line change
Expand Up @@ -1541,3 +1541,7 @@ msgstr "Couldn't check for updates"

msgid "check_connection_and_retry"
msgstr "Check your connection and try again"

msgid "feature_disabled_for_build"
msgstr "This feature is not available in this build"

4 changes: 4 additions & 0 deletions assets/locales/zh-Hant.po
Original file line number Diff line number Diff line change
Expand Up @@ -1543,3 +1543,7 @@ msgstr "Couldn't check for updates"

msgid "check_connection_and_retry"
msgstr "Check your connection and try again"

msgid "feature_disabled_for_build"
msgstr "This feature is not available in this build"

51 changes: 51 additions & 0 deletions docs/stealth-feature-gates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Stealth feature gates

Stealth artifacts disable high-identification product surfaces at compile time
with Dart environment defines. Normal builds remain unchanged.

## Build flags

Use either profile-style mode names or an explicit boolean flag:

```sh
flutter build apk --dart-define=STEALTH_MODE=stealth-vpn
flutter build apk --dart-define=STEALTH_MODE=stealth-novpn
flutter build apk --dart-define=STEALTH_MODE=true
flutter build apk --dart-define=STEALTH_BUILD=true
Comment thread
reflog marked this conversation as resolved.
flutter build apk --dart-define=STEALTH_NO_VPN=true
```
Comment thread
reflog marked this conversation as resolved.

`STEALTH_MODE=true` is kept as a compatibility alias for generic stealth
artifacts; profile-specific builds should prefer `stealth-vpn` or
`stealth-novpn`.

The app derives these gates from the stealth flag:

- `enableOAuth`
- `enablePayments`
- `enableStorePayments`
- `enableAppLinks`
- `enableSocialLinks`
- `enableAutoUpdate`

`STEALTH_NO_VPN=true` is the explicit no-VPN compatibility flag. It enables the
same feature gates as `STEALTH_BUILD=true` and is treated as stealth mode even
when `STEALTH_MODE` is not supplied.

## Disabled surfaces

Stealth builds hide or short-circuit:

- OAuth login buttons, callbacks, and SSO account deletion verification.
- Store purchase initialization, restore purchase, Google Play subscription
management, Stripe/payment redirect entry points, and upgrade CTAs.
- Runtime app-link handling in Flutter.
- Follow-us, forum, alternate download, referral/social, and project-link
surfaces.
- Desktop auto-update initialization, manual update checks, and appcast URL
resolution.

Native manifest and entitlement removal is handled by the stealth manifest
filtering build step. Artifact leakage checks should still scan final APK/IPA
and desktop bundles for OAuth provider names, app-link hosts/schemes, billing
entry points, social URLs, and appcast/update URLs.
30 changes: 30 additions & 0 deletions lib/core/common/app_build_info.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,36 @@ class AppBuildInfo {
defaultValue: false,
);

static const bool stealthBuild = bool.fromEnvironment(
'STEALTH_BUILD',
defaultValue: false,
);

static const String stealthModeName = String.fromEnvironment(
'STEALTH_MODE',
defaultValue: '',
);

static const bool stealthNoVpn = bool.fromEnvironment(
'STEALTH_NO_VPN',
defaultValue: false,
);

/// Removes high-identification UI and runtime flows from stealth artifacts.
static const bool suppressIdentifyingFeatures =
stealthBuild ||
Comment thread
reflog marked this conversation as resolved.
stealthNoVpn ||
stealthModeName == 'stealth-vpn' ||
stealthModeName == 'stealth-novpn' ||
stealthModeName == 'true';

static const bool enableOAuth = !suppressIdentifyingFeatures;
static const bool enablePayments = !suppressIdentifyingFeatures;
static const bool enableStorePayments = !suppressIdentifyingFeatures;
static const bool enableAppLinks = !suppressIdentifyingFeatures;
static const bool enableSocialLinks = !suppressIdentifyingFeatures;
static const bool enableAutoUpdate = !suppressIdentifyingFeatures;

/// Developer mode is exposed in debug and nightly builds only.
static bool get isDevModeEnabled => kDebugMode || buildType == 'nightly';
}
Expand Down
30 changes: 19 additions & 11 deletions lib/core/common/app_urls.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import 'package:lantern/core/common/app_build_info.dart';

class AppUrls {
static String lanternOfficial = 'https://lantern.io';
static String support = 'https://support.lantern.io';
static String lanternForums = 'https://lantern.io/forums';
static String get lanternForums =>
AppBuildInfo.enableSocialLinks ? 'https://lantern.io/forums' : '';
static String faq = '$lanternOfficial/faq';
static String privacyPolicy = '$lanternOfficial/privacy';
static String termsOfService = '$lanternOfficial/terms';
Expand All @@ -10,28 +13,33 @@ class AppUrls {
static String downloadIos = '$lanternOfficial/download?os=ios';
static String downloadMac = '$lanternOfficial/download?os=mac';
static String downloadLinux = '$lanternOfficial/download?os=linux';
static String lanternGithub = 'https://github.com/getlantern/lantern';
static String telegramBot = 'https://t.me/lantern_official_bot';
static String get lanternGithub => AppBuildInfo.enableSocialLinks
? 'https://github.com/getlantern/lantern'
: '';
static String get telegramBot =>
AppBuildInfo.enableSocialLinks ? 'https://t.me/lantern_official_bot' : '';
static String unbounded = 'https://unbounded.lantern.io';
static const appcastProd =
'https://s3.amazonaws.com/lantern.io/releases/production/latest/appcast.xml';
static const appcastBeta =
'https://s3.amazonaws.com/lantern.io/releases/beta/latest/appcast.xml';
static String manuallyServerSetupURL =
'https://github.com/getlantern/lantern-server-manager';
static String digitalOceanBillingUrl =
'https://cloud.digitalocean.com/account/billing';
static String androidSideloadUpdateEndpoint =
'https://update.getlantern.org/update/lantern';

static String appcastFor(String buildType) {
static String? appcastFor(String buildType) {
if (!AppBuildInfo.enableAutoUpdate) {
return null;
}
switch (buildType) {
case 'production':
return appcastProd;
return 'https://s3.amazonaws.com/lantern.io/releases/production/latest/appcast.xml';
case 'beta':
return appcastBeta;
return 'https://s3.amazonaws.com/lantern.io/releases/beta/latest/appcast.xml';
default:
return appcastProd;
return 'https://s3.amazonaws.com/lantern.io/releases/production/latest/appcast.xml';
}
}

static bool isLanternHost(String host) =>
host == 'lantern.io' || host == 'www.lantern.io';
Comment thread
reflog marked this conversation as resolved.
}
3 changes: 3 additions & 0 deletions lib/core/common/common.dart
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ String generatePaymentRedirectIdempotencyKey() {
}

bool isStoreVersion() {
if (!AppBuildInfo.enablePayments || !AppBuildInfo.enableStorePayments) {
return false;
}
if (!PlatformUtils.isMobile) {
return false;
}
Expand Down
5 changes: 4 additions & 1 deletion lib/core/services/injection_container.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:get_it/get_it.dart';
import 'package:lantern/core/common/app_build_info.dart';
import 'package:lantern/core/services/app_purchase.dart';
import 'package:lantern/core/services/local_storage_service.dart';
import 'package:lantern/core/services/notification_service.dart';
Expand Down Expand Up @@ -47,7 +48,9 @@ Future<void> injectServices() async {
appLogger.debug('Registering AppPurchase...');
final appPurchase = AppPurchase();
sl.registerSingleton<AppPurchase>(appPurchase);
if (PlatformUtils.isIOS) {
if (PlatformUtils.isIOS &&
AppBuildInfo.enablePayments &&
AppBuildInfo.enableStorePayments) {
// StoreKit can deliver pending transaction updates at launch; init()
// only subscribes to the stream and does not fetch product details.
appPurchase.init();
Expand Down
9 changes: 9 additions & 0 deletions lib/core/updater/updater.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ class Updater {
_initialized = true;

if (kDebugMode || !_isSupportedPlatform) return;
if (!AppBuildInfo.enableAutoUpdate) {
appLogger.info('autoUpdater disabled for this build');
return;
}

final flags = await _featureFlags();
if (_isAndroidPlatform) {
Expand Down Expand Up @@ -61,6 +65,10 @@ class Updater {
try {
final buildType = AppBuildInfo.buildType;
final feedUrl = AppUrls.appcastFor(buildType);
if (feedUrl == null || feedUrl.isEmpty) {
appLogger.info('autoUpdater disabled: no appcast URL for this build');
return;
}
final updater = AutoUpdater.instance;
await updater.setFeedURL(feedUrl);
await updater.setScheduledCheckInterval(3600);
Expand All @@ -86,6 +94,7 @@ class Updater {
}

Future<void> checkNow() async {
if (!AppBuildInfo.enableAutoUpdate) return;
if (!_isSupportedPlatform) return;
final flags = await _featureFlags();

Expand Down
13 changes: 9 additions & 4 deletions lib/core/utils/failure.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,20 @@ class Failure {

Failure({required this.error, required this.localizedErrorMessage});

factory Failure.featureDisabled(String feature) => Failure(
error: '$feature disabled for this build',
localizedErrorMessage: 'feature_disabled_for_build'.i18n,
);

@override
String toString() =>
'Failure(error: $error, localizedErrorMessage: $localizedErrorMessage)';
}

class VpnConflictFailure extends Failure {
VpnConflictFailure()
: super(
error: 'vpn_conflict',
localizedErrorMessage: 'vpn_conflict_body'.i18n,
);
: super(
error: 'vpn_conflict',
localizedErrorMessage: 'vpn_conflict_body'.i18n,
);
}
Loading
Loading