From cf85741e626121fb73dd96921914f52e711a50f3 Mon Sep 17 00:00:00 2001 From: Richard McDaniel <1130888+rmcdaniel@users.noreply.github.com> Date: Tue, 3 Nov 2026 20:35:16 -0600 Subject: [PATCH] Refuse inline entry into the tagged tree and unset PYTHONPATH for release tooling Follow-ups from the General interim review of #178 at 675236e2: - the release-source allowlist test also fails a step without working-directory that enters release-source with cd or pushd; - install-release-tooling.sh unsets PYTHONPATH, because pip runs the target venv interpreter as a child that -I does not protect. Co-Authored-By: Claude Opus 5.5 --- scripts/ci/install-release-tooling.sh | 4 ++++ scripts/ci/test-publish-rust-sdk.py | 21 ++++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/scripts/ci/install-release-tooling.sh b/scripts/ci/install-release-tooling.sh index d83c1d6..5827eb3 100755 --- a/scripts/ci/install-release-tooling.sh +++ b/scripts/ci/install-release-tooling.sh @@ -14,6 +14,10 @@ if [ -z "${GITHUB_PATH:-}" ]; then exit 2 fi +# -I protects the interpreters started here, but pip runs the target venv +# interpreter as a child that would still read PYTHONPATH. +unset PYTHONPATH + script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" release_tooling_venv="$1" diff --git a/scripts/ci/test-publish-rust-sdk.py b/scripts/ci/test-publish-rust-sdk.py index d9b1fe9..a75b9fe 100644 --- a/scripts/ci/test-publish-rust-sdk.py +++ b/scripts/ci/test-publish-rust-sdk.py @@ -1269,6 +1269,20 @@ def _step_run(step: str) -> str: "release": "release-tools/scripts/ci/install-release-tooling.sh", } + # A step outside the allowlist must not enter the tagged tree inline either. + ENTERS_TAGGED_TREE = re.compile(r"\b(?:cd|pushd)\b[^;&|\n]*release-source") + + def test_inline_directory_change_into_the_tagged_tree_is_detected(self) -> None: + for command in ("cd release-source && cargo package", "pushd ./release-source", + 'cd "$GITHUB_WORKSPACE/release-source"; python3 -I x.py', + "echo ok; cd -- release-source"): + with self.subTest(command=command): + self.assertRegex(command, self.ENTERS_TAGGED_TREE) + for command in ('head_commit="$(git -C release-source rev-parse HEAD)"', + "cd release-tools && ls release-source"): + with self.subTest(command=command): + self.assertNotRegex(command, self.ENTERS_TAGGED_TREE) + def test_tagged_tree_working_directory_runs_only_maintained_entrypoints(self) -> None: workflow = RELEASE_WORKFLOW.read_text(encoding="utf-8") # A defaults block would set a working directory no step declares. @@ -1280,6 +1294,8 @@ def test_tagged_tree_working_directory_runs_only_maintained_entrypoints(self) -> for step in self._steps(job): directories = re.findall(r"^ working-directory: (.*)$", step, flags=re.MULTILINE) if not directories: + with self.subTest(job=name, step=step.split("\n", 1)[0]): + self.assertNotRegex(self._step_run(step), self.ENTERS_TAGGED_TREE) continue with self.subTest(job=name, step=step.split("\n", 1)[0]): self.assertEqual(["release-source"], directories) @@ -1323,6 +1339,8 @@ def test_isolated_tools_use_the_bootstrapped_pip_client(self) -> None: #!{sys.executable} import json, os, pathlib, sys arguments = sys.argv[1:] + if "PYTHONPATH" in os.environ: + raise SystemExit("PYTHONPATH reached the release tooling interpreter") with pathlib.Path(os.environ["INSTALLER_CALLS"]).open("a") as handle: handle.write(json.dumps(arguments) + "\\n") if arguments[:3] == ["-I", "-m", "venv"]: @@ -1338,7 +1356,8 @@ def test_isolated_tools_use_the_bootstrapped_pip_client(self) -> None: result = subprocess.run( ["bash", str(RELEASE_TOOLING_INSTALLER), str(venv)], env={**os.environ, "PATH": f"{command_directory}:{os.environ['PATH']}", - "INSTALLER_CALLS": str(calls_file), "GITHUB_PATH": str(github_path)}, + "INSTALLER_CALLS": str(calls_file), "GITHUB_PATH": str(github_path), + "PYTHONPATH": str(root / "hostile")}, capture_output=True, text=True, check=False, timeout=10, ) self.assertEqual(0, result.returncode, result.stderr)