Sandboxing in iOS is a foundational security mechanism that isolates each app in its own secure environment. This isolation prevents unauthorized access to system resources and user data, ensuring that apps cannot interfere with one another.
For developers, understanding how to manage files and directories within this sandbox is crucial. It determines how and where persistent data, user-generated content, and temporary files are stored—directly affecting app functionality, user privacy, and compliance with App Store requirements.
The goal of this post is to demystify these concepts. By doing so, it empowers developers to build secure, reliable, and user-friendly applications that align with iOS’s strict security model while effectively leveraging the available file system APIs.
The Sandbox
In iOS, a sandbox is a security mechanism that restricts apps to their own designated area, preventing them from accessing files, resources, or data belonging to other apps or the system without explicit permission. This isolation ensures stability, security, and privacy for users.
Key Features of iOS Sandbox:
App Isolation
Each app runs in its own sandboxed environment with its own directory for files.
Apps cannot directly read or modify files from other apps.
Controlled Access to System Resources
Apps must request permissions (via entitlements or user consent) to access sensitive data like:
Contacts (
Contacts.framework
)Photos (
PHPhotoLibrary
)Location (
CoreLocation
)Camera & Microphone (
AVFoundation
)
File System Restrictions
Apps can only write files in their own sandbox directories, such as:
Documents/ (user-generated content, backed up by iTunes/iCloud)
Library/ (app support files, some backed up)
Caches/ (temporary files, can be purged by the system)
tmp/ (short-lived files, not backed up)
No Direct Hardware or Kernel Access
Apps interact with hardware (e.g., GPU, sensors) only through Apple’s frameworks.
No root-level system modifications are allowed (unlike jailbroken devices).
Inter-App Communication (Limited & Controlled)
Apps can share data only via:
URL Schemes (custom deep links like
myapp://
)App Groups (shared containers for related apps)
UIActivityViewController (share sheets)
Universal Clipboard (limited-time data sharing)
Why Does iOS Use a Sandbox?
Security: Prevents malware from spreading or tampering with other apps.
Privacy: Ensures apps access only permitted user data.
Stability: Crashes or bugs in one app don’t affect others.
Example: Accessing the Sandbox in Code
To get an app’s sandbox directory in Swift:
struct PeopleView: View {
@StateObject var viewModel = PeopleViewModel()
var body: some View {
NavigationView {
...
}.onAppear {
if let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
print("📂 Document Directory: \(documentsPath.path)")
}
}
}
}
The snippet is used to retrieve and print the path to the app’s Documents directory on an iOS or macOS device. Its parent folder is the sandbox root folder for your current app.
Exceptions to the Sandbox:
System apps (e.g., Messages, Mail) have broader privileges.
Jailbroken devices bypass sandbox restrictions (but violate Apple’s policies).
The sandbox is a core reason iOS is considered more secure than open platforms. Developers must work within its constraints while using Apple’s APIs for permitted interactions.
Our privacy is compromised the moment a malicious app can access another app’s sandbox. Theoretically, this kind of sandbox breach hasn’t been documented on iOS—at least not to my knowledge. However, the video “Broken Isolation – Draining Your Credentials from Popular macOS Password Managers“ by Wojciech Reguła (NSSpain 2024) demonstrates how, on macOS, a malicious app can gain access to the sandboxes of other apps that store user passwords—such as NordPass, KeePass, Proton Pass, and even 1Password.
Sandbox data container folders
Each iOS app has its own container directory with several subdirectories. Here’s a breakdown of the key folders and their purposes:
1. Documents
Path:
.../Documents/
Purpose: Stores user-generated content or data that should persist and be backed up to iCloud.
Example: Saved notes, PDFs, exported data.
Backup: ✅ Included in iCloud/iTunes backups.
Access: Read/Write.
2. Library
Path:
.../Library/
Purpose: Stores app-specific files and configuration data.
It has two main subfolders:
Preferences
.../Library/Preferences/
Stores user settings (e.g., using
UserDefaults
).Managed automatically by the system.
Caches
.../Library/Caches/
Used for data that can be regenerated (e.g., image cache).
Not backed up, and iOS may delete files here when space is low.
⚠️ Don’t store critical data here.
4. tmp
Path:
.../tmp/
Purpose: Temporary files your app doesn’t need to persist between launches.
Backup: ❌ Not backed up.
Auto-clean: iOS may clean this directory at any time.
Access: Read/Write.
Summary Table
Folder | Purpose | Persistent | Backed Up | iOS May Delete |
---|---|---|---|---|
App Bundle | App code and resources | ✅ | ✅ | ❌ |
Documents | User data/files | ✅ | ✅ | ❌ |
Library/Preferences | App settings (UserDefaults ) | ✅ | ✅ | ❌ |
Library/Caches | Cached data (non-critical) | ✅ | ❌ | ✅ |
tmp | Temporary files | ❌ | ❌ | ✅ |
Files operations
For this section, we have developed a sample iOS application that performs storage operations using files. The app displays an empty list with an “Add” button in the navigation bar. Each time the button is pressed, a new person is added to the list. The list of people serves as the model and is persisted as a .json
file.
When we deploy on simulator (or real device):

The component that handles files operations:
class FileManagerHelper {
static let shared = FileManagerHelper()
private let fileName = "people.json"
private var fileURL: URL {
let documents = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
return documents.appendingPathComponent(fileName)
}
func save(_ people: [Person]) {
do {
let data = try JSONEncoder().encode(people)
try data.write(to: fileURL)
} catch {
print("Error saving file: \(error)")
}
}
func load() -> [Person] {
do {
let data = try Data(contentsOf: fileURL)
let people = try JSONDecoder().decode([Person].self, from: data)
return people
} catch {
print("Error reading file: \(error)")
return []
}
}
func deleteFile() {
do {
try FileManager.default.removeItem(at: fileURL)
} catch {
print("Error deleting file: \(error)")
}
}
}
FileManagerHelper
is a singleton utility that manages saving, loading, and deleting a JSON file named people.json
in the app’s documents directory. It provides methods to encode an array of Person
objects into JSON and save it to disk (save
), decode and return the array from the saved file (load
), and remove the file entirely (deleteFile
). It handles errors gracefully by catching exceptions and printing error messages without crashing the app.
Conclusions
With that post, I just intended to give you an overview and demonstrate how easy it is to deal with file persistence as well.
You can find source code used for writing this post in following repository.
References
- App Sandbox
Apple Developer Documentation
- Broken isolation - draining your credentials ,,, - Wojciech Reguła
NSSpain 2024 speech