Dependency Security

Kotlin Gradle Dependency Verification

Implement dependency verification in Kotlin Gradle projects with checksums, PGP signatures, and repository filtering.

Shadab Khan
Application Security Architect
4 min read

Kotlin development, whether for server-side JVM, Android, or Kotlin Multiplatform, overwhelmingly uses Gradle for build management. Gradle's dependency verification features are powerful but underused. This guide walks through securing your Kotlin project's dependency chain from the ground up.

Why Kotlin Projects Need Dependency Verification

Kotlin projects typically combine the Kotlin standard library, JetBrains libraries, and a broad Java/JVM ecosystem. An Android project easily pulls in 200+ transitive dependencies across the Kotlin stdlib, AndroidX libraries, networking libraries, serialization frameworks, and more.

The risks are the same as any JVM ecosystem:

  • Compromised Maven Central artifacts
  • Dependency confusion with private repositories
  • Transitive dependencies pulling in vulnerable versions
  • Build plugins executing arbitrary code

But Kotlin adds specific concerns:

  • Kotlin compiler plugins run during compilation with full system access.
  • Gradle plugins (like the Kotlin Gradle Plugin itself) execute in the build process.
  • Kotlin Multiplatform projects pull dependencies from multiple platform-specific repositories.

Setting Up Dependency Verification

Gradle's built-in dependency verification creates a verification-metadata.xml file that records expected checksums and PGP signatures.

Generate Verification Metadata

./gradlew --write-verification-metadata sha256,pgp help

This generates gradle/verification-metadata.xml:

<?xml version="1.0" encoding="UTF-8"?>
<verification-metadata>
   <configuration>
      <verify-metadata>true</verify-metadata>
      <verify-signatures>true</verify-signatures>
   </configuration>
   <components>
      <component group="org.jetbrains.kotlin" name="kotlin-stdlib" version="1.9.21">
         <artifact name="kotlin-stdlib-1.9.21.jar">
            <sha256 value="abc123..." origin="Generated by Gradle"/>
            <pgp value="AABBCCDD"/>
         </artifact>
      </component>
   </components>
</verification-metadata>

Trusting Keys

You will need to configure trusted PGP keys for your dependencies. Add trusted keys in the verification metadata:

<trusted-keys>
   <trusted-key id="379CE192D401AB61" group="org.jetbrains.kotlin"/>
   <trusted-key id="72385FF0AF338D52" group="com.google"/>
</trusted-keys>

This tells Gradle which PGP keys are authorized to sign artifacts from specific groups.

Commit and Enforce

Commit gradle/verification-metadata.xml to your repository. On subsequent builds, Gradle verifies every artifact against the recorded checksums and signatures. If anything changes, the build fails.

Repository Filtering

Prevent dependency confusion by restricting which repositories serve which artifacts:

// build.gradle.kts
repositories {
    maven("https://maven.yourcompany.com/releases") {
        content {
            includeGroupByRegex("com\\.yourcompany\\..*")
        }
    }
    mavenCentral() {
        content {
            excludeGroupByRegex("com\\.yourcompany\\..*")
        }
    }
    google() {
        content {
            includeGroupByRegex("com\\.android\\..*")
            includeGroupByRegex("com\\.google\\..*")
            includeGroupByRegex("androidx\\..*")
        }
    }
}

This ensures your internal packages (com.yourcompany.*) can only come from your private Maven repository, Google's libraries only from Google's repository, and everything else from Maven Central.

Dependency Locking

Dependency locking provides an additional layer of version pinning:

// build.gradle.kts
dependencyLocking {
    lockAllConfigurations()
}

Generate lock files:

./gradlew dependencies --write-locks

This creates lock files in gradle/dependency-locks/ that record resolved versions. If a dependency resolution would produce different versions than what is locked, the build fails.

For Kotlin Multiplatform projects, lock all target configurations:

kotlin {
    sourceSets.all {
        languageSettings.apply {
            // your settings
        }
    }
}

dependencyLocking {
    lockAllConfigurations()
}

Securing Gradle Plugins

Gradle plugins are dependencies too, and they execute with elevated privileges in your build.

Plugin Management

Centralize plugin versions in settings.gradle.kts:

pluginManagement {
    repositories {
        gradlePluginPortal()
        mavenCentral()
        google()
    }
    plugins {
        kotlin("jvm") version "1.9.21"
        kotlin("android") version "1.9.21"
        id("com.android.application") version "8.2.0"
    }
}

Verify Plugin Artifacts

Plugin artifacts are verified by the same verification-metadata.xml system. Ensure you run the verification metadata generation with a task that triggers plugin resolution.

Kotlin-Specific Security Concerns

Compiler Plugins

Kotlin compiler plugins (like kotlinx-serialization, all-open, no-arg) run during compilation. They have access to:

  • The entire source code being compiled
  • The file system
  • Network (unless sandboxed)

Only use compiler plugins from trusted sources (JetBrains, Google, well-known open source projects).

Script Dependencies

Kotlin Gradle DSL (build.gradle.kts) files are Kotlin scripts. The buildSrc directory and included builds can pull their own dependencies. Ensure these are also covered by dependency verification.

Kotlin Multiplatform

KMP projects pull dependencies from multiple sources:

  • Maven Central (JVM/common)
  • Google's Maven (Android)
  • npm (Kotlin/JS)
  • Apple's ecosystem (Kotlin/Native)

Each target's dependencies need separate verification. The verification-metadata.xml covers JVM/Maven dependencies, but Kotlin/JS dependencies resolved through npm need additional attention.

Vulnerability Scanning

Use the OWASP Dependency-Check Gradle plugin:

plugins {
    id("org.owasp.dependencycheck") version "9.0.6"
}

dependencyCheck {
    failBuildOnCVSS = 7.0f
    analyzers.apply {
        assemblyEnabled = false
    }
}

Run it:

./gradlew dependencyCheckAnalyze

SBOM Generation

Generate CycloneDX SBOMs:

plugins {
    id("org.cyclonedx.bom") version "1.7.4"
}
./gradlew cyclonedxBom

This produces a CycloneDX SBOM in build/reports/ with your complete dependency tree.

How Safeguard.sh Helps

Safeguard.sh ingests CycloneDX SBOMs from your Kotlin/Gradle builds and provides continuous dependency monitoring across your entire project portfolio. For Android teams shipping multiple apps and libraries, Safeguard.sh maps vulnerabilities to specific apps and build variants. When a CVE drops that affects a transitive Kotlin or AndroidX dependency, Safeguard.sh shows you exactly which apps and modules are impacted, letting your team prioritize fixes instead of manually checking each project.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.