mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 22:27:01 +01:00
Fix: stop the Flutter SDK reading your own repo as itself inside git hooks
`git commit`/`git push` export GIT_DIR to every hook they run, and the
flutter tool shells out to git to identify its own SDK. `git -C
"$FLUTTER_ROOT" rev-parse HEAD` in bin/internal/shared.sh does not beat an
inherited GIT_DIR -- `-C` only changes directory, GIT_DIR overrides
repository discovery outright:
$ git -C /opt/flutter rev-parse HEAD
058e0af2c2... <- Flutter's HEAD
$ GIT_DIR=~/diet-guard/.git git -C /opt/flutter rev-parse HEAD
84bd550331... <- diet-guard's HEAD
So inside a hook, flutter reports the project as the SDK:
Flutter 3.47.0-0.1.pre • channel main • https://github.com/kuhyx/diet-guard
pub then resolves the SDK as 0.0.0-unknown and every version constraint
becomes unsatisfiable. flutter also writes that into its own version cache,
so the wrong answer outlives the hook and follows the next caller into an
unrelated repository -- which is why the identical command passes when you
re-run it by hand and fails again on the next push.
diet-guard's pre-push gate hit this today. Clearing the variables per call in
each hook works, and screen-locker and diet-guard now do, but only for as
long as every future hook author remembers -- deployment hygiene, not a fix.
This installs a wrapper ahead of /usr/bin on PATH that strips them for every
caller in every repo, covering both flutter and dart. It removes its own
directory from PATH before exec so the Flutter entry point's internal `which
flutter` cannot loop back into it, and it verifies itself by running flutter
against a throwaway poisoned GIT_DIR and failing unless the real SDK comes
back.
Not patching bin/internal/shared.sh: that file belongs to the flutter-bin
package, so the next update would silently restore the bug. Upstream already
unsets these variables in content_aware_hash.sh and update_engine_version.sh,
just not in the version path.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d91c0ab0e9
commit
071963b92b
@ -0,0 +1,58 @@
|
||||
{
|
||||
"intent": "Make it impossible for the Flutter SDK to misidentify itself when invoked from a git hook, so Flutter gates in any repo stop failing with 'The current Flutter SDK version is 0.0.0-unknown'.",
|
||||
"scope": [
|
||||
"linux_configuration/scripts/single_use/fixes/fix_flutter_git_env.sh (new)",
|
||||
"Installs to ~/.local/share/flutter-git-env/wrapper.sh with ~/.local/bin/{flutter,dart} symlinks",
|
||||
"Non-goal: patching /opt/flutter/bin/internal/shared.sh — pacman-owned by flutter-bin, an update would silently restore the bug",
|
||||
"Non-goal: CI runners, which do not set GIT_DIR because they call flutter from workflow steps rather than hooks"
|
||||
],
|
||||
"changes": [
|
||||
"Root cause: git exports GIT_DIR to its hooks, and `git -C \"$FLUTTER_ROOT\" rev-parse HEAD` in shared.sh does not beat it — `-C` only changes directory while GIT_DIR overrides repository discovery. flutter therefore reads the repo being committed as its own SDK, reports that repo's HEAD as the framework revision, resolves the SDK as 0.0.0-unknown, and caches the wrong answer in bin/cache/flutter.version.json where it poisons later callers in other repos.",
|
||||
"Adds a PATH wrapper ahead of /usr/bin that strips GIT_DIR and the other git hook variables, then execs the real binary. Covers flutter and dart (both are aur_* shims into /opt/flutter).",
|
||||
"The wrapper removes its own directory from PATH before exec, so the Flutter entry point's internal `which flutter` lookup cannot loop back into it.",
|
||||
"Ships --check (re-verify, e.g. after a flutter update) and --uninstall. Install is idempotent.",
|
||||
"Verification is built in: the installer creates a throwaway git repo, runs flutter with GIT_DIR pointed at it, and fails unless flutter still reports github.com/flutter/flutter."
|
||||
],
|
||||
"verification": [
|
||||
{
|
||||
"command": "GIT_DIR=/home/kuhy/diet-guard/.git git -C /opt/flutter rev-parse HEAD",
|
||||
"result": "reproduced",
|
||||
"evidence": "84bd5503316568914ed93b9451c397ff517a09d4 (diet-guard's HEAD) vs 058e0af2c2... without GIT_DIR — confirms `git -C` loses to GIT_DIR"
|
||||
},
|
||||
{
|
||||
"command": "GIT_DIR=/home/kuhy/diet-guard/.git flutter --version # before the fix",
|
||||
"result": "reproduced",
|
||||
"evidence": "Flutter 3.47.0-0.1.pre • channel main • https://github.com/kuhyx/diet-guard — the SDK reporting the project as itself"
|
||||
},
|
||||
{
|
||||
"command": "cd /home/kuhy/diet-guard/app && GIT_DIR=/home/kuhy/diet-guard/.git flutter analyze # after the fix, no per-call env -u",
|
||||
"result": "pass",
|
||||
"evidence": "Analyzing app... No issues found! (ran in 1.2s) — previously failed with 'url_launcher_platform_interface ^2.3.2 is forbidden' against 0.0.0-unknown"
|
||||
},
|
||||
{
|
||||
"command": "GIT_DIR=... dart --version; flutter --version; command -v flutter dart",
|
||||
"result": "pass",
|
||||
"evidence": "Dart SDK version: 3.12.2 (stable); Flutter 3.44.8 • channel stable • github.com/flutter/flutter; both resolve to /home/kuhy/.local/bin — normal use unaffected, no exec loop"
|
||||
},
|
||||
{
|
||||
"command": "bash fix_flutter_git_env.sh --check && bash fix_flutter_git_env.sh",
|
||||
"result": "pass",
|
||||
"evidence": "verified: Flutter 3.44.8 • channel stable • https://github.com/flutter/flutter.git; reinstall is idempotent"
|
||||
},
|
||||
{
|
||||
"command": "shellcheck linux_configuration/scripts/single_use/fixes/fix_flutter_git_env.sh",
|
||||
"result": "pass",
|
||||
"evidence": "no findings"
|
||||
}
|
||||
],
|
||||
"risks": [
|
||||
"Shadows /usr/bin/flutter and /usr/bin/dart on PATH for this user. Mitigated by exec'ing the real binary by absolute path and by the installer refusing to overwrite a non-symlink at either location.",
|
||||
"If ~/.local/bin ever stops being on PATH the wrapper silently stops applying; the installer and --check both fail loudly on that, but only when run.",
|
||||
"Per-call `env -u GIT_DIR` remains in screen-locker's and diet-guard's hooks on purpose: those repos may be cloned onto machines without this wrapper."
|
||||
],
|
||||
"rollback": [
|
||||
"bash linux_configuration/scripts/single_use/fixes/fix_flutter_git_env.sh --uninstall",
|
||||
"Confirm `command -v flutter` is /usr/bin/flutter again and `flutter --version` reports 3.x on channel stable",
|
||||
"Expect Flutter gates run from git hooks to start failing with 0.0.0-unknown again unless the per-call env -u fixes are present"
|
||||
]
|
||||
}
|
||||
183
linux_configuration/scripts/single_use/fixes/fix_flutter_git_env.sh
Executable file
183
linux_configuration/scripts/single_use/fixes/fix_flutter_git_env.sh
Executable file
@ -0,0 +1,183 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ============================================================================
|
||||
# Stop the Flutter SDK misreading YOUR repository as itself inside git hooks.
|
||||
#
|
||||
# `git commit` / `git push` export GIT_DIR (and GIT_WORK_TREE,
|
||||
# GIT_INDEX_FILE) to every hook they run. The flutter tool shells out to git
|
||||
# to identify its own SDK, and `git -C "$FLUTTER_ROOT" rev-parse HEAD`
|
||||
# (bin/internal/shared.sh) does NOT beat an inherited GIT_DIR -- `-C` only
|
||||
# changes directory, while GIT_DIR overrides repository discovery outright:
|
||||
#
|
||||
# $ git -C /opt/flutter rev-parse HEAD
|
||||
# 058e0af2c2... <- Flutter's HEAD
|
||||
# $ GIT_DIR=~/some-repo/.git git -C /opt/flutter rev-parse HEAD
|
||||
# 84bd550331... <- YOUR repo's HEAD
|
||||
#
|
||||
# So inside a hook, `flutter --version` reports your project as the SDK, pub
|
||||
# resolves the SDK as "0.0.0-unknown" and every version constraint becomes
|
||||
# unsatisfiable ("... is forbidden"). Worse, flutter caches that answer in
|
||||
# bin/cache/flutter.version.json, so the corruption outlives the hook and
|
||||
# follows the next caller into an unrelated repository -- which is why the
|
||||
# same command passes when you re-run it by hand and fails again next push.
|
||||
#
|
||||
# Upstream already unsets these variables in bin/internal/content_aware_hash.sh
|
||||
# and bin/internal/update_engine_version.sh, but not in the version path.
|
||||
#
|
||||
# THE FIX: a wrapper ahead of /usr/bin on PATH that strips the git-hook
|
||||
# variables and hands off to the real binary. Per-call `env -u GIT_DIR ...` in
|
||||
# each hook works too, but only for as long as every future hook author
|
||||
# remembers; this makes the failure impossible for every caller in every repo.
|
||||
#
|
||||
# Deliberately NOT patching /opt/flutter/bin/internal/shared.sh: that file is
|
||||
# owned by the flutter-bin package and the next update would silently restore
|
||||
# the bug.
|
||||
#
|
||||
# Usage: fix_flutter_git_env.sh [--check | --uninstall]
|
||||
# ============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_NAME="$(basename "$0")"
|
||||
readonly SCRIPT_NAME
|
||||
readonly BIN_DIR="${HOME}/.local/bin"
|
||||
readonly LIB_DIR="${HOME}/.local/share/flutter-git-env"
|
||||
readonly WRAPPER="${LIB_DIR}/wrapper.sh"
|
||||
# Every SDK entry point that shells out to git to identify itself.
|
||||
readonly TOOLS=(flutter dart)
|
||||
|
||||
log() { echo "$SCRIPT_NAME: $*"; }
|
||||
|
||||
fail() {
|
||||
echo "$SCRIPT_NAME: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
write_wrapper() {
|
||||
mkdir -p "$LIB_DIR" "$BIN_DIR"
|
||||
cat > "$WRAPPER" <<'WRAPPER_EOF'
|
||||
#!/bin/bash
|
||||
|
||||
# Strip the git-hook environment, then hand off to the real SDK binary.
|
||||
# Installed by linux_configuration/scripts/single_use/fixes/fix_flutter_git_env.sh
|
||||
# -- see that script for why this exists. Invoked through a symlink named
|
||||
# after the tool it fronts (flutter, dart).
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
tool="$(basename "$0")"
|
||||
real="/usr/bin/${tool}"
|
||||
|
||||
[[ -x $real ]] || {
|
||||
echo "flutter-git-env: $real is missing or not executable" >&2
|
||||
exit 127
|
||||
}
|
||||
|
||||
# Drop our own directory so a PATH lookup inside the real tool cannot find
|
||||
# this wrapper again. The Flutter entry point does exactly such a lookup.
|
||||
clean_path=""
|
||||
IFS=':' read -r -a path_parts <<< "$PATH"
|
||||
for part in "${path_parts[@]}"; do
|
||||
[[ $part == "$HOME/.local/bin" ]] && continue
|
||||
clean_path="${clean_path:+$clean_path:}$part"
|
||||
done
|
||||
|
||||
# GIT_DIR is the one that actually bites; the rest are exported by git in the
|
||||
# same breath and would mislead the SDK the same way.
|
||||
exec env \
|
||||
-u GIT_DIR \
|
||||
-u GIT_WORK_TREE \
|
||||
-u GIT_INDEX_FILE \
|
||||
-u GIT_COMMON_DIR \
|
||||
-u GIT_OBJECT_DIRECTORY \
|
||||
-u GIT_ALTERNATE_OBJECT_DIRECTORIES \
|
||||
-u GIT_NAMESPACE \
|
||||
-u GIT_PREFIX \
|
||||
PATH="$clean_path" \
|
||||
"$real" "$@"
|
||||
WRAPPER_EOF
|
||||
chmod +x "$WRAPPER"
|
||||
}
|
||||
|
||||
link_tools() {
|
||||
local tool link
|
||||
for tool in "${TOOLS[@]}"; do
|
||||
link="${BIN_DIR}/${tool}"
|
||||
if [[ -e $link && ! -L $link ]]; then
|
||||
fail "$link exists and is not a symlink — refusing to overwrite it"
|
||||
fi
|
||||
ln -sfn "$WRAPPER" "$link"
|
||||
log "linked $link -> $WRAPPER"
|
||||
done
|
||||
}
|
||||
|
||||
check_path() {
|
||||
case ":$PATH:" in
|
||||
*":$BIN_DIR:"*) ;;
|
||||
*) fail "$BIN_DIR is not on PATH — the wrapper would never be used" ;;
|
||||
esac
|
||||
local resolved
|
||||
for tool in "${TOOLS[@]}"; do
|
||||
resolved="$(command -v "$tool" || true)"
|
||||
[[ $resolved == "${BIN_DIR}/${tool}" ]] ||
|
||||
fail "$tool resolves to '$resolved', not the wrapper — check PATH order"
|
||||
done
|
||||
}
|
||||
|
||||
# The real test: does the SDK still know what it is with GIT_DIR poisoned?
|
||||
verify() {
|
||||
local probe_repo probe_version
|
||||
probe_repo="$(mktemp -d)"
|
||||
git -C "$probe_repo" init -q
|
||||
git -C "$probe_repo" -c user.email=x@y -c user.name=x commit -q --allow-empty -m probe
|
||||
|
||||
probe_version="$(GIT_DIR="$probe_repo/.git" flutter --version 2>&1 | head -1 || true)"
|
||||
rm -rf "$probe_repo"
|
||||
|
||||
if grep -q '0\.0\.0-unknown' <<< "$probe_version"; then
|
||||
fail "still broken: flutter reports an unknown SDK under GIT_DIR"
|
||||
fi
|
||||
if ! grep -q 'flutter/flutter' <<< "$probe_version"; then
|
||||
fail "still broken: flutter reports '$probe_version' under GIT_DIR"
|
||||
fi
|
||||
log "verified: $probe_version"
|
||||
}
|
||||
|
||||
uninstall() {
|
||||
local tool link
|
||||
for tool in "${TOOLS[@]}"; do
|
||||
link="${BIN_DIR}/${tool}"
|
||||
if [[ -L $link && "$(readlink -f "$link")" == "$WRAPPER" ]]; then
|
||||
rm -f "$link"
|
||||
log "removed $link"
|
||||
fi
|
||||
done
|
||||
rm -rf "$LIB_DIR"
|
||||
log "uninstalled"
|
||||
}
|
||||
|
||||
main() {
|
||||
case "${1:-}" in
|
||||
--uninstall)
|
||||
uninstall
|
||||
return
|
||||
;;
|
||||
--check)
|
||||
[[ -x $WRAPPER ]] || fail "wrapper is not installed"
|
||||
check_path
|
||||
verify
|
||||
return
|
||||
;;
|
||||
"") ;;
|
||||
*) fail "unknown option: $1 (expected --check or --uninstall)" ;;
|
||||
esac
|
||||
|
||||
command -v flutter >/dev/null 2>&1 || fail "flutter is not on PATH"
|
||||
write_wrapper
|
||||
link_tools
|
||||
check_path
|
||||
verify
|
||||
log "done — flutter/dart now ignore an inherited GIT_DIR"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Loading…
Reference in New Issue
Block a user