A report on r/netsec claims an arbitrary file overwrite in Android-Image-Cropper, the library behind the crop screen in a huge number of apps. The original thread is unreachable, there's no CVE, and anyone claiming to know the exact affected versions is guessing. Audit anyway.
The uncertainty buys you nothing. The ArthurHub repo (6.4k stars, 1.4k forks) states flatly in its README: "The Project is NOT currently maintained." The changelog ends at 2.8.0 with an Android O crash fix, no security entries anywhere. There is no maintainer left to ship a patch, so every release of that line stays exposed whatever the precise trigger turns out to be. An abandoned file-handling library whose setup requested WRITE_EXTERNAL_STORAGE does not belong in a shipping app. That judgment needs no advisory.
One codebase, three Maven coordinates
This is where audits actually fail. The lineage runs edmodo to ArthurHub to the maintained CanHub fork, and each hop left a coordinate behind:
com.theartofdev.edmodo:android-image-cropper:2.8.+ (ArthurHub line, abandoned)
com.github.arthurhub:android-image-cropper:2.7.0 (JitPack build of the same)
com.vanniktech:android-image-cropper:4.7.0 (CanHub fork, maintained)
An SCA rule keyed on one groupId sees at most a third of this family. Blacklist com.theartofdev.edmodo and the same jar arrives clean via com.github.arthurhub. And that first version string, 2.8.+, is its own problem: two builds a week apart can resolve different artifacts, so even a correct audit expires.
The audit you can run this afternoon
./gradlew :app:dependencies --configuration releaseRuntimeClasspath > deps.txt
grep -niE "theartofdev|arthurhub|canhub|vanniktech" deps.txt
A hit you don't remember adding means the cropper arrived transitively, often inside a messaging or onboarding SDK. Run ./gradlew :app:dependencyInsight --configuration releaseRuntimeClasspath --dependency android-image-cropper to name the parent.
Then check what the manifest merge granted with aapt dump permissions app-release.apk. If WRITE_EXTERNAL_STORAGE appears and your only broad-storage consumer is the crop flow, the library is inflating your blast radius.
Now your own call sites:
grep -rnE "CropImage\.activity|setImageUriAsync|getUri\(\)|CropImageView" app/src
The API is Uri in, Uri out. At each call site, ask who chooses where the cropped file lands. If the destination derives from anything outside your process (an intent extra, a deep link parameter, the source image's filename) and nothing confines it to a directory you own, that is the overwrite pattern. On pre-scoped-storage devices with the write permission granted, "anywhere" includes shared storage other apps depend on.
Fix in order of preference. Migrate to CanHub: pin 4.3.3, rewrite every import from com.theartofdev.edmodo.cropper to com.canhub.cropper, then climb minor by minor to 4.7.0. If it has to wait, wrap the old library so your code owns both ends of the Uri contract: CropImageView inside your own Activity, an output file you generate under getExternalFilesDir(), a name you chose, never derived from the source Uri. CanHub's stated direction is that Uri handling belongs to the app, so the wrapper is your end state either way.
What you can do today:
- Run both greps above against
releaseRuntimeClasspathand your source tree; usedependencyInsightto find which SDK drags the cropper in. - Replace every
+wildcard version with a pinned one, and fail CI on new ones. - Own the output destination: app-generated filename in app-private storage, never derived from the input.
- Teach your SCA to match artifact families (forks, JitPack mirrors) and to flag archived repos as findings even when no advisory exists.
Has your tooling ever flagged a dependency for being abandoned rather than for a CVE? Curious how many teams treat maintenance state as a finding on its own.
Longer writeup if you want the full verification trail and migration path: https://axeploit.com/blog/one-cropper-three-coordinates-finding-the-android-image-overwrite-in-your-dependency-tree
Top comments (0)