Skip to content

Agent-mode apply in a Gradle-only project patches the ~/.m2 copy Gradle never reads, reports success, and VEX attests not_affected while the build uses the unpatched ~/.gradle jar #551

Description

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

Summary

In a Gradle-only project (settings.gradle + build.gradle, repositories { mavenCentral() }), agent-mode apply resolves the Maven patch against the Maven local repository ($MAVEN_REPO_LOCAL / ~/.m2/repository) whenever that repository happens to hold the same GAV, for example from unrelated Maven use on the same machine or CI image. It patches that jar in place and exits 0 with applied: 1, and vex then emits not_affected / inline_mitigations_already_exist. Gradle never reads ~/.m2 unless the build declares mavenLocal(). It resolves from $GRADLE_USER_HOME/caches/modules-2/files-2.1/…, so the real build keeps compiling and running the unpatched jar.

This is the agent-mode analogue of #397 (NuGet) and #387 (cargo). It's separate from #349: #349 is the discovery gap (scan finds 0 packages and never fires). Here a manifest is already present (committed by a teammate, or saved by get), and apply + vex claim protection that the build doesn't have.

Impact

A false VEX attestation, plus a success exit code, for a vulnerability that's still present in the shipped Gradle build. As a side effect, the jar under ~/.m2 is mutated (with its .sha1 sidecar now stale) for every other Maven project on the machine.

Repro (Linux, Gradle 8.14.3, JDK 21, main 61cfb9b)

export GRADLE_USER_HOME=$PWD/gh MAVEN_REPO_LOCAL=$PWD/m2
mkdir proj && cd proj
echo "rootProject.name='p'" > settings.gradle
cat > build.gradle <<'EOF'
plugins { id 'java' }
repositories { mavenCentral() }
dependencies { implementation 'org.apache.commons:commons-text:1.10.0' }
tasks.register('printCp') { doLast { configurations.runtimeClasspath.files.each { println "CP " + it } } }
EOF
gradle -q printCp                                   # fills $GRADLE_USER_HOME/caches/modules-2
mvn -q -Dmaven.repo.local=$MAVEN_REPO_LOCAL dependency:get -Dartifact=org.apache.commons:commons-text:1.10.0   # the same GAV, from unrelated Maven use
# stage an agent-mode record: files {"commons-text-1.10.0.jar": {beforeHash, afterHash}} + blob,
# where the patched jar adds a marker line to META-INF/NOTICE.txt
git init -q && git remote add origin https://github.com/example/gp.git
socket-patch apply --json --offline      # "status":"success", applied: 1, exit 0
socket-patch vex --offline --product pkg:github/example/gp   # status "not_affected"
unzip -p $MAVEN_REPO_LOCAL/org/apache/commons/commons-text/1.10.0/commons-text-1.10.0.jar META-INF/NOTICE.txt | grep -c MARKER   # 1  (m2 copy patched)
gradle -q printCp | grep commons-text    # …/gh/caches/modules-2/files-2.1/org.apache.commons/commons-text/1.10.0/3363381a…/commons-text-1.10.0.jar
unzip -p <that jar> META-INF/NOTICE.txt | grep -c MARKER                     # 0  (the build uses the unpatched jar)

I ran this twice from clean directories, and both runs gave the same result.

Control: with repositories { mavenLocal(); mavenCentral() } (and -Dmaven.repo.local pointing at the same m2), Gradle resolves m2/…/commons-text-1.10.0.jar and the marker is present. So the in-place patch itself is fine. The defect is that the CLI targets a copy that the build doesn't use, and attests it.

Expected vs actual

  • Expected: docs/ecosystems.md (the Maven row) describes agent mode as in-place jar patching of ~/.m2, and the crawler deliberately accepts build.gradle* / settings.gradle* as project markers (maven_crawler.rs:581). For a Gradle build that has no mavenLocal(), the m2 copy isn't the installed package. apply should refuse or warn (for example, "Gradle builds resolve from $GRADLE_USER_HOME; use vendor"), or patch the copy Gradle actually uses. README.md says to "Generate VEX after installing to verify the copies your build consumes", so vex must not attest not_affected from a copy the build doesn't consume.
  • Actual: apply exits 0 with applied: 1, vex gives not_affected, and the Gradle build is unpatched. No warning is printed anywhere.

Matrix

OS Gradle (JDK) Build repos apply vex Jar the build uses
Linux 8.14.3 (21) mavenCentral() success, applied 1 ❌ not_affected ❌ unpatched (~/.gradle cache) ❌
Linux 8.14.3 (21) mavenLocal(); mavenCentral() success not_affected patched m2 jar ✅ (control)
macOS / Windows, Gradle 6–9 — — untested. The selection logic is OS- and version-independent, and the modules-2/files-2.1 layout has been unchanged since Gradle 1.x.

Related

Suspect code

  • crates/socket-patch-core/src/crawlers/maven_crawler.rs:580-605: Gradle markers select m2_repo_path() (:709) as the install root, although a Gradle build without mavenLocal() never reads it.
  • crates/socket-patch-core/src/crawlers/maven_crawler.rs find_by_purls: Maven layout only, so the copy Gradle uses can never be patched or verified.

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