diff --git a/.github/workflows/preview-pdf.yml b/.github/workflows/preview-pdf.yml index bd7d324..06083ad 100644 --- a/.github/workflows/preview-pdf.yml +++ b/.github/workflows/preview-pdf.yml @@ -112,23 +112,35 @@ jobs: This is a mutable preview without formal provenance. Use a tagged release for attested artifacts. EOF - - name: Create preview release when absent + - name: Synchronize mutable preview tag run: | - gh release view preview-pdf >/dev/null 2>&1 || \ + if gh api --silent "repos/${GITHUB_REPOSITORY}/git/ref/tags/preview-pdf" 2>/dev/null; then + gh api --silent --method PATCH \ + "repos/${GITHUB_REPOSITORY}/git/refs/tags/preview-pdf" \ + --raw-field sha="$GITHUB_SHA" \ + --field force=true + else + gh api --silent --method POST \ + "repos/${GITHUB_REPOSITORY}/git/refs" \ + --raw-field ref="refs/tags/preview-pdf" \ + --raw-field sha="$GITHUB_SHA" + fi + + - name: Create or update preview release + run: | + if gh release view preview-pdf >/dev/null 2>&1; then + gh release edit preview-pdf \ + --title "Latest Preview PDF" \ + --notes-file dist/release-notes.md \ + --prerelease + else gh release create preview-pdf \ --title "Latest Preview PDF" \ --notes-file dist/release-notes.md \ --prerelease \ --latest=false \ - --target "$GITHUB_SHA" - - - name: Update preview release metadata - run: | - gh release edit preview-pdf \ - --title "Latest Preview PDF" \ - --notes-file dist/release-notes.md \ - --prerelease \ - --target "$GITHUB_SHA" + --verify-tag + fi - name: Replace preview assets run: | diff --git a/tests/test_workflow_security.py b/tests/test_workflow_security.py index 0c51dcf..40e19f0 100644 --- a/tests/test_workflow_security.py +++ b/tests/test_workflow_security.py @@ -62,6 +62,28 @@ class WorkflowSecurityTests(unittest.TestCase): name, ) + def test_mutable_preview_explicitly_moves_tag_before_updating_release(self): + preview = (WORKFLOW_DIR / "preview-pdf.yml").read_text(encoding="utf-8") + publish = preview.split("\n publish:\n", 1)[1] + + get_ref = 'gh api --silent "repos/${GITHUB_REPOSITORY}/git/ref/tags/preview-pdf"' + update_ref = '"repos/${GITHUB_REPOSITORY}/git/refs/tags/preview-pdf"' + create_ref = '"repos/${GITHUB_REPOSITORY}/git/refs"' + self.assertIn(get_ref, publish) + self.assertIn("--method PATCH", publish) + self.assertIn(update_ref, publish) + self.assertIn('--raw-field sha="$GITHUB_SHA"', publish) + self.assertIn("--field force=true", publish) + self.assertIn("--method POST", publish) + self.assertIn(create_ref, publish) + self.assertIn('--raw-field ref="refs/tags/preview-pdf"', publish) + self.assertIn("--verify-tag", publish) + self.assertLess(publish.index(get_ref), publish.index("gh release view preview-pdf")) + self.assertNotRegex( + publish, + r"(?ms)gh release edit preview-pdf.*?--target\s+\"?\$GITHUB_SHA", + ) + def test_downloads_dependencies_and_link_checker_are_integrity_pinned(self): combined = "\n".join(path.read_text(encoding="utf-8") for path in self.workflows()) link_text = (WORKFLOW_DIR / "check-link.yml").read_text(encoding="utf-8")