37 Appendix

37.1 Der Gradle Dependency Constraints Irrtum: Warum “Constraints” nicht wirklich einschränken

37.2 Zusammenfassung

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.

37.3 Das Problem: Wenn Constraints nicht constrainen

37.3.1 Was die Dokumentation verspricht

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.

37.3.2 Die überraschende Realität

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!

37.4 Die tatsächliche Prioritätshierarchie

Nach der Rich Versions Dokumentation gilt folgende Prioritätsreihenfolge:

  1. strictly - Höchste Priorität, erzwingt exakte Version
  2. require (normale implementation/api Dependencies) - Übersteuert Constraints!
  3. Constraints - Werden von beiden oberen übersteuert
  4. prefer - Niedrigste Priorität

37.4.1 Strictly vs. Constraints

dependencies {
    // 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")
    }
}

37.4.2 Normale Dependencies vs. Constraints

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!

37.5 Warum ist das problematisch?

37.5.1 1. Sicherheitsrisiken

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!
}

37.5.2 2. Missverständliche Nomenklatur

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

37.5.3 3. Verletzung des Principle of Least Surprise

Entwickler erwarten natürlicherweise, dass ein “Constraint” tatsächlich eine Einschränkung darstellt, die nicht einfach überschrieben werden kann.

37.6 Das Metadata Publishing Problem

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.

37.7 Alternative Lösungsansätze

37.7.1 1. Enforced Platform (Empfohlen)

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

37.7.2 2. Resolution Strategy

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

37.7.3 3. Strict Version Checking

configurations.all {
    resolutionStrategy {
        failOnVersionConflict() // Fehler bei Versionskonflikten
    }
}

37.7.4 4. Dependency Verification

<!-- 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

37.8 Empfehlungen für die Praxis

37.8.1 Für Entwickler

  1. Verlassen Sie sich nicht auf Constraints für Sicherheitsupdates

  2. Dokumentieren Sie Ihre Absicht klar

    constraints {
        implementation("...") {
            because("ACHTUNG: Kann von direkten Dependencies übersteuert werden!")
        }
    }
  3. Nutzen Sie Dependency Insight zur Fehlersuche

    ./gradlew dependencyInsight --dependency guava

37.8.2 Für Build-Administratoren

  1. Implementieren 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 { /* ... */ }
        }
    }
  2. Verwenden Sie Gradle Enterprise Build Scans

  3. Etablieren Sie Dependency-Review-Prozesse

37.9 Folgerung

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.

37.10 Quellen und weiterführende Literatur

Offizielle Gradle Dokumentation: - Dependency Constraints - Rich Version Constraints - Dependency Resolution - Dependency Version Alignment - Publishing Gradle Module Metadata