Skip to content

Add per-app access control for remote automation - #6206

Open
eliotcougar wants to merge 9 commits into
2dust:masterfrom
eliotcougar:codex/remote-control-access
Open

eliotcougar wants to merge 9 commits into
2dust:masterfrom
eliotcougar:codex/remote-control-access

Conversation

@eliotcougar

@eliotcougar eliotcougar commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Problem and motivation

The exported Locale/Tasker receiver currently accepts start/stop commands from other installed apps without authorization. An unrelated app can stop the user's VPN; it does not need to share v2rayNG's signing key.

There is also a separate configuration-import issue described in #6231: external shared text and install links can silently replace profiles in the default group and change the selected profile. This update protects that entry point with the same automation grants. The independent reproduction used harmless loopback profiles and verified replacement and selection changes, not live traffic interception.

I have received a confidential report that two government-affiliated apps in Russia already detect v2rayNG and send its stop command, disabling the user's VPN. The source cannot be disclosed. The independent reproduction verifies the exposed control mechanism, not the app-specific attribution.

Leaving this channel open makes it easy for more domestic apps to follow suit as they face government demands to restrict VPN users. RBC reported that the Ministry of Digital Development instructed major platforms to restrict access for users with active VPNs, with April 15, 2026 as the target date. This provides public context for the risk of wider adoption; it does not establish the reported stop-command behavior.

Changes

  • Add Settings → Advanced → Automation app access, with the explanation Allow other apps to control v2rayNG, using the existing app picker. Access is denied by default, including after migration, and is independent of per-app proxy routing. Leaving the picker saves changes; unchanged selections do not trigger package lookups or storage writes.
  • Bind grants to each app's signing certificates and installation timestamp. Store grants and random per-app capabilities in a separate, device-local MMKV directory excluded from system and configuration backups. Serialize updates with a stable process-local monitor and MMKV's cross-process lock.
  • Authenticate broadcasts using Android's platform-provided sender UID when available (API 34+ with identity sharing), otherwise require the saved per-app capability. A caller-supplied package name is never sufficient.
  • Authorize the Tasker editor through Activity.getCallingPackage() before loading profiles and before returning a capability. Preserve editor state, disable controls while loading, and distinguish a missing profile selection from denied authorization.
  • Validate the action, command fields, and explicit profile GUID before dispatch. Keep the widget receiver private to close the alternative direct-broadcast path; launcher widgets continue using the existing immutable PendingIntent.
  • Protect external configuration imports with the same grants and authorization manager. Authenticated, allowed apps retain unattended replacement for automated updates. Other callers must confirm before an import or subscription download; confirmed one-off imports append instead of replacing the default group. Cancelling makes no change, and confirming does not grant ongoing access.
  • Identify configuration senders through the platform launcher UID on API 34+, or the existing capability when that identity is unavailable. Do not trust referrers, claimed package names, or getCallingPackage() for this operation: result forwarding can make the latter identify the result recipient rather than the sender.
  • Keep pending confirmation and import work in a ViewModel across activity recreation, prevent duplicate submissions, decode only the outer link wrapper, and remove raw incoming-configuration logging.
  • Update all nine maintained locale catalogs and document both protocols in docs/remote-control.md. No new grant store, native changes, androidTest sources, or generated build artifacts are included.

Compatibility

The public integration entry points remain exported so independently signed automation hosts can still use them. Making them private or requiring v2rayNG's signing key would break those integrations.

Users must allow their automation host, then reopen and save existing v2rayNG plugin actions to receive a capability. Legacy anonymous Send Intent broadcasts are rejected. Import clients can share their platform identity on API 34+ or supply the saved capability bundle; without authentication they take the confirmation-and-append path. Approved automation apps can still replace configurations for automated updates.

Saving a revocation invalidates the host's capabilities for both control and imports; granting access again issues a new token. Exporting a task containing a capability delegates that access, so hosts must treat it as a secret. VPN consent and Android background-execution restrictions still apply.

Validation

On 60908cee8:

  • :app:testPlaystoreDebugUnitTest: 105 tests passed, including 18 new import parser/ViewModel tests.
  • :app:compilePlaystoreDebugKotlin, :app:assemblePlaystoreDebug -PABI_FILTERS=x86_64, and :app:assemblePlaystoreRelease -PABI_FILTERS=x86_64: passed.
  • XML parsing, all three new keys in all nine locale catalogs, and git diff --check: passed.
  • The debug APK signer matches the previously installed APK, and all three required x86_64 native libraries are present. Existing native inputs were preserved, not rebuilt.
  • Pixel 9 Pro/API 37: unauthorized import rejection and authorized replacement were repeated with the exact committed APK.

The broader API 37 import checks covered untrusted shared text and install links, Cancel via keyboard/D-pad, confirmation across rotation, confirmed append preserving the selected profile, replacement by an independently signed allowed host, capability-based replacement without shared launcher identity, and forged/revoked capabilities falling back to confirmation. Only line wrapping and JVM-test synchronization changed afterward. Grant setup/revocation used the existing manager; the settings row and picker were opened, but their full interaction matrix was not repeated.

Earlier branch revisions also exercised API 33/API 37 start/stop authorization, capability issuance and revocation/regrant, invalid GUID rejection, backup exclusion, widgets, picker persistence, missing-profile editing, activity recreation, proxy-only start/stop, and MMKV serialization across four threads. Those earlier device scenarios were not all repeated on this revision.

Full :app:lintPlaystoreDebug ran before the final formatting/test-only adjustment and reported four unchanged upstream NewApi errors in RootManager.kt, RootShell.kt, and the day/night themes. No findings referenced the new import files or strings. The final release build's lint-vital checks passed; full lint is not clean.

Not run

  • Actual Tasker/Locale/MacroDroid interoperability: independent protocol-compatible test hosts were used.
  • API 24–33 import device runs, API 24–27 legacy signature lookup, and physical devices: this import update was tested on API 37; older start/stop checks are identified above.
  • Real network-backed subscription updates, live VPN traffic interception, and root-mode end-to-end: import fixtures used loopback profiles.
  • Spoken TalkBack and the full locale/theme/accessibility/IME matrix, actual app reinstall/signer replacement, process-death restoration, and forwarded-result-chain execution: only the focused checks above were performed.
  • Fresh full lint after the final formatting/test-only adjustment, other ABIs, and a native source-provenance rebuild.

Fixes #6231 Silent configuration import.

@eliotcougar
eliotcougar marked this pull request as ready for review September 7, 2026 15:01
@2dust

2dust commented Sep 11, 2026

Copy link
Copy Markdown
Owner

非常感谢
虽然是为了安全性考虑,但是这样也造成了人为的限制,这不是我们想要的。
本来用自动化的用户已经非常少了,如果加了此功能,会导致不了解的用户无法使用

@eliotcougar

Copy link
Copy Markdown
Contributor Author

非常感谢 虽然是为了安全性考虑,但是这样也造成了人为的限制,这不是我们想要的。 本来用自动化的用户已经非常少了,如果加了此功能,会导致不了解的用户无法使用

It's exactly backwards. The feature that is used by a small number of people makes everybody else vulnerable.

It is extremely uncomfortable when an unrelated domestic app can have an update any moment that will make it possible to disable your VPN whenever it wants. And it already happens.

Existing users who need automation will only have to check what automation apps can access v2rayNG.

We can also try to preselect the most popular known automation apps to make the update more seamless.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Security: requesting a private disclosure channel (exported component allows silent config hijack)

2 participants