fix(hooks): seed Gradle distribution from a verified mirror in web sandbox

Claude Code Web routes github.com through a git-only proxy, so the Gradle
wrapper's distributionUrl (services.gradle.org, which 307-redirects to a
github.com release asset) can't bootstrap — it 403s even at Full network
access, and `./gradlew` fails before running any task.

Seed the pinned distribution in the existing web-only SessionStart hook,
reusing the same idempotent curl-download pattern already used there for the
Android SDK and Kotlin/Native deps: skip if already installed, else fetch
Gradle's OFFICIAL sha256 (served from services.gradle.org, reachable here),
download the zip from a mirror, and verify before extracting so a tampered or
wrong mirror file is rejected and never executed. The wrapper cache dir is
derived as base36(md5(distributionUrl)) so it survives version bumps.

Also pin distributionSha256Sum in gradle-wrapper.properties as defense in
depth: Gradle then verifies any distribution it installs (mirror-seeded, CI,
or local) against the known-good hash.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A7bocRTSPyXwz6zVMzDbKR
This commit is contained in:
Claude
2026-07-17 02:11:56 +00:00
parent bd6635b311
commit e7f883f08b
2 changed files with 55 additions and 0 deletions
+54
View File
@@ -211,6 +211,60 @@ install_konan_dep "llvm-19-x86_64-linux-essentials-109" \
install_konan_dep "libffi-3.2.1-2-linux-x86-64" \
"$KONAN_DEPS_URL/libffi-3.2.1-2-linux-x86-64.tar.gz"
# --- Gradle distribution: pre-seed the wrapper distribution ---
# The wrapper's distributionUrl (services.gradle.org) 307-redirects to
# github.com release assets, which the web sandbox's git-only GitHub proxy
# blocks (403) even at Full network access — so `./gradlew` can't bootstrap.
# Download the pinned distribution from a mirror instead, but verify it against
# Gradle's OFFICIAL sha256 (served from services.gradle.org, reachable here)
# so a tampered/wrong mirror file is rejected and never executed. Idempotent:
# skips entirely if the distribution is already installed.
seed_gradle_distribution() {
local props="$CLAUDE_PROJECT_DIR/gradle/wrapper/gradle-wrapper.properties"
[ -f "$props" ] || return 0
local url zip name hash dir ver official mirror ok=""
url=$(sed -n 's/^distributionUrl=//p' "$props" | sed 's/\\//g')
[ -n "$url" ] || return 0
zip=${url##*/}; name=${zip%.zip}
# Gradle stores the dist under base36(md5(distributionUrl)) — derive it so this
# keeps working across version bumps instead of hardcoding the hash dir.
hash=$(python3 - "$url" <<'PY'
import hashlib, sys
n = int.from_bytes(hashlib.md5(sys.argv[1].encode()).digest(), 'big')
d = "0123456789abcdefghijklmnopqrstuvwxyz"; s = ""
while n:
s = d[n % 36] + s; n //= 36
print(s or "0")
PY
)
dir="${GRADLE_USER_HOME:-$HOME/.gradle}/wrapper/dists/$name/$hash"
ver=${name%-bin}; ver=${ver%-all}
if [ -x "$dir/$ver/bin/gradle" ]; then return 0; fi # already installed
echo "Seeding Gradle distribution $ver (github release blocked; using verified mirror)..." >&2
mkdir -p "$dir"
official=$(curl -fsSL "https://services.gradle.org/distributions/${zip}.sha256") || {
echo "Could not fetch official Gradle checksum; leaving gradlew to fail as before." >&2
return 0
}
for mirror in \
"https://mirrors.cloud.tencent.com/gradle" \
"https://mirrors.huaweicloud.com/gradle"; do
if curl -fsSL -o "$dir/$zip" "$mirror/$zip" \
&& echo "${official} $dir/$zip" | sha256sum -c - >/dev/null 2>&1; then
ok=1; break
fi
echo "Mirror $mirror failed download/verify; trying next." >&2
rm -f "$dir/$zip"
done
if [ -z "$ok" ]; then
echo "Gradle seed failed against all mirrors; leaving gradlew to fail as before." >&2
return 0
fi
unzip -q "$dir/$zip" -d "$dir" && touch "$dir/$zip.ok"
echo "Gradle $ver seeded and verified against official sha256." >&2
}
seed_gradle_distribution
cd "$CLAUDE_PROJECT_DIR"
./gradlew --version > /dev/null 2>&1
+1
View File
@@ -1,6 +1,7 @@
#Wed Jan 04 09:23:50 EST 2023
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip
distributionSha256Sum=553c78f50dafcd54d65b9a444649057857469edf836431389695608536d6b746
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME