GitHub Actions security checklist
This checklist reviews the repository’s CI workflows and repository settings against the OWASP GitHub Actions Security Cheat Sheet. Vulnerability reporting stays in SECURITY.md, and dependency and release triage in Security-incident triage.
Status vocabulary
Section titled “Status vocabulary”Each row uses exactly one of these statuses:
| Status | Meaning |
|---|---|
reviewed (YYYY-MM-DD) |
The topic was assessed against the repository on that date. Its controls are in place, or the Notes cell says why a gap is accepted |
not a concern (YYYY-MM-DD) |
The OWASP topic doesn’t apply to this repository, as with self-hosted runners |
open risk |
A gap remains, and the Notes cell states the risk |
When a risk closes, update its row to reviewed or not a concern with the date it closed. An open risk row can split one gap out of an OWASP topic. It follows that topic’s row and names the gap.
Topics
Section titled “Topics”| OWASP Topic | Status | Notes |
|---|---|---|
| Pipeline Governance | ||
| Treat CI/CD as critical production code | reviewed (2026-10-05) |
The full pnpm run check gate, and this checklist with the repository settings it reviews. |
| Authentication & Authorization | ||
Default GITHUB_TOKEN permissions |
reviewed (2026-07-27) |
Repo default is read-only. |
Workflow-level permissions: {} |
reviewed (2026-10-04) |
Empty top-level on every workflow; job-scoped writes. |
persist-credentials: false |
reviewed (2026-10-11) |
Set on all actions/checkout steps. Release passes RELEASE_GITHUB_TOKEN as step env to its token check and its publish step only (not persisted at checkout). |
| Eliminate static credentials | reviewed (2026-10-05) |
npm publish uses OIDC. Landings and develop syncs push with GITHUB_TOKEN. The release and Dependabot changeset pushes use fine-grained PATs, RELEASE_GITHUB_TOKEN and DEPENDABOT_CHANGESET_TOKEN, because a GITHUB_TOKEN push doesn’t start a workflow, and the release push must get past main’s rules. |
| OIDC for cloud providers | reviewed (2026-07-30) |
Trusted Publishing on release.yml (push to main + workflow_dispatch). |
| Secure handling of static credentials | reviewed (2026-10-04) |
RELEASE_GITHUB_TOKEN is a secret of the release environment, as Release token sets it up. The workflows never log either PAT, and the repository doesn’t store classic cloud keys. |
| Dependabot changeset token | open risk |
DEPENDABOT_CHANGESET_TOKEN is a repository secret with Contents write, so the copy of a workflow that any pushed branch runs can read it. Publishing sets it up. |
| Secrets: inherit | not a concern (2026-07-27) |
Not used. |
| Mask sensitive data | reviewed (2026-10-05) |
GitHub masks secrets in logs. Repository settings lists the required checks on main. |
| Workflows & Execution | ||
| Pin actions to commit SHA | reviewed (2026-07-27) |
All third-party actions pinned to full commit SHAs with tag comments in workflows. |
| Third-party actions caution | reviewed (2026-07-27) |
Documented current set. |
| Sanitize untrusted context / input | reviewed (2026-10-04) |
Workflow contexts via env. Release notes come from Changesets CHANGELOGs (review pending .changeset in Release plan job before env approval). |
pull_request_target trigger |
reviewed (2026-10-04) |
Used only by dependabot-changeset.yml: the job runs when the PR author is dependabot[bot], checks out the base branch, and reads PR files as data. |
workflow_run trigger |
not a concern (2026-07-27) |
Not used. |
issue_comment trigger |
not a concern (2026-07-27) |
Not used. |
| Curated shared workflows | not a concern (2026-07-27) |
Single-repo monorepo; no centralized shared-workflows repository. |
| Multi-repo shared workflows | not a concern (2026-07-27) |
Not used. |
| Runners & Environments | ||
| Self-hosted runners | not a concern (2026-07-27) |
Using ubuntu-latest GitHub-hosted runners. |
| Runner groups | not a concern (2026-07-27) |
Not applicable to GitHub-hosted runners. |
| Egress monitoring | reviewed (2026-07-27) |
Harden-Runner in audit mode only (not block). |
| Environment required reviewers | reviewed (2026-10-05) |
Bound to release env; required reviewers are maintainer ops. environments.tf turns admin bypass on. The owner is the only admin and also the reviewer. The bypass skips only the owner’s own approval. |
| Code & Supply Chain | ||
| Branch protection baseline | reviewed (2026-10-05) |
main is protected with PR, code-owner review and status checks. develop takes tested landings, a clean main sync among them, plus Dependabot PRs and the sync’s conflict PR. Repository settings lists the branch rules. Missing branch guards are an open risk row of their own. |
| Missing branch guards | open risk |
infra/github/ doesn’t declare a rule for develop. On main, protection.tf doesn’t require Release source, and with enforce_admins off an admin can push directly. The release job pushes that way, after running less than a landing’s gate. Formal models says what each gap lets through. |
| Require approval for external | reviewed (2026-09-10) |
CODEOWNERS on main; require_code_owner_reviews enabled. Separate approval count not required — only maintainers can merge. |
| Dependabot for Actions | reviewed (2026-07-27) |
Configured for weekly updates. |
| Dependabot cooldown | reviewed (2026-07-27) |
Explicit 7-day cooldown on npm and github-actions. |
| Artifact / cache poisoning | reviewed (2026-10-05) |
release.yml, pages.yml and dependabot-changeset.yml turn the dependency cache off, because they publish, deploy or push with a PAT. CI and the land workflow cache the pnpm store. |
| Secret scanning | reviewed (2026-10-11) |
Gitleaks scans every claude/** and land/** push and every pull request that isn’t a draft, and infra/github/protection.tf declares its Scan job as a required check on main. Landing on develop says which pushes it misses. |
| Static analysis (CodeQL/Zizmor) | reviewed (2026-10-05) |
CodeQL (javascript-typescript + actions) and Zizmor run on every pull request that isn’t a draft. Neither is a required check (Required checks and scanners). |
| AI-in-CI | not a concern (2026-07-27) |
No AI assistants used in CI. |
| Incident Response | ||
| Incident response plan | reviewed (2026-07-27) |
Covered in SECURITY.md and triage docs. |
Re-review cadence
Section titled “Re-review cadence”This cadence is advisory, because no workflow re-runs the checklist. Whoever makes one of the changes below should re-run the rows it affects. When a coding agent makes the change, it should commit the re-run on the same branch. For a Dependabot bump of an action version, the maintainer who merges the PR should re-run those rows. Each re-run leaves its evidence as a new date on those rows.
Re-run the affected rows when any of the following change:
- Workflow files under
.github/workflows/(triggers, permissions, action versions, secrets usage) - Repository settings, or organization Actions settings (default
GITHUB_TOKENpermissions, allowed actions, environments) - Branch protection, rulesets, or required checks that gate merges and releases
- Dependabot or secret-scanning configuration
- Release mechanics (Publishing: OIDC, environments, npm trust)
The Zizmor job checks part of that review for workflow-file changes, because it fails on a workflow misconfiguration. It scans every pull request that isn’t a draft, Dependabot’s among them. Its first scan of a workflow change that lands through the land workflow, or that a maintainer pushes to develop directly, is on the release PR (Landing on develop).
Even when nothing changes, a maintainer should run a periodic pass by hand about once a quarter so third-party action advisories and OWASP guidance updates do not drift unnoticed. A full pass gives every dated row a new date, so an oldest date more than a quarter old shows that a pass is overdue.