Skip to content

feat(fs): access security scoped resources on iOS#3185

Merged
FabianLars merged 14 commits intov2from
feat/security-scoped-ios
Mar 4, 2026
Merged

feat(fs): access security scoped resources on iOS#3185
FabianLars merged 14 commits intov2from
feat/security-scoped-ios

Conversation

@lucasfernog
Copy link
Copy Markdown
Member

Add iOS security-scoped resource support

Summary

Adds support for handling security-scoped URLs on iOS, enabling access to files outside the app's sandbox (e.g., files selected via file picker).

Changes

  • iOS security-scoped resource handling: The open() method in ios.rs now automatically calls startAccessingSecurityScopedResource() when opening files via file:// URLs
  • New API: Added stopAccessingSecurityScopedResource() command and guest-js binding to manually stop accessing security-scoped resources when done
  • Path resolution: Updated resolve_path() to start accessing security-scoped resources early in the path resolution process
  • Code organization: Split mobile.rs into separate ios.rs and android.rs files for better maintainability
  • Dependencies: Added objc2-foundation dependency for iOS to interact with NSURL APIs

Platform-specific behavior

  • iOS: Automatically handles security-scoped URLs; provides API to stop access
  • Android: No changes (continues using content URI handling)
  • Desktop: No changes (no-op for security-scoped resource API)

Breaking changes

None.

@lucasfernog lucasfernog requested a review from a team as a code owner December 29, 2025 11:01
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Dec 29, 2025

Package Changes Through 526e2e1

There are 10 changes which include fs with minor, fs-js with minor, http with patch, http-js with patch, updater with patch, updater-js with patch, dialog with minor, dialog-js with minor, deep-link with patch, deep-link-js with patch

Planned Package Versions

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

package current next
api-example 2.0.41 2.0.42
api-example-js 2.0.37 2.0.38
deep-link-example-js 2.2.10 2.2.11
deep-link 2.4.7 2.4.8
deep-link-js 2.4.7 2.4.8
fs 2.4.5 2.5.0
fs-js 2.4.5 2.5.0
dialog 2.6.0 2.7.0
dialog-js 2.6.0 2.7.0
http 2.5.7 2.5.8
http-js 2.5.7 2.5.8
persisted-scope 2.3.5 2.3.6
single-instance 2.4.0 2.4.1
updater 2.10.0 2.10.1
updater-js 2.10.0 2.10.1

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


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

Comment thread plugins/fs/src/ios.rs Outdated
// the file handle needs to remain accessible while the File is in use.
// The access will be automatically stopped when the app is backgrounded or terminated.
unsafe {
let _ = ns_url.startAccessingSecurityScopedResource();
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.

The return value indicates whether the URL is actually a security-scoped resource. Ignoring it means:

  • No way to know if the call was necessary
  • No error handling if the resource can't be accessed

Recommendation: Check the return value and potentially log/warn if it returns false for a URL that was expected to be security-scoped.

Comment thread plugins/fs/src/ios.rs
// This is required for files outside the app's sandbox (e.g., from file picker)
// Note: We don't call stopAccessingSecurityScopedResource here because
// the file handle needs to remain accessible while the File is in use.
// The access will be automatically stopped when the app is backgrounded or terminated.
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.

This comment is not entirely correct. Apple's documentation states: "You must balance every call to this method with a call to stopAccessingSecurityScopedResource()."

While the OS will eventually reclaim resources on termination, this doesn't constitute proper resource management for several reasons:

  • Resource exhaustion — The system limits how many security-scoped resources can be active
  • Long-running apps — Apps that stay active may accumulate leaked handles.
  • Bookmark invalidation — Improperly managed access can contribute to security-scoped bookmark staleness.

Recommendation: Consider implementing RAII-style cleanup or at minimum document the requirement to call stopAccessingSecurityScopedResource() explicitly.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

direct cleanup can't be implemented without breaking changes in this case :/ we'll have to update the docs

Copy link
Copy Markdown
Member

@FabianLars FabianLars Mar 2, 2026

Choose a reason for hiding this comment

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

direct cleanup can't be implemented without breaking changes in this case

How would these look like? Mind adding a TODO comment (or issue) so we can follow up in v3? Edit: or would it be too ugly and therefore won't be implemented?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Comment thread plugins/fs/src/commands.rs Outdated
Comment thread plugins/fs/src/ios.rs
@velocitysystems
Copy link
Copy Markdown
Contributor

@onehumandev Can you please take a look at this and compare with changes in PR 3136?

@onehumandev
Copy link
Copy Markdown
Contributor

c. @velocitysystems Short version: AFAIK, for startAccessingSecurityScopedResource to hav an effect, FilePickerController.swift](https://github.com/tauri-apps/plugins-workspace/blob/31415effdf5a9ced19934a681cb044a732174088/plugins/dialog/ios/Sources/FilePickerController.swift#L122C1-L123C59) will need to be modified to not automatically save a copy of the selected file to the app sandbox.

Long version:
Currently, the current implementation of the FilePickerController.swift, copies any file selected from the document picker to the app sandbox.

This means that startAccessingSecurityScopedResource is not required for any file that is opened with the current implementation, at the expense of duplicated files.

For startAccessingSecurityScopedResource to have an effect, an option would need to be included for files that are opened by the file picker to not copy the file to the sandbox, but rather allow access with startAccessingSecurityScopedResource.

In other words, it would essentially be an "open mode"; either "copy" (the current production behavior) or "scoped" (which would include a call to startAccessingSecurityScopedResource).

PR #3136 includes these changes; however, if that is not the path the project would like to take, I am happy to help with whatever alternative is preferred :)

Further explanation, from comment in above linked PR:


    /// In which cases do we need to save a copy of a file selected by a user to the app sandbox?
	/// In short, only when the file is **not** selected using UIDocumentPickerDelegate.
	/// For the rest of the cases, we need to write a copy of the file to the app sandbox. 
	/// 
	/// For PHPicker (used for photos and videos), `NSItemProvider.loadFileRepresentation` returns a temporary file URL that is deleted after the completion handler.
	/// The recommendation is to [Persist](https://developer.apple.com/documentation/foundation/nsitemprovider/2888338-loadfilerepresentation) the file by moving/copying
	/// it to your app’s directory within the completion handler.
	/// 
	/// If available, `loadInPlaceFileRepresentation` can open a file in place; Photos assets typically do not support true in-place access,
	/// so fall back to persisting a local file.
	/// Ref: https://developer.apple.com/documentation/foundation/nsitemprovider/2888335-loadinplacefilerepresentation
	/// 
	/// For UIDocumentPicker, prefer “open in place” and avoid copying when possible.
	/// Ref: https://developer.apple.com/documentation/uikit/uidocumentpickerviewcontroller

@lucasfernog
Copy link
Copy Markdown
Member Author

@onehumandev I love the dialog plugin changes you suggested. What I don't really agree with is adding startFileAccess and endFileAccess to the dialog plugin - the same mechanism will be required to e.g. read file association input. That's why I decided to make some changes to the filesystem plugin instead.

If you can drop the file access APIs from your PR I can merge it to include the changes required to not create a file copy on the dialog APIs.

onehumandev added a commit to onehumandev/plugins-workspace that referenced this pull request Jan 6, 2026
On iOS, when trying to access a file that exists outside of the app sandbox, one of 2 things need to happen to be able to perform any operations on said file:

* A copy of the file needs to be made to the internal app sandbox
* The method startAccessingSecurityScopedResource needs to be called.

Previously, a copy of the file was always being made when a file was selected through the picker dialog.

While this did ensure there were no file access exceptions when reading from the file, it does not scale well for large files.

To resolve this, we now support `fileAccessMode`, which allows a file handle to be returned without copying the file to the app sandbox.

This MR only supports this change for iOS; MacOS has a different set of needs for security scoped resources.

See discussion in #3716 for more discussion of the difference between iOS and MacOS.
See MR tauri-apps#3185 to see how these scoped files will be accessible using security scoping.
onehumandev added a commit to onehumandev/plugins-workspace that referenced this pull request Jan 6, 2026
On iOS, when trying to access a file that exists outside of the app sandbox, one of 2 things need to happen to be able to perform any operations on said file:

* A copy of the file needs to be made to the internal app sandbox
* The method startAccessingSecurityScopedResource needs to be called.

Previously, a copy of the file was always being made when a file was selected through the picker dialog.

While this did ensure there were no file access exceptions when reading from the file, it does not scale well for large files.

To resolve this, we now support `fileAccessMode`, which allows a file handle to be returned without copying the file to the app sandbox.

This MR only supports this change for iOS; MacOS has a different set of needs for security scoped resources.

See discussion in #3716 for more discussion of the difference between iOS and MacOS.
See MR tauri-apps#3185 to see how these scoped files will be accessible using security scoping.
@onehumandev
Copy link
Copy Markdown
Contributor

@lucasfernog Sounds good; updated #3136 in accord with latest comments

@velocitysystems
Copy link
Copy Markdown
Contributor

Thanks @lucasfernog @onehumandev. I think this is a great solution all around. 🎉

So...We have new APIs for handling security scoped resources available in the fs plugin and these can be used for scoped access in the dialog plugin where required. Are any further changes required in the dialog plugin @onehumandev to make these work together?

lucasfernog added a commit that referenced this pull request Jan 14, 2026
* feat(dialog) - Support fileAccessMode for open dialog (#3030)

On iOS, when trying to access a file that exists outside of the app sandbox, one of 2 things need to happen to be able to perform any operations on said file:

* A copy of the file needs to be made to the internal app sandbox
* The method startAccessingSecurityScopedResource needs to be called.

Previously, a copy of the file was always being made when a file was selected through the picker dialog.

While this did ensure there were no file access exceptions when reading from the file, it does not scale well for large files.

To resolve this, we now support `fileAccessMode`, which allows a file handle to be returned without copying the file to the app sandbox.

This MR only supports this change for iOS; MacOS has a different set of needs for security scoped resources.

See discussion in #3716 for more discussion of the difference between iOS and MacOS.
See MR #3185 to see how these scoped files will be accessible using security scoping.

* fmt, clippy

* use enum

---------

Co-authored-by: Lucas Nogueira <lucas@tauri.app>
Copy link
Copy Markdown
Contributor

@velocitysystems velocitysystems left a comment

Choose a reason for hiding this comment

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

Looks good @lucasfernog. Please see my above comment. What further changes are required in the dialog plugin to make this work with this functionality in the fs plugin?
cc: @onehumandev

@onehumandev
Copy link
Copy Markdown
Contributor

Looks good @lucasfernog. Please see my above comment. What further changes are required in the dialog plugin to make this work with this functionality in the fs plugin? cc: @onehumandev

I will deep dive into this tomorrow and Friday and make sure everything works as expected, and if there are any wires needing connecting.

@lucasfernog
Copy link
Copy Markdown
Member Author

Looks good @lucasfernog. Please see my above comment. What further changes are required in the dialog plugin to make this work with this functionality in the fs plugin? cc: @onehumandev

@velocitysystems I tried this branch with #3136 and seems like everything is working fine now. Should be good to go.

@onehumandev
Copy link
Copy Markdown
Contributor

Looks good @lucasfernog. Please see my above comment. What further changes are required in the dialog plugin to make this work with this functionality in the fs plugin? cc: @onehumandev

@velocitysystems I tried this branch with #3136 and seems like everything is working fine now. Should be good to go.

@velocitysystems Confirmed; I tested it locally as well with our current use cases, and all works as expected.

@FabianLars FabianLars added the plugin: fs Includes former "fs-extra" and "fs-watch" plugins label Jan 20, 2026
mrquantumoff pushed a commit to mrquantumoff/plugins-workspace that referenced this pull request Jan 26, 2026
… (tauri-apps#3136)

* feat(dialog) - Support fileAccessMode for open dialog (tauri-apps#3030)

On iOS, when trying to access a file that exists outside of the app sandbox, one of 2 things need to happen to be able to perform any operations on said file:

* A copy of the file needs to be made to the internal app sandbox
* The method startAccessingSecurityScopedResource needs to be called.

Previously, a copy of the file was always being made when a file was selected through the picker dialog.

While this did ensure there were no file access exceptions when reading from the file, it does not scale well for large files.

To resolve this, we now support `fileAccessMode`, which allows a file handle to be returned without copying the file to the app sandbox.

This MR only supports this change for iOS; MacOS has a different set of needs for security scoped resources.

See discussion in #3716 for more discussion of the difference between iOS and MacOS.
See MR tauri-apps#3185 to see how these scoped files will be accessible using security scoping.

* fmt, clippy

* use enum

---------

Co-authored-by: Lucas Nogueira <lucas@tauri.app>
FabianLars
FabianLars previously approved these changes Mar 2, 2026
FabianLars
FabianLars previously approved these changes Mar 2, 2026
@FabianLars FabianLars merged commit f5f6806 into v2 Mar 4, 2026
156 of 157 checks passed
@FabianLars FabianLars deleted the feat/security-scoped-ios branch March 4, 2026 12:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

plugin: fs Includes former "fs-extra" and "fs-watch" plugins

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants