Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
e110d91
fix: reduce stealth noVPN leakage surface
reflog May 18, 2026
c160347
fix: log stealth service coroutine failures
Copilot May 18, 2026
795cb83
fix: harden stealth noVPN sanitizer and service error handling
Copilot May 18, 2026
0092f16
fix: require explicit signing env vars for sanitizer resign
Copilot May 18, 2026
cbc0b25
fix: log stealth service coroutine failures
Copilot May 18, 2026
c8c33c4
Merge remote-tracking branch 'origin/stealth/leakage-cleanup-20260518…
reflog May 19, 2026
3928bc7
fix: consume Radiance noVPN split tunnel cleanup
reflog May 19, 2026
1e683a8
fix: remove unused stealth flutter mode flag
reflog May 19, 2026
9bb05ef
fix(stealth): harden garble coverage and go.buildinfo scrubbing
reflog Jun 11, 2026
821a171
consolidate: apply approved component updates + integration fixes
reflog Jun 11, 2026
cfb8267
docs(stealth): document novpn loopback proxy access control limitation
reflog Jun 11, 2026
dde245a
fix(stealth): profile-drive STEALTH_DIRECT_CONNECTION_APPS + wire nm …
reflog Jun 11, 2026
470de11
fix(stealth): implement missing PlatformInterface methods in stealth-…
reflog Jun 11, 2026
e7b4f72
fix(stealth): narrow allowlist for 3 irreducible protocol/schema tokens
reflog Jun 11, 2026
5a95b01
docs(stealth): document DNS-leak prevention intent and network-switch…
reflog Jun 11, 2026
1334c2b
chore(deps): pin radiance to stealth/novpn-radiance-socks-surface (68…
reflog Jun 11, 2026
5793075
feat(stealth): add stealth-vpn brand-only leakage mode
reflog Jun 11, 2026
5b95297
fix(stealth): simplify stealth-vpn mode to lantern_identity+social_ur…
reflog Jun 11, 2026
67532b5
fix(stealth): exclude OAuth2 gomobile exports from all stealth builds
reflog Jun 11, 2026
5db11eb
docs(stealth): document garble blind-spot, nm -D gate, and signing ce…
reflog Jun 11, 2026
9ea7b0b
fix: remove app.identity.* meta-data from manifest (integration regre…
reflog Jun 12, 2026
63dc164
fix: regular build — drop stealth-only foundation.bridge imports from…
reflog Jun 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 85 additions & 24 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -115,15 +115,14 @@ ANDROID_LIB_PATH := android/app/libs/$(LANTERN_LIB_NAME).aar
ANDROID_DEBUG_BUILD := $(BUILD_DIR)/app/outputs/flutter-apk/app-debug.apk
ANDROID_APK_RELEASE_BUILD := $(BUILD_DIR)/app/outputs/flutter-apk/app-release.apk
ANDROID_AAB_RELEASE_BUILD := $(BUILD_DIR)/app/outputs/bundle/release/app-release.aab
# Split APK vs AAB ABI targeting (2026-05-15):
# ABI targeting: arm64-only for both APK and AAB (2026-05-28).
#
# The sideload APK (uploaded to the GitHub release / Slack release channel)
# drops armeabi-v7a so the download is small enough for the Iranian audience
# on constrained bandwidth. The AAB keeps both ABIs so Play Store users on
# legacy 32-bit-only Android devices still receive updates — Play delivers
# per-ABI splits, so this doesn't penalize arm64 Play users.
# armeabi-v7a (32-bit) was dropped from the AAB too: Go >=1.23.2 trips
# Android 8-10 seccomp on 32-bit, killing libgojni.so with SIGSYS at startup
# (golang/go#70495 — ~54% of v9 crashes; those devices were crash-looping
# anyway). Re-add android-arm here if/when that's fixed upstream.
ANDROID_APK_TARGET_PLATFORMS := android-arm64
ANDROID_AAB_TARGET_PLATFORMS := android-arm,android-arm64
ANDROID_AAB_TARGET_PLATFORMS := android-arm64
# Back-compat: keep ANDROID_TARGET_PLATFORMS as the fat union for any
# external caller / debug target that still references it.
ANDROID_TARGET_PLATFORMS := $(ANDROID_AAB_TARGET_PLATFORMS)
Expand Down Expand Up @@ -153,7 +152,7 @@ ANDROID_GENERATED_IDENTITY_PROFILE := $(BUILD_DIR)/stealth/android-identity.prop
ANDROID_IDENTITY_PROFILE ?= $(if $(filter 1 true yes,$(ANDROID_GENERATE_IDENTITY_PROFILE)),$(ANDROID_GENERATED_IDENTITY_PROFILE),)
ANDROID_IDENTITY_ENV = $(if $(strip $(ANDROID_IDENTITY_PROFILE)),ANDROID_IDENTITY_PROFILE="$(abspath $(ANDROID_IDENTITY_PROFILE))",)
ANDROID_AUTH_SCHEME = $(strip $(if $(ANDROID_IDENTITY_PROFILE),$(shell sed -n 's/^appAuthScheme=//p' "$(ANDROID_IDENTITY_PROFILE)" 2>/dev/null | tail -n 1),))
ANDROID_IDENTITY_DART_DEFINES = $(if $(ANDROID_AUTH_SCHEME),--dart-define=APP_AUTH_SCHEME=$(ANDROID_AUTH_SCHEME))
ANDROID_IDENTITY_DART_DEFINES = $(if $(STEALTH_ENABLED),,$(if $(ANDROID_AUTH_SCHEME),--dart-define=APP_AUTH_SCHEME=$(ANDROID_AUTH_SCHEME)))

IOS_INSTALLER := $(INSTALLER_NAME)$(if $(filter-out production,$(BUILD_TYPE)),-$(BUILD_TYPE)).ipa
IOS_DIR := ios/
Expand Down Expand Up @@ -186,12 +185,15 @@ GOMOBILE_REPOS = \
github.com/sagernet/sing-box/experimental/libbox \
./lantern-core/mobile \
./lantern-core/utils
GOMOBILE_REPOS_STEALTH_NOVPN = \
./lantern-core/mobile

GARBLE ?= garble
GARBLE_VERSION ?= v0.16.0
GARBLE_SEED ?= $(STEALTH_GARBLE_SEED)
GARBLE_FLAGS ?= -literals
GARBLE_GOGARBLE ?= github.com/getlantern/lantern
STEALTH_GOGARBLE_PACKAGES := github.com/getlantern/lantern,github.com/getlantern/radiance,github.com/getlantern/common,github.com/getlantern/lantern-box,github.com/getlantern/kindling,github.com/getlantern/netx,github.com/getlantern/flashlight,github.com/getlantern/golog,github.com/getlantern/sing-box-minimal,github.com/getlantern/amp,github.com/getlantern/broflake,github.com/getlantern/samizdat,github.com/getlantern/domainfront,github.com/getlantern/semconv,github.com/getlantern/publicip,github.com/getlantern/dnstt,github.com/getlantern/keepcurrent,github.com/getlantern/algeneva,github.com/getlantern/lantern-water,github.com/getlantern/water,github.com/getlantern/wazero,github.com/getlantern/wireguard-go,github.com/getlantern/sing,github.com/getlantern/osversion,github.com/getlantern/timezone,github.com/getlantern/pluriconfig,github.com/getlantern/appdir,github.com/getlantern/context,github.com/getlantern/fronted,github.com/getlantern/lantern-server-provisioner,github.com/getlantern/ops
GARBLE_GOGARBLE ?= $(if $(STEALTH_ENABLED),$(STEALTH_GOGARBLE_PACKAGES),github.com/getlantern/lantern)
GARBLE_LDFLAGS ?= -w -s -buildid=

ifeq ($(OS),Windows_NT)
Expand All @@ -213,13 +215,17 @@ get-command = $(shell which="$$(which $(1) 2> /dev/null)" && if [[ ! -z "$$which
APPDMG := $(call get-command,appdmg)

DART_DEFINES := --dart-define=BUILD_TYPE=$(BUILD_TYPE) $(if $(VERSION),--dart-define=VERSION=$(VERSION),)
STEALTH_NOVPN_DART_DEFINES := $(DART_DEFINES) --dart-define=STEALTH_NO_VPN=true
STEALTH_NOVPN_BUILD_VARS := BUILD_TYPE=stealth-novpn STEALTH_MODE=stealth-novpn STEALTH_LEAKAGE_MODE=stealth-novpn
STEALTH_VPN_BUILD_VARS := BUILD_TYPE=stealth-vpn STEALTH_MODE=stealth-vpn STEALTH_LEAKAGE_MODE=stealth-vpn
STEALTH_ICON_SEED ?=
STEALTH_ICON_RES_DIR ?= android/app/build/generated/stealth-icons/res
STEALTH_ICON_RES_DIR ?= android/app/build/generated/icons/res
export STEALTH_ICON_SEED

STEALTH_PROFILE_SCRIPT := scripts/stealth/generate_profile.py
STEALTH_PROFILE_TOOL := python3 $(STEALTH_PROFILE_SCRIPT)
STEALTH_FLUTTER_BUILD_SCRIPT := scripts/stealth/run_flutter_build.py
STEALTH_ANDROID_ARTIFACT_SANITIZER := scripts/stealth/sanitize_android_artifact.py
STEALTH_ANDROID_ARTIFACT_SIGNING_FLAGS := $(if $(filter 1 true yes,$(STEALTH_ALLOW_DEBUG_KEYSTORE)),--allow-debug-keystore,)
STEALTH_PROFILE ?=
STEALTH_MODE ?=
STEALTH_PACKAGE_NAME ?=
Expand All @@ -239,6 +245,12 @@ STEALTH_DART_DEFINES := $(if $(STEALTH_ENABLED),--dart-define-from-file=$(STEALT
STEALTH_GO_TAGS = $(if $(STEALTH_ENABLED),$(if $(wildcard $(STEALTH_GO_TAGS_FILE)),$(shell cat "$(STEALTH_GO_TAGS_FILE)"),$(error missing $(STEALTH_GO_TAGS_FILE); run make stealth-profile before building)),)
STEALTH_PROFILE_ENV := $(if $(STEALTH_ENABLED),ORG_GRADLE_PROJECT_stealthProfile="$(abspath $(STEALTH_PROFILE_OUT))",)
MAYBE_STEALTH_PROFILE := $(if $(STEALTH_ENABLED),$(STEALTH_PROFILE_STAMP),)
STEALTH_IS_NOVPN = $(if $(findstring stealth_novpn,$(STEALTH_GO_TAGS)),1,)
GOMOBILE_REPOS_EFFECTIVE = $(if $(STEALTH_IS_NOVPN),$(GOMOBILE_REPOS_STEALTH_NOVPN),$(GOMOBILE_REPOS))
GOMOBILE_JAVAPKG = $(if $(STEALTH_ENABLED),foundation.engine,lantern.io)
STEALTH_FLUTTER_PREFIX = $(if $(STEALTH_ENABLED),$(PYTHON) $(STEALTH_FLUTTER_BUILD_SCRIPT) --profile "$(STEALTH_PROFILE_OUT)" --,)
STEALTH_FLUTTER_TARGET = $(if $(STEALTH_ENABLED),--target lib/main_stealth.dart,)
FLUTTER_DART_DEFINES = $(if $(STEALTH_ENABLED),$(if $(VERSION),--dart-define=VERSION=$(VERSION),),$(DART_DEFINES))

INSTALLER_RESOURCES := installer-resources

Expand Down Expand Up @@ -729,11 +741,11 @@ build-android: check-android-sdk check-gomobile $(MAYBE_STEALTH_PROFILE)
GOTOOLCHAIN=$(GO_VERSION) GOOS=android gomobile bind -v \
-androidapi=23 \
-target="$(GOMOBILE_ANDROID_TARGET)" \
-javapkg=lantern.io \
-javapkg=$(GOMOBILE_JAVAPKG) \
-tags=$(TAGS)$(STEALTH_GO_TAGS) -trimpath \
-o=$(ANDROID_LIB_BUILD) \
-ldflags="$(ANDROID_GOMOBILE_LDFLAGS) $(GO_EXTRA_LDFLAGS)" \
$(GOMOBILE_REPOS)
$(GOMOBILE_REPOS_EFFECTIVE)

cp $(ANDROID_LIB_BUILD) $(ANDROID_LIBS_DIR)
@echo "Built Android library: $(ANDROID_LIBS_DIR)/$(ANDROID_LIB)"
Expand All @@ -757,42 +769,80 @@ build-android-obfuscated: check-android-sdk check-gomobile check-garble-seed che
GOTOOLCHAIN=$(GO_VERSION) GOOS=android gomobile bind -v \
-androidapi=23 \
-target="$(GOMOBILE_ANDROID_TARGET)" \
-javapkg=lantern.io \
-javapkg=$(GOMOBILE_JAVAPKG) \
-tags=$(TAGS)$(STEALTH_GO_TAGS) -trimpath \
-o=$(ANDROID_LIB_BUILD) \
-ldflags="$(ANDROID_GOMOBILE_LDFLAGS) $(GARBLE_LDFLAGS) $(EXTRA_LDFLAGS)" \
$(GOMOBILE_REPOS)
$(GOMOBILE_REPOS_EFFECTIVE)

cp $(ANDROID_LIB_BUILD) $(ANDROID_LIBS_DIR)
@echo "Built obfuscated Android library: $(ANDROID_LIBS_DIR)/$(ANDROID_LIB)"
$(if $(STEALTH_ENABLED),$(MAKE) ANDROID_LIBS_DIR="$(ANDROID_LIBS_DIR)" ANDROID_LIB="$(ANDROID_LIB)" verify-stealth-jni,)

# verify-stealth-jni: hard gate on JNI symbol namespace in the stealth AAR.
# Extracts arm64-v8a/libgojni.so and asserts:
# - Java_lantern_io_* is ABSENT (GOMOBILE_JAVAPKG=foundation.engine applied)
# - Java_foundation_engine_* is PRESENT (gomobile binding succeeded)
# Called automatically by build-android-obfuscated when STEALTH_ENABLED is set.
# Can also be called standalone: make verify-stealth-jni
.PHONY: verify-stealth-jni
verify-stealth-jni:
@echo "=== verify-stealth-jni: checking JNI namespace in $(ANDROID_LIBS_DIR)/$(ANDROID_LIB) ==="
@tmpdir=$$(mktemp -d); \
trap 'rm -rf "$$tmpdir"' EXIT; \
aar="$(ANDROID_LIBS_DIR)/$(ANDROID_LIB)"; \
if [ ! -f "$$aar" ]; then \
echo "FAIL: AAR not found at $$aar — build stealth AAR first"; \
exit 1; \
fi; \
unzip -q "$$aar" "jni/arm64-v8a/libgojni.so" -d "$$tmpdir" 2>/dev/null || \
{ echo "FAIL: libgojni.so not found in $$aar (expected jni/arm64-v8a/libgojni.so)"; exit 1; }; \
so="$$tmpdir/jni/arm64-v8a/libgojni.so"; \
bad=$$(nm -D "$$so" 2>/dev/null | grep -c "Java_lantern_io" || true); \
if [ "$$bad" -gt 0 ]; then \
echo "FAIL: $$bad Java_lantern_io_* symbol(s) found — GOMOBILE_JAVAPKG=foundation.engine not applied:"; \
nm -D "$$so" | grep "Java_lantern_io"; \
exit 1; \
fi; \
good=$$(nm -D "$$so" 2>/dev/null | grep -c "Java_foundation_engine" || true); \
if [ "$$good" -eq 0 ]; then \
echo "FAIL: no Java_foundation_engine_* symbols found — gomobile javapkg binding is missing"; \
exit 1; \
fi; \
echo "PASS: JNI namespace gate — Java_lantern_io=0, Java_foundation_engine=$$good"

.PHONY: android-debug
android-debug: $(ANDROID_DEBUG_BUILD)

$(ANDROID_DEBUG_BUILD): $(ANDROID_LIB_BUILD) $(MAYBE_STEALTH_PROFILE)
$(MAKE) android-identity-profile
$(STEALTH_PROFILE_ENV) $(ANDROID_IDENTITY_ENV) flutter build apk --target-platform $(ANDROID_APK_TARGET_PLATFORMS) --verbose --debug $(DART_DEFINES) $(STEALTH_DART_DEFINES) $(ANDROID_IDENTITY_DART_DEFINES)
$(STEALTH_PROFILE_ENV) $(ANDROID_IDENTITY_ENV) $(STEALTH_FLUTTER_PREFIX) flutter build apk --target-platform $(ANDROID_APK_TARGET_PLATFORMS) --verbose --debug $(STEALTH_FLUTTER_TARGET) $(FLUTTER_DART_DEFINES) $(STEALTH_DART_DEFINES) $(ANDROID_IDENTITY_DART_DEFINES)

# --target-platform restricts Flutter's libapp.so / libflutter.so to arm64;
# -Plantern.thinAbi=true tells android/app/build.gradle to drop armeabi-v7a
# from abiFilters + add it to packagingOptions.jniLibs.excludes so the
# arm32 libgojni.so in the AAR doesn't get packed. Sideload-APK target only.
.PHONY: android-apk-release
android-apk-release: android-identity-profile $(MAYBE_STEALTH_PROFILE)
$(STEALTH_PROFILE_ENV) $(ANDROID_IDENTITY_ENV) flutter build apk --target-platform $(ANDROID_APK_TARGET_PLATFORMS) --verbose --release $(DART_DEFINES) $(STEALTH_DART_DEFINES) $(ANDROID_IDENTITY_DART_DEFINES) -Plantern.thinAbi=true
$(STEALTH_PROFILE_ENV) $(ANDROID_IDENTITY_ENV) $(STEALTH_FLUTTER_PREFIX) flutter build apk --target-platform $(ANDROID_APK_TARGET_PLATFORMS) --verbose --release $(STEALTH_FLUTTER_TARGET) $(FLUTTER_DART_DEFINES) $(STEALTH_DART_DEFINES) $(ANDROID_IDENTITY_DART_DEFINES) -Plantern.thinAbi=true
$(if $(STEALTH_ENABLED),$(PYTHON) $(STEALTH_ANDROID_ARTIFACT_SANITIZER) --resign $(STEALTH_ANDROID_ARTIFACT_SIGNING_FLAGS) $(ANDROID_APK_RELEASE_BUILD),true)
cp $(ANDROID_APK_RELEASE_BUILD) $(ANDROID_RELEASE_APK)

# AAB keeps both ABIs so Play Store delivers per-ABI splits (arm64 users
# get the small split; legacy arm32-only users still get updates). The
# Play path doesn't need the size cut — only the sideload APK does.
.PHONY: android-aab-release
android-aab-release: android-identity-profile $(MAYBE_STEALTH_PROFILE)
$(STEALTH_PROFILE_ENV) $(ANDROID_IDENTITY_ENV) flutter build appbundle --target-platform $(ANDROID_AAB_TARGET_PLATFORMS) --verbose --release $(DART_DEFINES) $(STEALTH_DART_DEFINES) $(ANDROID_IDENTITY_DART_DEFINES)
$(STEALTH_PROFILE_ENV) $(ANDROID_IDENTITY_ENV) $(STEALTH_FLUTTER_PREFIX) flutter build appbundle --target-platform $(ANDROID_AAB_TARGET_PLATFORMS) --verbose --release $(STEALTH_FLUTTER_TARGET) $(FLUTTER_DART_DEFINES) $(STEALTH_DART_DEFINES) $(ANDROID_IDENTITY_DART_DEFINES)
$(if $(STEALTH_ENABLED),$(PYTHON) $(STEALTH_ANDROID_ARTIFACT_SANITIZER) --resign $(STEALTH_ANDROID_ARTIFACT_SIGNING_FLAGS) $(ANDROID_AAB_RELEASE_BUILD),true)
cp $(ANDROID_AAB_RELEASE_BUILD) $(ANDROID_RELEASE_AAB)
Comment thread
reflog marked this conversation as resolved.
$(MAKE) android-copy-play-artifacts

.PHONY: android-copy-play-artifacts
android-copy-play-artifacts:
ifneq ($(strip $(STEALTH_ENABLED)),)
@echo "Skipping Play mapping and native symbols copy for stealth build"
else
# Copy Play console artifacts
@if [ -f "$(ANDROID_MAPPING_SRC)" ]; then \
cp "$(ANDROID_MAPPING_SRC)" mapping.txt; \
Expand All @@ -803,20 +853,31 @@ android-copy-play-artifacts:
elif [ -d "build/app/intermediates/merged_native_libs/release/out/lib" ]; then \
(cd build/app/intermediates/merged_native_libs/release/out && zip -r ../../../../../../debug-symbols.zip lib >/dev/null); \
fi
endif

.PHONY: android-stealth-novpn-apk-release
android-stealth-novpn-apk-release:
ORG_GRADLE_PROJECT_stealthNoVpn=true flutter build apk --target-platform $(ANDROID_TARGET_PLATFORMS) --verbose --release $(STEALTH_NOVPN_DART_DEFINES)
cp $(ANDROID_APK_RELEASE_BUILD) $(ANDROID_STEALTH_NOVPN_APK)
$(MAKE) $(STEALTH_NOVPN_BUILD_VARS) android pubget gen android-apk-release

.PHONY: android-stealth-novpn-aab-release
android-stealth-novpn-aab-release:
ORG_GRADLE_PROJECT_stealthNoVpn=true flutter build appbundle --target-platform $(ANDROID_TARGET_PLATFORMS) --verbose --release $(STEALTH_NOVPN_DART_DEFINES)
cp $(ANDROID_AAB_RELEASE_BUILD) $(ANDROID_STEALTH_NOVPN_AAB)
$(MAKE) android-copy-play-artifacts
$(MAKE) $(STEALTH_NOVPN_BUILD_VARS) android pubget gen android-aab-release

.PHONY: android-stealth-novpn-release
android-stealth-novpn-release: android pubget gen android-stealth-novpn-apk-release android-stealth-novpn-aab-release
android-stealth-novpn-release:
$(MAKE) $(STEALTH_NOVPN_BUILD_VARS) android-release-ci

.PHONY: android-stealth-vpn-apk-release
android-stealth-vpn-apk-release:
$(MAKE) $(STEALTH_VPN_BUILD_VARS) android pubget gen android-apk-release

.PHONY: android-stealth-vpn-aab-release
android-stealth-vpn-aab-release:
$(MAKE) $(STEALTH_VPN_BUILD_VARS) android pubget gen android-aab-release

.PHONY: android-stealth-vpn-release
android-stealth-vpn-release:
$(MAKE) $(STEALTH_VPN_BUILD_VARS) android-release-ci


.PHONY: android-release
Expand Down
Loading
Loading