Skip to content

fix(tauri): enforce ACL for remote origins even without AppManifest#15266

Merged
FabianLars merged 1 commit into
tauri-apps:devfrom
mtsgrd:restrict-remote-ipc-without-acl
May 4, 2026
Merged

fix(tauri): enforce ACL for remote origins even without AppManifest#15266
FabianLars merged 1 commit into
tauri-apps:devfrom
mtsgrd:restrict-remote-ipc-without-acl

Conversation

@mtsgrd

@mtsgrd mtsgrd commented Apr 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Custom (non-plugin) commands registered via invoke_handler bypass ACL checks entirely when no AppManifest is configured in build.rs. This means if a webview navigates away from the bundled frontend to a remote URL, that remote page can invoke any custom IPC command without restriction.

This PR adds || !is_local to the existing ACL enforcement condition in Webview::on_message(), so that requests from non-local origins always go through resolve_access():

// Before:
if (plugin_command.is_some() || has_app_acl_manifest)

// After:
if (plugin_command.is_some() || has_app_acl_manifest || !is_local)

Behavior after this change

Origin AppManifest configured? Result
Local No Unchanged - ACL skipped, commands work (permissive default)
Local Yes Unchanged - ACL enforced as before
Remote No Changed - ACL enforced, no entries found, rejected
Remote Yes, with explicit remote capability Unchanged - resolve_access matches, allowed

Breaking change

Apps that intentionally allow remote origins to call custom commands without configuring an AppManifest will need to add an AppManifest with explicit remote capabilities to restore the previous behavior. This is expected to be uncommon, since most apps either use local-only webviews or already configure an AppManifest when working with remote content.

Test plan

  • Added remote_origin_blocked_for_custom_commands_without_app_manifest unit test that verifies:
    • A custom command from https://some-hostile-domain.com is rejected
    • The same command from tauri://localhost is not blocked by the remote-origin guard
  • All 53 existing tauri lib tests continue to pass

@mtsgrd
mtsgrd requested a review from a team as a code owner April 20, 2026 08:47
@FabianLars
FabianLars requested a review from tweidinger April 20, 2026 08:59
@github-actions

github-actions Bot commented Apr 20, 2026

Copy link
Copy Markdown
Contributor

Package Changes Through 7a2672f

There are 11 changes which include tauri with minor, @tauri-apps/api with minor, tauri-build with minor, tauri-macos-sign with patch, tauri-bundler with minor, @tauri-apps/cli with minor, tauri-cli with minor, tauri-runtime with minor, tauri-runtime-wry with minor, tauri-utils with minor, tauri-plugin with minor

Planned Package Versions

The following package releases are the planned based on the context of changes in this pull request.

package current next
@tauri-apps/api 2.10.1 2.11.0
tauri-utils 2.8.3 2.9.0
tauri-macos-sign 2.3.3 2.3.4
tauri-bundler 2.8.1 2.9.0
tauri-runtime 2.10.1 2.11.0
tauri-runtime-wry 2.10.1 2.11.0
tauri-codegen 2.5.5 2.5.6
tauri-macros 2.5.5 2.5.6
tauri-plugin 2.5.4 2.6.0
tauri-build 2.5.6 2.6.0
tauri 2.10.3 2.11.0
@tauri-apps/cli 2.10.1 2.11.0
tauri-cli 2.10.1 2.11.0

Add another change file through the GitHub UI by following this link.


Read about change files or the docs at github.com/jbolda/covector

tweidinger
tweidinger previously approved these changes Apr 20, 2026

@tweidinger tweidinger left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just reviewed the code without testing. Breaking should be fine since unintentional and security related. LGTM and thanks!
Maybe next time via the disclosure feature though 😅

@tweidinger

Copy link
Copy Markdown
Contributor

Please fix tests/fmt/clippy before merge @mtsgrd or @FabianLars . I'm on mobile atm.

@mtsgrd
mtsgrd force-pushed the restrict-remote-ipc-without-acl branch from 6c38293 to 2476cae Compare April 20, 2026 15:06
Custom (non-plugin) commands registered via `invoke_handler` currently
bypass ACL checks entirely when no `AppManifest` is configured in
`build.rs`. This means if a webview navigates to a remote URL, that
remote content can invoke any custom IPC command without restriction.

Add `|| !is_local` to the existing ACL enforcement condition so that
requests from non-local origins always go through `resolve_access()`.

Behavior changes:
- Local origin + no AppManifest: unchanged (permissive, commands work)
- Remote origin + no AppManifest: now enforced, rejected (no ACL entries)
- Remote origin + explicit `remote` capability: allowed (resolve_access
  matches the configured capability)

This is a **breaking change** for apps that rely on remote origins being
able to call custom commands without configuring an AppManifest. Such
apps will need to add an AppManifest with explicit `remote` capabilities
to restore the previous behavior.

Normalize test URLs to tauri scheme and tidy formatting

Update test expectations to use the tauri://localhost URL scheme instead
of http://tauri.localhost to reflect the app's custom URL scheme
handling. Also reformat some test code for readability by collapsing and
simplifying builder expressions and replace a match-based error check
with an if-let to make the intent clearer and reduce indentation.
@mtsgrd
mtsgrd force-pushed the restrict-remote-ipc-without-acl branch from 2476cae to 7a2672f Compare April 21, 2026 05:34
@mtsgrd

mtsgrd commented Apr 21, 2026

Copy link
Copy Markdown
Contributor Author

@tweidinger thanks for reviewing! Let me know if there's anything else I can do?

Maybe next time via the disclosure feature though 😅

Indeed, that would have been better.

@mtsgrd

mtsgrd commented Apr 27, 2026

Copy link
Copy Markdown
Contributor Author

@tweidinger how can we move this pr forward?

@Legend-Master Legend-Master added this to the 2.11 milestone Apr 30, 2026
@Qodo-Free-For-OSS

Copy link
Copy Markdown

Hi, The new unit test hard-codes tauri://localhost as a local origin, but on Windows/Android local origins are represented as http(s)://tauri.localhost, so the test’s “local” request can be classified as remote and incorrectly blocked.

Severity: action required | Category: correctness

How to fix: Use platform-specific local URL

Agent prompt to fix - you can give this to your LLM of choice:

Issue description

remote_origin_blocked_for_custom_commands_without_app_manifest() uses tauri://localhost as the local origin. On Windows/Android, Tauri local origins are represented as http(s)://tauri.localhost, so the test can misclassify the request as remote.

Issue Context

Webview::is_local_url() has platform-specific logic for custom protocols on Windows/Android.

Fix Focus Areas

  • crates/tauri/src/webview/mod.rs[2344-2397]

Suggested change

In the test, build the local URL similarly to the doctest helpers:

let local_url: url::Url = if cfg!(any(windows, target_os = "android")) {
  "http://tauri.localhost"
} else {
  "tauri://localhost"
}.parse().unwrap();

Use local_url for the local request.

We noticed a couple of other issues in this PR as well - happy to share if helpful.


Found by Qodo code review

@FabianLars FabianLars modified the milestones: 2.11, 2.12 May 1, 2026
@tweidinger

Copy link
Copy Markdown
Contributor

Hey, I am traveling atm. Poking @FabianLars for getting this merged in dev.

@FabianLars
FabianLars merged commit 1b26769 into tauri-apps:dev May 4, 2026
19 checks passed
mtsgrd added a commit to gitbutlerapp/gitbutler that referenced this pull request May 6, 2026
The `forge_provider` backend command was missing from the Tauri ACL
permissions list (fixed in an earlier commit), which meant the query
silently failed for all desktop users since 0.19.4. Regular github.com
users were unaffected because the frontend had redundant domain-matching
logic that masked the failure. GitHub Enterprise Server users (e.g.
github.ourinternaldomain.net) had no such fallback and lost their forge
integration (PR button, reviews, etc.).

ACLs are going away soon (tauri-apps/tauri#15266), so rather than
papering over backend failures with frontend fallbacks, this commit:

- Removes the redundant frontend domain-matching in forgeFactory that
  was masking the broken backend query
- Simplifies forge type resolution to: backend detection → manual
  override → default
- Adds Rust tests confirming GHES subdomain detection works for both
  SSH and HTTPS URL forms
mtsgrd added a commit to gitbutlerapp/gitbutler that referenced this pull request May 6, 2026
The `forge_provider` backend command was missing from the Tauri ACL
permissions list (fixed in an earlier commit), which meant the query
silently failed for all desktop users since 0.19.4. Regular github.com
users were unaffected because the frontend had redundant domain-matching
logic that masked the failure. GitHub Enterprise Server users (e.g.
github.ourinternaldomain.net) had no such fallback and lost their forge
integration (PR button, reviews, etc.).

ACLs are going away soon (tauri-apps/tauri#15266), so rather than
papering over backend failures with frontend fallbacks, this commit:

- Removes the redundant frontend domain-matching in forgeFactory that
  was masking the broken backend query
- Simplifies forge type resolution to: backend detection → manual
  override → default
- Adds Rust tests confirming GHES subdomain detection works for both
  SSH and HTTPS URL forms
mtsgrd added a commit to gitbutlerapp/gitbutler that referenced this pull request May 6, 2026
The `forge_provider` backend command was missing from the Tauri ACL
permissions list (fixed in an earlier commit), which meant the query
silently failed for all desktop users since 0.19.4. Regular github.com
users were unaffected because the frontend had redundant domain-matching
logic that masked the failure. GitHub Enterprise Server users (e.g.
github.ourinternaldomain.net) had no such fallback and lost their forge
integration (PR button, reviews, etc.).

ACLs are going away soon (tauri-apps/tauri#15266), so rather than
papering over backend failures with frontend fallbacks, this commit:

- Removes the redundant frontend domain-matching in forgeFactory that
  was masking the broken backend query
- Simplifies forge type resolution to: manual override → backend
  detection → default
- Adds Rust tests confirming GHES subdomain detection works for both
  SSH and HTTPS URL forms
qiin2333 added a commit to qiin2333/sunshine-control-panel that referenced this pull request May 10, 2026
Root cause of v0.3.22 release bug ('Command install_vigem_driver not
allowed by ACL'): src-tauri/Cargo.lock was gitignored, so CI pulled
tauri 2.11.x while local builds had older tauri without strict ACL
enforcement. Tauri 2.11 (PR tauri-apps/tauri#15266) enforces capability
ACL for IPC calls from any remote origin even without an explicit
AppManifest; previously custom (non-plugin) commands bypassed ACL
entirely. Sunshine GUI loads its real UI from https://localhost:47990
which tauri classifies as remote (the URL is not relative to the
packaged frontendDist), so every custom command got rejected.

This commit ships both the dep-hygiene fix (so CI is reproducible) and
the proper ACL adaptation. Supersedes the previous rust-toolchain.toml
pin workaround (v0.3.23) which was based on a misdiagnosis (rustc
version drift) - the toolchain file is removed here.

Changes:

* Track src-tauri/Cargo.lock (binary crate). Adds .gitignore comment
  documenting why. cargo update bumped tauri 2.10.3 -> 2.11.1.
* Remove src-tauri/rust-toolchain.toml (obsolete v0.3.23 workaround).
* build.rs now parses src/main.rs to extract every command listed in
  the tauri::generate_handler block at compile time, then calls
  AppManifest::commands so tauri-build auto-generates allow-/deny-
  permission TOMLs under permissions/autogenerated/ (gitignored). It
  also writes capabilities/app-commands.generated.json (gitignored)
  binding every allow-* permission to sunshine backend remote URLs
  (localhost and 127.0.0.1, both schemes). Tauri auto-loads
  ./capabilities/**/* alongside the inline default capability in
  tauri.conf.json, which keeps managing windows / plugin perms /
  external whitelist URLs by hand.
* Rename windows::_webview_heartbeat -> webview_heartbeat to avoid the
  ugly auto-generated allow--webview-heartbeat double-dash slug.

Adding a new tauri command now requires only a line in main.rs;
build.rs regenerates ACL on next compile - single source of truth.
mtsgrd added a commit to gitbutlerapp/gitbutler that referenced this pull request May 28, 2026
tauri 2.11.1 includes tauri-apps/tauri#15266, which enforces ACL for
non-local origins even when no AppManifest is configured. The hand-rolled
`allow-custom-commands` permission existed solely to flip
`has_app_acl_manifest` so custom command invocations from local origins
would not bypass the ACL gate (and, by side effect, also be checked for
remote origins).

With the upstream fix in place, remote origins are checked regardless,
and local origins remain permissive without an AppManifest. The
capability declares `local: true`, so the workaround is no longer
load-bearing.

Drop `permissions/default.toml`, the `allow-custom-commands` entry from
`capabilities/main.json`, and the explicit `try_build(... AppManifest ...)`
plumbing in `build.rs` -- back to plain `tauri_build::build()`.
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.

5 participants