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 it can read and write, and which nothing else can see. Most bugs about data that will not clear, settings that survive an uninstall, or files that vanish overnight come down to putting something in the wrong part of it.

The directories

Documents/ — user-generated content that the user would be upset to lose. Backed up to iCloud and to a computer backup. Visible in the Files app if the app declares 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. Small values only; this is not storage.

Library/Caches/ — regenerable data. Backed up? No. Purged by the system under storage pressure, without warning and without telling you. This is the correct place for downloaded images and API responses, and the wrong place for anything you cannot re-fetch.

tmp/ — scratch space. Not backed up, purged aggressively, and can disappear between launches. Anything you want to survive a single run does not belong here.

The rules this implies

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

A database in Caches/ disappears. Not often, and never while you are testing — which is exactly why this bug reaches production. It surfaces as “the app forgot everything” on devices that ran low on space.

Anything in tmp/ is gone. A download resumed from tmp/ after a relaunch will sometimes find nothing there.

Documents/ is backed up, so keep it small. A 4 GB media cache in Documents/ means the user’s backup is 4 GB larger than it needs to be, and restoring their phone takes an hour longer.

Reading the container

The fastest way to settle any of this is to look. Over a trusted USB connection you can browse an app’s container, decrypt what it stored, and pull files down.

What that answers, in about a minute each:

  • Is the cache actually in Caches/, or somewhere permanent?
  • Did the app write the new response, or is it serving a stale one it never replaced?
  • Is that database file the size you expect, or has a table been growing without a trim?
  • Did the migration produce the schema you thought it did?
  • Is the downloaded asset intact, or truncated?

Every one of these is otherwise a round of “add a log line and ship a build”.

Reproducing a user’s state

The most useful trick in this area: export a user’s container from their device, load it into a build on your own, and the bug that “only happens for that one account” reproduces at your desk.

It is also the trick that requires the most care. Which brings us to the part that matters more than any of the above.

This is a person’s data

An app container holds messages, photos, location history, tokens and whatever else the app has stored. Exporting one is not a technical operation with no 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 the narrowest thing that answers the question — one file, not the whole container, unless you genuinely need the whole container.
  • Delete the export when the ticket closes. An exported container sitting on a laptop is a data breach waiting for a stolen bag.
  • Never put one in a shared ticket attachment without stripping it.

Teams that get this wrong do not usually do it maliciously. They do it by exporting a container to debug something, finishing, and forgetting the folder exists.

A note on what is not in there

The keychain. Credentials, tokens and anything else stored properly live outside the container and do not come out with it. If you are looking for a session token in Documents/, either it is not there or the app has a security bug worth more attention than the one you started with.