# Unsanitized download filename path traversal

Filename from URL, header, provider, deeplink or ZIP entry used as save path without canonicalization

_Area: Mobile app security. Seen in 7 disclosed reports; 3 confirmed bounties, median $475, range $50-$10,000; latest 2024-07-09. Updated 2026-10-23._

## How it works

A download, attachment, share, deeplink or archive handler derives a filename from untrusted input (URL last segment, content-disposition header, provider `_display_name`, deeplink query parameter, ZIP entry name) and joins it to a destination directory. The code assumes the name is a bare filename. When the name contains `../` or separators, the join escapes the intended directory and the write lands elsewhere in app-private or shared storage. The broken assumption: "the name I was given is a name, not a path."

## Where it shows up

- `new File(dir, name)` or string concatenation of a directory with a name taken from a URL, HTTP header, intent extra, content provider column, or archive entry.
- Exported share/receive activities with `SEND`/`*/*` filters that copy provider files into app storage [E3](https://bugbountylibrary.com/r/h1%3A1115864.md).
- Deeplink handlers accepting a `filename`/`path` query parameter for local saves [E6](https://bugbountylibrary.com/r/h1%3A2553411.md).
- ZIP extractors writing entry names directly [E5](https://bugbountylibrary.com/r/h1%3A859469.md).
- Destination directories that are code-loading paths (`lib/`, Play Core `splitcompat`/native-libraries) [E1](https://bugbountylibrary.com/r/blog%3Ahulkvision-github-io-blog-post1.md)[E2](https://bugbountylibrary.com/r/h1%3A1377748.md).
- Prior fixes using `contains`/substring checks on the path [E4](https://bugbountylibrary.com/r/h1%3A1650270.md).

## How to review

Find every sink that builds a filesystem path from external input. The invariant: after resolving, the canonical destination path must have the intended directory as a canonical prefix, and the final component must contain no separators. Check that the fix canonicalizes (`getCanonicalPath`) and compares prefixes, not substrings, and that it covers every filename source feeding the same sink. Confirm the destination directory is not a native library or code-loading path.

## Variants seen

- URL last path segment as filename [E1](https://bugbountylibrary.com/r/blog%3Ahulkvision-github-io-blog-post1.md).
- `content-disposition` header filename [E2](https://bugbountylibrary.com/r/h1%3A1377748.md).
- Content provider `_display_name` via exported activity [E3](https://bugbountylibrary.com/r/h1%3A1115864.md).
- Deeplink query parameter to shared external storage [E6](https://bugbountylibrary.com/r/h1%3A2553411.md).
- ZIP entry names during extraction [E5](https://bugbountylibrary.com/r/h1%3A859469.md).
- Bypassable substring check in a prior fix [E4](https://bugbountylibrary.com/r/h1%3A1650270.md).

## Why fixes fail

E4 shows a patch that only rejected `/data` and package-name substrings, still bypassable via `..` or the app's own content provider. E2 shows a fix covering one filename source while another (the header) still fed the same sink.

## Impact and severity

Arbitrary file write within app-private storage, escalating to native library overwrite and code execution [E1](https://bugbountylibrary.com/r/blog%3Ahulkvision-github-io-blog-post1.md)[E2](https://bugbountylibrary.com/r/h1%3A1377748.md)[E3](https://bugbountylibrary.com/r/h1%3A1115864.md), or private data written to shared storage readable by other apps [E6](https://bugbountylibrary.com/r/h1%3A2553411.md). Severity rises when the write target is a code-loading directory or shared external storage.

## Evidence
- E1: https://bugbountylibrary.com/r/blog%3Ahulkvision-github-io-blog-post1.md
- E2: https://bugbountylibrary.com/r/h1%3A1377748.md
- E3: https://bugbountylibrary.com/r/h1%3A1115864.md
- E4: https://bugbountylibrary.com/r/h1%3A1650270.md
- E5: https://bugbountylibrary.com/r/h1%3A859469.md
- E6: https://bugbountylibrary.com/r/h1%3A2553411.md
