May 27, 2026
Your Android App’s FileProvider Might Be Exposing Your Entire Filesystem — 4 Real Patterns I Found
Android’s `FileProvider` is supposed to be the secure way to share files between apps. Instead of exposing raw `file://` URIs, you create…
Yehor Mamaiev
6 min read
Android's FileProvider is supposed to be the secure way to share files between apps. Instead of exposing raw file:// URIs, you create scoped content:// URIs that grant temporary, controlled access.
That's the theory.
In practice, I scanned multiple open-source Android apps and found that 4 out of 10 had FileProvider configurations that exposed far more than they should — in some cases, the entire device filesystem.
The scary part: this isn't a complex vulnerability. It's a single XML file, usually under 10 lines, that most developers copy from Stack Overflow without understanding what each line does.
Let me show you the four patterns I found, from the most basic mistake to the most dangerous.
How FileProvider Works (30-Second Version)
Every FileProvider has two parts:
- The manifest declaration:
<provider
android:authorities="com.example.app.provider"
android:exported="false"
android:grantUriPermissions="true"
android:name="androidx.core.content.FileProvider">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
</provider><provider
android:authorities="com.example.app.provider"
android:exported="false"
android:grantUriPermissions="true"
android:name="androidx.core.content.FileProvider">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
</provider>2. The path configuration (res/xml/provider_paths.xml):
<paths>
<files-path name="internal" path="shared/" />
</paths><paths>
<files-path name="internal" path="shared/" />
</paths>The path configuration defines which directories the FileProvider can serve files from. Each element maps to a specific location on the device:
The path attribute narrows the scope within that directory. path="shared/" means only the shared/ subdirectory is accessible. path="." means the entire directory.
This is where things go wrong.
Pattern 1: The Copy-Paste Config
App type: Podcast manager (open source, actively maintained)
<paths>
<external-path name="external_storage" path="." />
<external-files-path name="external_file_storage" path="." />
<files-path name="name" path="." />
<root-path name="root" path="." />
</paths><paths>
<external-path name="external_storage" path="." />
<external-files-path name="external_file_storage" path="." />
<files-path name="name" path="." />
<root-path name="root" path="." />
</paths>Four entries. Every single one set to path=".". This is the "I need file sharing to work and I'll add paths until it does" configuration.
Let's break down what this exposes:
- external-path path="." → all of
/sdcard/— photos, downloads, documents - external-files-path path="." → all of the app's external files
- files-path path="." → all of the app's internal files (databases, preferences)
- root-path path="." → the entire device filesystem
The root-path alone makes the other three redundant — it already covers everything. The fact that all four are present suggests this was copied from a documentation example or Stack Overflow answer without understanding the implications.
The name attribute is a giveaway: name="name" for files-path — that's a placeholder that was never changed.
With this configuration, any content URI can address any file:
content://[authority]/root/data/data/[package]/databases/podcasts.db
content://[authority]/root/sdcard/DCIM/Camera/photo.jpg
content://[authority]/root/proc/self/environcontent://[authority]/root/data/data/[package]/databases/podcasts.db
content://[authority]/root/sdcard/DCIM/Camera/photo.jpg
content://[authority]/root/proc/self/environWhy it's still exploitable even though it's non-exported
The provider is exported="false", so no app can query it directly. But grantUriPermissions="true" means the app can grant temporary URI access through intents.
The app has 16 exported components — including 4 broadcast receivers without permission guards. If any component in the sharing flow constructs a FileProvider URI based on user-controllable input, an attacker app can influence which file the URI points to.
Pattern 2: The One-Liner
App type: Privacy-focused video frontend (open source)
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<root-path name="root" path="." />
</paths><paths xmlns:android="http://schemas.android.com/apk/res/android">
<root-path name="root" path="." />
</paths>One line. The single most dangerous FileProvider configuration possible.
<root-path path="." /> maps to / — the filesystem root. Every file on the device is addressable through this provider. No subdirectory scoping, no path restrictions.
What makes this case ironic: this app exists specifically to protect user privacy. Users choose it to avoid tracking. But the FileProvider configuration means a malicious app on the same device could access the complete viewing history database — exactly the data the user was trying to protect.
I confirmed that the app's codebase actively uses FLAG_GRANT_READ_URI_PERMISSION in its download and sharing flows, meaning the URI grant mechanism that makes non-exported providers exploitable is present and actively used.
Pattern 3: Almost Got It Right
App type: Cloud storage client (open source)
<paths>
<files-path name="user_files_internal" path="<app_name>/" />
<files-path name="log" path="log/" />
<cache-path name="attachments" path="attachments" />
<external-path name="external_files" path="." />
<root-path name="external_files" path="/storage/" />
</paths><paths>
<files-path name="user_files_internal" path="<app_name>/" />
<files-path name="log" path="log/" />
<cache-path name="attachments" path="attachments" />
<external-path name="external_files" path="." />
<root-path name="external_files" path="/storage/" />
</paths>This is the most instructive example. Look at the first three entries:
path="<app_name>/"— only the synced files directory ✅path="log/"— only log files ✅path="attachments"— only cached attachments ✅
The developer clearly understood the concept of scoped paths. These three entries are exactly how FileProvider should be configured — narrow, specific, minimal.
Then lines 4 and 5 undo everything:
external-path path="."→ all of/sdcard/root-path path="/storage/"→ all mounted storage (internal, SD cards, USB drives)
The /storage/ root path is particularly concerning. On Android, /storage/ contains mount points for every storage device:
/storage/emulated/0/ → Internal shared storage
/storage/XXXX-XXXX/ → SD cards
/storage/usb0/ → USB OTG drives/storage/emulated/0/ → Internal shared storage
/storage/XXXX-XXXX/ → SD cards
/storage/usb0/ → USB OTG drivesFor a cloud storage app, this means a malicious app could potentially access synced files from all user accounts — the very data the user chose to self-host to keep private.
The positive example within the same app: A secondary image cache provider in the same app IS properly protected with android:permission="android.permission.MANAGE_DOCUMENTS". This proves the team is capable of correct provider security — the broad paths in the main provider are an oversight, not a lack of knowledge.
Pattern 4: The Exported Provider
App type: Media player (open source, 1M+ downloads)
This is the most unique and dangerous case. Unlike the other three apps, this one has two separate providers — and one of them is directly exported without any permission:
<!-- Custom provider — EXPORTED, NO PERMISSION -->
<provider
android:authorities="[package].thumbprovider"
android:exported="true"
android:name="[package].FileProvider"/>
<!-- Standard provider — non-exported, broad paths -->
<provider
android:authorities="[package].provider"
android:exported="false"
android:grantUriPermissions="true"
android:name="androidx.core.content.FileProvider">
<meta-data android:resource="@xml/provider_paths" ... />
</provider><!-- Custom provider — EXPORTED, NO PERMISSION -->
<provider
android:authorities="[package].thumbprovider"
android:exported="true"
android:name="[package].FileProvider"/>
<!-- Standard provider — non-exported, broad paths -->
<provider
android:authorities="[package].provider"
android:exported="false"
android:grantUriPermissions="true"
android:name="androidx.core.content.FileProvider">
<meta-data android:resource="@xml/provider_paths" ... />
</provider>The custom provider is exported="true" with no android:permission attribute. Any app on the device can call its openFile() method directly. No URI grants needed, no exported activity required — direct access.
The security checks inside openFile()
The developer added application-level security checks:
// Reconstructed from decompiled code
public ParcelFileDescriptor openFile(Uri uri, String mode) {
String path = uri.getPath();
// Block path traversal
if (path.contains("..")) throw new SecurityException("Illegal access");
// Only allow: medialib directory OR /app_update
if (path.startsWith(externalFilesDir + "/medialib") || path.equals("/app_update")) {
if (path.equals("/app_update")) {
return open(new File(cacheDir, "update.apk"));
}
// Verify canonical path against mount point allowlist
// ... serve the file
} else {
throw new SecurityException("Illegal access");
}
}// Reconstructed from decompiled code
public ParcelFileDescriptor openFile(Uri uri, String mode) {
String path = uri.getPath();
// Block path traversal
if (path.contains("..")) throw new SecurityException("Illegal access");
// Only allow: medialib directory OR /app_update
if (path.startsWith(externalFilesDir + "/medialib") || path.equals("/app_update")) {
if (path.equals("/app_update")) {
return open(new File(cacheDir, "update.apk"));
}
// Verify canonical path against mount point allowlist
// ... serve the file
} else {
throw new SecurityException("Illegal access");
}
}The checks are reasonable — path traversal blocked, paths limited to medialib/ and /app_update. But application-level checks in an exported provider are fundamentally weaker than framework-level permissions:
- Any bug in the path validation logic exposes files
- The validation must be maintained as the codebase evolves
- A single missed edge case creates a vulnerability
The supply chain vector
The /app_update path serves a cached APK file — update.apk from the app's cache directory. This file is downloaded by the app's auto-update mechanism over plaintext HTTP.
The attack chain:
- Attacker performs MITM on the HTTP update download → injects malicious APK
- App saves the malicious APK as
update.apkin cache - Any app on the device can read this file through the exported provider:
content://[authority]/app_update - The user installs the "update" through the app's own mechanism
HTTP download + exported provider + no APK signature verification = textbook supply chain risk.
The standard provider's paths
<external-path name="external_files" path="." />
<root-path name="external_files" path="/storage/" />
<files-path name="files" path="." /><external-path name="external_files" path="." />
<root-path name="external_files" path="/storage/" />
<files-path name="files" path="." />All of external storage, all mounted storage devices, and all internal app files.
How to Check Your App
1. Find your FileProvider path configuration
# In your project
find . -name "*provider_paths*" -o -name "*file_paths*" | xargs cat
# In a decompiled APK
cat res/xml/*provider* res/xml/*file_path* 2>/dev/null# In your project
find . -name "*provider_paths*" -o -name "*file_paths*" | xargs cat
# In a decompiled APK
cat res/xml/*provider* res/xml/*file_path* 2>/dev/null2. Check for dangerous patterns
# root-path with broad scope
grep -r "root-path" res/xml/ --include="*.xml"
# Any path set to "."
grep -r 'path="\."' res/xml/ --include="*.xml"
# Exported providers without permissions
grep -B2 -A5 'exported="true"' AndroidManifest.xml | grep -i provider# root-path with broad scope
grep -r "root-path" res/xml/ --include="*.xml"
# Any path set to "."
grep -r 'path="\."' res/xml/ --include="*.xml"
# Exported providers without permissions
grep -B2 -A5 'exported="true"' AndroidManifest.xml | grep -i provider3. Red flags in the path config
- — exposes entire filesystem
- — exposes all mounted storage
- — exposes all of /sdcard/
- Any path="." — exposes the entire category instead of a subdirectory
- An exported provider without
android:permission
The Fix
Before (vulnerable):
<paths>
<root-path name="root" path="." />
<external-path name="external" path="." />
<files-path name="files" path="." />
</paths><paths>
<root-path name="root" path="." />
<external-path name="external" path="." />
<files-path name="files" path="." />
</paths>After (scoped):
<paths>
<!-- Only share files from specific directories the app actually needs -->
<files-path name="shared_content" path="shared/" />
<cache-path name="temp_files" path="tmp/" />
<external-files-path name="exports" path="exports/" />
</paths><paths>
<!-- Only share files from specific directories the app actually needs -->
<files-path name="shared_content" path="shared/" />
<cache-path name="temp_files" path="tmp/" />
<external-files-path name="exports" path="exports/" />
</paths>Rules:
-
Never use
root-pathunless you have a specific, documented reason. There is almost never a legitimate need to serve files from the filesystem root. -
Never use
path="."— always scope to the specific subdirectory your app needs to share. -
If a provider must be exported, always add
android:permissionwith an appropriate protection level (signaturefor same-developer apps, or a custom permission for specific callers). -
Replace blanket
grantUriPermissions="true"with specific<grant-uri-permission>path patterns when possible. -
Audit the actual file-sharing code — trace every
FileProvider.getUriForFile()call to ensure it only constructs URIs from known, safe paths.
Key Takeaways
-
FileProvider misconfiguration is the most common vulnerability I find in Android apps. 4 out of 10 apps in my research had this issue. It's the low-hanging fruit of mobile security.
-
The vulnerability is in an XML file, not in code. Most security reviews focus on Java/Kotlin logic. The 5-line
provider_paths.xmlis often overlooked. -
"Non-exported" doesn't mean "safe." With
grantUriPermissions="true"and exported components elsewhere in the app, a non-exported FileProvider can still be exploited through URI permission forwarding. -
Developers copy FileProvider configs from Stack Overflow. The most upvoted answers often use
root-path path="."because it "just works." It works because it exposes everything. -
The fix takes 5 minutes. Replace broad paths with scoped ones. That's it. There's no architectural change, no refactoring, no breaking change — just a narrower XML file.
I'm a mobile security researcher specializing in Android application security. If your app uses FileProvider and you want to verify your configuration is secure — reach out: yehor.mamaiev@gmail.com