From e1e45533ed776a4c64fb907eda715e77572c5f79 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 14 Sep 2026 13:44:52 +0000 Subject: [PATCH 1/4] fix(security): patch known vulnerabilities in the build toolchain Resolving every Gradle configuration and scanning it against OSV turned up 40 advisories across 11 artifacts, none of which Dependabot could see: GitHub's dependency graph for this repo contains six packages, all GitHub Actions. A Gradle project only reaches the graph via dependency submission, so Gradle dependencies have never been covered by Dependabot alerts here. All 40 are build-time only. Pin them to patched versions: - io.netty 4.1.93.Final -> 4.1.138.Final (30 advisories, incl. CVE-2026-75595) - protobuf 3.22.3/3.24.4 -> 3.25.9 (CVE-2024-7254) - jsoup 1.16.1 -> 1.23.2 (CVE-2026-71497) - jackson 2.12.7 -> 2.14.3, which also lifts woodstox 6.2.4 -> 6.5.1 (CVE-2022-40152) netty and protobuf come from AGP's Unified Test Platform, the rest from Dokka. Nothing here is declared by this project, so the pins go through resolutionStrategy rather than dependency constraints - constraints cannot reach AGP's internal _internal-unified-test-platform-* configurations. Jackson stops at 2.14.3 on purpose. Dokka 1.9.20 is compiled against Jackson 2.12-2.15 and calls TypeFactory(LRUMap), which 2.16 replaced, so a fully patched 2.18.x makes dokkaHtml fail at runtime and would break the javadoc jar on every release. 2.14.3 is the newest version Dokka can load; it clears CVE-2026-50193 and CVE-2025-49128 and stops short of 2.15.x, which introduces two advisories that 2.12 and 2.14 do not have. The six remaining Jackson advisories all need 2.18.8+ and are polymorphic-typing issues requiring attacker-controlled JSON; Dokka only deserializes its own build-generated configuration. Clearing them properly means moving to Dokka 2.2+, which drops Jackson entirely. releaseRuntimeClasspath and releaseCompileClasspath are unchanged, so the published AAR and its POM are unaffected. Also add the Dependabot coverage that would have caught these: - .github/dependabot.yml for weekly Gradle and GitHub Actions updates - .github/workflows/dependency-submission.yml to submit the resolved Gradle graph on pushes to main, so Dependabot alerts can see Gradle dependencies Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0125fdWPosfenDkd5ob7xuHp --- .github/dependabot.yml | 24 +++++++++++++ .github/workflows/dependency-submission.yml | 36 +++++++++++++++++++ android/build.gradle.kts | 39 +++++++++++++++++++++ gradle/libs.versions.toml | 10 ++++++ 4 files changed, 109 insertions(+) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/dependency-submission.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..b69872f --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,24 @@ +version: 2 +updates: + # Gradle dependencies, including the gradle/libs.versions.toml version catalog. + # Note: Dependabot *alerts* for Gradle need the dependency graph, which this repo + # submits from .github/workflows/dependency-submission.yml. + - package-ecosystem: gradle + directory: "/" + schedule: + interval: weekly + open-pull-requests-limit: 5 + groups: + androidx: + patterns: + - "androidx.*" + kotlin: + patterns: + - "org.jetbrains.kotlin*" + - "org.jetbrains.kotlinx*" + + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: weekly + open-pull-requests-limit: 5 diff --git a/.github/workflows/dependency-submission.yml b/.github/workflows/dependency-submission.yml new file mode 100644 index 0000000..f998ee7 --- /dev/null +++ b/.github/workflows/dependency-submission.yml @@ -0,0 +1,36 @@ +# Submits the resolved Gradle dependency graph to GitHub so that Dependabot can +# raise alerts for transitive dependencies. Without this, GitHub only sees the +# GitHub Actions used by this repository and none of the Gradle dependencies. +name: Dependency Submission + +on: + push: + branches: + - main + workflow_dispatch: + +permissions: + contents: write + +jobs: + dependency-submission: + name: Submit Gradle dependency graph + runs-on: ubuntu-latest + + steps: + - name: Harden Runner + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + + - name: Check out code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Set up JDK 17 + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + with: + java-version: "17" + distribution: "temurin" + + - name: Generate and submit dependency graph + uses: gradle/actions/dependency-submission@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0 diff --git a/android/build.gradle.kts b/android/build.gradle.kts index 9622652..459cf89 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -20,6 +20,45 @@ jacoco { toolVersion = "0.8.11" } +// Force known-vulnerable transitive dependencies of the build toolchain onto patched +// versions. None of these are dependencies of the SDK itself - they are pulled in by the +// Android Gradle Plugin's Unified Test Platform (netty, protobuf) and by Dokka +// (jackson, jsoup), so the published AAR and its POM are unaffected. +// Drop an entry once the tool that brings it in ships a patched version by default. +run { + val securityPins = mapOf( + "io.netty" to libs.versions.netty.get(), + "org.jsoup" to libs.versions.jsoup.get(), + // Dokka 1.9.20 is compiled against Jackson 2.12-2.15 (it calls the + // TypeFactory(LRUMap) constructor that 2.16 replaced), so it cannot run on a + // fully patched 2.18.x. 2.14.3 is the best version it can load: it clears + // CVE-2026-50193 and CVE-2025-49128 without pulling in the advisories that + // first appear in 2.15.x. The rest need Dokka 2.2+, which drops Jackson entirely. + "com.fasterxml.jackson" to libs.versions.jackson.get(), + "com.fasterxml.jackson.core" to libs.versions.jackson.get(), + "com.fasterxml.jackson.dataformat" to libs.versions.jackson.get(), + "com.fasterxml.jackson.module" to libs.versions.jackson.get(), + ) + + configurations.configureEach { + resolutionStrategy.eachDependency { + securityPins[requested.group]?.let { pinned -> + useVersion(pinned) + because("security pin - see gradle/libs.versions.toml") + } + + // protobuf-java 4.x is a breaking change for the tooling that depends on it, + // so stay on the patched 3.25.x line. + if (requested.group == "com.google.protobuf" && + requested.version?.startsWith("3.") == true + ) { + useVersion(libs.versions.protobuf.get()) + because("CVE-2024-7254 - patched in the 3.25.x line") + } + } + } +} + android { namespace = "com.formbricks.android" compileSdk = 35 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 35174a5..f9e6dbc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -22,6 +22,16 @@ lifecycleViewmodelKtx = "2.9.0" fragmentKtx = "1.8.7" databindingCommon = "8.9.2" +# Security pins for vulnerable transitive dependencies of the build toolchain +# (Android Gradle Plugin's Unified Test Platform and Dokka). These never reach +# the published AAR - see the resolutionStrategy block in android/build.gradle.kts, +# which explains why `jackson` is capped below the latest patched release. +# Re-check these whenever `agp` or the Dokka plugin is upgraded. +netty = "4.1.138.Final" +protobuf = "3.25.9" +jackson = "2.14.3" +jsoup = "1.23.2" + [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } junit = { group = "junit", name = "junit", version.ref = "junit" } From dc03ae94f1b8b4718648c245bba6bc573d3de796 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 15 Sep 2026 08:08:52 +0000 Subject: [PATCH 2/4] fix(security): drop unused Dokka plugin, make the pins floors Addresses pandeymangg's review on #61. Dokka was never actually run. `publishAndReleaseToMavenCentral --dry-run` resolves to AGP's own javaDocReleaseGeneration + javaDocReleaseJar with no dokka* task anywhere, and no workflow or doc invokes it either. The Jackson cap in the previous commit was protecting a code path nothing executes, and the stated reason for deferring the real fix was wrong. Since the plugin is unused, remove it rather than upgrade it. Dokka 2.2.0 was measured first and is not the clean win it looked like: it still resolves Jackson 2.15.3, and 2.15.x *introduces* GHSA-3pjw-73gf-8qr5 and GHSA-72hv-8253-57qq that 2.14.3 does not have, so it only moves 15 advisories to 14. Removing the plugin takes Jackson, jsoup and woodstox out of the build entirely. Both surfaces now scan clean against OSV: - project configurations: 269 artifact versions, 0 advisories (was 40) - Gradle plugin classpath: 27 artifact versions, 0 advisories (was 9) The Jackson and jsoup floors go with it - nothing pulls either any more, so keeping them would be dead configuration. netty and protobuf stay; they come from AGP's Unified Test Platform, not Dokka. Those two are now floors rather than overrides. Bare `useVersion` also drags a newer version back down, so an `agp` bump shipping netty above 4.1.138 would have been quietly reverted to today's pin - and this PR makes those bumps weekly. A numeric comparison leaves anything at or above the floor alone. This also retires the `startsWith("3.")` guard on protobuf: 4.x now sorts above the floor on its own. Documented in the catalog that Dependabot cannot bump these entries - its Gradle parser only reaches [versions] through a version.ref and nothing references them. As floors, a stale entry is inert rather than harmful. Verified: assembleRelease green; :android:dependencies and :android:buildEnvironment rescanned; publishAndReleaseToMavenCentral --dry-run unchanged, still producing the javadoc jar from AGP's tasks. Note for reviewers: this removes the ability to run `./gradlew dokkaHtml` locally. Nothing in CI, publishing or the README used it, but restoring the plugin is a one-line revert if anyone did. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0125fdWPosfenDkd5ob7xuHp --- android/build.gradle.kts | 56 +++++++++++++++++++-------------------- gradle/libs.versions.toml | 15 ++++++----- 2 files changed, 35 insertions(+), 36 deletions(-) diff --git a/android/build.gradle.kts b/android/build.gradle.kts index 459cf89..8d3bf67 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -5,7 +5,6 @@ plugins { kotlin("android") kotlin("kapt") kotlin("plugin.serialization") version "2.1.0" - id("org.jetbrains.dokka") version "1.9.20" id("jacoco") id("com.vanniktech.maven.publish") version "0.31.0" id("org.sonarqube") version "4.4.1.3373" @@ -20,40 +19,39 @@ jacoco { toolVersion = "0.8.11" } -// Force known-vulnerable transitive dependencies of the build toolchain onto patched -// versions. None of these are dependencies of the SDK itself - they are pulled in by the -// Android Gradle Plugin's Unified Test Platform (netty, protobuf) and by Dokka -// (jackson, jsoup), so the published AAR and its POM are unaffected. -// Drop an entry once the tool that brings it in ships a patched version by default. +// Raise known-vulnerable transitive dependencies of the build toolchain to patched +// versions. Neither is a dependency of the SDK itself - both are pulled in by the Android +// Gradle Plugin's Unified Test Platform, so the published AAR and its POM are unaffected. +// +// These are floors, not overrides: `useVersion` on its own would also drag a *newer* +// version back down, so anything at or above the floor is left alone and only older +// versions are raised. That matters now that Dependabot bumps `agp` weekly and each bump +// can ship newer transitives of its own. run { - val securityPins = mapOf( + val securityFloors = mapOf( "io.netty" to libs.versions.netty.get(), - "org.jsoup" to libs.versions.jsoup.get(), - // Dokka 1.9.20 is compiled against Jackson 2.12-2.15 (it calls the - // TypeFactory(LRUMap) constructor that 2.16 replaced), so it cannot run on a - // fully patched 2.18.x. 2.14.3 is the best version it can load: it clears - // CVE-2026-50193 and CVE-2025-49128 without pulling in the advisories that - // first appear in 2.15.x. The rest need Dokka 2.2+, which drops Jackson entirely. - "com.fasterxml.jackson" to libs.versions.jackson.get(), - "com.fasterxml.jackson.core" to libs.versions.jackson.get(), - "com.fasterxml.jackson.dataformat" to libs.versions.jackson.get(), - "com.fasterxml.jackson.module" to libs.versions.jackson.get(), + "com.google.protobuf" to libs.versions.protobuf.get(), ) + fun isBelowFloor(current: String?, floor: String): Boolean { + if (current.isNullOrBlank()) return true + fun numericParts(v: String) = v.split('.', '-', '_').mapNotNull(String::toIntOrNull) + val actual = numericParts(current) + val wanted = numericParts(floor) + for (i in 0 until maxOf(actual.size, wanted.size)) { + val a = actual.getOrElse(i) { 0 } + val b = wanted.getOrElse(i) { 0 } + if (a != b) return a < b + } + return false + } + configurations.configureEach { resolutionStrategy.eachDependency { - securityPins[requested.group]?.let { pinned -> - useVersion(pinned) - because("security pin - see gradle/libs.versions.toml") - } - - // protobuf-java 4.x is a breaking change for the tooling that depends on it, - // so stay on the patched 3.25.x line. - if (requested.group == "com.google.protobuf" && - requested.version?.startsWith("3.") == true - ) { - useVersion(libs.versions.protobuf.get()) - because("CVE-2024-7254 - patched in the 3.25.x line") + val floor = securityFloors[requested.group] ?: return@eachDependency + if (isBelowFloor(requested.version, floor)) { + useVersion(floor) + because("security floor - see gradle/libs.versions.toml") } } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f9e6dbc..70940f5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -22,15 +22,16 @@ lifecycleViewmodelKtx = "2.9.0" fragmentKtx = "1.8.7" databindingCommon = "8.9.2" -# Security pins for vulnerable transitive dependencies of the build toolchain -# (Android Gradle Plugin's Unified Test Platform and Dokka). These never reach -# the published AAR - see the resolutionStrategy block in android/build.gradle.kts, -# which explains why `jackson` is capped below the latest patched release. -# Re-check these whenever `agp` or the Dokka plugin is upgraded. +# Security floors for vulnerable transitive dependencies of the Android Gradle Plugin's +# Unified Test Platform. These never reach the published AAR - see the resolutionStrategy +# block in android/build.gradle.kts. +# +# Dependabot does not track these: its Gradle parser only reaches [versions] through a +# `version.ref` in [libraries]/[plugins], and nothing references them. Re-check them by +# hand when `agp` is upgraded - because they are floors, one at or below what AGP already +# ships is a no-op, so a stale entry is inert rather than harmful. netty = "4.1.138.Final" protobuf = "3.25.9" -jackson = "2.14.3" -jsoup = "1.23.2" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } From 582030bfd86b36439080f247c758270e43e51889 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 15 Sep 2026 08:14:30 +0000 Subject: [PATCH 3/4] ci: bump setup-android to v4.0.1 so the SDK step stops installing `tools` SonarCloud went red on dc03ae9 in "Setup Android SDK", before any Gradle invocation: [command] .../cmdline-tools/16.0/bin/sdkmanager tools Warning: Failed to find package 'tools' Error: The process '.../sdkmanager' failed with exit code 1 android-actions/setup-android v3.2.2 installs the obsolete `tools` package, which Google has delisted from the SDK repository, so the step now fails regardless of the diff. v4.0.1 installs only `cmdline-tools`. Not caused by this PR: the step runs before Gradle and never reads build.gradle.kts or libs.versions.toml, and the identical workflow and action SHA passed on e1e4553 eight minutes earlier. Confirmed environmental rather than flaky by one re-run of the failed job, which reproduced it exactly. Kept the step rather than deleting it, even though ubuntu-latest presets ANDROID_HOME: the later steps call `sdkmanager --licenses` and install platforms;android-33, and setup-android is what puts sdkmanager on PATH. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0125fdWPosfenDkd5ob7xuHp --- .github/workflows/sonarcloud.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index c9d0b3f..0773517 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -35,7 +35,7 @@ jobs: cache: gradle - name: Setup Android SDK - uses: android-actions/setup-android@9fc6c4e9069bf8d3d10b2204b1fb8f6ef7065407 # v3.2.2 + uses: android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699 # v4.0.1 # Setup KVM for hardware acceleration - name: Setup KVM From eef6ee4b5df89c8d4d5a642f4831f1a57fbaf4ca Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 15 Sep 2026 08:16:27 +0000 Subject: [PATCH 4/4] ci: drop the obsolete `tools` package from the setup-android input The v4.0.1 bump in 582030b did not fix the failure and I should not have expected it to. v4.0.1 failed identically: [command] .../cmdline-tools/20.0/bin/sdkmanager tools Warning: Failed to find package 'tools' The `tools` install comes from the action's default *input*, not its code: packages: default: 'tools platform-tools' That default is byte-identical in v3.2.2 and v4.0.1, so no version of the action avoids it. I checked the action's src/ for a hardcoded install and concluded the bump was the fix, without reading action.yml, where the real cause was. Overriding the input is the actual fix. `platform-tools` is kept because it is what the default intended minus the dead package, and "Install SDK components" installs it again below, so nothing is lost. Keeping the v4.0.1 bump rather than reverting it: it was not the fix, but it is not wrong either - it ships cmdline-tools 20.0 and declares node24 natively, which clears the Node 20 deprecation warning the same run emitted for this action. Reverting to v3.2.2 with this same input override would work equally well if a smaller diff is preferred. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0125fdWPosfenDkd5ob7xuHp --- .github/workflows/sonarcloud.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 0773517..f259b2a 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -36,6 +36,11 @@ jobs: - name: Setup Android SDK uses: android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699 # v4.0.1 + with: + # The action's default is "tools platform-tools". `tools` is obsolete and Google + # has delisted it, so sdkmanager exits 1 and fails the step. platform-tools is + # installed again by "Install SDK components" below, so this loses nothing. + packages: platform-tools # Setup KVM for hardware acceleration - name: Setup KVM