test_q3_release_desktop_security.py
file-level
1
files
1
commits
0
hotspots
0
🧊 dead
0
💥 blast risk
| 1 | """Security tests for Q3-release desktop installers (§QR.13).""" |
| 2 | |
| 3 | from __future__ import annotations |
| 4 | |
| 5 | from pathlib import Path |
| 6 | |
| 7 | import pytest |
| 8 | |
| 9 | from tests.fixtures.desktop_release import ( |
| 10 | GIT_SHA_FIXTURE, |
| 11 | RELEASE_TEMPLATE, |
| 12 | RELEASE_WORKFLOW, |
| 13 | SMOKE_WORKFLOW, |
| 14 | sample_signed_artifacts, |
| 15 | ) |
| 16 | from tests.support import KIT_ROOT |
| 17 | from tools.desktop_release.allowlist import AllowlistError, refuse_disallowed_asset |
| 18 | from tools.desktop_release.manifest import ManifestError, build_manifest |
| 19 | from tools.desktop_release.refuse import scan_text_for_secret_patterns |
| 20 | from tools.desktop_release.workflow_lint import ( |
| 21 | assert_release_workflow_contract, |
| 22 | load_workflow, |
| 23 | workflow_triggers, |
| 24 | ) |
| 25 | |
| 26 | |
| 27 | def test_no_secret_literals_in_workflows() -> None: |
| 28 | for path in (RELEASE_WORKFLOW, SMOKE_WORKFLOW, RELEASE_TEMPLATE): |
| 29 | text = path.read_text(encoding="utf-8") |
| 30 | assert scan_text_for_secret_patterns(text) == [] |
| 31 | assert "BEGIN PRIVATE KEY" not in text |
| 32 | assert "BEGIN RSA PRIVATE KEY" not in text |
| 33 | |
| 34 | |
| 35 | def test_gitignore_covers_signing_patterns() -> None: |
| 36 | text = (KIT_ROOT / ".gitignore").read_text(encoding="utf-8") |
| 37 | assert "*.p12" in text |
| 38 | assert "*.pfx" in text |
| 39 | assert "apple-api-key.json" in text |
| 40 | |
| 41 | |
| 42 | def test_release_workflow_does_not_echo_secrets() -> None: |
| 43 | text = RELEASE_WORKFLOW.read_text(encoding="utf-8") |
| 44 | # Forbid echoing secret values; references via secrets.* are OK. |
| 45 | for line in text.splitlines(): |
| 46 | stripped = line.strip() |
| 47 | if stripped.startswith("#"): |
| 48 | continue |
| 49 | lower = stripped.lower() |
| 50 | if "echo" in lower and "secret" in lower and "secrets." in stripped: |
| 51 | # echo of secret *names* in comments is fine; fail on echo "$SECRET" |
| 52 | if "${{" in stripped and "secrets." in stripped and "echo" in lower: |
| 53 | # GitHub expressions in env blocks are OK; shell echo of expanded secrets not. |
| 54 | if "echo" in lower and ("$APPLE_" in stripped or "$WINDOWS_" in stripped or "$LINUX_" in stripped): |
| 55 | pytest.fail(f"possible secret echo: {stripped}") |
| 56 | |
| 57 | |
| 58 | def test_pull_request_cannot_publish_releases() -> None: |
| 59 | data = load_workflow(RELEASE_WORKFLOW) |
| 60 | triggers = workflow_triggers(data) |
| 61 | assert "pull_request" not in triggers |
| 62 | assert_release_workflow_contract(data) |
| 63 | smoke = load_workflow(SMOKE_WORKFLOW) |
| 64 | smoke_text = SMOKE_WORKFLOW.read_text(encoding="utf-8").lower() |
| 65 | assert "action-gh-release" not in smoke_text |
| 66 | assert "gh release" not in smoke_text |
| 67 | |
| 68 | |
| 69 | def test_permissions_least_privilege() -> None: |
| 70 | data = load_workflow(RELEASE_WORKFLOW) |
| 71 | perms = data["permissions"] |
| 72 | assert perms == {"contents": "write"} or ( |
| 73 | perms.get("contents") == "write" and "id-token" not in perms |
| 74 | ) |
| 75 | |
| 76 | |
| 77 | def test_track_q_launcher_still_ok_app_only() -> None: |
| 78 | launcher = (KIT_ROOT / "desktop" / "src-tauri" / "src" / "launcher.rs").read_text( |
| 79 | encoding="utf-8" |
| 80 | ) |
| 81 | assert "ok" in launcher |
| 82 | assert "app" in launcher |
| 83 | assert "0.0.0.0" not in launcher |
| 84 | assert "hosted-dashboard" not in launcher |
| 85 | |
| 86 | |
| 87 | def test_rejection_unsigned_labeled_signed_refused() -> None: |
| 88 | arts = sample_signed_artifacts() |
| 89 | arts[0]["signing"] = {"status": "signed", "method": "none"} |
| 90 | with pytest.raises(ManifestError): |
| 91 | build_manifest(version="0.1.0", git_sha=GIT_SHA_FIXTURE, artifacts=arts) |
| 92 | |
| 93 | |
| 94 | def test_rejection_non_allowlisted_asset_types() -> None: |
| 95 | for name in ("pkg.deb", "pkg.rpm", "Setup.exe", "Overseer.app.zip"): |
| 96 | with pytest.raises(AllowlistError): |
| 97 | refuse_disallowed_asset(name, version="0.1.0") |