Skip to content

Instantly share code, notes, and snippets.

@Sal7one
Created August 14, 2025 07:39
Show Gist options
  • Save Sal7one/af0464aa98f35eca4a93ba15e0ffa193 to your computer and use it in GitHub Desktop.
Save Sal7one/af0464aa98f35eca4a93ba15e0ffa193 to your computer and use it in GitHub Desktop.
Remove permission on merge android
import com.android.build.api.artifact.SingleArtifact
androidComponents {
onVariants { variant ->
val cap = variant.name.replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }
val manifestProvider = variant.artifacts.get(SingleArtifact.MERGED_MANIFEST)
val taskName = "enforceNoUnexpectedPermissions$cap"
tasks.register(taskName) {
inputs.file(manifestProvider)
doLast {
val xml = manifestProvider.get().asFile.readText()
// Match both <uses-permission> and <uses-permission-sdk-23>
val rx = Regex("""<uses-permission(?:-sdk-23)?\s+android:name="([^"]+)"""")
val found = rx.findAll(xml).map { it.groupValues[1] }.toSet()
// Only allow what you truly need. If you want *no* permissions at all, leave this empty.
val allowed = setOf<String>(
// e.g. "android.permission.POST_NOTIFICATIONS"
)
val forbidden = found - allowed
if (forbidden.isNotEmpty()) {
throw GradleException("Forbidden permissions in ${variant.name}: $forbidden")
}
}
}
// Run after manifest merge so it always sees the final result
tasks.matching { it.name == "merge${cap}Manifest" }
.configureEach { finalizedBy(taskName) }
}
}
@Sal7one
Copy link
Author

Sal7one commented Aug 14, 2025

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:tools="http://schemas.android.com/tools">

    <!-- Remove for all SDKs -->
    <uses-permission android:name="android.permission.INTERNET" tools:node="remove"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" tools:node="remove"/>

    <!-- Some libs declare the -sdk-23 variant; remove those too -->
    <uses-permission-sdk-23 android:name="android.permission.INTERNET" tools:node="remove"/>
    <uses-permission-sdk-23 android:name="android.permission.ACCESS_NETWORK_STATE" tools:node="remove"/>

    <!-- …the rest of your manifest… -->
</manifest>

@Sal7one
Copy link
Author

Sal7one commented Aug 14, 2025

Current Solution I use

fun String.cap() = replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }

androidComponents {
    onVariants { variant ->
        val cap = variant.name.cap()
        val manifestProvider = variant.artifacts.get(SingleArtifact.MERGED_MANIFEST)

        val checkTask = tasks.register("checkNoNetPerm$cap") {
            inputs.file(manifestProvider)
            doLast {
                val manifestFile = manifestProvider.get().asFile
                val xml = manifestFile.readText()

                // Match regardless of spacing / attribute order / newlines
                val rx = Regex(
                    """<\s*uses-permission\b[^>]*\bandroid:name\s*=\s*"(android\.permission\.INTERNET|android\.permission\.ACCESS_NETWORK_STATE)"""",
                    setOf(RegexOption.IGNORE_CASE, RegexOption.DOT_MATCHES_ALL))

                if (rx.containsMatchIn(xml)) {
                    throw GradleException(
                        "Blocked: merged manifest for ${variant.name} contains a network permission at: $manifestFile"
                    )
                }
            }
        }

        // Run the check after manifest is merged in all paths we care about
        // 1) After merge<Variant>Manifest (always exists)
        tasks.matching { it.name == "merge${cap}Manifest" }
            .configureEach { finalizedBy(checkTask) }

        // 2) Make packaging tasks depend on the check (covers assemble/bundle invocations)
        tasks.matching { it.name == "package$cap" || it.name == "bundle$cap" }
            .configureEach { dependsOn(checkTask) }

        // 3) As a catch-all, if your build calls high-level tasks like assemble/bundle:
        tasks.matching { it.name == "assemble$cap" || it.name == "bundle$cap" }
            .configureEach { dependsOn(checkTask) }
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment