diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 630a9d0a..7d6036e7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -636,6 +636,7 @@ jobs: RELEASE_SHA: ${{ needs.validate.outputs.source_sha }} RELEASE_VERSION: ${{ needs.validate.outputs.version }} NOTES_PATH: ${{ needs.validate.outputs.notes_path }} + PYTHONDONTWRITEBYTECODE: '1' steps: - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 with: diff --git a/scripts/check_release_workflow.py b/scripts/check_release_workflow.py index f9cdafa8..a69ce11c 100644 --- a/scripts/check_release_workflow.py +++ b/scripts/check_release_workflow.py @@ -190,7 +190,7 @@ "quality": "43599825f68088f3cc744220227267f77454e2c7ce389a52b8aee5a492491eec", "macos_arm64": "f913508e4f041614a967f680515cc0b8779800f0b2a7b4b6ff29f24f4bb1efd2", "windows_x64": "5f012278c20843ba1b38cffb27f3c614716bd92e3fec63a11885b41692be78b5", - "publish": "dd325630c78f236e972ca425ccc83018d12b4ec35a6b8a80d1af94990c8179a2", + "publish": "89192ac021d48cc8c8ef17b612a2da666d96c01d195c4e2b58eec24b2cc24d1b", } @@ -969,6 +969,9 @@ def validate_workflow(workflow: str) -> list[str]: return list(dict.fromkeys(errors)) if publish.get("permissions") != {"contents": "write"}: errors.append("publish-only contents write permission") + publish_env = _as_mapping(publish.get("env")) + if publish_env is None or publish_env.get("PYTHONDONTWRITEBYTECODE") != "1": + errors.append("publish Python helpers cannot write bytecode into the checkout") initialize_publish = _structured_step(publish, "Initialize isolated publish root") download_macos = _structured_step(publish, "Download macOS artifact") download_windows = _structured_step(publish, "Download Windows artifact") diff --git a/scripts/test_check_release_workflow.py b/scripts/test_check_release_workflow.py index 1beb6184..7c2198f0 100644 --- a/scripts/test_check_release_workflow.py +++ b/scripts/test_check_release_workflow.py @@ -334,6 +334,27 @@ def test_publish_outputs_must_stay_outside_the_worktree(self) -> None: ) self.assert_rejected(mutated, "publish outputs stay outside the worktree") + def test_publish_python_helpers_cannot_write_bytecode_into_checkout(self) -> None: + protected_env = " PYTHONDONTWRITEBYTECODE: '1'\n" + if protected_env in WORKFLOW: + protected = WORKFLOW + else: + protected = self.mutate( + " NOTES_PATH: ${{ needs.validate.outputs.notes_path }}\n", + " NOTES_PATH: ${{ needs.validate.outputs.notes_path }}\n" + + protected_env, + ) + unprotected = protected.replace(protected_env, "", 1) + + self.assertNotIn( + "publish Python helpers cannot write bytecode into the checkout", + contract.validate_workflow(protected), + ) + self.assert_rejected( + unprotected, + "publish Python helpers cannot write bytecode into the checkout", + ) + def test_macos_package_command_cannot_be_faked_by_an_echo_string(self) -> None: original = ( " run: >-\n"