proguardFiles: A Cautionary Tale
This week, an assumption I made about the Android Gradle plugin method proguardFiles
nearly resulted in a minor security slip. Let’s all learn from my mistake.
Background
The client codebase I’m currently working on has three build types: debug
, beta
, and release
. The beta
build type is a minor variation of the debug
build type, so it’s configured using the Android Gradle plugin’s initWith
method as shown below:
buildTypes {
debug {
// ...
}
beta {
initWith(buildTypes.debug)
// ...
}
release {
// ...
}
}
My tasks today included enabling ProGuard for every single build type. Here’s the code I initially wrote to accomplish this:
buildTypes {
debug {
// ...
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro', 'proguard-debug.pro'
}
beta {
initWith(buildTypes.debug)
// ...
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
release {
// ...
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
The common ProGuard rules defined in the proguard-android.txt
and proguard-rules.pro
files are applied to all three build types. The extra ProGuard rules defined in the proguard-debug.pro
file are applied to the debug
build type only.
Right?
Wrong!
The proguard-debug.pro
file contains exactly one line:
-dontobfuscate
Obfuscation is a useful (but certainly not impenetrable) defense against reverse engineering of a compiled application. However, I have found in the past that it interferes with Android Studio’s debugger, so I like to disable it for the non-production build variants I actively develop with.
As described above, my intention was to disable obfuscation for the debug
build type only, leaving obfuscation enabled for the beta
and release
build types. To test that this was working as expected, I assembled a beta
build and inspected the APK contents using ClassyShark. Here’s what our Parcelable
utility class looked like in ClassyShark:

Those method names are definitely not obfuscated.
Huh?
Confused, I jumped to the definition of the proguardFiles
method commonly used to apply ProGuard configuration to a build type (remember that in Android Studio you can do this using the “Go To Declaration” shortcut):
public BuildType proguardFiles(Object... files) {
Object[] var2 = files;
int var3 = files.length;
for(int var4 = 0; var4 < var3; ++var4) {
Object file = var2[var4];
this.proguardFile(file);
}
return this;
}
Inside the for
loop, each configuration file is passed to the proguardFile
method:
public BuildType proguardFile(Object proguardFile) {
this.getProguardFiles().add(this.project.file(proguardFile));
return this;
}
Ah-ha!
The proguardFiles
method adds to an internal list of configuration files rather than specifying a new list of configuration files. In my opinion this is not obvious from the method name. At least the documented behavior is clear, though confusingly the name proguardFiles
may also be used to refer to a getter defined on the same type!
Armed with this knowledge, we can now walk through exactly what happens (with respect to ProGuard rules) when the beta
build type is configured:
-
initWith(buildTypes.debug)
is called, which results in three ProGuard configuration files (proguard-android.txt
,proguard-rules.pro
, andproguard-debug.pro
) being added to thebeta
build type’s internal list; -
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
is called, which results in duplicates of theproguard-android.txt
andproguard-rules.pro
being added to thebeta
build type’s internal list.
The result: proguard-debug.pro
is unintentionally included in the beta
build type’s internal list of configuration files, and the -dontobfuscate
ProGuard rule is therefore applied when packaging our application. This is consistent with what we found in the decompiled APK.
setProguardFiles to the rescue
The most obvious solution to this problem might be to avoid initializing the beta
build type using the debug
build type. However, this would have lead to a lot more duplication in our build.gradle file. Luckily there’s a better way.
The BuildType
class exposes a setProguardFiles
method, defined as follows:
public BuildType setProguardFiles(Iterable<?> proguardFileIterable) {
this.getProguardFiles().clear();
this.proguardFiles(Iterables.toArray(proguardFileIterable, Object.class));
return this;
}
This is exactly how I originally assumed the proguardFiles
method worked! Any existing configuration files are explicitly replaced by those contained in the argument passed to setProguardFiles
. So, here’s a fixed version of our build.gradle file:
buildTypes {
debug {
// ...
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro', 'proguard-debug.pro'
}
beta {
initWith(buildTypes.debug)
// ...
// New!
setProguardFiles([getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'])
}
release {
// ...
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
To check that obfuscation was now properly enabled for beta
builds, I assembled a new APK and navigated to our Parcelable
utility class using ClassyShark:

Much better!
Takeaways
-
The Android Gradle plugin source code is accessible - utilize it;
-
Proactively seek ways to validate your assumptions, no matter how obviously-correct they may seem/feel;
-
Double-triple-check changes that potentially impact application security.