From 2dce5c6b7647ed2d5c1ae5c63af67d7bd8f9e692 Mon Sep 17 00:00:00 2001 From: Adam Moussa Date: Mon, 22 Jun 2026 19:18:47 -0400 Subject: [PATCH] fix(agent-team): post-build denied-path check writes scratch outside the checkout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Live smoke exposed a self-pollution bug (pre-existing from PR #17): the post-build denied-path check wrote its own _build_diff_z.bin / _build_status_z.bin into the working tree, then its own `git status --untracked-files=all` flagged them as out-of-scope writes — failing any run with a narrow declared_scope (the smoke's docs/**). Write them to $RUNNER_TEMP instead (read via $_DIFF_Z/$_STATUS_Z), so the check no longer sees its own temp files. The agent-team pytest artifacts were already correctly gitignored; only the check's own files tripped it. Test harness updated to pass the env paths. 1044 tests, ruff clean. --- .github/workflows/agent-team-apply-verify.yml | 15 +++++++++++---- .../tests/test_apply_verify_workflow_hardening.py | 15 ++++++++++++--- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/.github/workflows/agent-team-apply-verify.yml b/.github/workflows/agent-team-apply-verify.yml index d1392ed..e18c6ee 100644 --- a/.github/workflows/agent-team-apply-verify.yml +++ b/.github/workflows/agent-team-apply-verify.yml @@ -742,8 +742,15 @@ jobs: # mangling is done anywhere. A path that cannot be cleanly decoded is # treated as a VIOLATION (fail closed). git config core.quotepath false - git diff -z --name-only HEAD > ./_build_diff_z.bin || true - git status --porcelain=v1 -z --untracked-files=all > ./_build_status_z.bin || true + # Write the scratch capture files OUTSIDE the checkout ($RUNNER_TEMP) so + # the `git status --untracked-files=all` below does not see — and flag — + # the check's OWN temp files as out-of-scope writes (they would otherwise + # appear as untracked and fail a narrow declared_scope). + _DIFF_Z="${RUNNER_TEMP:-/tmp}/_build_diff_z.bin" + _STATUS_Z="${RUNNER_TEMP:-/tmp}/_build_status_z.bin" + export _DIFF_Z _STATUS_Z + git diff -z --name-only HEAD > "$_DIFF_Z" || true + git status --porcelain=v1 -z --untracked-files=all > "$_STATUS_Z" || true python3 - <<'PY' from __future__ import annotations @@ -885,7 +892,7 @@ jobs: """`git diff -z --name-only` records: each NUL field is one path.""" ok: list[str] = [] bad: list[bytes] = [] - for rec in _read_z("./_build_diff_z.bin"): + for rec in _read_z(os.environ["_DIFF_Z"]): dec = _decode(rec) (ok if dec is not None else bad).append(dec if dec is not None else rec) return ok, bad @@ -901,7 +908,7 @@ jobs: """ ok: list[str] = [] bad: list[bytes] = [] - recs = _read_z("./_build_status_z.bin") + recs = _read_z(os.environ["_STATUS_Z"]) i = 0 while i < len(recs): rec = recs[i] diff --git a/agent-team/tests/test_apply_verify_workflow_hardening.py b/agent-team/tests/test_apply_verify_workflow_hardening.py index a8790df..e5f0d4c 100644 --- a/agent-team/tests/test_apply_verify_workflow_hardening.py +++ b/agent-team/tests/test_apply_verify_workflow_hardening.py @@ -390,9 +390,18 @@ def _run_post_build( script = tmp_path / "post_build.py" script.write_text(_extract_post_build_script(), encoding="utf-8") - (tmp_path / "_build_diff_z.bin").write_bytes(diff_z) - (tmp_path / "_build_status_z.bin").write_bytes(status_z) - env = dict(os.environ, DECLARED_SCOPE=scope) + diff_z_path = tmp_path / "_build_diff_z.bin" + status_z_path = tmp_path / "_build_status_z.bin" + diff_z_path.write_bytes(diff_z) + status_z_path.write_bytes(status_z) + # The script now reads its capture files from $_DIFF_Z / $_STATUS_Z (written + # outside the checkout in CI so the check's own temp files are not flagged). + env = dict( + os.environ, + DECLARED_SCOPE=scope, + _DIFF_Z=str(diff_z_path), + _STATUS_Z=str(status_z_path), + ) result = subprocess.run( [sys.executable, str(script)], env=env, -- 2.50.1