Skip to content

Vendored Gradle: gradle_exclusive_content_conflict refusal doesn't fire for a subproject build script or a buildSrc convention plugin, so vendor exits 0, the build then fails with "Could not find", and VEX attests not_affected #461

Description

[agent] Found by the scheduled Gradle bug-hunt routine (ledger #319).

Summary

The v5 Gradle vendoring planner refuses with gradle_exclusive_content_conflict when a user exclusiveContent rule claims the patched group. That check only runs on the settings files and the root build.gradle(.kts) (crates/socket-patch-core/src/vendor/jvm/gradle.rs:117-120, :156-158). It never sees the build scripts where repositories usually live in a multi-project build:

  • a subproject's app/build.gradle
  • a buildSrc (or build-logic) precompiled convention plugin such as buildSrc/src/main/groovy/bh.repos.gradle

In these shapes vendor exits 0 and reports applied. Every build of the vendored checkout then fails at resolution with Could not find org.apache.commons:commons-text:1.10.0, because the user's exclusive repository and socketPatchVendor both claim the module. vex attests not_affected for that build anyway.

This also breaks the documented hosted → vendored path. The hosted Gradle snippet (redirect_gradle_manual_snippet, docs/ecosystems.md "Gradle (hosted Maven)") is an exclusiveContent block that users paste into the build script that declares the dependency, which in a multi-project build is the subproject's script. If that snippet pins the base version (the legacy, non-suffixed form) and the user then runs vendor, they hit exactly this failure.

Impact

  • vendor reports success but leaves a build that can't resolve the patched dependency. It fails loudly, so the unpatched jar never ships. Still, CI breaks right after a "successful" vendor run, with an error that doesn't mention socket-patch.
  • vex emits not_affected / inline_mitigations_already_exist for a build that can't run.
  • The same rule placed in root allprojects {} / subprojects {} is refused correctly (control below), so this is an inconsistent refusal, not a design choice.

Repro

I used the repo's own vendored-JVM fixture: prebuilt_common::prepare_command + a staged .socket/manifest.json/blob for pkg:maven/org.apache.commons/commons-text@1.10.0, the same as e2e_vendor_jvm_build. Steps:

settings.gradle:      rootProject.name = 'ex'
                      include 'app'
app/build.gradle:     plugins { id 'java' }
                      repositories {
                          exclusiveContent {
                              forRepository { mavenCentral() }
                              filter { includeGroup 'org.apache.commons' }
                          }
                          mavenCentral()
                      }
                      dependencies { implementation 'org.apache.commons:commons-text:1.10.0' }
                      tasks.register('printCp') { def rc = configurations.runtimeClasspath
                          doLast { rc.files.each { println 'SOCKET-CP ' + it } } }
  1. gradle :app:printCp: OK, resolves Central's jar.
  2. Stage the manifest + blob, then socket-patch vendor --json: exit 0, "action": "applied", summary.failed = 0.
  3. Copy the committable files (no manifest or blobs) to a fresh checkout, then gradle :app:printCp:
    > Could not resolve all dependencies for configuration ':app:runtimeClasspath'.
       > Could not find org.apache.commons:commons-text:1.10.0.
    
  4. socket-patch vex --product pkg:maven/com.example/ex@1 --output vex.json: exit 0, "status": "not_affected" for commons-text.

Variants with the same result (vendor exit 0, then the build fails):

  • conv: the same repositories { exclusiveContent … } block in buildSrc/src/main/groovy/bh.repos.gradle (with id 'groovy-gradle-plugin'), applied by app with plugins { id 'bh.repos' }.
  • snippet: the hosted snippet shape, exclusiveContent { forRepository { maven { url "<hosted repo>" } } filter { includeVersion("org.apache.commons", "commons-text", "1.10.0") } }, in app/build.gradle.

Control (correct): the same block inside root build.gradle allprojects { … } or subprojects { … } gives vendor exit 1, vendor_jvm_shape_unsupported, reason: gradle_exclusive_content_conflict: build.gradle has an exclusiveContent rule for org.apache.commons; drop org.apache.commons:commons-text from it, and nothing is written.

Expected vs actual

  • Expected: docs/design/maven-vendoring.md (Gradle) says "Android, Kotlin Multiplatform, available-at module redirects, conflicting exclusive-content rules and paths leaving the checkout are refused." The check_exclusive_content doc comment says such a rule "would make ours unreachable ('Could not find')". Per CLI_CONTRACT a failed patch is reported as failed, so vendor should refuse (or at least warn) for every build script it can statically find. That includes included subproject scripts and buildSrc / literal includeBuild convention-plugin sources, the same way it already scans settings, buildSrc settings and included builds.
  • Actual: only the root build script is checked. Subproject and convention-plugin rules are missed, so the result is a success report, a broken build, and a not_affected VEX.

Matrix

OS Gradle (JDK) sub conv (buildSrc) snippet in sub root allprojects/subprojects (control)
Linux 8.14.3 (21) repro repro repro refused (correct)
Linux 9.8.0 (21) repro repro repro not run
macOS / Windows — untested; the planner is a pure static scan, so I expect the same result

Each cell ran twice across this run (8.14.3 for sub, conv and snippet; 9.8.0 for sub, conv and snippet). I didn't bisect: the Gradle vendoring backend is new in 2463257 (#277), which is the current main and is unreleased (latest release v4.0.0 refuses Gradle vendoring outright).

Suspect code

  • crates/socket-patch-core/src/vendor/jvm/gradle.rs:117-120: only read_build_script(read, "") (the root) goes through check_exclusive_content.
  • crates/socket-patch-core/src/vendor/jvm/gradle.rs:156-158: targets are settings files of the root, buildSrc and included builds. Subproject build scripts and convention-plugin sources aren't targets.

Tested on main 2463257.

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions