KeyMob
All articles

What is actually inside an iOS app sandbox

3 min readApp dataDebugging

Every iOS app gets a container: a private directory tree that the app can read and write and no other app can see. Most bugs about data that will not clear, settings that survive an uninstall, or files that disappear overnight come from storing something in the wrong part of it.

The directories

Documents/: content the user created and would not want to lose. Included in iCloud and computer backups. Visible in the Files app if the app enables it. Never purged by the system.

Library/Application Support/: data your app needs that the user did not create directly. Backed up. Never purged. This is where a database belongs.

Library/Preferences/: UserDefaults. Backed up. For small values only, not for storage.

Library/Caches/: data that can be regenerated. Not backed up. The system purges it under storage pressure, without warning or notification. This is the right place for downloaded images and API responses, and the wrong place for anything you cannot download again.

tmp/: scratch space. Not backed up, purged aggressively, and can disappear between launches. Do not store anything here that must survive a single run.

What this means in practice

A cache in Documents/ never clears. If users report that storage keeps growing and clearing the cache does nothing, check where the cache is stored. In Documents/ it is not a cache; it is permanent data with a misleading name. It is also backed up, which makes the user’s iCloud backup much larger.

A database in Caches/ disappears. Not often, and rarely during testing, which is why this bug reaches production. It shows up as “the app forgot everything” on devices that ran low on storage.

Anything in tmp/ can be gone. A download resumed from tmp/ after a relaunch may find nothing there.

Documents/ is backed up, so keep it small. A 4 GB media cache in Documents/ makes the user’s backup 4 GB larger than necessary, and restoring the phone takes much longer.

Reading the container

The fastest way to settle these questions is to look. Over a trusted USB connection you can browse an app’s container, decrypt its stored data and download files.

Each of these questions takes about a minute to answer:

  • Is the cache actually in Caches/, or somewhere permanent?
  • Did the app write the new response, or is it serving an old one it never replaced?
  • Is the database file the expected size, or has a table been growing without cleanup?
  • Did the migration produce the expected schema?
  • Is the downloaded asset complete, or truncated?

Without access to the container, each one means adding a log line and shipping another build.

Reproducing a user’s state

A useful technique in this area: export a user’s container from their device and load it into a build on your own device. A bug that occurs “only on one account” can then be reproduced at your desk.

This technique also requires the most care, which leads to the most important part of this article.

This is personal data

An app container holds messages, photos, location history, tokens and anything else the app has stored. Exporting one is not a technical operation without consequences.

  • Do it only on devices you own or are explicitly authorized to examine.
  • Tell the owner what you are taking and why, before you take it.
  • Take only what answers the question: one file rather than the whole container, unless you really need the whole container.
  • Delete the export when the ticket is closed. An exported container left on a laptop is a data-security risk.
  • Never attach one to a shared ticket without removing personal data first.

Teams that get this wrong rarely do so on purpose. Usually they export a container to debug something, finish the work, and forget the folder exists.

What is not in the container

The keychain. Credentials, tokens and other properly stored secrets live outside the container and are not exported with it. If you are looking for a session token in Documents/, either it is not there, or the app has a security bug that deserves more attention than the one you started with.