App Sandbox: So isoliert Android deine App vom Rest des Systems
Die App Sandbox trennt jede Android-App per UID und Linux-Prozess. Du lernst, was das für Daten, Berechtigungen und deinen Code bedeutet.
Wenn du eine Android-App startest, denkst du selten darüber nach, wie streng das System sie vom Rest des Geräts trennt. Genau diese Trennung ist die App Sandbox: ein Sicherheitsmodell, das auf dem Linux-Kernel aufbaut und dafür sorgt, dass deine App weder die Daten anderer Apps lesen noch im System wahllos Schaden anrichten kann. Für dich als Android-Entwicklerin oder -Entwickler ist die Sandbox kein abstraktes Detail, sondern der Rahmen, in dem dein Code lebt. Dieser Artikel zeigt dir, wie das Modell funktioniert, woran du es im Alltag merkst und welche Stolperfallen du kennen solltest, bevor du eine App in den Play Store schiebst.
Was ist das?
Die App Sandbox ist Androids Grundprinzip zur Isolation von Apps. Jede installierte App bekommt vom System eine eigene Linux-User-ID, kurz UID. Diese UID wird der App beim Installieren zugewiesen und bleibt während der gesamten Lebensdauer auf dem Gerät stabil. Aus Sicht des Linux-Kernels ist deine App also ein eigener Nutzer, mit eigenem Home-Verzeichnis und eigenen Prozessrechten. Andere Apps haben andere UIDs und damit auch andere Rechte. Sie können standardmäßig weder deine Dateien lesen noch deinen Speicher inspizieren.
Im Android-Kontext bedeutet das: Die Sandbox ist die erste und wichtigste Verteidigungslinie für die Daten deiner Nutzer. Sie sorgt dafür, dass eine schlecht programmierte oder bösartige App nicht ohne Weiteres an die Notizen, Fotos oder Tokens einer anderen App kommt. Das Modell ist mandatory: Du kannst es als Entwickler weder ein- noch ausschalten. Es greift, sobald die App installiert ist, und es greift unabhängig davon, ob du Java, Kotlin, NDK-Code oder eine Webview-Schicht verwendest. Die Sandbox ist also nicht etwas, das du aktivierst, sondern etwas, dem du dich anpasst.
Wichtig ist die Abgrenzung zu anderen Begriffen. Die Sandbox ist nicht dasselbe wie das Permission-System. Permissions regeln, welche zusätzlichen Ressourcen deine App anfragen darf, etwa Standort, Kamera oder Kontakte. Die Sandbox dagegen ist die Grundeinstellung: Sie verbietet erstmal alles außerhalb deines eigenen App-Bereichs, und Permissions öffnen einzelne Türen kontrolliert wieder.
Wie funktioniert es?
Technisch baut die App Sandbox auf zwei Mechanismen aus dem Linux-Kernel auf: User-Isolation per UID und Prozess-Isolation. Wenn das System deine App startet, erzeugt es einen neuen Prozess (typischerweise via Zygote-Fork), der unter der UID deiner App läuft. Dieser Prozess hat einen eigenen Adressraum. Andere App-Prozesse können weder in deinen Speicher schreiben noch ihn lesen, weil der Kernel jede Speicheranfrage an den UIDs prüft.
Dazu kommt ein abgegrenzter Dateibereich. Jede App bekommt unter /data/data/<package-name>/ ein internes Verzeichnis, das nur dem eigenen UID gehört. Dort liegen databases/, shared_prefs/, files/ und cache/. Die Standard-Permissions im Filesystem sind so gesetzt, dass nur dein UID lesen und schreiben darf. Wenn du also getFilesDir(), openFileOutput() oder eine Room-Datenbank nutzt, landen die Daten automatisch innerhalb deiner Sandbox.
Auf modernen Android-Versionen (ab Android 10 mit Scoped Storage) gilt ein ähnliches Prinzip auch für den externen Speicher. Deine App bekommt einen privaten Bereich unter /storage/emulated/0/Android/data/<package-name>/, den nur sie selbst direkt sieht. Auf gemeinsame Medien wie Fotos oder Downloads greifst du nicht mehr per direktem Pfad zu, sondern über die MediaStore-API oder den Storage Access Framework. Auch hier ist die Sandbox der Treiber: Das System will verhindern, dass eine App heimlich den gesamten Speicher durchforstet.
Über die Sandbox hinaus kommunizieren Apps nur über klar definierte Kanäle. Dazu zählen Intents, ContentProvider, BroadcastReceiver, Service-Bindungen und seit Android 11 stark eingeschränkte Package-Sichtbarkeit. Jeder dieser Kanäle ist explizit. Du musst Daten als Parcelable oder Bundle packen und das System prüft, ob die andere App die nötigen Rechte hat. Direkt in den Speicher einer fremden App zu greifen, geht nicht – auch nicht mit Reflection oder JNI.
Es gibt einen Spezialfall, den du kennen solltest: Zwei Apps können sich eine UID teilen, wenn sie das sharedUserId-Attribut im Manifest setzen und mit demselben Zertifikat signiert sind. Das war früher praktisch, gilt heute aber als veraltet (deprecated) und sollte in Neuentwicklungen nicht mehr verwendet werden. Google empfiehlt, Apps stattdessen sauber per Intents oder gemeinsamem Backend zu integrieren.
Was die Sandbox dir abnimmt
- Speicher-Schutz: Du musst dich nicht selbst gegen mitlesende Nachbar-Apps absichern, solange du innerhalb deiner Sandbox bleibst.
- Identifikation: Das System weiß über die UID immer, wer ein API-Aufruf ausgelöst hat. Permissions werden gegen diese UID geprüft.
- Prozess-Stabilität: Stürzt deine App ab, reißt sie keine andere App mit. Der OOM-Killer beendet ebenfalls UID-spezifisch.
Was die Sandbox NICHT abdeckt
Die Sandbox schützt dich nicht vor Fehlern, die du selbst ins Modell reißt. Wer Dateien mit MODE_WORLD_READABLE schreibt (in modernen API-Levels nicht mehr erlaubt, aber als Beispiel), wer exported="true" an Activities setzt ohne Permission-Schutz, oder wer sensible Daten in der Cloud-Backup-Datei speichert, untergräbt die Isolation aktiv. Auf einem gerooteten Gerät wiederum ist die Sandbox prinzipiell überwindbar – Root-Rechte stehen über jedem UID. Verlasse dich also nie allein auf die Sandbox als einzige Sicherheitsmaßnahme.
In der Praxis
Im Alltag merkst du die Sandbox an drei Punkten: beim Speichern, beim Teilen und beim Debuggen. Der wichtigste Reflex sollte sein, sensible Daten konsequent in den eigenen, internen App-Speicher zu legen. Ein typischer Code-Schnipsel sieht in Kotlin so aus:
class TokenStorage(private val context: Context) {
private val file: File
get() = File(context.filesDir, "auth_token.txt")
fun save(token: String) {
file.writeText(token, Charsets.UTF_8)
}
fun read(): String? = if (file.exists()) file.readText() else null
}
context.filesDir zeigt automatisch in deine Sandbox unter /data/data/<package-name>/files/. Andere Apps haben dort keinen Zugriff. Für noch sensiblere Daten wie Tokens oder Passwörter solltest du die Datei zusätzlich verschlüsseln, etwa mit der Jetpack-Security-Bibliothek (EncryptedFile, EncryptedSharedPreferences). Die Sandbox ist die Basis, Verschlüsselung ist die zweite Schicht – beide zusammen ergeben Defense in Depth.
Eine konkrete Entscheidungsregel für dich: Speichere niemals sensible Daten dort, wo andere Apps sie sehen könnten. Heißt konkret:
- Auth-Tokens, persönliche Notizen, Cache mit Nutzerinhalten gehören in
filesDirodercacheDir, nicht ingetExternalFilesDir(null)und schon gar nicht in den öffentlichen Downloads-Ordner. - Möchtest du Daten gezielt mit anderen Apps teilen, nutze einen
ContentProvidermit Permission-Schutz oder verwendeFileProvidermitgrantUriPermissions. - Schalte in Release-Builds das Backup ab oder konfiguriere
data_extraction_rules.xml, wenn deine Daten nicht in Google-Backups landen sollen.
Eine typische Stolperfalle: exportierte Komponenten
Eine sehr häufige Quelle für Sandbox-Lecks sind ungewollt exportierte Activities, Services oder Receiver. Bis Android 11 konnte das System manchmal selbst entscheiden, was exportiert wird. Ab API-Level 31 musst du android:exported explizit angeben, sobald die Komponente einen Intent-Filter hat. Vergisst du das, baut die App nicht mehr. Setzt du es vorschnell auf true, machst du eine Tür auf, die fremde Apps nutzen können:
<activity
android:name=".SettingsActivity"
android:exported="false">
<intent-filter>
<action android:name="com.example.app.OPEN_SETTINGS" />
</intent-filter>
</activity>
Frage dich bei jeder Komponente: Soll wirklich eine fremde App das auslösen können? Wenn nein, bleibt es bei exported="false". Wenn ja, schütze sie zusätzlich mit einer eigenen signature-Permission oder mit einer System-Permission.
Verstehen mit ADB und dem Device File Explorer
Du kannst die Sandbox direkt sichtbar machen. Verbinde ein Debug-Gerät oder einen Emulator und probiere aus:
adb shell
run-as com.example.app
ls -l files/
Mit run-as darfst du als deine eigene App-UID ins Sandbox-Verzeichnis schauen – aber nur, wenn die App debuggable ist. Versuche denselben Befehl ohne run-as, und du wirst „Permission denied” bekommen. Genau das ist die Sandbox in Aktion. Im Android Studio Device File Explorer kannst du dasselbe per UI sehen: Bei Release-Builds bleibt der App-Ordner verschlossen.
Für die Validierung in deinem eigenen Workflow lohnt sich eine kurze Routine im Code-Review: Bei jedem PR, der eine neue Activity, einen Service oder einen Provider hinzufügt, prüfst du explizit das exported-Attribut und alle deklarierten Permissions. Bei jedem PR, der etwas ins Dateisystem schreibt, prüfst du, ob der Pfad innerhalb der Sandbox liegt. Diese zwei Fragen kosten Sekunden und verhindern die meisten Sandbox-bezogenen Sicherheitsprobleme.
Fazit
Die App Sandbox ist die unsichtbare Wand, die deine App im Inneren des Geräts schützt. Sie wird vom Linux-Kernel über UIDs und Prozess-Isolation durchgesetzt, sie reicht vom internen Dateisystem bis in den Scoped Storage und sie bestimmt, wie deine App mit anderen Apps interagiert. Du kannst sie nicht abschalten, aber du kannst sie aushöhlen, wenn du Komponenten unbedacht exportierst, sensible Daten in öffentliche Speicher schreibst oder Backups falsch konfigurierst. Nimm dir nach diesem Artikel zehn Minuten Zeit: Öffne dein aktuelles Projekt, suche im Manifest nach jedem android:exported, prüfe, wo deine App Dateien anlegt, und probiere adb shell run-as an einem Debug-Build aus. So spürst du das Modell selbst – und entwickelst ein verlässliches Bauchgefühl dafür, wann eine Codeänderung an der Sandbox kratzt und wann nicht.