Skip to content
Open
Changes from all commits
Commits
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
259 changes: 259 additions & 0 deletions docs/stealth-builds.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
# Stealth build QA and release policy

This document defines the verification, release, and support policy for Android
stealth artifacts. It covers the normal, `stealth-vpn`, and `stealth-novpn`
profiles used by the Stealth Lantern epic.

## Build profiles

Target Android stealth artifacts must be built through the Makefile release path
so the gomobile AAR, Flutter artifacts, generated profile, and manifest
filtering are produced from the same inputs. The command shape below becomes
actionable only after the companion build-profile and manifest-selection PRs are
integrated; this policy PR does not make `STEALTH_MODE` runnable by itself.

```sh
STEALTH_MODE=vpn make android-release-ci
STEALTH_MODE=novpn make android-release-ci
Comment thread
reflog marked this conversation as resolved.
```

Direct Gradle invocations are only acceptable for local manifest experiments
after the matching AAR has already been generated by the Makefile.

`vpn` keeps the Android `VpnService` surface but removes verified app links,
public custom-scheme filters, broad package visibility, write-settings access,
payment query declarations, wallet metadata, and cleartext traffic allowance
from the generated manifest.

`novpn` applies the same filtering and also removes Android VPN service
components, quick-tile VPN controls, boot receiver, and VPN-related permissions.
The companion no-VPN runtime PR routes startup through a normal Android
`Service`, which configures radiance with:

```text
RADIANCE_USE_SOCKS_PROXY=true
RADIANCE_SOCKS_ADDRESS=127.0.0.1:<profile-specific-port>
```

At the pinned radiance version, that env pair replaces the TUN inbound with a
loopback mixed HTTP/SOCKS inbound. The listener must have local access control;
a profile-specific port only reduces cross-artifact stability and is not enough
to pass hostile-app probing by itself. If the runtime cannot enforce local proxy
access control, the no-VPN artifact must remain experimental and fail release
qualification when hostile apps can identify the listener signature. Build
Flutter with the no-VPN profile's generated Dart defines so VPN controls,
full-device routing, and split tunneling are hidden.

## Static checks

Every stealth release candidate must publish the normal Android artifact plus
the two stealth Android artifacts from the same source revision. CI should fail
the release candidate when any of these checks fail.

| Check | Normal | Stealth VPN | Stealth No-VPN |
| --- | --- | --- | --- |
| Build mode | normal Makefile release path | Makefile release path with `STEALTH_MODE=vpn` | Makefile release path with `STEALTH_MODE=novpn` |
| Package identity | canonical package | private profile package name | private profile package name |
| Manifest parser | baseline manifest is valid | no app links, broad queries, wallet metadata, write-settings, or cleartext allowance | same as VPN plus no `VpnService`, quick tile, boot receiver, or VPN permission |
| Dart defines | default | default | no-VPN profile defines required |
| Asset policy | public assets allowed | private profile assets only | private profile assets only |
| Dependency policy | normal allowlist | no new detector-facing SDKs without review | no new detector-facing SDKs without review |
| Symbol and metadata review | standard release checks | no public profile names in artifact metadata | no public profile names in artifact metadata |

Minimum automated CI coverage:

- Build the three Android release variants from a clean checkout.
- Decode each generated APK or AAB manifest and compare it with the expected
profile allowlist.
- Verify the no-VPN artifact has no `android.permission.BIND_VPN_SERVICE`,
`android.net.VpnService`, quick-settings VPN tile, boot-time VPN startup path,
split-tunneling UI entry point, or full-device-routing UI entry point.
- Verify the VPN artifact still has the expected `VpnService` declaration and
excluded-app configuration path.
- Verify package name, app label, icon, supported schemes, exported components,
permissions, queries, services, receivers, providers, and metadata match the
private profile manifest contract.
- Save the manifest diff and build metadata as CI artifacts for support
traceability.

## Dynamic Android validation

Run dynamic validation on a physical Android device for every stealth release
candidate. Repeat the full matrix whenever a release changes Android networking,
manifest generation, startup, payments, account flows, updates, support
metadata, package rotation, or stealth profile inputs.

| Scenario | Normal | Stealth VPN | Stealth No-VPN |
| --- | --- | --- | --- |
| Fresh install | installs from official channel | installs from private channel | installs from private channel |
| First launch | public app identity visible | private app identity visible | private app identity visible |
| Connect | full-device VPN can connect | full-device VPN can connect | local proxy mode can connect |
| Android VPN indicator | visible when connected | visible when connected | never visible |
| TUN interface | present when connected | present when connected | absent |
| Split tunneling | available when supported | excluded-app behavior works | hidden or disabled |
| Non-excluded traffic | routed through Lantern | routed through Lantern | routes only through app-local proxy integrations |
| Excluded traffic | follows normal split rules | does not see Lantern-routed network state | not applicable |
| Reboot | expected normal behavior | no unexpected public identity exposure | no VPN startup exposure |
| Uninstall/reinstall | normal state cleanup | private profile cleanup | private profile cleanup |

Required device evidence:

- Android version, device model, build fingerprint, profile name, package name,
version code, version name, source commit, artifact hash, and tester.
- Screenshots or command output showing VPN indicator behavior for both stealth
profiles.
- `adb shell dumpsys package <package>` output confirming package and component
exposure.
- `adb shell dumpsys connectivity` or equivalent evidence showing VPN/TUN state
for `stealth-vpn` and absence of VPN/TUN state for `stealth-novpn`.
- Traffic evidence from at least one non-excluded browser or test app for
`stealth-vpn`.
- Traffic evidence from each hostile excluded app listed for the profile.
- Local loopback probing evidence for the no-VPN proxy listener showing that
hostile apps cannot identify a stable unauthenticated proxy signature.

## Hostile app detection matrix

Each private profile owns a hostile-app list. The list is private support
metadata and must not be embedded in public release notes. QA must validate the
apps that are active for the profile and record the exact app versions tested.

| Detector class | Example observation | Stealth VPN expectation | Stealth No-VPN expectation |
| --- | --- | --- | --- |
| VPN state readers | `ConnectivityManager`, active network, VPN transport | excluded hostile apps do not see Lantern-routed network state | no VPN transport is exposed |
| TUN/interface readers | `/proc/net`, network interfaces, local routing table | excluded hostile apps do not observe Lantern TUN routing | no TUN interface is created |
| Package scanners | installed packages, launcher labels, signatures | rotated private package and labels are used | rotated private package and labels are used |
| Intent/app-link scanners | supported schemes, app links, exported components | public Lantern app links are absent | public Lantern app links and VPN components are absent |
| Permission scanners | requested permissions and metadata | only profile-approved permissions remain | VPN-related permissions are absent |
| Traffic probes | DNS, HTTP, TCP route checks from hostile app | hostile app traffic bypasses Lantern | hostile app traffic is not routed by a VPN |
| Non-hostile control app | browser or QA traffic probe | traffic routes through Lantern | only explicitly proxy-aware flows use Lantern |

Pass criteria:

- Every hostile app in the profile is tested on a clean install.
- Hostile apps excluded from `stealth-vpn` do not see Lantern-routed network
state and their traffic does not traverse Lantern.
- At least one non-excluded app still routes through Lantern in `stealth-vpn`.
- `stealth-novpn` never exposes Android VPN state, TUN interfaces, VPN
permissions, VPN service components, or VPN controls.
- Any detector result that differs from the private profile contract blocks the
release until the profile owner approves a documented exception.

## Release package rotation policy

Stealth artifacts are direct-distribution builds. They are not uploaded to
public app stores unless a profile explicitly says otherwise.

Package-name rotation rules:

- Rotate the private package name when a hostile app begins matching the current
package, label, icon, app-link surface, metadata, or other artifact
fingerprint.
- If a hostile app matches the signing certificate or APK signature lineage,
package-name rotation alone is not sufficient. Rotate to a profile-specific
signing lineage when policy and distribution constraints allow it; otherwise
the profile owner must approve a documented exception before release.
- Rotate when a distribution channel, support channel, or telemetry path leaks a
private profile identifier.
- Rotate when a profile owner intentionally changes the user population,
distribution partner, or risk model.
- Do not rotate only to ship routine bug fixes; unnecessary rotation increases
support and migration risk.

Release package requirements:

- Each private package name maps to one profile, one distribution channel, one
release train, and one support retention policy.
- Release notes for recipients must explicitly state feature sacrifices, update
behavior, migration behavior, and rollback expectations.
- Public release notes must not disclose private profile names, hostile-app
lists, distribution partners, or package-name mappings.
- The release owner must retain artifact hashes, signing certificate lineage,
source commit, CI run, build profile, package name, version code, version
name, distribution channel, and rollout window.
- Rollback means distributing the previous approved artifact for the same
private package. Cross-package rollback is a migration and requires support
approval.

## Manual update implications

Direct-distribution stealth builds cannot assume public store update behavior.

- Users installed on one private package name will not receive updates for a new
package name through Android package replacement. A package-name rotation is a
parallel install or manual migration unless a profile-specific migration path
is implemented.
- Automatic in-app update prompts must be reviewed for each profile because
update URLs, app labels, package names, and support copy can expose profile
identity.
- A direct APK update can replace an installed app only when package name and
signing lineage are compatible. AABs are not directly installable device
updates; they require store delivery or conversion to APK/split APK artifacts.
- Manual migration instructions must explain whether account login, Pro status,
private-server configuration, diagnostics, and local settings carry over.
- If account or purchase flows are disabled or hidden in a stealth profile, the
release notes must tell support how the user receives entitlement or recovery
help.
- Users must be told when a new stealth package is a replacement, a parallel
install, or a rollback.

## Support mapping and runbook

Support needs to map build IDs to private profiles without exposing that mapping
inside the artifact or in public issue trackers.

Retain the mapping in a private support source of truth with access limited to
release, QA, and support leads. The mapping must include:

- Private profile identifier.
- Package name.
- App label and icon set identifier.
- Version code and version name.
- Source commit and CI run.
- Build ID.
- Artifact hashes.
- Signing certificate lineage.
- Distribution channel and rollout window.
- Hostile-app list version.
- Feature sacrifices and disabled flows.
- Migration, rollback, and uninstall guidance.
- Support escalation owner.
Comment thread
reflog marked this conversation as resolved.

Support workflow:

1. Ask the user for the visible app label, version name, version code when
available, the distribution channel, and an installation date.
2. If diagnostics are available, collect the build ID and artifact channel but
do not ask the user to post private profile names publicly.
3. Resolve the build ID to the private profile in the private support mapping.
4. Use the profile runbook to answer feature availability, update, migration,
entitlement, and rollback questions.
5. Escalate to release and QA when a user report suggests detector exposure,
package collision, signing mismatch, or update failure.
6. Record any hostile-app detection report against the private profile and app
version tested.

## Acceptance criteria

This policy is only satisfied by a release candidate after the companion
implementation and CI PRs have merged. The document alone is not a release gate.

A stealth release candidate is acceptable only when all of the following are
true:

- Release automation builds the normal, `stealth-vpn`, and `stealth-novpn`
Android artifacts and runs automated static manifest/profile checks.
- Manual dynamic Android validation is complete for `stealth-vpn` and
`stealth-novpn`.
- Hostile excluded apps do not see Lantern-routed network state in
`stealth-vpn`.
- Non-excluded apps still route through Lantern in `stealth-vpn`.
- `stealth-novpn` exposes no VPN/TUN state, VPN controls, VPN permissions, or
VPN service components.
- Release notes describe feature sacrifices, direct-distribution behavior,
update behavior, package-name rotation implications, migration behavior, and
rollback expectations.
- Support can map build IDs to private profiles through private metadata without
exposing that mapping in the artifact or public channels.
- Artifact hashes, source commit, CI run, package name, version code, version
name, signing lineage, distribution channel, and rollout window are retained.
Loading