|
| 1 | +#!/usr/bin/env bash |
| 2 | +set -uo pipefail |
| 3 | + |
| 4 | +# Re-sanitizes the git config surfaces a PAT-bearing git step is about to |
| 5 | +# read, AFTER branch/agent code has run on the host. The inlined job-start |
| 6 | +# sanitize steps are pre-checkout hygiene; between them and the push, the |
| 7 | +# verification gates run branch test code on the host and the sandboxed |
| 8 | +# agent has the workspace mounted — either can plant exec keys in the |
| 9 | +# repo's LOCAL .git/config (the highest-precedence file, which the push |
| 10 | +# reads) or rewrite the runner user's REAL global config: the gates' env |
| 11 | +# redirect is inherited-env enforcement, not a filesystem boundary — a |
| 12 | +# direct file write, `env -u GIT_CONFIG_GLOBAL git config --global`, or |
| 13 | +# `git config --file "$HOME/.gitconfig"` all bypass it (probe-verified in |
| 14 | +# the #8961 review). |
| 15 | +# |
| 16 | +# Invoked as `bash "${RUNNER_TEMP}/resanitize-git-config.sh"` from the |
| 17 | +# copy the staging step took off the TRUSTED base checkout — never from |
| 18 | +# the working tree, which holds the branch under test at call time. |
| 19 | +# |
| 20 | +# The allowlist and denylist are copies of the inlined pre-checkout |
| 21 | +# sanitize steps in qwen-autofix.yml (which cannot call this script: it |
| 22 | +# does not exist on disk before their checkout). The workflow contract |
| 23 | +# tests pin every copy byte-identical — edit them together. |
| 24 | + |
| 25 | +if [ -e .git ]; then |
| 26 | + # Repo-scope redirect files first. `.git/commondir` (the file twin of |
| 27 | + # GIT_COMMON_DIR) repoints local config, refs AND objects — a plant makes |
| 28 | + # the very --local sweep below act on the ATTACKER's config, and lets the |
| 29 | + # PAT push deliver attacker content; `.git/shallow` (twin of |
| 30 | + # GIT_SHALLOW_FILE) narrows the object graph. A normal actions/checkout is |
| 31 | + # not a linked worktree, so neither file legitimately exists here — |
| 32 | + # removing them cannot break a real checkout, only defuse a plant. Then |
| 33 | + # config.worktree (can carry core.hooksPath, invisible to `git config |
| 34 | + # --local`), then the local allowlist sweep. |
| 35 | + GIT_DIR_PATH="$(git rev-parse --git-dir 2>/dev/null || echo .git)" |
| 36 | + rm -f "${GIT_DIR_PATH}/commondir" "${GIT_DIR_PATH}/shallow" 2>/dev/null || true |
| 37 | + rm -f "$(git rev-parse --git-path config.worktree 2>/dev/null || echo /nonexistent)" 2>/dev/null || true |
| 38 | + git config --local --unset-all extensions.worktreeConfig 2>/dev/null || true |
| 39 | + git config --local --name-only --list 2>/dev/null \ |
| 40 | + | { grep -ivE '^(core\.(repositoryformatversion|bare|filemode|symlinks|ignorecase|precomposeunicode|logallrefupdates|worktree|hidedotfiles|protecthfs|protectntfs)|remote\..+\.(url|fetch|pushurl)|branch\.|extensions\.|gc\.|pack\.|fetch\.|index\.|safe\.|submodule\..+\.(url|active|branch))' || true; } \ |
| 41 | + | while IFS= read -r key; do git config --local --unset-all "$key" 2>/dev/null || true; done |
| 42 | +fi |
| 43 | +# The GLOBAL scope spans TWO files — ~/.gitconfig and |
| 44 | +# ${XDG_CONFIG_HOME:-~/.config}/git/config — but with both present, |
| 45 | +# `git config --global` lists and unsets ONLY ~/.gitconfig (probed on |
| 46 | +# git 2.43 and 2.55: the listing omits the XDG keys and --unset-all |
| 47 | +# exits 5 with them live), so sweep each file explicitly by pointing |
| 48 | +# GIT_CONFIG_GLOBAL at it — the env var replaces the whole global |
| 49 | +# scope with exactly that file, for reads and writes alike. |
| 50 | +for global_file in "${HOME}/.gitconfig" "${XDG_CONFIG_HOME:-${HOME}/.config}/git/config"; do |
| 51 | + { GIT_CONFIG_GLOBAL="${global_file}" git config --global --name-only --list 2>/dev/null || true; } \ |
| 52 | + | { grep -iE '^(core\.(hookspath|fsmonitor|pager|editor|sshcommand|askpass|alternaterefscommand|gitproxy)$|diff\.external$|diff\..+\.(command|textconv)$|merge\..+\.driver$|filter\.|alias\.|pager\.|difftool\.|mergetool\.|interactive\.difffilter$|sequence\.editor$|gpg\.(.+\.)?program$|init\.templatedir$|remote\..+\.(uploadpack|receivepack)$|submodule\..+\.update$|url\..+\.(insteadof|pushinsteadof)$|http\.(.+\.)?(sslverify|sslcainfo)$|include\.|includeif\.|protocol\.(ext\.)?allow$)' || true; } \ |
| 53 | + | while IFS= read -r key; do GIT_CONFIG_GLOBAL="${global_file}" git config --global --unset-all "$key" 2>/dev/null || true; done |
| 54 | +done |
0 commit comments