This repository hosts GitHub Actions developed by the ASF community and approved for any ASF top level project to use. It also manages the organization wide allow list of GitHub Actions via ‘Configuration as Code’.
To contribute a GitHub Action to this repository:
/MyNewAction)The Infrastructure team will review each proposed Action based on:
Once approved, the Infrastructure team will merge the pull request and add the new Action to the list of available Actions for all ASF projects.
We highly appreciate contributed reviews, especially from people associated with the projects that (would like to) use a particular action, even if they‘re not committers on this project: you’re especially qualified to judge and vouch for the safety and correctness of the action.
As stated in the ASF GitHub Actions Policy, GitHub Actions from external sources are blocked by default in all apache/* repositories. Only actions from the following namespaces are automatically allowed:
apache/*github/*actions/*All other actions must be explicitly added to the allow list after undergoing a security review. This review process applies to both new actions and new versions of previously approved actions (though reviews for new versions are typically expedited).
actions.yml is the source of truth for approved actions. From it, two generated files are kept in sync automatically: approved_patterns.yml (consumed by the ASF org-wide allow list) and .github/actions/for-dependabot-triggered-reviews/action.yml (the composite action Dependabot watches, so it can propose version bumps). The sections below describe the two entry points — manual PRs to add a new action, and the Dependabot-driven flow for updating versions of already-approved actions — and the workflows that implement each.
The diagram below summarizes every entry point, workflow and generated file involved in keeping the allow list in shape. Each subsequent section zooms in on one slice of this flow.
graph LR human["Human PR<br/>(add action / older version /<br/>urgent removal)"] dependabot["Dependabot PR<br/>(version bump)"] cron["Daily 02:04 UTC"] actions["actions.yml<br/><i>source of truth</i>"] composite[".github/actions/<br/>for-dependabot-triggered-reviews/<br/>action.yml"] approved["approved_patterns.yml<br/><i>ASF org allow list</i>"] human-->actions dependabot-->composite dependabot-.verified by.-verify["verify_dependabot_action.yml<br/>(rebuild & diff)"] composite=="update_actions.yml<br/>(on merge)"==>actions cron=="remove_expired.yml"==>actions actions=="update_composite_action.yml"==>composite actions=="update_composite_action.yml"==>approved guard["check_approved_limit.yml<br/>(fails at 800 / 1000)"]-.monitors.-approved classDef source fill:#fff3b0,stroke:#8a6d0b,color:#333 classDef generated fill:#e0f0ff,stroke:#2563a6,color:#333 classDef trigger fill:#f3e0ff,stroke:#6a1b9a,color:#333 classDef workflow fill:#e6ffe6,stroke:#1b5e20,color:#333 class actions source class composite,approved generated class human,dependabot,cron trigger class verify,guard workflow
Solid arrows (==>) are workflow regeneration edges — the “source → generated” flows that keep actions.yml, approved_patterns.yml and the dependabot composite in sync. Thin arrows feed the pipeline with new content (human or Dependabot PRs, cron), and dotted arrows are observer workflows that verify or guard rather than mutate.
[!NOTE]
check_approved_limit.ymlguards the whole pipeline: the org-wide allow list has a hard cap of 1000 entries, and this workflow fails at 800 to give maintainers room to clean up before hitting the wall.
graph TD; manual["manual PR"]--new entry-->actions.yml actions.yml--"update_composite_action.yml"-->composite[".github/actions/for-dependabot-triggered-reviews/action.yml"] actions.yml--"update_composite_action.yml"-->approved["approved_patterns.yml"]
A human-authored PR edits actions.yml directly. Once it merges to main, the update_composite_action.yml workflow regenerates both .github/actions/for-dependabot-triggered-reviews/action.yml and approved_patterns.yml from the new entries, so contributors never have to hand-edit the generated files.
To request addition of an action to the allow list:
actions.yml using the following format:repo/owner: '<exact-commit-sha>': tag: vX.Y.Z
Create a PR against the main branch
Include in your PR description:
Wait for review by the infrastructure team
[!NOTE] Always pin actions to exact commit SHAs, never use tags or branch references.
The infrastructure team will review your request and either approve, request changes, or provide feedback on alternatives.
graph TD; dependabot--"PR updates"-->composite[".github/actions/for-dependabot-triggered-reviews/action.yml"] dependabot-.verified by.-verify["verify_dependabot_action.yml"] composite--"update_actions.yml (on merge)"-->actions.yml actions.yml--"update_actions.yml"-->approved["approved_patterns.yml"]
In most cases, new versions are automatically added through Dependabot:
.github/actions/for-dependabot-triggered-reviews/action.yml to update actions to the newest releasesverify_dependabot_action.yml runs on each such PR, rebuilds the action's compiled JavaScript in Docker, and diffs it against the published version (see Automated Verification in CI)update_actions.yml reflects the new commit SHAs back into actions.yml and regenerates approved_patterns.ymlexpires_at date 3 months out, giving projects a grace period to update their workflows; see Automatic Expiration of Old Versions for how the cleanup runsProjects are encouraged to help review updates to actions they use. Please have a look at the diff and mention in your approval what you have checked and why you think the action is safe.
Many GitHub Actions ship pre-compiled JavaScript in their dist/ directory. To verify that the published compiled JS matches a clean rebuild from source, use the verification script:
uv run utils/verify-action-build.py org/repo@commit_hash
For example:
uv run utils/verify-action-build.py dorny/test-reporter@dc3a92680fcc15842eef52e8c4606ea7ce6bd3f3
The script will:
dist/ files as published in the repositorynpm ci && npm run build, yarn, or pnpm), Dart (dart compile js when a pubspec.yaml is present), or Deno (deno task bundle when a deno.json/deno.jsonc is present)A clean result confirms that the compiled JS was built from the declared source. Any differences will be flagged for manual inspection.
When reviewing an action (new or updated), watch for these potential issues in the source diff between the approved and new versions:
GITHUB_TOKEN, AWS_*, ACTIONS_RUNTIME_TOKEN) and sends them to external endpoints via fetch, http, net, or shell commands (curl, wget).eval(), new Function(), child_process.exec/spawn with unsanitised inputs, or downloading and running scripts from remote URLs at build or runtime.post or cleanup steps that run after the main action.contents: write, id-token: write, packages: write) beyond what their functionality requires.package.json that are unpopular, recently published, or have been involved in known compromises; mismatches between package-lock.json and package.json.dist/).$GITHUB_WORKSPACE), modifying $GITHUB_ENV, $GITHUB_PATH, or $GITHUB_OUTPUT in unexpected ways to influence subsequent workflow steps.dist/ and a clean rebuild — this is the primary check the verification script performs.For the full approval policy and requirements, see the ASF GitHub Actions Policy.
To review all open dependabot PRs at once, run:
uv run utils/verify-action-build.py --check-dependabot-prs
This will:
gh CLIIf you prefer not to install the gh CLI, you can use --no-gh to make all GitHub API calls via Python requests instead. In this mode you must provide a GitHub token either via --github-token or the GITHUB_TOKEN environment variable:
# Using the flag: uv run utils/verify-action-build.py --no-gh --github-token ghp_... org/repo@commit_hash # Or via environment variable: export GITHUB_TOKEN=ghp_... uv run utils/verify-action-build.py --no-gh --check-dependabot-prs
The --no-gh mode supports all the same features as the default gh-based mode.
Two workflows in .github/workflows/ run verify-action-build on PRs that touch the allow list, so the verification status is visible on every PR as a required-candidate status check:
verify_dependabot_action.yml — triggers on Dependabot PRs that modify .github/actions/for-dependabot-triggered-reviews/action.yml. Extracts the action reference from the PR, rebuilds the compiled JavaScript in Docker, and compares it against the published version.verify_manual_action.yml — triggers on human-authored PRs that modify actions.yml or approved_patterns.yml (i.e. manual allow-list additions / version bumps). Dependabot-authored PRs are skipped, since they are already covered by the workflow above.Both workflows use a regular pull_request trigger with read-only permissions and no PR comments — pass/fail is surfaced through the status check. Neither workflow auto-approves or merges; a human reviewer must still approve.
The script exits with code 1 (failure) when something is unexpectedly broken — for example, the action cannot be compiled, the rebuilt JavaScript is invalid, or required tools are missing. In all other cases it exits with code 0 and produces reviewable diffs: a large diff does not by itself cause an error (e.g. major version bumps will naturally have big diffs). It is always up to a human reviewer to inspect the output, assess the changes, and decide whether the update is safe to approve.
To verify a specific PR locally (non-interactively), use:
uv run utils/verify-action-build.py --ci --from-pr 123
The --ci flag skips all interactive prompts (auto-selects the newest approved version for diffing, auto-accepts exclusions, disables paging). The --from-pr flag extracts the action reference from the given PR number.
Additional flags:
--no-cache — rebuild the Docker image from scratch without using the layer cache.--show-build-steps — display a summary of Docker build steps on successful builds (the summary is always shown on failure).[!NOTE] Prerequisites:
dockeranduv. When using the default mode (without--no-gh),gh(GitHub CLI, authenticated viagh auth login) is also required. The build runs in anode:20-slimcontainer so no local Node.js installation is needed.
This repository uses a Dependabot cooldown period of 0 days so that maintainers can review before Dependabot opens a PR on project repositories.
[!TIP] We recommend that ASF projects configure a cooldown in their own
dependabot.ymlto avoid being overwhelmed by update PRs and to catch up with approved actions here:updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" cooldown: default: 4Adjust the
defaultvalue (in days) to match your project's review capacity.
If you need to add a specific version of an already approved action (especially an older one):
actions.yml with the following format:existing/action: '<exact-commit-sha>': keep: true tag: vX.Y.Z
if this is the newest version of the action (make sure to remove the keep: true from the previously newest version and add expires_at: <date> to it, if you want to set an expiration date for it),
or
existing/action: '<exact-commit-sha>': expires_at: 2025-01-01 tag: vX.Y.Z
If you add older version of the action and want to set an expiration date for it.
main branch[!WARNING] Older versions may contain security vulnerabilities or performance issues. Always evaluate if using the latest version is possible before requesting older versions.
graph TD; entry["actions.yml entry<br/>with expires_at"]--"remove_expired.yml (daily, 02:04 UTC)"-->actions.yml actions.yml--"update_composite_action.yml"-->composite[".github/actions/for-dependabot-triggered-reviews/action.yml"] actions.yml--"update_composite_action.yml"-->approved["approved_patterns.yml"]
Routine cleanup of superseded versions is automated:
actions.yml with an expires_at: YYYY-MM-DD field is a candidate for removal.expires_at to 3 months out on the previously approved version. For manually added older versions, set expires_at explicitly (see Manual Addition of Specific Versions).remove_expired.yml workflow runs daily at 02:04 UTC. Every entry whose expires_at date has passed is deleted from actions.yml; the workflow then commits the change and lets update_composite_action.yml regenerate approved_patterns.yml and the dependabot composite.expires_at (for example, keep: true wildcards and the current approved version) are never auto-removed — removal of those requires a manual PR.No human action is required for the routine case: projects get a 3-month grace window after a version bump, and the old entry disappears on its own afterwards.
Routine removal is already automated: set expires_at on the entry and the daily remove_expired.yml workflow will delete it once the date passes. Use the manual process below only when you need an immediate removal that can't wait for the entry to expire.
[!IMPORTANT] If a version or entire action needs to be removed immediately due to a security vulnerability:
actions.ymlmain branchThe infrastructure team will prioritize these removal requests and may take additional steps to notify affected projects if necessary.
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.