Gradle’s Dependency Constraints versprechen Kontrolle über die Versionsauflösung von Dependencies, aber in der Praxis werden sie überraschend leicht von normalen Dependencies übersteuert. Dieser Artikel untersucht den fundamentalen Designfehler, der zu gefährlichen Sicherheitslücken und frustrierenden Debugging-Sessions führen kann.
Laut der offiziellen Gradle-Dokumentation sollen Dependency Constraints die Kontrolle über transitive Dependencies ermöglichen:
“Dependency constraints allow you to control the selection of transitive dependencies” Quelle: Gradle User Guide - Dependency Constraints
Der Begriff “Constraint” (Einschränkung/Beschränkung) suggeriert einen harten Kontrollmechanismus - eine Grenze, die nicht überschritten werden kann.
Betrachten wir folgendes Beispiel:
plugins {
java
}
repositories {
mavenCentral()
}
dependencies {
// Diese normale Dependency...
implementation("com.google.guava:guava:31.1-jre")
}
constraints {
// ...übersteuert diesen "Constraint"!
implementation("com.google.guava:guava:32.0.1-jre") {
because("Sicherheitsupdate - nur Version 32+ erlaubt")
}
}Ergebnis: Version 31.1-jre wird verwendet, nicht 32.0.1-jre!
Nach der Rich Versions Dokumentation gilt folgende Prioritätsreihenfolge:
strictly - Höchste Priorität, erzwingt
exakte Versionrequire (normale
implementation/api Dependencies) -
Übersteuert Constraints!prefer - Niedrigste Prioritätdependencies {
// Strictly hat höchste Priorität
implementation("com.google.guava:guava:{strictly 31.1-jre}")
}
constraints {
// Dieser Constraint wird ignoriert
implementation("com.google.guava:guava:32.0.1-jre") {
because("Wird trotz wichtigem Grund ignoriert")
}
}Noch problematischer: Selbst normale Dependencies (mit implizitem
require) übersteuern Constraints:
dependencies {
// Diese normale Deklaration (implizit: require 2.14.0)
implementation("log4j:log4j:2.14.0")
}
constraints {
// Dieser Sicherheits-Constraint wird ignoriert!
implementation("log4j:log4j:2.17.1") {
because("CVE-2021-44228 - Log4Shell Vulnerability")
}
}
// Resultat: Verwundbare Version 2.14.0 wird verwendet!Ein zentrales Build-Team könnte versuchen, Sicherheitsupdates zu erzwingen:
// In corporate-standards.gradle
constraints {
implementation("org.springframework:spring-core:5.3.20") {
because("CVE-2022-22965 - Spring4Shell")
}
}
// In irgendeinem Subprojekt
dependencies {
implementation("org.springframework:spring-core:5.3.17")
// Verwundbare Version gewinnt trotz Sicherheits-Constraint!
}Der Begriff “Constraint” impliziert: - ❌ Eine harte Grenze - ❌ Erzwungene Regel - ❌ Zentraler Kontrollmechanismus
Was es tatsächlich ist: - ✅ Eine Empfehlung - ✅ Fallback für transitive Dependencies - ✅ Schwache Präferenz
Entwickler erwarten natürlicherweise, dass ein “Constraint” tatsächlich eine Einschränkung darstellt, die nicht einfach überschrieben werden kann.
Laut der Publishing Dokumentation:
“Rich version information is preserved in the Gradle Module Metadata format. However, converting this information to Ivy or Maven metadata formats is lossy. The highest level of version declaration—strictly or require over prefer—will be published, and any reject will be ignored.” Quelle: Gradle User Guide - Publishing
Dies bedeutet, dass beim Publishing nur die “stärkste” Version-Deklaration übertragen wird, was die Constraint-Problematik zusätzlich verschärft.
dependencies {
// enforcedPlatform kann NICHT übersteuert werden
implementation(enforcedPlatform("com.mycompany:approved-versions:1.0"))
implementation("com.google.guava:guava") // Verwendet Version aus Platform
}Referenz: Gradle Platform Documentation
configurations.all {
resolutionStrategy {
eachDependency { details ->
if (details.requested.group == 'log4j' &&
details.requested.name == 'log4j') {
details.useVersion('2.17.1')
details.because('Unternehmensweite Sicherheitsrichtlinie')
}
}
}
}Referenz: Resolution Strategy Documentation
configurations.all {
resolutionStrategy {
failOnVersionConflict() // Fehler bei Versionskonflikten
}
}<!-- In verification-metadata.xml -->
<components>
<component group="log4j" name="log4j">
<artifact name="log4j-2.14.0.jar">
<rejected>Sicherheitslücke CVE-2021-44228</rejected>
</artifact>
</component>
</components>Referenz: Dependency Verification Documentation
Verlassen Sie sich nicht auf Constraints für Sicherheitsupdates
enforcedPlatform oder
Resolution StrategiesDokumentieren Sie Ihre Absicht klar
constraints {
implementation("...") {
because("ACHTUNG: Kann von direkten Dependencies übersteuert werden!")
}
}Nutzen Sie Dependency Insight zur Fehlersuche
./gradlew dependencyInsight --dependency guavaImplementieren Sie strikte Kontrollen für kritische Dependencies
// Zentrale build-conventions/security.gradle
configurations.all {
resolutionStrategy {
force("log4j:log4j:2.17.1") // Deprecated aber funktioniert
// Oder moderne Alternative:
eachDependency { /* ... */ }
}
}Verwenden Sie Gradle Enterprise Build Scans
Etablieren Sie Dependency-Review-Prozesse
Gradle’s Dependency Constraints sind ein mächtiges Feature, aber ihre Benennung und das tatsächliche Verhalten stehen in einem fundamentalen Widerspruch. Was als “Constraint” (Einschränkung) bezeichnet wird, ist in Wirklichkeit nur eine schwache Empfehlung, die leicht übersteuert werden kann.
Für sicherheitskritische oder compliance-relevante
Versionsanforderungen sollten Entwickler auf stärkere Mechanismen wie
enforcedPlatform oder Resolution Strategies zurückgreifen.
Die Gradle-Community würde von einer klareren Benennung (z.B.
“suggestions” oder “preferences”) oder einer Änderung des Verhaltens
profitieren, um die Erwartungen besser zu erfüllen.
Offizielle Gradle Dokumentation: - Dependency Constraints - Rich Version Constraints - Dependency Resolution - Dependency Version Alignment - Publishing Gradle Module Metadata