diff --git a/hooks/pre-push b/hooks/pre-push index 04d4477..b853a65 100755 --- a/hooks/pre-push +++ b/hooks/pre-push @@ -43,7 +43,18 @@ done unset _v shared_git() { - env "${_shared_git_scrub[@]}" git "$@" + # `-c` overrides neutralize execution-capable settings a poisoned checkout + # could plant in its own .git/config: core.fsmonitor runs a command during + # `git status`, and the *Proxy/*Command hooks run during fetch. + env "${_shared_git_scrub[@]}" git \ + -c core.fsmonitor= \ + -c core.hooksPath=/dev/null \ + -c core.sshCommand= \ + -c core.askPass= \ + -c credential.helper= \ + -c protocol.ext.allow=never \ + -c uploadpack.packObjectsHook= \ + "$@" } scanner="$shared_dir/scripts/scan-commit-range.sh" diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh index 750c89e..3b44293 100755 --- a/scripts/bootstrap.sh +++ b/scripts/bootstrap.sh @@ -71,7 +71,19 @@ policy_git() { for v in GIT_CONFIG_GLOBAL GIT_CONFIG_SYSTEM GIT_CONFIG_NOSYSTEM; do scrub+=(-u "$v") done - env "${scrub[@]}" git "$@" + # `-c` overrides neutralize execution-capable settings a poisoned cache could + # plant in its own .git/config: core.fsmonitor runs a command during `git + # status`, and the *Proxy/*Command hooks run during fetch. These must be off + # for every command that touches a cache we have not yet authenticated. + env "${scrub[@]}" git \ + -c core.fsmonitor= \ + -c core.hooksPath=/dev/null \ + -c core.sshCommand= \ + -c core.askPass= \ + -c credential.helper= \ + -c protocol.ext.allow=never \ + -c uploadpack.packObjectsHook= \ + "$@" } cleanup() { diff --git a/scripts/install-pre-push.sh b/scripts/install-pre-push.sh index 237a61c..c399c7b 100755 --- a/scripts/install-pre-push.sh +++ b/scripts/install-pre-push.sh @@ -74,7 +74,18 @@ done unset _v shared_git() { - env "${_shared_git_scrub[@]}" git "$@" + # `-c` overrides neutralize execution-capable settings a poisoned checkout + # could plant in its own .git/config: core.fsmonitor runs a command during + # `git status`, and the *Proxy/*Command hooks run during fetch. + env "${_shared_git_scrub[@]}" git \ + -c core.fsmonitor= \ + -c core.hooksPath=/dev/null \ + -c core.sshCommand= \ + -c core.askPass= \ + -c credential.helper= \ + -c protocol.ext.allow=never \ + -c uploadpack.packObjectsHook= \ + "$@" } shared_git -C "$shared_dir" rev-parse --is-inside-work-tree >/dev/null 2>&1 || { diff --git a/tests/run.sh b/tests/run.sh index 5d02dc5..147dbc0 100755 --- a/tests/run.sh +++ b/tests/run.sh @@ -256,6 +256,23 @@ if VANA_POLICY_SHA="$policy_sha" "$bootstrap" bogus-action >/dev/null 2>&1; then fi printf '[remote "origin"]\n\turl = https://github.com/vana-com/.github\n' >"$test_root/spoof.gitconfig" +# A poisoned cache must not execute code through its own local config while it +# is being authenticated: core.fsmonitor runs a command during `git status`. +fsmonitor_home="$test_root/fsmonitor-home" +mkdir -p "$fsmonitor_home/vana-secret-scan/policy" +fsmonitor_cache="$fsmonitor_home/vana-secret-scan/policy/$policy_sha" +git clone -q "$root" "$fsmonitor_cache" +git -C "$fsmonitor_cache" remote set-url origin https://github.com/vana-com/.github.git +git -C "$fsmonitor_cache" checkout -q "$policy_sha" +printf '#!/bin/sh\ntouch %q\nexit 1\n' "$test_root/fsmonitor-fired" >"$test_root/evil-fsmonitor" +chmod 0755 "$test_root/evil-fsmonitor" +git -C "$fsmonitor_cache" config core.fsmonitor "$test_root/evil-fsmonitor" +XDG_DATA_HOME="$fsmonitor_home" VANA_POLICY_SHA="$policy_sha" "$bootstrap" status >/dev/null 2>&1 || true +if [[ -e "$test_root/fsmonitor-fired" ]]; then + printf 'expected bootstrap to neutralize core.fsmonitor on an unauthenticated cache\n' >&2 + exit 1 +fi + bootstrap_home="$test_root/bootstrap-home" mkdir -p "$bootstrap_home/vana-secret-scan/policy" bootstrap_cache="$bootstrap_home/vana-secret-scan/policy/$policy_sha"