-
Notifications
You must be signed in to change notification settings - Fork 498
Harden post-agent cache Git commits #53879
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
21727d9
d1d394d
74861fa
0836ac1
e267d5e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,8 +24,54 @@ fi | |
|
|
||
| cd "$CACHE_DIR" | ||
|
|
||
| scrub_git_config_entries() { | ||
| local key_prefix="$1" | ||
| while IFS= read -r key_name; do | ||
| [ -n "$key_name" ] || continue | ||
| git config --unset-all "$key_name" >/dev/null 2>&1 || true | ||
| done < <( | ||
| git config --local --name-only --list 2>/dev/null \ | ||
| | grep -E -i "^${key_prefix}\\." \ | ||
| | sort -u | ||
| ) | ||
| } | ||
|
|
||
| has_symlinked_git_metadata() { | ||
| [ -L .git ] || [ -n "$(find .git -type l -print -quit 2>/dev/null)" ] | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/diagnosing-bugs] After 💡 SuggestionMove the if has_symlinked_git_metadata; then
rm -rf .git
git init -q
git config user.email "gh-aw@github.com"
git config user.name "gh-aw"
fi@copilot please address this. |
||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. shrink: four separate |
||
| if has_symlinked_git_metadata; then | ||
| echo "Refusing to mutate symlinked cache-memory git metadata" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| # Agent-written cache state may contain hooks or configuration that executes | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/diagnosing-bugs] 💡 Suggested fixAdd this alongside the other git config --unset-all core.worktree >/dev/null 2>&1 || true
@copilot please address this. |
||
| # during staging or committing. Clear those command surfaces before either step. | ||
| if [ -d .git/hooks ]; then | ||
| find .git/hooks -mindepth 1 -maxdepth 1 \( -type f -o -type l \) ! -name '*.sample' -delete | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/diagnosing-bugs] 💡 Suggested fixgit config --unset-all core.gitProxy >/dev/null 2>&1 || trueAlso consider @copilot please address this. |
||
| fi | ||
| mkdir -p .git/info | ||
| rm -f .git/info/exclude .git/info/attributes .git/info/grafts .git/info/sparse-checkout | ||
| rm -f .git/config.worktree | ||
|
|
||
| git config --unset-all extensions.worktreeConfig >/dev/null 2>&1 || true | ||
| git config --unset-all core.attributesFile >/dev/null 2>&1 || true | ||
| git config --unset-all core.fsmonitor >/dev/null 2>&1 || true | ||
| git config --unset-all core.sshCommand >/dev/null 2>&1 || true | ||
| git config --unset-all core.hooksPath >/dev/null 2>&1 || true | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
git config --unset-all core.worktree >/dev/null 2>&1 || trueafter the existing @copilot please address this. |
||
| git config --unset-all core.worktree >/dev/null 2>&1 || true | ||
| git config --unset-all core.gitProxy >/dev/null 2>&1 || true | ||
| scrub_git_config_entries include | ||
| scrub_git_config_entries includeif | ||
| scrub_git_config_entries credential | ||
| scrub_git_config_entries alias | ||
| scrub_git_config_entries filter | ||
| scrub_git_config_entries merge | ||
|
|
||
| git config user.email "gh-aw@github.com" | ||
| git config user.name "gh-aw" | ||
| git config core.hooksPath /dev/null | ||
| git config core.fsmonitor false | ||
|
|
||
| # --- Log cache directory contents before commit --- | ||
| echo "=== Cache directory: non-git files being committed ===" | ||
|
|
@@ -41,7 +87,7 @@ git add -A | |
|
|
||
| # Commit on the current integrity branch; allow empty commits in case | ||
| # the agent made no changes (idempotent). | ||
| if git commit --allow-empty -m "run-${RUN_ID}" -q 2>/tmp/gh-aw-commit-err; then | ||
| if git -c commit.gpgSign=false commit --no-verify --allow-empty -m "run-${RUN_ID}" -q 2>/tmp/gh-aw-commit-err; then | ||
| echo "Cache memory git commit complete (run: $RUN_ID)" | ||
| else | ||
| # Distinguish "nothing to commit" (benign) from real errors | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,112 @@ | ||
| #!/usr/bin/env bash | ||
| set -euo pipefail | ||
|
|
||
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||
| SCRIPT="${SCRIPT_DIR}/commit_cache_memory_git.sh" | ||
|
|
||
| TESTS_PASSED=0 | ||
| TESTS_FAILED=0 | ||
| WORKSPACE="$(mktemp -d)" | ||
|
|
||
| cleanup() { | ||
| rm -rf "${WORKSPACE}" | ||
| } | ||
| trap cleanup EXIT | ||
|
|
||
| assert() { | ||
| local name="$1" | ||
| shift | ||
| if "$@" 2>/dev/null; then | ||
| echo " ✓ ${name}" | ||
| TESTS_PASSED=$((TESTS_PASSED + 1)) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
@copilot please address this. |
||
| else | ||
| echo " ✗ ${name}" | ||
| TESTS_FAILED=$((TESTS_FAILED + 1)) | ||
| fi | ||
| } | ||
|
|
||
| run_script() { | ||
| GH_AW_CACHE_DIR="$1" GITHUB_RUN_ID="test-run" bash "${SCRIPT}" 2>&1 | ||
| } | ||
|
|
||
| echo "Testing commit_cache_memory_git.sh" | ||
| echo "" | ||
|
|
||
| echo "Test 1: Script syntax is valid" | ||
| assert "script passes bash -n" bash -n "${SCRIPT}" | ||
| echo "" | ||
|
|
||
| echo "Test 2: Agent-controlled hooks and filters cannot execute" | ||
| D="${WORKSPACE}/test2" | ||
| SENTINEL_HOOK="${WORKSPACE}/hook-executed" | ||
| SENTINEL_FILTER="${WORKSPACE}/filter-executed" | ||
| SENTINEL_SIGNING="${WORKSPACE}/signing-executed" | ||
| mkdir -p "${D}/evil-hooks" | ||
| git -C "${D}" init -q | ||
| git -C "${D}" config user.email "test@example.com" | ||
| git -C "${D}" config user.name "Test" | ||
| touch "${D}/initial" | ||
| git -C "${D}" add initial | ||
| git -C "${D}" commit -qm initial | ||
| git -C "${D}" config extensions.worktreeConfig true | ||
| git -C "${D}" config --worktree core.hooksPath "${D}/evil-hooks" | ||
| git -C "${D}" config --worktree filter.evil.clean "touch ${SENTINEL_FILTER}" | ||
| git -C "${D}" config core.worktree "${WORKSPACE}" | ||
| git -C "${D}" config core.gitProxy "touch ${WORKSPACE}/proxy-executed" | ||
| git -C "${D}" config commit.gpgSign true | ||
| git -C "${D}" config gpg.program "${D}/evil-gpg" | ||
| cat > "${D}/evil-hooks/post-commit" <<EOF | ||
| #!/usr/bin/env bash | ||
| touch "${SENTINEL_HOOK}" | ||
| EOF | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/tdd] The test only covers 💡 Suggested additional testecho "Test 3: Symlinked .git metadata is reinitialized"
D3="${WORKSPACE}/test3"
REAL_GIT="${WORKSPACE}/real-git"
git -C "$(mkdir -p "${D3}" && echo ${D3})" init -q
mv "${D3}/.git" "${REAL_GIT}"
ln -s "${REAL_GIT}" "${D3}/.git" # simulate symlinked metadata
run_script "${D3}" >/dev/null
assert "symlinked .git was replaced with real dir" "[ ! -L '${D3}/.git' ]"
assert "repo still functional after reinit" "git -C '${D3}' status"@copilot please address this. |
||
| cat > "${D}/evil-gpg" <<EOF | ||
| #!/usr/bin/env bash | ||
| touch "${SENTINEL_SIGNING}" | ||
| exit 1 | ||
| EOF | ||
| chmod +x "${D}/evil-hooks/post-commit" "${D}/evil-gpg" | ||
| printf 'content\n' > "${D}/agent-data" | ||
| printf 'agent-data filter=evil\n' > "${D}/.gitattributes" | ||
| run_script "${D}" >/dev/null | ||
| assert "post-commit hook was not executed" test ! -e "${SENTINEL_HOOK}" | ||
| assert "clean filter was not executed" test ! -e "${SENTINEL_FILTER}" | ||
| assert "signing program was not executed" test ! -e "${SENTINEL_SIGNING}" | ||
| assert "hooks path hardened" test "$(git -C "${D}" config --default '' core.hooksPath)" = "/dev/null" | ||
| assert "worktree override removed" test -z "$(git -C "${D}" config --local --get core.worktree || true)" | ||
| assert "git proxy removed" test -z "$(git -C "${D}" config --local --get core.gitProxy || true)" | ||
| assert "worktree config removed" test ! -e "${D}/.git/config.worktree" | ||
| assert "worktree config extension removed" test -z "$(git -C "${D}" config --local --get extensions.worktreeConfig || true)" | ||
| assert "agent changes committed" test "$(git -C "${D}" log -1 --format=%s)" = "run-test-run" | ||
| echo "" | ||
|
|
||
| echo "Test 3: Symlinked git metadata is rejected without losing history" | ||
| D="${WORKSPACE}/test3" | ||
| REAL_GIT="${WORKSPACE}/test3-git" | ||
| mkdir -p "${D}" | ||
| git -C "${D}" init -q | ||
| git -C "${D}" config user.email "test@example.com" | ||
| git -C "${D}" config user.name "Test" | ||
| touch "${D}/initial" | ||
| git -C "${D}" add initial | ||
| git -C "${D}" commit -qm initial | ||
| INITIAL_COMMIT="$(git -C "${D}" rev-parse HEAD)" | ||
| mv "${D}/.git" "${REAL_GIT}" | ||
| ln -s "${REAL_GIT}" "${D}/.git" | ||
| if run_script "${D}" >/dev/null; then | ||
| SYMLINK_REJECTED=false | ||
| else | ||
| SYMLINK_REJECTED=true | ||
| fi | ||
| assert "symlinked metadata was rejected" test "${SYMLINK_REJECTED}" = true | ||
| assert "symlinked metadata was not replaced" test -L "${D}/.git" | ||
| assert "existing history was preserved" test "$(git -C "${D}" rev-parse HEAD)" = "${INITIAL_COMMIT}" | ||
| echo "" | ||
|
|
||
| echo "Tests passed: ${TESTS_PASSED}" | ||
| echo "Tests failed: ${TESTS_FAILED}" | ||
|
|
||
| if [ "${TESTS_FAILED}" -gt 0 ]; then | ||
| exit 1 | ||
| fi | ||
|
|
||
| echo "✓ All tests passed!" | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[/diagnosing-bugs]
scrub_git_config_entriescallsgit config --local, but if.gitwas just reinitialized (empty config), there is nothing to unset — that is fine. However if thegrep -E -imatch produces zero lines thewhileloop body never runs and exits 0. The subtle risk:git config --name-only --listmay emit keys with uppercase prefixes that the^${key_prefix}\.pattern misses on case-sensitive filesystems ifkey_prefixis passed in mixed case. The-iflag ongrepmitigates this, butkey_prefixis always lower-case at the call sites, so the-iis redundant and its presence may give false confidence.💡 Suggestion
Document the lower-case contract in the function comment, or normalise with
tr '[:upper:]' '[:lower:]'before the grep, so callers never need to think about case:@copilot please address this.