scripts/audit_workflow_security.py
Rama main · réplica de lectura del repositorio original.
"""Check GitHub Actions workflows for least-privilege permissions and stable refs."""
from __future__ import annotations
import argparse
import re
from pathlib import Path
from scripts.audit_common import print_failures, require_root
_ACTION_REF = re.compile(r"\buses:\s*([^\s#]+)")
def audit(root: Path) -> list[str]:
root = require_root(root)
workflow_dir = root / ".github" / "workflows"
workflows = sorted([*workflow_dir.glob("*.yml"), *workflow_dir.glob("*.yaml")]) if workflow_dir.is_dir() else []
if not workflows:
return [".github/workflows: no workflows found"]
failures: list[str] = []
for path in workflows:
text = path.read_text(encoding="utf-8")
rel = path.relative_to(root)
if "pull_request_target:" in text:
failures.append(f"{rel}: pull_request_target is forbidden")
if not re.search(r"(?ms)^permissions:\s*\n\s+contents:\s*read\s*$", text):
failures.append(f"{rel}: workflow must declare permissions contents: read")
for ref in _ACTION_REF.findall(text):
if "@" not in ref or ref.rsplit("@", 1)[1].lower() in {"main", "master", "head"}:
failures.append(f"{rel}: action reference must use an explicit stable version: {ref}")
return failures
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("root", nargs="?", default=".")
args = parser.parse_args(argv)
return print_failures(audit(Path(args.root)))
if __name__ == "__main__":
raise SystemExit(main())