[agent] Found by the scheduled Gradle bug-hunt routine (ledger #319).
Summary
Gradle vendoring commits two text files that later commands compare byte for byte: .socket/gradle/socket-patch.settings.gradle and .socket/vendor/gradle-index.tsv. The * -text .gitattributes it writes covers only .socket/vendor/gradle/ (GITATTRIBUTES_REL), so neither file is protected. Git for Windows ships core.autocrlf=true in its system gitconfig, as GitHub's windows-latest runners do (file:C:/Program Files/Git/etc/gitconfig true). On such a checkout both files come out CRLF, and:
vendor --check fails on a pristine checkout: exit 1, partialFailure, vendor_check_failed: "vendored wiring or metadata drifted: .socket/gradle/socket-patch.settings.gradle". After fixing that file, it reports …: .socket/vendor/gradle-index.tsv. Any Windows CI that gates on vendor --check (the documented offline integrity audit) is permanently red.
vendor --revert, remove <purl> and rollback exit 0 but leave .socket/gradle/socket-patch.settings.gradle behind, with vendor_lock_entry_drifted (".socket/gradle/socket-patch.settings.gradle was modified; left alone"), so the revert isn't clean. In the buildSrc scenario, buildSrc/settings.gradle (created by vendor) is also left behind as M instead of being removed.
The Gradle build itself is fine: the script is CRLF-tolerant and the jar/pom are protected by -text, so :printCp resolves the patched vendored jar. Only socket-patch's own audit and revert are wrong.
Repro (Linux, simulating the Windows default)
I used the e2e_vendor_jvm_build patch-service fixture and a staged manifest + blob for pkg:maven/org.apache.commons/commons-text@1.10.0, as in gradle_multi_project_vendor_locked_offline_tamper_and_byte_exact_revert. The Gradle-only project is settings.gradle + a Groovy build.gradle with repositories { mavenCentral() } and implementation 'org.apache.commons:commons-text:1.10.0'.
socket-patch vendor --json --offline # exit 0, applied 1
git init -q && git add -A && git commit -qm vendored
git -c core.autocrlf=true clone -q . ../win && cd ../win
file .socket/vendor/gradle-index.tsv .socket/gradle/socket-patch.settings.gradle # both "with CRLF line terminators"
socket-patch vendor --check --json # exit 1: vendor_check_failed "...drifted: .socket/gradle/socket-patch.settings.gradle"
sed -i 's/\r$//' .socket/gradle/socket-patch.settings.gradle
socket-patch vendor --check --json # exit 1: "...drifted: .socket/vendor/gradle-index.tsv"
sed -i 's/\r$//' .socket/vendor/gradle-index.tsv
socket-patch vendor --check --json # exit 0: vendor_check_ok
# fresh CRLF clone again:
socket-patch vendor --revert --json --offline # exit 0, skipped vendor_lock_entry_drifted; the script file remains
Controls: a core.autocrlf=input clone, or an LF clone, gives vendor_check_ok, and revert removes everything. A CRLF settings.gradle on its own is handled correctly (the apply-line fragment matches across endings).
Expected vs actual
- Expected:
docs/design/maven-vendoring.md says vendoring commits "a local repository so another checkout can build without socket-patch", and that vendor --check "checks artifact hashes, recorded tree files, wiring, Gradle's index and script". docs/ecosystems.md sets the precedent: vlt's vendored tree carries "a .gitattributes that turns EOL conversion off so an autocrlf checkout stays byte-exact". A normal Windows checkout of a committed vendored project should pass vendor --check and revert cleanly.
- Actual:
vendor --check exits 1 on a pristine Windows checkout. Revert, remove and rollback exit 0 but leave socket-owned files behind.
OS × Gradle
Probe run (real windows-latest, default Git for Windows config, real Gradle builds): https://github.com/SocketDev/socket-patch/actions/runs/36821988108
| OS |
Gradle |
vendor --check on fresh clone |
revert on fresh clone |
Gradle build |
| Windows (autocrlf=true default) |
6.9.4 (JDK 11) |
exit 1 |
script left behind |
patched ✓ |
| Windows |
7.6.6 (JDK 17) |
exit 1 |
script left behind |
patched ✓ |
| Windows |
9.8.0 (JDK 21) |
exit 1 |
script left behind |
patched ✓ |
Linux, git -c core.autocrlf=true clone |
8.14.3 |
exit 1 (2/2) |
script left behind |
patched ✓ |
| Linux / macOS, default git |
6.9.4 / 7.6.6 / 9.8.0 |
ok |
clean |
patched ✓ (run https://github.com/SocketDev/socket-patch/actions/runs/36821273765) |
First bad version: main 2463257 (#277), which introduced Gradle vendoring.
Suspect code
crates/socket-patch-core/src/vendor/jvm/gradle.rs:32: GITATTRIBUTES_REL = ".socket/vendor/gradle/.gitattributes" only covers the artifact tree, not INDEX_REL (.socket/vendor/gradle-index.tsv) or SCRIPT_REL (.socket/gradle/socket-patch.settings.gradle).
crates/socket-patch-core/src/vendor/jvm/apply.rs:767: vendor --check re-plans and treats any byte difference in a planned write as drift. The revert's owned-file comparison behaves the same way (vendor_lock_entry_drifted, apply.rs:449/:529).
I haven't checked whether the Maven reactor backend's LF files (.mvn/maven.config) have the same exposure. That belongs to the maven routine.
[agent] Found by the scheduled Gradle bug-hunt routine (ledger #319).
Summary
Gradle vendoring commits two text files that later commands compare byte for byte:
.socket/gradle/socket-patch.settings.gradleand.socket/vendor/gradle-index.tsv. The* -text.gitattributesit writes covers only.socket/vendor/gradle/(GITATTRIBUTES_REL), so neither file is protected. Git for Windows shipscore.autocrlf=truein its system gitconfig, as GitHub'swindows-latestrunners do (file:C:/Program Files/Git/etc/gitconfig true). On such a checkout both files come out CRLF, and:vendor --checkfails on a pristine checkout: exit 1,partialFailure,vendor_check_failed: "vendored wiring or metadata drifted: .socket/gradle/socket-patch.settings.gradle". After fixing that file, it reports…: .socket/vendor/gradle-index.tsv. Any Windows CI that gates onvendor --check(the documented offline integrity audit) is permanently red.vendor --revert,remove <purl>androllbackexit 0 but leave.socket/gradle/socket-patch.settings.gradlebehind, withvendor_lock_entry_drifted(".socket/gradle/socket-patch.settings.gradle was modified; left alone"), so the revert isn't clean. In the buildSrc scenario,buildSrc/settings.gradle(created by vendor) is also left behind asMinstead of being removed.The Gradle build itself is fine: the script is CRLF-tolerant and the jar/pom are protected by
-text, so:printCpresolves the patched vendored jar. Only socket-patch's own audit and revert are wrong.Repro (Linux, simulating the Windows default)
I used the
e2e_vendor_jvm_buildpatch-service fixture and a staged manifest + blob forpkg:maven/org.apache.commons/commons-text@1.10.0, as ingradle_multi_project_vendor_locked_offline_tamper_and_byte_exact_revert. The Gradle-only project issettings.gradle+ a Groovybuild.gradlewithrepositories { mavenCentral() }andimplementation 'org.apache.commons:commons-text:1.10.0'.Controls: a
core.autocrlf=inputclone, or an LF clone, givesvendor_check_ok, and revert removes everything. A CRLFsettings.gradleon its own is handled correctly (the apply-line fragment matches across endings).Expected vs actual
docs/design/maven-vendoring.mdsays vendoring commits "a local repository so another checkout can build without socket-patch", and thatvendor --check"checks artifact hashes, recorded tree files, wiring, Gradle's index and script".docs/ecosystems.mdsets the precedent: vlt's vendored tree carries "a.gitattributesthat turns EOL conversion off so anautocrlfcheckout stays byte-exact". A normal Windows checkout of a committed vendored project should passvendor --checkand revert cleanly.vendor --checkexits 1 on a pristine Windows checkout. Revert, remove and rollback exit 0 but leave socket-owned files behind.OS × Gradle
Probe run (real
windows-latest, default Git for Windows config, real Gradle builds): https://github.com/SocketDev/socket-patch/actions/runs/36821988108vendor --checkon fresh clonegit -c core.autocrlf=true cloneFirst bad version: main
2463257(#277), which introduced Gradle vendoring.Suspect code
crates/socket-patch-core/src/vendor/jvm/gradle.rs:32:GITATTRIBUTES_REL = ".socket/vendor/gradle/.gitattributes"only covers the artifact tree, notINDEX_REL(.socket/vendor/gradle-index.tsv) orSCRIPT_REL(.socket/gradle/socket-patch.settings.gradle).crates/socket-patch-core/src/vendor/jvm/apply.rs:767:vendor --checkre-plans and treats any byte difference in a planned write as drift. The revert's owned-file comparison behaves the same way (vendor_lock_entry_drifted,apply.rs:449/:529).I haven't checked whether the Maven reactor backend's LF files (
.mvn/maven.config) have the same exposure. That belongs to the maven routine.