Skip to content

Commit 10496a8

Browse files
committed
Add actions-audit.py script for auditing Apache repo security tooling
Adds a new utility script that audits apache/ GitHub repositories for baseline Actions security configurations (dependabot, CodeQL, zizmor, allowlist-check) and can create PRs to add missing ones. Key features: - Uses GraphQL to batch-fetch workflow file contents per repo - Dry-run mode shows detailed preview of what PRs would contain - Prints zizmor findings so users can see issues before creating PRs - Skips secrets-outside-env zizmor rule (too noisy for initial rollout) - Includes zizmor error output in PR body when workflows are commented out
1 parent d79f0ef commit 10496a8

File tree

5 files changed

+1382
-4
lines changed

5 files changed

+1382
-4
lines changed

README.md

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ This repository hosts GitHub Actions developed by the ASF community and approved
3030
- [Dependabot Cooldown Period](#dependabot-cooldown-period)
3131
- [Manual Version Addition](#manual-addition-of-specific-versions)
3232
- [Removing a Version](#removing-a-version-manually)
33+
- [Auditing Repositories for Actions Security Tooling](#auditing-repositories-for-actions-security-tooling)
3334

3435
## Submitting an Action
3536

@@ -285,3 +286,107 @@ If you add older version of the action and want to set an expiration date for it
285286
The infrastructure team will prioritize these removal requests and may take additional steps to notify affected projects if necessary.
286287

287288
For 'regular' removals (not security responses), you can use `./utils/action-usage.sh someorg/theaction` to see if/how an action is still used anywhere in the ASF, and create a 'regular' PR removing it from `actions.yml` (or adding an expiration date) when it is no longer used.
289+
290+
## Auditing Repositories for Actions Security Tooling
291+
292+
Recent security breaches have shown that GitHub Actions can fail silently, leaving repositories vulnerable without any visible indication. The `actions-audit.py` script helps ensure that all Apache repositories using GitHub Actions have a baseline set of security tooling in place.
293+
294+
### Why This Matters
295+
296+
GitHub Actions workflows can introduce security risks in several ways:
297+
- **Unpinned or unreviewed action versions** may contain malicious code or vulnerabilities
298+
- **Missing static analysis** means workflow misconfigurations (secret exposure, injection vulnerabilities) go undetected
299+
- **No dependabot** means action versions never get updated, accumulating known vulnerabilities over time
300+
301+
The audit script checks each repository for four security configurations and can automatically open PRs to add any that are missing:
302+
303+
| Check | What it does |
304+
|-------|-------------|
305+
| **Dependabot** | Keeps GitHub Actions dependencies up to date with a 4-day cooldown to avoid overwhelming reviewers |
306+
| **CodeQL** | Runs static analysis on workflow files to detect security issues in Actions syntax |
307+
| **Zizmor** | Specialized scanner for GitHub Actions anti-patterns: credential leaks, injection vulnerabilities, excessive permissions |
308+
| **ASF Allowlist Check** | Ensures every action used is on the ASF Infrastructure approved allowlist |
309+
310+
### Prerequisites
311+
312+
- **Python 3.11+** and [**uv**](https://docs.astral.sh/uv/) **>= 0.9.17** (dependencies are managed inline via PEP 723). Make sure your uv is up to date — depending on how you installed it, run `uv self update`, `pip install --upgrade uv`, `pipx upgrade uv`, or `brew upgrade uv`
313+
- **`gh`** (GitHub CLI, authenticated via `gh auth login`) — or provide a `--github-token` with `repo` scope and use `--no-gh`
314+
- **`zizmor`** ([install instructions](https://docs.zizmor.dev/installation/)) — required for PR creation mode; not needed for `--dry-run`. If missing, zizmor pre-checks are skipped with a warning
315+
316+
### Usage
317+
318+
Always start with `--dry-run` to see what the script would do without making any changes:
319+
320+
```bash
321+
# Audit all repos for a specific PMC (prefix before first '-' in repo name)
322+
uv run utils/actions-audit.py --dry-run --pmc spark --max-num 10
323+
324+
# Audit multiple PMCs
325+
uv run utils/actions-audit.py --dry-run --pmc kafka --pmc flink
326+
327+
# Audit the first 50 repos (no PMC filter)
328+
uv run utils/actions-audit.py --dry-run --max-num 50
329+
330+
# Increase GraphQL page size for fewer API round-trips
331+
uv run utils/actions-audit.py --dry-run --max-num 200 --batch-size 100
332+
```
333+
334+
When satisfied with the dry-run output, remove `--dry-run` to create PRs:
335+
336+
```bash
337+
# Create PRs for spark repos missing security tooling
338+
uv run utils/actions-audit.py --pmc spark --max-num 10
339+
```
340+
341+
#### Options
342+
343+
| Flag | Description |
344+
|------|-------------|
345+
| `--pmc PMC` | Filter by PMC prefix (repeatable). The prefix is the text before the first `-` in the repo name, e.g. `spark` matches `spark`, `spark-connect-go`, `spark-docker`. |
346+
| `--dry-run` | Report findings without creating PRs or branches. |
347+
| `--max-num N` | Maximum number of repositories to check (0 = unlimited, default). |
348+
| `--batch-size N` | Number of repos to fetch per GraphQL request (default: 50, max: 100). |
349+
| `--github-token TOKEN` | GitHub token. Defaults to `GH_TOKEN` or `GITHUB_TOKEN` environment variable. |
350+
| `--no-gh` | Use Python `requests` instead of the `gh` CLI for all API calls. Requires `--github-token` or a token env var. |
351+
352+
#### How PMC Filtering Works
353+
354+
The `--pmc` flag matches repos by prefix: the text before the first hyphen in the repository name. For example, `--pmc spark` matches `apache/spark`, `apache/spark-connect-go`, and `apache/spark-docker`. If the repo name has no hyphen, the full name is used as the prefix.
355+
356+
The script downloads the list of known PMCs from `whimsy.apache.org` on first run and caches it locally (`~/.cache/asf-actions-audit/pmc-list.json`) for 24 hours. If a `--pmc` value doesn't match any known PMC, a warning is printed but it is still used as a prefix filter.
357+
358+
#### What the PRs Contain
359+
360+
For each repository that is missing one or more checks, the script creates a single PR on a branch named `asf-actions-security-audit` containing only the missing files:
361+
362+
- `.github/dependabot.yml` — created or updated to include the `github-actions` ecosystem with a 4-day cooldown
363+
- `.github/workflows/codeql-analysis.yml` — CodeQL scanning for the `actions` language
364+
- `.github/workflows/zizmor.yml` — Zizmor scanning with SARIF upload
365+
- `.github/workflows/allowlist-check.yml` — ASF allowlist verification on workflow changes
366+
367+
#### Zizmor Pre-Check
368+
369+
Before creating a PR, the script runs `zizmor` against the repository's existing workflow files. If zizmor finds errors, the **CodeQL and Zizmor workflow files are added but commented out**, with instructions explaining:
370+
- That zizmor found existing issues in the workflows
371+
- How to auto-fix common issues (`zizmor --fix .github/workflows/`)
372+
- That the PMC should uncomment the workflows and fix remaining issues in a follow-up PR
373+
374+
This avoids creating PRs that would immediately fail CI due to pre-existing problems.
375+
376+
#### Interactive Confirmation
377+
378+
When not in `--dry-run` mode, the script prompts for confirmation before creating each PR:
379+
380+
```
381+
Create PR for apache/spark?
382+
Will add: dependabot, codeql, zizmor, allowlist-check
383+
Proceed? [yes/no/quit] (yes):
384+
```
385+
386+
- **yes** (default) — create the PR
387+
- **no** — skip this repository and continue to the next
388+
- **quit** — stop processing entirely and print the summary
389+
390+
#### Idempotency
391+
392+
The script is safe to re-run. Before creating a PR for a repository, it checks whether a PR with the branch name `asf-actions-security-audit` already exists — open, closed, or merged — and skips the repo if so.

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,5 @@ dev = [
3131
]
3232

3333
[tool.uv]
34+
required-version = ">=0.9.17"
3435
exclude-newer = "4 days"

0 commit comments

Comments
 (0)