attempt to improve security #2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Check and update workflow action references | ||
| on: | ||
| schedule: | ||
| - cron: '0 0 * * 1' # Run weekly on Mondays | ||
| workflow_dispatch: # Allow manual trigger | ||
| jobs: | ||
| check-and-update-workflows: | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: write | ||
| pull-requests: write | ||
| steps: | ||
| - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 | ||
| - name: Set up Python | ||
| uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 | ||
| with: | ||
| python-version: '3.11' | ||
| - name: Install dependencies | ||
| run: | | ||
| python -m pip install --upgrade pip | ||
| pip install PyGithub pyyaml | ||
| - name: Check workflows and update action references | ||
| env: | ||
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| run: | | ||
| python - <<'EOF' | ||
| import os | ||
| import re | ||
| import yaml | ||
| import requests | ||
| from github import Github | ||
| # Initialize GitHub client | ||
| g = Github(os.environ["GITHUB_TOKEN"]) | ||
| repo = g.get_repo(os.environ["GITHUB_REPOSITORY"]) | ||
| def get_latest_sha(action_owner, action_name, version): | ||
| # Get the latest SHA for the given action and version | ||
| url = f"https://api.github.com/repos/{action_owner}/{action_name}/commits/{version}" | ||
| headers = {"Accept": "application/vnd.github.v3+json"} | ||
| if "GITHUB_TOKEN" in os.environ: | ||
| headers["Authorization"] = f"token {os.environ['GITHUB_TOKEN']}" | ||
| response = requests.get(url, headers=headers) | ||
| if response.status_code == 200: | ||
| return response.json()["sha"] | ||
| return None | ||
| # Get all workflow files | ||
| workflow_dir = ".github/workflows" | ||
| updates = {} | ||
| for filename in os.listdir(workflow_dir): | ||
| if filename.endswith((".yml", ".yaml")) and filename != "sha.yml": | ||
| filepath = os.path.join(workflow_dir, filename) | ||
| with open(filepath, 'r') as f: | ||
| content = f.read() | ||
| # Look for uses: statements that don't include SHA | ||
| lines = content.split('\n') | ||
| updated_lines = lines.copy() | ||
| needs_update = False | ||
| for i, line in enumerate(lines): | ||
| # Match pattern like: uses: actions/checkout@v3 or uses: owner/repo@v1.2.3 | ||
| matches = re.search(r'^\s*uses:\s+([a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+)@(v\d+[^\s#]*)(?:\s+#.*)?$', line) | ||
| if matches: | ||
| action = matches.group(1) | ||
| version = matches.group(2) | ||
| # Skip if line already has SHA comment | ||
| if not re.search(r'#\s*[a-f0-9]{40}', line): | ||
| sha = get_latest_sha(action.split('/')[0], action.split('/')[1], version) | ||
| if sha: | ||
| # Update line with SHA as comment | ||
| updated_line = re.sub(r'@(v\d+[^\s#]*)(?:\s+#.*)?$', f'@{sha[:40]} # {version}', line) | ||
| updated_lines[i] = updated_line | ||
| needs_update = True | ||
| print(f"Updating {filename}: {line.strip()} -> {updated_line.strip()}") | ||
| if needs_update: | ||
| updates[filepath] = '\n'.join(updated_lines) | ||
| if updates: | ||
| branch_name = f"action-sha-updates-{os.environ.get('GITHUB_RUN_ID', '')}" | ||
| default_branch = repo.default_branch | ||
| # Create a new branch | ||
| ref = f"refs/heads/{branch_name}" | ||
| try: | ||
| repo.create_git_ref(ref=ref, sha=repo.get_branch(default_branch).commit.sha) | ||
| except Exception as e: | ||
| print(f"Error creating branch: {e}") | ||
| exit(1) | ||
| # Update files | ||
| commit_message = "chore: update GitHub Action references with SHA pins" | ||
| for filepath, content in updates.items(): | ||
| try: | ||
| file = repo.get_contents(filepath, ref=branch_name) | ||
| repo.update_file(filepath, commit_message, content, file.sha, branch=branch_name) | ||
| except Exception as e: | ||
| print(f"Error updating {filepath}: {e}") | ||
| # Create PR | ||
| try: | ||
| pr = repo.create_pull( | ||
| title="Update GitHub Action references with SHA pins", | ||
| body="This PR updates GitHub Action references to include SHA pins for better security and reproducibility.", | ||
| head=branch_name, | ||
| base=default_branch | ||
| ) | ||
| print(f"Created PR #{pr.number}: {pr.html_url}") | ||
| except Exception as e: | ||
| print(f"Error creating PR: {e}") | ||
| else: | ||
| print("No updates needed - all action references already use SHA pins.") | ||
| EOF | ||