Kraken’s nightly dependency-CVE scan opened #850: three open Dependabot alerts on
org.bouncycastle:bcprov-jdk18on (GHSA-574f-3g2m-x479, critical, <= 1.80.1;
GHSA-c3fc-8qff-9hwx, < 1.84) and org.bouncycastle:bcpkix-jdk18on
(GHSA-wg6q-6289-32hp, < 1.84). The lead concluded “no change needed — the
buildscript block already forces 1.84.”
The buildscript block does force 1.84, and it landed in #376, an ancestor of the commit Kraken scanned. The alerts were open anyway. A fix that is already present and an alert that is still open means the fix does not cover what the alert sees.
buildscript { configurations.all { resolutionStrategy.force(...) } } in the root
build.gradle.kts constrains exactly one thing: the buildscript classpath. It
does not reach project configurations, which Gradle resolves independently. The
bc-jdk18on-bom platform import in server/build.gradle.kts likewise constrains
only :server.
Bouncy Castle is not a :server dependency at all — :server:dependencyInsight
--configuration jvmRuntimeClasspath --dependency bcprov-jdk18on returns no match.
It arrives through the Android Gradle Plugin, on project configurations belonging to
:androidApp, :composeApp and :shared:
androidLintTool → com.android.tools:sdk-common:32.2.1 → bcpkix/bcprov/bcutil:1.79unified-test-platform-android-test-plugin-result-listener-gradle → same 1.79 chainThose are ordinary project configurations, so the buildscript force never applied
and they kept resolving 1.79. GitHub’s dependency graph reflected exactly that — the
SBOM carried sdk-common:32.2.1 twice, once depending on BC 1.84 (the forced
buildscript context) and once on BC 1.79 (the unguarded project context). Dependabot
was reading a real, still-vulnerable resolution, not a stale one.
The alert’s “build / toolchain only — NOT on the shipped runtime classpath” classification was correct: lint and the test-platform result listener are never packaged. The real cost was three permanently-unclosable alerts, one of them critical, masking any genuine BC regression that might land later.
build.gradle.kts — added an allprojects { configurations.configureEach {
resolutionStrategy.eachDependency { … } } } floor for org.bouncycastle:*-jdk18on,
reading the existing bouncycastle = "1.84" ref from gradle/libs.versions.toml.
This mirrors the floors already in the file for gson, guava, log4j2, httpclient and
commons-lang3, and reaches project configurations the buildscript block cannot.
The rule only raises: a helper compares dot-separated numeric versions so a future
1.85 is never dragged back down to the 1.84 floor. This follows the guava rule’s
conditional shape rather than gson’s unconditional useVersion.
Verified with :androidApp, :composeApp and :shared dependency trees — every
1.79 coordinate now shows 1.79 -> 1.84, and :server still resolves no Bouncy
Castle at all. ./gradlew :androidApp:lintDebug is green, proving AGP’s lint tool
runs against the raised classpath rather than silently needing the 1.79 API.
buildscript { … } blocks, platform()
BOM imports, and allprojects { configurations.configureEach { … } } guard three
disjoint sets of configurations. Fixing a CVE in one of them and assuming the
coordinate is clean everywhere is the failure this issue is made of. When flooring
a transitive, state which configurations the mechanism actually covers.git merge-base --is-ancestor).
If it does, the alert is telling you the fix has a hole — do not close it as stale.gh api repos/<owner>/<repo>/dependency-graph/sbom. The same module appearing at
two versions in the SBOM is the fingerprint of a partially-scoped resolution rule.
(The Dependabot alerts REST endpoint needs a token scope the agent PAT lacks — the
SBOM endpoint does not, and carries the same resolved versions.)