Skip to content

security(deps): force BouncyCastle 1.85 on :app configs AND the root buildscript classpath (2 critical alerts) #291

Description

@DocGerd

Priority: P1

Context

Two open critical Dependabot alerts, #33 and #34, both GHSA-574f-3g2m-x479 on org.bouncycastle:bcprov-jdk18on ("GOST 28147 CTR mode reuses keystream after 255 blocks").

They are two alerts for one advisory because the advisory declares multiple disjoint version ranges, and this repo has BouncyCastle on three different classpaths from two different sources:

Alert Range Source Classpaths
#34 = 1.81.0 org.robolectric:robolectric:4.16.1 debugUnitTest{Compile,Runtime,LintChecks}Classpath
#33 >= 1.59, <= 1.80.1 com.android.tools:sdk-common:32.2.1 androidLintTool, UTP result-listener
#33 >= 1.59, <= 1.80.1 com.android.tools.build:apkzlib:9.2.1 root buildscript / plugin classpath

Fixing only one side leaves 1.79 in the submitted graph and #33 stays open. Two levers are required.

Evidence

  • BouncyCastle is absent from releaseRuntimeClasspath, debugRuntimeClasspath, and app/gradle.lockfile. Build-time only — never ships in the APK.
  • Reachability of the CVE itself is nil regardless: it is specific to GOST 28147 (a Russian national cipher) in CTR mode. Nothing in this codebase or the Android signing/lint toolchain invokes it.
  • AGP is not a lever. apkzlib 9.3.2 — the latest stable AGP — still declares bcprov 1.79 (verified by fetching the POM). Waiting for AGP does not fix this.

Correction to a committed claim

docs/security-posture.md (~line 340) states that plugin/buildscript-classpath transitives are AGP-version-gated and not user-forceable. That is false and was empirically disproven — see the WI-2b block below, which was actually run on this repo's Gradle 9.5.1 and produced bcpkix 1.79 -> 1.85, bcprov 1.79 -> 1.85. Tracked separately in the docs issue.

Proposed fix

Target 1.85 — not the alerts' first_patched_version

The advisory has a third range the alerts do not surface: >= 1.82, <= 1.83, fixed in 1.84. So the alerts' suggested 1.80.2 / 1.81.1 are dead-end branches.

1.85 is the newest version published for all three modules and clears every range. (bcprov alone has 1.85.2; bcpkix/bcutil stop at 1.85 — a 1.85.2 force fails with Could not find org.bouncycastle:bcpkix-jdk18on:1.85.2, confirmed by running it.) 1.85 also clears the already-dismissed #17/#18, whose rationale is now stale.

(a) :app project configurations

app/build.gradle.kts — first statement inside the existing resolutionStrategy.eachDependency { } (between current lines 309 and 310):

// GHSA-574f-3g2m-x479: AGP lint/UTP (`com.android.tools:sdk-common`) pulls
// bcpkix/bcprov/bcutil 1.79 onto `androidLintTool` and the UTP result-listener
// config; Robolectric 4.16.1 pulls bcprov 1.81 onto the debugUnitTest* classpaths.
// Build-time only — absent from both locked runtime classpaths and app/gradle.lockfile.
// 1.85 is the newest version published for all three modules and clears every range of
// this advisory, incl. the 1.82-1.83 branch the alerts' first_patched_version does NOT.
// Drop once stable AGP + Robolectric both ship BouncyCastle >= 1.84.
if (requested.group == "org.bouncycastle" && requested.version.orEmpty().startsWith("1.")) {
    useVersion("1.85")
    because(
        "GHSA-574f-3g2m-x479: pin AGP-lint/UTP + Robolectric BouncyCastle to a " +
            "patched release (build-time only; not shipped).",
    )
}

(b) root buildscript (plugin) classpath

build.gradle.kts — between the header comment and the plugins { } block. In the Kotlin DSL buildscript {} must precede plugins {}:

// GHSA-574f-3g2m-x479: AGP 9.2.1's com.android.tools.build:apkzlib pulls
// bcpkix/bcprov/bcutil 1.79 onto the Gradle PLUGIN classpath — build-time only, never in
// the APK, but Automatic Dependency Submission force-resolves this classpath too, so it is
// what alert #33 sees. No stable AGP fixes it: apkzlib 9.3.2 still declares 1.79.
//
// `configurations.classpath` here IS the root buildscript classpath the `plugins {}` block
// resolves its markers on, so a force declared here reaches AGP's transitives.
// Drop once a stable AGP ships apkzlib with BouncyCastle >= 1.84.
buildscript {
    configurations.classpath {
        resolutionStrategy {
            force(
                "org.bouncycastle:bcprov-jdk18on:1.85",
                "org.bouncycastle:bcpkix-jdk18on:1.85",
                "org.bouncycastle:bcutil-jdk18on:1.85",
            )
        }
    }
}

Neither block references a java.* package root, so the documented .kts shadow gotcha does not apply here.

Risk register

Risk Status
BC 1.79 → 1.85 under apksig/apkzlib (APK signing) ✅ Tested — debug + release packaging succeed, v2 signature verifies, signer cert ec9a4bb8…b3d9 unchanged
BC 1.79 → 1.85 on androidLintTool :app:lintDebug green
bcprov 1.81 → 1.85 under Robolectric 4.16.1 ✅ Full testDebugUnitTest green, screenshot goldens included
BC on the UTP result-listener config (instrumented runs) ⚠️ Not covered locally — gate on the required androidTest check
useVersion/force downgrade semantics Accepted; guarded to the 1. line, drop-condition in comments
Mixed BC module versions Avoided by forcing all three together
APK / UAT impact None — lockfiles byte-identical → shipped artifact unchanged → no Room/UAT re-run

Acceptance criteria

Same stop rule as the netty issue: if still open ~24 h post-merge after a successful submission run, dismiss as tolerable_risk rather than re-engineering.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    choresecurityIssues addressing concrete security findings (see docs/security/)

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions