"""Deliberate violations must fail the production workflow guard.""" import shutil import subprocess import tempfile import unittest from pathlib import Path class HardeningTests(unittest.TestCase): def test_publication_and_required_job_mutations_fail(self): root = Path(__file__).resolve().parents[2] script = root / ".github/scripts/check_workflow_hardening.py" with tempfile.TemporaryDirectory() as directory: work = Path(directory) shutil.copytree(root / ".github/workflows", work / ".github/workflows") shutil.copyfile(root / "build.rs", work / "build.rs") def check(): return subprocess.run(["python3", str(script)], cwd=work, capture_output=True, text=True, check=False) self.assertEqual(check().returncode, 0) for name, old, new, expected in [ ("ci.yml", "actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5", "actions/checkout@main", "not pinned"), ("ci.yml", "needs: [plan, checks, service, qualification]", "needs: [plan]", "missing qualification guard"), ("qualification.yml", "needs: [quality, artifacts, service, specialized]", "needs: [quality]", "missing qualification guard"), ("qualification.yml", "cargo fetch --locked", "echo missing bootstrap", "missing qualification guard"), ("release.yml", "github.ref == 'refs/heads/main'", "true", "missing publication-resume guard"), ("release.yml", "scripts/ci/publication.py --repo", "scripts/ci/skip.py --repo", "missing publication-resume guard"), ("release.yml", "run-id: ${{ steps.validate.outputs.run_id }}", "run-id: 1", "missing publication-resume guard"), ("release.yml", "verify-loaded-container", "skip-loaded-container", "missing publication-resume guard"), ("release.yml", "needs: qualification", "needs: validate", "missing qualification guard"), ("service-smoke.yml", "service-smoke-${{ github.workflow }}-", "service-smoke-${{ github.workflow }}-${{ github.run_id }}-", "cancel obsolete runs"), ("service-smoke.yml", " workflow_call:", " workflow_call:\n pull_request:", "reusable only"), ("checks.yml", "run: python3 scripts/check-contracts", "run: echo skipped contracts", "missing required routing/gate guard"), ("ci.yml", 'required += ["service"]', 'required += []', "missing qualification guard"), ("nightly.yml", "contents: read", "contents: write", "publication credentials"), ]: path = work / ".github/workflows" / name original = path.read_text() self.assertIn(old, original) path.write_text(original.replace(old, new)) result = check() self.assertNotEqual(result.returncode, 0, name) self.assertIn(expected, result.stderr) path.write_text(original) if __name__ == "__main__": unittest.main()